Skip to content

Commit c4655e3

Browse files
gh-143962: Improve name suggestions for not normalized names
Suggest the normalized name or the closest name to the normalized name. If the suggested name is not ASCII, include also its ASCII representation.
1 parent c5cfcdf commit c4655e3

3 files changed

Lines changed: 49 additions & 2 deletions

File tree

Lib/test/test_traceback.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4250,6 +4250,21 @@ def __dir__(self):
42504250
actual = self.get_suggestion(A(), 'blech')
42514251
self.assertNotIn("Did you mean", actual)
42524252

4253+
def test_suggestions_not_normalized(self):
4254+
class A:
4255+
analization = None
4256+
fiⁿₐˡᵢᶻₐᵗᵢᵒₙ = None
4257+
4258+
self.assertIn("'finalization'", self.get_suggestion(A(), 'fiⁿₐˡᵢᶻₐᵗᵢᵒₙ'))
4259+
4260+
class B:
4261+
attr_a = None
4262+
attr_µ = None
4263+
4264+
suggestion = self.get_suggestion(B(), 'attr_\xb5')
4265+
self.assertIn("'attr_\u03bc'", suggestion)
4266+
self.assertIn(r"'attr_\u03bc'", suggestion)
4267+
42534268

42544269
class GetattrSuggestionTests(BaseSuggestionTests):
42554270
def test_suggestions_no_args(self):
@@ -4872,6 +4887,18 @@ def foo(self):
48724887
actual = self.get_suggestion(instance.foo)
48734888
self.assertIn("self.blech", actual)
48744889

4890+
def test_name_error_with_instance_not_normalized(self):
4891+
class A:
4892+
def __init__(self):
4893+
self.fiⁿₐˡᵢᶻₐᵗᵢᵒₙ = None
4894+
def foo(self):
4895+
analization = 1
4896+
x = fiⁿₐˡᵢᶻₐᵗᵢᵒₙ
4897+
4898+
instance = A()
4899+
actual = self.get_suggestion(instance.foo)
4900+
self.assertIn("self.finalization", actual)
4901+
48754902
def test_unbound_local_error_with_instance(self):
48764903
class A:
48774904
def __init__(self):

Lib/traceback.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,7 +1111,10 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
11111111
wrong_name = getattr(exc_value, "name_from", None)
11121112
suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
11131113
if suggestion:
1114-
self._str += f". Did you mean: '{suggestion}'?"
1114+
if suggestion.isascii():
1115+
self._str += f". Did you mean: '{suggestion}'?"
1116+
else:
1117+
self._str += f". Did you mean: '{suggestion}' ({suggestion!a})?"
11151118
elif exc_type and issubclass(exc_type, ModuleNotFoundError):
11161119
module_name = getattr(exc_value, "name", None)
11171120
if module_name in sys.stdlib_module_names:
@@ -1129,7 +1132,10 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
11291132
wrong_name = getattr(exc_value, "name", None)
11301133
suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
11311134
if suggestion:
1132-
self._str += f". Did you mean: '{suggestion}'?"
1135+
if suggestion.isascii():
1136+
self._str += f". Did you mean: '{suggestion}'?"
1137+
else:
1138+
self._str += f". Did you mean: '{suggestion}' ({suggestion!a})?"
11331139
if issubclass(exc_type, NameError):
11341140
wrong_name = getattr(exc_value, "name", None)
11351141
if wrong_name is not None and wrong_name in sys.stdlib_module_names:
@@ -1654,6 +1660,13 @@ def _check_for_nested_attribute(obj, wrong_name, attrs):
16541660
def _compute_suggestion_error(exc_value, tb, wrong_name):
16551661
if wrong_name is None or not isinstance(wrong_name, str):
16561662
return None
1663+
not_normalized = False
1664+
if not wrong_name.isascii():
1665+
from unicodedata import normalize
1666+
normalized_name = normalize('NFKC', wrong_name)
1667+
if normalized_name != wrong_name:
1668+
not_normalized = True
1669+
wrong_name = normalized_name
16571670
if isinstance(exc_value, AttributeError):
16581671
obj = exc_value.obj
16591672
try:
@@ -1699,6 +1712,8 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
16991712
+ list(frame.f_builtins)
17001713
)
17011714
d = [x for x in d if isinstance(x, str)]
1715+
if not_normalized and wrong_name in d:
1716+
return wrong_name
17021717

17031718
# Check first if we are in a method and the instance
17041719
# has the wrong name as attribute
@@ -1711,6 +1726,8 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
17111726
if has_wrong_name:
17121727
return f"self.{wrong_name}"
17131728

1729+
if not_normalized and wrong_name in d:
1730+
return wrong_name
17141731
try:
17151732
import _suggestions
17161733
except ImportError:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Name suggestion for not normalized name suggests now the normalized name or
2+
the closest name to the normalized name. If the suggested name is not ASCII,
3+
include also its ASCII representation.

0 commit comments

Comments
 (0)