Skip to content

Commit 7ca17ed

Browse files
miss-islingtonambv
andauthored
[3.13] gh-130999: Avoid exiting the new REPL when there are non-string candidates for suggestions (gh-131001) (gh-135020)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent 11f7270 commit 7ca17ed

File tree

4 files changed

+51
-4
lines changed

4 files changed

+51
-4
lines changed

Lib/test/test_pyrepl/test_pyrepl.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,17 @@ def test_null_byte(self):
13421342
self.assertEqual(exit_code, 0)
13431343
self.assertNotIn("TypeError", output)
13441344

1345+
@force_not_colorized
1346+
def test_non_string_suggestion_candidates(self):
1347+
commands = ("import runpy\n"
1348+
"runpy._run_module_code('blech', {0: '', 'bluch': ''}, '')\n"
1349+
"exit()\n")
1350+
1351+
output, exit_code = self.run_repl(commands)
1352+
self.assertEqual(exit_code, 0)
1353+
self.assertNotIn("all elements in 'candidates' must be strings", output)
1354+
self.assertIn("bluch", output)
1355+
13451356
def test_readline_history_file(self):
13461357
# skip, if readline module is not available
13471358
readline = import_module('readline')

Lib/test/test_traceback.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4176,6 +4176,15 @@ def __dir__(self):
41764176
self.assertNotIn("blech", actual)
41774177
self.assertNotIn("oh no!", actual)
41784178

4179+
def test_attribute_error_with_non_string_candidates(self):
4180+
class T:
4181+
bluch = 1
4182+
4183+
instance = T()
4184+
instance.__dict__[0] = 1
4185+
actual = self.get_suggestion(instance, 'blich')
4186+
self.assertIn("bluch", actual)
4187+
41794188
def test_attribute_error_with_bad_name(self):
41804189
def raise_attribute_error_with_bad_name():
41814190
raise AttributeError(name=12, obj=23)
@@ -4211,8 +4220,8 @@ def make_module(self, code):
42114220

42124221
return mod_name
42134222

4214-
def get_import_from_suggestion(self, mod_dict, name):
4215-
modname = self.make_module(mod_dict)
4223+
def get_import_from_suggestion(self, code, name):
4224+
modname = self.make_module(code)
42164225

42174226
def callable():
42184227
try:
@@ -4289,6 +4298,13 @@ def test_import_from_suggestions_underscored(self):
42894298
self.assertIn("'_bluch'", self.get_import_from_suggestion(code, '_luch'))
42904299
self.assertNotIn("'_bluch'", self.get_import_from_suggestion(code, 'bluch'))
42914300

4301+
def test_import_from_suggestions_non_string(self):
4302+
modWithNonStringAttr = textwrap.dedent("""\
4303+
globals()[0] = 1
4304+
bluch = 1
4305+
""")
4306+
self.assertIn("'bluch'", self.get_import_from_suggestion(modWithNonStringAttr, 'blech'))
4307+
42924308
def test_import_from_suggestions_do_not_trigger_for_long_attributes(self):
42934309
code = "blech = None"
42944310

@@ -4385,6 +4401,15 @@ def func():
43854401
actual = self.get_suggestion(func)
43864402
self.assertIn("'ZeroDivisionError'?", actual)
43874403

4404+
def test_name_error_suggestions_with_non_string_candidates(self):
4405+
def func():
4406+
abc = 1
4407+
custom_globals = globals().copy()
4408+
custom_globals[0] = 1
4409+
print(eval("abv", custom_globals, locals()))
4410+
actual = self.get_suggestion(func)
4411+
self.assertIn("abc", actual)
4412+
43884413
def test_name_error_suggestions_do_not_trigger_for_long_names(self):
43894414
def func():
43904415
somethingverywronghehehehehehe = None

Lib/traceback.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,7 +1490,11 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
14901490
if isinstance(exc_value, AttributeError):
14911491
obj = exc_value.obj
14921492
try:
1493-
d = dir(obj)
1493+
try:
1494+
d = dir(obj)
1495+
except TypeError: # Attributes are unsortable, e.g. int and str
1496+
d = list(obj.__class__.__dict__.keys()) + list(obj.__dict__.keys())
1497+
d = sorted([x for x in d if isinstance(x, str)])
14941498
hide_underscored = (wrong_name[:1] != '_')
14951499
if hide_underscored and tb is not None:
14961500
while tb.tb_next is not None:
@@ -1505,7 +1509,11 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
15051509
elif isinstance(exc_value, ImportError):
15061510
try:
15071511
mod = __import__(exc_value.name)
1508-
d = dir(mod)
1512+
try:
1513+
d = dir(mod)
1514+
except TypeError: # Attributes are unsortable, e.g. int and str
1515+
d = list(mod.__dict__.keys())
1516+
d = sorted([x for x in d if isinstance(x, str)])
15091517
if wrong_name[:1] != '_':
15101518
d = [x for x in d if x[:1] != '_']
15111519
except Exception:
@@ -1523,6 +1531,7 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
15231531
+ list(frame.f_globals)
15241532
+ list(frame.f_builtins)
15251533
)
1534+
d = [x for x in d if isinstance(x, str)]
15261535

15271536
# Check first if we are in a method and the instance
15281537
# has the wrong name as attribute
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Avoid exiting the new REPL and offer suggestions even if there are non-string
2+
candidates when errors occur.

0 commit comments

Comments
 (0)