GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-146406: Add cross-language method suggestions for builtin AttributeError by mvanhorn · Pull Request #146407 · python/cpython

Conversation

@mvanhorn

@mvanhorn mvanhorn commented Mar 25, 2026 •
edited by bedevere-app Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Summary

When an AttributeError on a builtin type (list, str, dict) has no
Levenshtein-based suggestion, check a static table of common method names
from other languages.

Before:

>>> [1, 2, 3].push(4)
AttributeError: 'list' object has no attribute 'push'

After:

>>> [1, 2, 3].push(4)
AttributeError: 'list' object has no attribute 'push'. Did you mean '.append' instead of '.push'?

Discourse discussion

Discussed at https://discuss.python.org/t/106632 (420 views, 25 likes, 15 posts, 3 core devs).

Design decisions from the thread:

  • Flat table per @pf_moore (post 14, 4 likes):

    "the table can simply map the method name and object type onto a suggestion. So add on a list is mapped straight to 'did you mean to use a set?' No need to check whether add is a set method - you know it is already."

  • list.add() suggests set, not append per @Storchaka (post 8):

    "It can also mean that you passed a list instead of set. Changing 'add' to 'append' will not fix the error, it will introduce yet one error."

    Reinforced by @tjreedy (post 12):

    "When a method exists for another, related, Python class, I think it better to suggest the other class rather than assume a foreign language match."

  • Scope guardrails per @dr_carlos (post 3): only add entries backed by real confusion evidence, not just because methods are similar.

  • Static table for builtins only (Option 1) - community consensus. 11 entries covering JavaScript, Java, C#, and Ruby.

Verification

Before (system Python 3.13, no cross-language hints):

>>> [1, 2, 3].push(4)
AttributeError: 'list' object has no attribute 'push'

>>> 'hello'.toUpperCase()
AttributeError: 'str' object has no attribute 'toUpperCase'

>>> {}.keySet()
AttributeError: 'dict' object has no attribute 'keySet'

>>> [].add(1)
AttributeError: 'list' object has no attribute 'add'

After (this PR):

>>> [1, 2, 3].push(4)
AttributeError: 'list' object has no attribute 'push'. Did you mean '.append' instead of '.push'?

>>> 'hello'.toUpperCase()
AttributeError: 'str' object has no attribute 'toUpperCase'. Did you mean '.upper' instead of '.toUpperCase'?

>>> {}.keySet()
AttributeError: 'dict' object has no attribute 'keySet'. Did you mean '.keys' instead of '.keySet'?

>>> [].add(1)
AttributeError: 'list' object has no attribute 'add'. Did you mean to use a set? Sets have an .add() method

Levenshtein still takes priority (trim->strip, indexOf->index already work and are not in the table). Only exact builtin types are matched - subclasses are not affected.

Changes

  • Lib/traceback.py: Added _CROSS_LANGUAGE_HINTS dict (11 entries), _get_cross_language_hint() function, and a 4-line fallback hook in TracebackException.__init__ that runs only when Levenshtein found nothing.
  • Lib/test/test_traceback.py: 15 test cases covering all entries, priority ordering, unknown attrs, and subclass exclusion.
  • Misc/NEWS.d/: NEWS entry.

Evidence

Source Evidence
SO: push vs append 320K views
SO: trim whitespace 1.5M views
JetBrains/PSF Survey 2024 40% of Python devs also use JS
CPython precedent elseif->elif (gh-132449), import x from y->from x import y (gh-98931)
Discourse thread 420 views, 25 likes, 3 core devs supportive

Fixes #146406

  • Issue: Add cross-language method suggestions for builtin AttributeError #146406

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

m-aciek reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@mvanhorn
Add cross-language method suggestions for builtin AttributeError
7adad8b
When Levenshtein-based suggestions find no match for an AttributeError
on list, str, or dict, check a static table of common method names from
JavaScript, Java, C#, and Ruby.

For example, [].push() now suggests .append(), "".toUpperCase() suggests
.upper(), and {}.keySet() suggests .keys().

The list.add() case suggests using a set instead of suggesting .append(),
since .add() is a set method and the user may have passed a list where
a set was expected (per discussion with Serhiy Storchaka, Terry Reedy,
and Paul Moore).

