Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -4564,6 +4564,58 @@ def __init__(self):
actual = self.get_suggestion(Outer(), 'target')
self.assertIn("'.normal.target'", actual)

def test_cross_language_list_suggestions(self):
# Common list method names from other languages that have no
# close Levenshtein match in list's attributes.
cases = [
([1, 2, 3], "push", "append"), # JavaScript / Ruby
([1, 2, 3], "addAll", "extend"), # Java
]
for obj, wrong, expected in cases:
with self.subTest(wrong=wrong):
actual = self.get_suggestion(obj, wrong)
self.assertIn(f"'.{expected}'", actual)

def test_cross_language_str_suggestions(self):
# Common str method names from other languages that have no
# close Levenshtein match in str's attributes.
cases = [
("hello", "toUpperCase", "upper"), # JavaScript / Java
("hello", "toLowerCase", "lower"), # JavaScript / Java
("hello", "trimStart", "lstrip"), # JavaScript
]
for obj, wrong, expected in cases:
with self.subTest(wrong=wrong):
actual = self.get_suggestion(obj, wrong)
self.assertIn(f"'.{expected}'", actual)

def test_cross_language_dict_suggestions(self):
# Common dict method names from other languages that have no
# close Levenshtein match in dict's attributes.
cases = [
({"a": 1}, "putAll", "update"), # Java
({"a": 1}, "entrySet", "items"), # Java
]
for obj, wrong, expected in cases:
with self.subTest(wrong=wrong):
actual = self.get_suggestion(obj, wrong)
self.assertIn(f"'.{expected}'", actual)

def test_cross_language_no_suggestion_for_subclasses(self):
# Cross-language hints only trigger for exact builtin types,
# not subclasses, to avoid false positives on custom classes.
class CustomList(list):
pass

actual = self.get_suggestion(CustomList([1, 2, 3]), 'push')
self.assertNotIn("'.append'", actual)

def test_cross_language_no_suggestion_for_unknown_attr(self):
# Attributes not in the cross-language table should not get
# suggestions from it (and may get no suggestion at all).
actual = self.get_suggestion([1, 2, 3], 'frobulate')
self.assertNotIn("Did you mean", actual)

def make_module(self, code):
tmpdir = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, tmpdir)
Expand Down
49 changes: 49 additions & 0 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -1715,6 +1715,47 @@ def _get_safe___dir__(obj):
)


# Mapping of common method names from other programming languages to their
# Python equivalents. Used as a fallback when Levenshtein-based suggestion
# matching finds no close match. Only covers cases where a real Python
# method exists on the same builtin type.
_CROSS_LANGUAGE_ATTR_HINTS = {
# JavaScript / Ruby -> Python (list)
(list, "push"): "append",
(list, "indexOf"): "index",
(list, "unshift"): "insert",
(list, "concat"): "extend",
(list, "add"): "append", # Java / C#
(list, "addAll"): "extend", # Java
# JavaScript / Java -> Python (str)
(str, "toUpperCase"): "upper",
(str, "toLowerCase"): "lower",
(str, "indexOf"): "index",
(str, "trim"): "strip",
(str, "trimStart"): "lstrip",
(str, "trimEnd"): "rstrip",
(str, "replaceAll"): "replace",
# Java / C# -> Python (dict)
(dict, "putAll"): "update",
(dict, "keySet"): "keys",
(dict, "entrySet"): "items",
(dict, "getOrDefault"): "get",
(dict, "remove"): "pop",
}


def _check_cross_language_hint(obj, wrong_name):
"""Check if wrong_name is a common method name from another language.

Only checks exact builtin types (list, str, dict) to avoid false
positives on custom subclasses that may intentionally omit methods.
"""
obj_type = type(obj)
if obj_type not in (list, str, dict):
return None
return _CROSS_LANGUAGE_ATTR_HINTS.get((obj_type, wrong_name))


def _compute_suggestion_error(exc_value, tb, wrong_name):
if wrong_name is None or not isinstance(wrong_name, str):
return None
Expand Down Expand Up @@ -1830,6 +1871,14 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
if nested_suggestion:
return nested_suggestion

# If still no suggestion, check for common method names from other
# programming languages (e.g., push -> append, trim -> strip).
if not suggestion and isinstance(exc_value, AttributeError):
with suppress(Exception):
cross_lang = _check_cross_language_hint(exc_value.obj, wrong_name)
if cross_lang is not None:
return cross_lang

return suggestion


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Added cross-language method suggestions to :exc:`AttributeError` messages.
When an attribute name from another programming language is used on a
builtin type (e.g., ``list.push`` from JavaScript, ``str.toUpperCase`` from
Java), Python now suggests the equivalent Python method (e.g., ``append``,
``upper``). This runs as a fallback after the existing Levenshtein-based
suggestion matching.
Loading