Design: flat (type, attr) -> suggestion text table, no runtime
introspection. Only exact builtin types are matched to avoid false
positives on subclasses.

Discussion: https://discuss.python.org/t/106632
@bedevere-app bedevere-app Bot mentioned this pull request Mar 25, 2026
Add cross-language method suggestions for builtin AttributeError #146406
Closed
@bedevere-app bedevere-app Bot added the awaiting review label Mar 25, 2026
@mvanhorn
fix NEWS entry filename for bedevere bot
aecaacb
@mvanhorn

mvanhorn commented Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

Verification screenshots

Built CPython from source and ran the REPL to verify before/after behavior.

Before (Python 3.13, no cross-language hints)

before

After (this PR)

after

All 447 tests in test_traceback pass (including 28 new cross-language tests).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@picnixz
picnixz self-requested a review March 25, 2026 09:05
@picnixz

picnixz commented Mar 25, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

I will make a review of this PR when I have time (by the end of the week), so fellow core devs, please hold off any merge, TiA!

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nedbat

nedbat commented Mar 25, 2026

Copy link
Copy Markdown
Member

Thanks for taking this on! From a language perspective, I think these can be shorter. Instead of:

AttributeError: 'list' object has no attribute 'push'. Did you mean '.append' instead of '.push'?

I think it is enough to say:

AttributeError: 'list' object has no attribute 'push'. Did you mean '.append' instead?

or even:

AttributeError: 'list' object has no attribute 'push'. Did you mean '.append'?
ZeroIntensity and JoBeGaming reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

ZeroIntensity
ZeroIntensity reviewed Mar 25, 2026
View reviewed changes

@ZeroIntensity ZeroIntensity left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very cool!

It'd also be nice if we could suggest using language constructs in some cases. For example:

  1. dict.put -> dict[x] = y
  2. list.contains -> x in list

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/traceback.py Outdated
Comment on lines +1671 to +1672
#
# See https://discuss.python.org/t/106632 for the design discussion.

@ZeroIntensity ZeroIntensity Mar 25, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's link to the GH issue instead.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@mvanhorn mvanhorn Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, switched to the GH issue link.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/traceback.py Outdated
Comment on lines +1675 to +1676
(list, "push"): "append",
(list, "concat"): "extend",

@ZeroIntensity ZeroIntensity Mar 25, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to keep standard convention and just use a single space after the :. Alignment adds extra maintenance (because we need to change each entry if we add something that breaks it) and also creates a false-symmetry between each entry (e.g., for our purposes (list, "push"): "append" has no functional relation to (list, "concat"): "extend").

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@mvanhorn mvanhorn Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, dropped the alignment.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Misc/NEWS.d/next/Library/2026-03-25-07-17-41.gh-issue-146406.ydsmqe.rst Outdated
Comment on lines +4 to +5
``"".toUpperCase()`` suggests ``upper``. The ``list.add()`` case suggests
using a set instead, following feedback from the community discussion.

@ZeroIntensity ZeroIntensity Mar 25, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This last sentence isn't particularly useful:

Suggested change
``"".toUpperCase()`` suggests ``upper``. The ``list.add()`` case suggests
using a set instead, following feedback from the community discussion.
``"".toUpperCase()`` suggests ``upper``.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@mvanhorn mvanhorn Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied your suggestion.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/traceback.py Outdated
else:
self._str += f". Did you mean '.{suggestion}' ({suggestion!a}) instead of '.{wrong_name}' ({wrong_name!a})?"
elif hasattr(exc_value, 'obj'):
with suppress(Exception):

@ZeroIntensity ZeroIntensity Mar 25, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, what needs to be suppressed here?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@mvanhorn mvanhorn Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing realistic - _get_cross_language_hint is just a dict lookup on (type(obj), wrong_name), both of which are already validated by the time we get here. I had it as a defensive measure since we're inside traceback formatting, but on reflection it's unnecessary given how simple the function is. Removed it.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mvanhorn
Address review feedback from @nedbat and @ZeroIntensity
6d58cdc
- Shorten hint format to "Did you mean '.append'?" (drop redundant
  "instead of '.push'" since the error already names the attribute)
- Add dict.put and list.contains entries suggesting language constructs
  (dict[key] = value, x in list) per @ZeroIntensity's review
- Replace suppress(Exception) with direct call (function is safe)
- Link to GH issue instead of Discourse thread in comment
- Drop column alignment in hint table entries
- Trim NEWS entry last sentence
@mvanhorn

mvanhorn commented Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

Good call - the error message already says "has no attribute 'push'" so repeating it in the hint is noise. Shortened to Did you mean '.append'? in 6d58cdc.

I kept the existing Levenshtein format as-is (that's a separate code path and probably a separate discussion), so the cross-language hints now have their own slighty shorter style.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mvanhorn

mvanhorn commented Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

@ZeroIntensity re: suggesting language constructs - added both in 6d58cdc. They use the custom hint format since they suggest constructs rather than method equivalents:

>>> {}.put("a", 1)
AttributeError: 'dict' object has no attribute 'put'. Use dict[key] = value for item assignment

>>> [].contains(1)
AttributeError: 'list' object has no attribute 'contains'. Use 'x in list' to check membership

The existing architecture already handles this - entries with a space in the hint string are rendered as-is rather than wrapped in "Did you mean" format.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mvanhorn

mvanhorn commented Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

I will make a review of this PR when I have time (by the end of the week), so fellow core devs, please hold off any merge, TiA!

appreciat you digging in when you're ready! ::anxiously waits::

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ZeroIntensity

ZeroIntensity commented Mar 25, 2026

Copy link
Copy Markdown
Member

Oh, you also need to add an entry to "What's New in Python 3.15".

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mvanhorn
mvanhorn requested a review from AA-Turner as a code owner March 25, 2026 13:10
@mvanhorn

mvanhorn commented Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

Added in 579d037 - covers the basic hint format and the language-construct variant (dict.put, list.contains).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mvanhorn
Add What's New entry for cross-language AttributeError hints
8a39e32
@mvanhorn
mvanhorn force-pushed the osc/cross-language-attr-hints branch from 57b8fc8 to 8a39e32 Compare March 25, 2026 13:17
picnixz
picnixz reviewed Mar 25, 2026
View reviewed changes
Comment thread Lib/traceback.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/traceback.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/traceback.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/traceback.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/traceback.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/traceback.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/traceback.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Doc/whatsnew/3.15.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

@mvanhorn
Address review feedback from @picnixz
196dbe4
- Use (hint, is_raw) tuples instead of space-based raw detection
- Shorten list.add hint to "Did you mean to use a 'set' object?"
- Use d[k] = v instead of dict[key] = value for dict.put hint
- Add dict.entries -> items (JavaScript)
- Remove Levenshtein guardrail from code comment (belongs on issue)
- Add periods to raw hint messages
- Add test for dict.entries
picnixz
picnixz reviewed Mar 25, 2026
View reviewed changes
Comment thread Lib/traceback.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@mvanhorn
Keep Levenshtein criterion, remove survey restriction per picnixz
99e106a
vstinner
vstinner reviewed Mar 25, 2026
View reviewed changes
Comment thread Lib/test/test_traceback.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/traceback.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/traceback.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@mvanhorn
Consolidate cross-language tests, shorten raw-message hints
57a4d39
Address review feedback from @vstinner:
- Merge 14 individual test_cross_language_* methods into a single
  parameterized test_cross_language using subTest
- Shorten raw-message hints: "Use 'x in list'." and "Use d[k] = v."
- Fix pre-existing levenshtein priority test assertion
- Update whatsnew entry to match shortened hint text
vstinner
vstinner reviewed Mar 25, 2026
View reviewed changes
Comment thread Lib/test/test_traceback.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@mvanhorn
test: restructure test_cross_language to use assertEndsWith for full …
2a2c0d5
…suffix matching

Apply vstinner's review suggestion: use assertEndsWith instead of assertIn
for more precise test assertions. Split cases into method hints (checked
via Did you mean pattern) and raw hints (checked via exact suffix).
vstinner
vstinner approved these changes Mar 25, 2026
View reviewed changes

@vstinner vstinner left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The implementation is correct and well tested. IMO the feature makes sense and is useful.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/traceback.py Outdated
# If is_raw is True, the suggestion is rendered as-is.
#
# See https://github.com/python/cpython/issues/146406.
_CROSS_LANGUAGE_HINTS = {

@vstinner vstinner Mar 25, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might use a frozendict for such global constant dictionary.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@mvanhorn mvanhorn Mar 26, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in f702e79 - wrapped in types.MappingProxyType.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ZeroIntensity ZeroIntensity Mar 26, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need types.MappingProxyType. We have a native frozendict type in 3.15 as of PEP 814.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@mvanhorn mvanhorn Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice - frozendict is cleaner than the MappingProxyType wrapper. Thanks for applying it.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@vstinner vstinner Apr 10, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the frozendict was lost in the meanwhile.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@bedevere-app bedevere-app Bot added awaiting merge and removed awaiting review labels Mar 25, 2026
@mvanhorn
Use MappingProxyType for _CROSS_LANGUAGE_HINTS per vstinner review
f702e79
@vstinner
Update Lib/traceback.py
3ad54c8
@vstinner

vstinner commented Mar 26, 2026

Copy link
Copy Markdown
Member

I applied my suggestion to replace types.MappingProxyType with frozendict.

mvanhorn reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Apr 7, 2026

Copy link
Copy Markdown
Member

@ZeroIntensity @picnixz: Are you ok with this change?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

picnixz
picnixz approved these changes Apr 7, 2026
View reviewed changes

@picnixz picnixz left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One nit and otherwise LGTM. I think @serhiy-storchaka had some more reservations/suggestions so I'd wait a bit for his feedback maybe?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/traceback.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

ZeroIntensity
ZeroIntensity approved these changes Apr 8, 2026
View reviewed changes

@ZeroIntensity ZeroIntensity left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine with me!

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mvanhorn

mvanhorn commented Apr 9, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @picnixz for the review and approval! Happy to wait for @serhiy-storchaka's feedback.

Is the nit you mentioned something I should address now, or was it from the earlier round (the Levenshtein point and the raw hint formatting)? Those should be fixed in the latest commits but let me know if I missed something.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@picnixz

picnixz commented Apr 9, 2026

Copy link
Copy Markdown
Member

The nit is just adding a newline before the "1.".

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mvanhorn @claude
Add blank line before criteria list per review
0089761
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@mvanhorn

mvanhorn commented Apr 10, 2026

Copy link
Copy Markdown
Contributor Author

Added the blank line in 0089761. Waiting on @serhiy-storchaka whenever he has time.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@serhiy-storchaka

serhiy-storchaka commented Apr 10, 2026

Copy link
Copy Markdown
Member

See my comments on the issue.

First, it should work for subclasses. We should also fix suggestions for float.__or__ and other wrong suggestions. Add suggestions for tuple.append, frozenset.add, frozendict.update, etc. Maybe add some suggestions for None.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mvanhorn @claude
Address serhiy-storchaka feedback: isinstance, mutable/immutable, Non…
e2c12ec
…e, float

- Use isinstance() instead of type(obj) for hint lookup, so subclasses
  of builtin types (e.g. OrderedDict, custom list subclasses) also
  get cross-language hints.
- Restructure table to index by method name for efficient isinstance
  iteration.
- Add mutable-on-immutable hints: tuple.append/extend/insert/remove
  suggest list, frozenset.add/discard/remove/update suggest set,
  frozendict.update suggests dict.
- Add NoneType hints: common methods (keys, upper, sort, etc.) tried
  on None suggest the type the user likely expected.
- Add float bitwise hints: __or__/__and__/__xor__/__lshift__/__rshift__
  suggest using int, fixing the misleading __dir__ Levenshtein suggestion.
- Cross-language hints now take priority over Levenshtein (they are more
  specific; Levenshtein still fires as fallback when no table match).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@mvanhorn

mvanhorn commented Apr 10, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @serhiy-storchaka for the thorough feedback! Addressed all your points in e2c12ec:

Subclass support: Switched from type(obj) exact match to isinstance() check. OrderedDict().entries() now suggests .items(), custom list subclasses get hints too. Table restructured to index by method name for efficient isinstance iteration.

Mutable on immutable: Added tuple.append/extend/insert/remove (suggests list), frozenset.add/discard/remove/update (suggests set), frozendict.update (suggests dict).

None suggestions: Added hints for common methods tried on None: None.keys() suggests dict, None.upper() suggests str, None.sort() suggests list, etc.

Float bitwise: Added float.__or__/__and__/__xor__/__lshift__/__rshift__ suggesting int. Cross-language hints now take priority over Levenshtein (more specific), so the misleading __dir__ suggestion is replaced. Levenshtein still fires as fallback when no table match exists.

Demo (built from source, Python REPL):

demo

All 433 tests in test_traceback pass.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

vstinner
vstinner reviewed Apr 10, 2026
View reviewed changes
Comment thread Lib/traceback.py Outdated
"__lshift__": [(float, "Did you mean to use an 'int' object? Bitwise operators are not supported by 'float'.", True)],
"__rshift__": [(float, "Did you mean to use an 'int' object? Bitwise operators are not supported by 'float'.", True)],
# NoneType -- common methods tried on None (got None instead of expected type)
"keys": [(type(None), "Did you expect a 'dict'?", True)],

@vstinner vstinner Apr 10, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can replace type(None) with types.NoneType.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Doc/whatsnew/3.15.rst Outdated

.. doctest::

>>> None.keys() # doctest: +ELLIPSIS

@vstinner vstinner Apr 10, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the None suggestions. Yes, None has no keys() method, but I don't see how I'm supposed to replace it with a dict in this example.

I expected an example where a variable is set to None by mistake, such as:

>>> lst=[1,2,3].sort()
>>> lst.pop()

cc @serhiy-storchaka

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/traceback.py Outdated
# If is_raw is True, the suggestion is rendered as-is.
#
# See https://github.com/python/cpython/issues/146406.
_CROSS_LANGUAGE_HINTS = {

@vstinner vstinner Apr 10, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the frozendict was lost in the meanwhile.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
mvanhorn added 2 commits April 10, 2026 16:55
@mvanhorn
Fix test_cross_language_works_for_subclasses CI failure
564ca3a
Add missing @force_not_colorized decorator to three cross-language
test methods. The CPython C-level traceback path includes ANSI color
codes in error output, causing assertEndsWith to fail when the
expected string doesn't account for trailing escape sequences.

Also add the decorator to test_cross_language_levenshtein_fallback and
test_cross_language_no_hint_for_unknown_attr for consistency with all
other cross-language tests.
@mvanhorn
Address vstinner review: frozendict wrapper, types.NoneType, doctest
e1eb6f4
1. Restore frozendict() wrapper for _CROSS_LANGUAGE_HINTS table
   (lost during the isinstance() refactor in e2c12ec). Convert
   inner lists to tuples for consistency with immutable container.

2. Replace type(None) with types.NoneType per review.

3. Replace contrived None.keys() doctest with a realistic example:
   lst = [3, 1, 2].sort() followed by lst.pop(), showing the
   common "method returned None" mistake vstinner suggested.
@mvanhorn

mvanhorn commented Apr 10, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all three in e1eb6f4:

  1. type(None) → types.NoneType - Done.

  2. frozendict wrapper restored - The frozendict() wrapper was lost during the isinstance() refactor in e2c12ec. Restored it, and converted inner lists to tuples for consistency with the immutable container.

  3. None doctest - Good point, None.keys() was contrived. Replaced with your example:

>>> lst = [3, 1, 2].sort()
>>> lst.pop()

This shows the real scenario (method returned None instead of the expected object). Also updated the prose to explain why.

(Also fixed the CI failure from the previous push in 564ca3a - three cross-language tests were missing @force_not_colorized.)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

vstinner
vstinner reviewed Apr 11, 2026
View reviewed changes

@vstinner vstinner left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/traceback.py Outdated
"__xor__": ((float, "Did you mean to use an 'int' object? Bitwise operators are not supported by 'float'.", True),),
"__lshift__": ((float, "Did you mean to use an 'int' object? Bitwise operators are not supported by 'float'.", True),),
"__rshift__": ((float, "Did you mean to use an 'int' object? Bitwise operators are not supported by 'float'.", True),),
# NoneType -- common methods tried on None (got None instead of expected type)

@vstinner vstinner Apr 11, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced that suggestions on None are useful, but @serhiy-storchaka suggested them. For me, they sound a little bit too generic.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@serhiy-storchaka serhiy-storchaka May 4, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, let's remove them. Most of these methods can actually be defined in several classes, for example str, bytes and bytearray, or list and dict, or dict and frozendict.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@vstinner vstinner May 4, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I removed suggestions on None.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
vstinner added 2 commits May 4, 2026 23:09
@vstinner
Merge branch 'main' into osc/cross-language-attr-hints
023fe8c
@vstinner
Remove suggestions on None
5ad1a90
@read-the-docs-community

read-the-docs-community Bot commented May 4, 2026

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #32533375 | 📁 Comparing 5ad1a90 against main (de66149)

  🔍 Preview build  

2 files changed
± whatsnew/3.15.html
± whatsnew/changelog.html
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner
vstinner enabled auto-merge (squash) May 4, 2026 21:18
@vstinner
vstinner merged commit 7acee98 into python:main May 4, 2026
52 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the awaiting merge label May 4, 2026
@vstinner

vstinner commented May 5, 2026

Copy link
Copy Markdown
Member

Merged, thanks for your contribution to Python.

mvanhorn reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mvanhorn

mvanhorn commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

@vstinner was a great experience. Thank you and team

vstinner reacted with hooray emoji
All reactions
  • 🎉 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@henryiii

henryiii commented May 7, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Awesome to see! But shouldn't all the mutable methods be included? The first thing I tried was .clear(), which was missing from frozendict, frozenset, and tuple. I think it would be best to check all the missing methods and add all that make sense, it's a small list for each:

>>> set(dir(dict)) - set(dir(frozendict))
{'clear', 'pop', 'update', '__ior__', 'setdefault', 'popitem', '__setitem__', '__delitem__'}
>>> set(dir(list)) - set(dir(tuple))
{'__imul__', 'sort', '__reversed__', 'clear', 'pop', 'insert', 'copy', 'reverse', '__iadd__', 'extend', '__setitem__', 'append', '__delitem__', 'remove'}
>>> set(dir(set)) - set(dir(frozenset))
{'__isub__', '__iand__', 'clear', 'pop', 'update', '__ior__', 'difference_update', 'intersection_update', 'symmetric_difference_update', 'add', '__ixor__', 'discard', 'remove'}
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented May 13, 2026

Copy link
Copy Markdown
Member

I'm not sure that it's worth to add suggestions for all methods, but adding clear() sounds reasonable.

@mvanhorn: Do you want to propose a PR adding clear() suggestions?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mvanhorn

mvanhorn commented May 16, 2026

Copy link
Copy Markdown
Contributor Author

@vstinner sure, happy to. I'll open a follow-up PR adding clear() to the cross-language hints table for tuple, frozenset, and frozendict (each suggesting the mutable counterpart, same shape as the existing append/discard/update entries). Aiming for this week. Thanks @henryiii for the catch.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mvanhorn mvanhorn mentioned this pull request May 16, 2026
gh-146406: add clear() cross-language hint for immutable types #149927
Merged
@serhiy-storchaka serhiy-storchaka mentioned this pull request Jun 9, 2026
gh-151128: Improve SyntaxError message for cross language keywords #151129
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@vstinner vstinner vstinner approved these changes
@serhiy-storchaka serhiy-storchaka serhiy-storchaka left review comments
@picnixz picnixz picnixz approved these changes
@ZeroIntensity ZeroIntensity ZeroIntensity approved these changes
@AA-Turner AA-Turner Awaiting requested review from AA-Turner AA-Turner is a code owner

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Add cross-language method suggestions for builtin AttributeError

Uh oh!

There was an error while loading. Please reload this page.

7 participants

@mvanhorn @picnixz @nedbat @ZeroIntensity @vstinner @serhiy-storchaka @henryiii
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.