Skip to content

Align all remaining error messages with CPython - #7993

Open
JamesClarke7283 wants to merge 19 commits into
RustPython:mainfrom
JamesClarke7283:align-aug-assign-delete-set-dict-errors
Open

Align all remaining error messages with CPython#7993
JamesClarke7283 wants to merge 19 commits into
RustPython:mainfrom
JamesClarke7283:align-aug-assign-delete-set-dict-errors

Conversation

@JamesClarke7283

@JamesClarke7283 JamesClarke7283 commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Continues the parser error-message alignment from #7928 / #7933 / #7988. Translates many more ruff ParseErrorType variants to CPython 3.14.5's exact wording in CompileError::from_ruff_parse_error (using the source slice / parsed AST kind), plus a few codegen/symtable checks.

crates/vm/src/stdlib/_ast.rs now routes its parse errors through from_ruff_parse_error, so ast.parse() / compile(..., PyCF_ONLY_AST) produce the same CPython-aligned messages as the exec path (they previously leaked raw ruff strings — and this fixes a regression where the var-param message diverged on the AST path).

What's aligned

  • aug-assign / delete / set / dict / f-string / t-string assignment targets
  • cannot use {attribute,subscript,tuple,list,literal,function call} as import target; import X from Y → "Did you mean to use 'from ... import ...' instead?"
  • string-prefix incompatibility ('u' and 'b' prefixes are incompatible); invalid character 'X' (U+XXXX)
  • parenthesized def/lambda params; missing default / argument value; dict : and value syntax
  • 'elif' block follows an 'else' block; raise from; comprehension if / unparenthesized target; ternary statement-keyword hints
  • match case … as <target>cannot use {kind} as pattern target, and case … as _cannot use '_' as a target
  • __debug__ as a def/class/type-param/except-handler name → cannot assign to __debug__
  • name 'x' is nonlocal and global; generic type-parameter wording (… cannot be used within the definition of a generic)

This drops the now-passing # TODO: RUSTPYTHON; Wrong error message doctest markers and @expectedFailure decorators across test_syntax.py, test_genexps.py, test_named_expressions.py, and test_patma.py.

Not covered (intentionally left marked)

A few test_syntax.py cases need ruff-parser or deeper codegen changes and remain marked rather than emitting a silently-wrong message: the type-comment bare * case, class C(x for x in L) and f((x)=2) (ruff accepts these without a parse error), and dict(...); x $ y (duplicate-keyword vs lexer ordering). Runtime-error messages in unrelated subsystems (struct-sequence, format-spec, datetime, __import__, ast __replace__) are out of scope for this parser-focused change.

Verification

  • cargo clippy -p rustpython-compiler -p rustpython-codegen -- -D warnings clean; cargo fmt.
  • Every changed message diff-tested against CPython 3.14.5 through both the exec path and ast.parse().
  • test_syntax test_genexps test_named_expressions test_patma test_type_params test_scope test_grammar test_compile test_exceptions all pass (961 tests).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Reject the reserved identifier debug in more assignment, function/class, keyword-arg and type-parameter contexts
    • Disallow using the wildcard _ as a capture/store target
    • Prevent conflicting global/nonlocal declarations
    • More CPython-aligned and clearer syntax/compile error messages; improved parse/compile error reporting and wording
  • Chores

    • Minor lint/implementation adjustments and a WASM import linkage hint for host imports

Review Change Stack

@github-actions

github-actions Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

[ ] test: cpython/Lib/test/test_dict.py (TODO: 4)
[x] test: cpython/Lib/test/test_dictcomps.py (TODO: 1)
[ ] test: cpython/Lib/test/test_dictviews.py (TODO: 1)
[x] test: cpython/Lib/test/test_userdict.py
[ ] test: cpython/Lib/test/mapping_tests.py

dependencies:

dependent tests: (no tests depend on dict)

[x] lib: cpython/Lib/ast.py
[x] lib: cpython/Lib/_ast_unparse.py
[x] test: cpython/Lib/test/test_unparse.py
[x] test: cpython/Lib/test/test_type_comments.py

dependencies:

  • ast

dependent tests: (149 tests)

  • ast: test_ast test_builtin test_compile test_compiler_codegen test_dis test_fstring test_future_stmt test_peepholer test_peg_generator test_site test_ssl test_type_comments test_ucn test_unparse
    • annotationlib: test_annotationlib test_functools test_grammar test_inspect test_reprlib test_type_annotations test_type_params test_typing
      • dataclasses: test__colorize test_copy test_ctypes test_enum test_genericalias test_patma test_pprint test_pydoc test_regrtest test_zoneinfo
      • inspect: test_abc test_argparse test_asyncgen test_buffer test_clinic test_code test_collections test_coroutines test_decimal test_generators test_monitoring test_ntpath test_operator test_posixpath test_signal test_sqlite3 test_traceback test_turtle test_types test_unittest test_yield_from test_zipimport test_zipimport_support
    • dbm.dumb: test_dbm_dumb
    • inspect:
      • bdb: test_bdb test_pdb
      • cmd: test_cmd
      • importlib.metadata: test_importlib
      • pkgutil: test_pkgutil test_pyrepl test_runpy
      • rlcompleter: test_pyrepl test_rlcompleter
      • trace: test_trace
      • xmlrpc.server: test_docxmlrpc test_xmlrpc
    • pyclbr: test_pyclbr
    • traceback: test_asyncio test_code_module test_contextlib test_contextlib_async test_dictcomps test_exceptions test_http_cookiejar test_importlib test_iter test_listcomps test_pyexpat test_setcomps test_socket test_subprocess test_sys test_threadedtempfile test_threading test_unittest test_with
      • concurrent.futures.process: test_compileall test_concurrent_futures
      • http.cookiejar: test_urllib2
      • logging: test_asyncio test_hashlib test_logging test_support test_urllib2net
      • multiprocessing: test_asyncio test_concurrent_futures test_fcntl test_memoryview test_multiprocessing_main_handling test_re
      • py_compile: test_cmd_line_script test_importlib test_modulefinder test_py_compile
      • socketserver: test_imaplib test_socketserver test_wsgiref
      • threading: test_android test_asyncio test_bytes test_bz2 test_concurrent_futures test_context test_ctypes test_email test_external_inspection test_fork1 test_frame test_ftplib test_gc test_httplib test_httpservers test_importlib test_io test_ioctl test_itertools test_largefile test_linecache test_opcache test_pathlib test_poll test_poplib test_pyrepl test_queue test_robotparser test_sched test_smtplib test_super test_syslog test_termios test_threading_local test_time test_urllib2_localnet test_weakref test_winreg test_zstd
      • timeit: test_timeit

[ ] test: cpython/Lib/test/test_str.py (TODO: 5)
[ ] test: cpython/Lib/test/test_fstring.py (TODO: 13)
[x] test: cpython/Lib/test/test_string_literals.py (TODO: 4)

dependencies:

dependent tests: (no tests depend on str)

[ ] test: cpython/Lib/test/test_descr.py (TODO: 31)
[ ] test: cpython/Lib/test/test_descrtut.py (TODO: 2)

dependencies:

dependent tests: (no tests depend on descr)

[ ] test: cpython/Lib/test/test_builtin.py (TODO: 14)

dependencies:

dependent tests: (no tests depend on builtin)

[x] lib: cpython/Lib/lzma.py
[x] test: cpython/Lib/test/test_lzma.py (TODO: 8)

dependencies:

  • lzma

dependent tests: (101 tests)

  • lzma: test_lzma test_tarfile
    • shutil: test_argparse test_bz2 test_compileall test_ctypes test_embed test_filecmp test_glob test_httpservers test_importlib test_inspect test_largefile test_launcher test_logging test_modulefinder test_os test_peg_generator test_pkgutil test_py_compile test_reprlib test_sax test_shutil test_site test_string_literals test_subprocess test_support test_sysconfig test_tempfile test_traceback test_unicode_file test_venv test_zoneinfo
      • ctypes.util: test_ctypes
      • ensurepip: test_ensurepip
      • http.server: test_robotparser test_urllib2_localnet test_xmlrpc
      • multiprocessing.util: test_asyncio test_concurrent_futures
      • pathlib: test_ast test_dbm_sqlite3 test_importlib test_json test_pathlib test_pyrepl test_runpy test_tomllib test_tools test_unparse test_winapi test_zipapp test_zipfile test_zstd
      • tempfile: test_asyncio test_bytes test_cmd_line test_compile test_concurrent_futures test_contextlib test_cprofile test_csv test_dis test_doctest test_faulthandler test_fileinput test_generated_cases test_genericalias test_hashlib test_importlib test_linecache test_mailbox test_ntpath test_pickle test_pkg test_posix test_pstats test_pydoc test_pyrepl test_regrtest test_selectors test_socket test_sys test_sys_settrace test_tabnanny test_termios test_threadedtempfile test_tokenize test_turtle test_urllib test_urllib2 test_urllib_response test_winconsoleio test_zipfile test_zipfile64
      • webbrowser: test_webbrowser
      • zipapp: test_pdb
      • zipfile: test_zipfile test_zipimport test_zipimport_support
    • zipfile:
      • importlib.metadata: test_importlib

[ ] lib: cpython/Lib/json
[ ] test: cpython/Lib/test/test_json (TODO: 10)

dependencies:

  • json (native: _json, decoder, encoder, json.tool, sys)
    • argparse (native: sys)
    • _colorize, codecs, re

dependent tests: (13 tests)

  • json: test_embed test_logging test_plistlib test_pyrepl test_subprocess test_sysconfig test_tomllib test_tools test_traceback test_zoneinfo
    • importlib.metadata: test_importlib
    • multiprocessing.resource_tracker: test_concurrent_futures
    • pdb: test_pdb

[x] test: cpython/Lib/test/test_coroutines.py (TODO: 14)

dependencies:

dependent tests: (7 tests)
- [ ] asyncio: test_asyncio test_external_inspection test_inspect test_logging test_os test_pdb test_unittest

[x] test: cpython/Lib/test/test_tstring.py (TODO: 2)

dependencies:

dependent tests: (no tests depend on tstring)

[ ] test: cpython/Lib/test/test_exceptions.py (TODO: 21)
[ ] test: cpython/Lib/test/test_baseexception.py
[x] test: cpython/Lib/test/test_except_star.py (TODO: 1)
[ ] test: cpython/Lib/test/test_exception_group.py (TODO: 5)
[x] test: cpython/Lib/test/test_exception_hierarchy.py (TODO: 2)
[x] test: cpython/Lib/test/test_exception_variations.py

dependencies:

dependent tests: (no tests depend on exception)

[x] lib: cpython/Lib/enum.py
[x] test: cpython/Lib/test/test_enum.py (TODO: 3)

dependencies:

  • enum

dependent tests: (16 tests)

  • enum: test_argparse test_ast test_enum test_httplib test_json test_patma test_pstats test_pydoc test_signal test_socket test_ssl test_str test_time test_types test_typing test_uuid

[ ] test: cpython/Lib/test/test_structseq.py

dependencies:

dependent tests: (no tests depend on structseq)

[x] lib: cpython/Lib/plistlib.py
[ ] test: cpython/Lib/test/test_plistlib.py (TODO: 5)

dependencies:

  • plistlib

dependent tests: (25 tests)

  • plistlib: test_plistlib
    • platform: test__locale test__osx_support test_asyncio test_baseexception test_builtin test_cmath test_ctypes test_fcntl test_math test_mimetypes test_os test_platform test_posix test_regrtest test_shutil test_socket test_ssl test_strptime test_sysconfig test_time test_winreg test_wsgiref
      • pydoc: test_enum test_pydoc

[ ] test: cpython/Lib/test/test_bytes.py (TODO: 18)

dependencies:

dependent tests: (no tests depend on bytes)

[x] lib: cpython/Lib/struct.py
[ ] test: cpython/Lib/test/test_struct.py (TODO: 6)

dependencies:

  • struct

dependent tests: (179 tests)

  • struct: test_array test_buffer test_call test_compileall test_ctypes test_deque test_fcntl test_float test_gzip test_ioctl test_itertools test_logging test_math test_memoryview test_ordered_dict test_os test_pickle test_plistlib test_socket test_ssl test_str test_struct test_sys test_tools test_venv test_wave test_xml_etree_c test_xpickle test_zipfile test_zipimport test_zoneinfo
    • base64: test_base64 test_email test_gettext test_httpservers test_smtplib test_urllib2 test_urllib2_localnet test_xmlrpc
      • http.server: test_robotparser
      • logging.handlers: test_concurrent_futures test_pkgutil
      • secrets: test_secrets
      • smtplib: test_smtpnet
      • ssl: test_asyncio test_ftplib test_httplib test_imaplib test_poplib test_urllib
      • urllib.request: test_http_cookiejar test_pathlib test_pydoc test_sax test_site test_urllib2net test_urllibnet
    • ctypes: test_android test_bytes test_code test_codecs test_ctypes test_genericalias test_io test_ntpath
      • platform: test__locale test__osx_support test_asyncio test_baseexception test_builtin test_cmath test_mimetypes test_platform test_posix test_regrtest test_shutil test_strptime test_sysconfig test_time test_winreg test_wsgiref
      • webbrowser: test_webbrowser
    • dbm: test_dbm test_dbm_dumb test_dbm_sqlite3 test_shelve
    • gettext:
      • argparse: test_argparse
      • getopt: test_getopt
      • optparse: test_decimal test_optparse
    • gzip: test_fileinput test_tarfile
    • multiprocessing: test_asyncio test_concurrent_futures test_multiprocessing_main_handling test_re
      • concurrent.futures.process: test_concurrent_futures
    • pickle: test_annotationlib test_ast test_bool test_bz2 test_collections test_configparser test_coroutines test_csv test_defaultdict test_descr test_dict test_dictviews test_email test_enum test_enumerate test_exceptions test_fractions test_functools test_generators test_http_cookies test_importlib test_inspect test_ipaddress test_iter test_list test_lzma test_memoryio test_minidom test_opcache test_operator test_picklebuffer test_pickletools test_positional_only_arg test_random test_range test_set test_slice test_statistics test_string test_structseq test_super test_trace test_tuple test_turtle test_type_aliases test_type_params test_types test_typing test_unittest test_uuid test_xml_dom_minicompat test_xml_etree test_zipfile test_zlib test_zoneinfo
      • tracemalloc: test_tracemalloc
    • tarfile:
      • shutil: test_embed test_filecmp test_glob test_importlib test_largefile test_launcher test_modulefinder test_peg_generator test_py_compile test_reprlib test_string_literals test_subprocess test_support test_tempfile test_traceback test_unicode_file
    • zipfile: test_pdb test_zipapp test_zipfile test_zipfile64 test_zipimport_support
      • importlib.metadata: test_importlib
    • zipimport: test_cmd_line_script test_importlib
      • pkgutil: test_pyrepl test_runpy

[x] test: cpython/Lib/test/test_asyncgen.py (TODO: 3)

dependencies:

dependent tests: (no tests depend on asyncgen)

[ ] test: cpython/Lib/test/test_syntax.py (TODO: 2)

dependencies:

dependent tests: (no tests depend on syntax)

[ ] test: cpython/Lib/test/test_class.py (TODO: 12)
[x] test: cpython/Lib/test/test_genericclass.py
[x] test: cpython/Lib/test/test_subclassinit.py

dependencies:

dependent tests: (no tests depend on class)

[ ] test: cpython/Lib/test/test_generators.py (TODO: 7)
[ ] test: cpython/Lib/test/test_genexps.py (TODO: 4)
[x] test: cpython/Lib/test/test_generator_stop.py
[x] test: cpython/Lib/test/test_yield_from.py (TODO: 1)

dependencies:

dependent tests: (no tests depend on generator)

[x] lib: cpython/Lib/datetime.py
[x] lib: cpython/Lib/_strptime.py
[ ] lib: cpython/Lib/_pydatetime.py
[ ] test: cpython/Lib/test/test_datetime.py
[x] test: cpython/Lib/test/test_strptime.py

dependencies:

  • datetime (native: _datetime, _thread, math, sys, time)
    • _pydatetime
    • _strptime
    • _pydatetime (native: math, sys, time)
    • locale (native: _locale, builtins, encodings.aliases, sys)
    • warnings (native: _contextvars, _thread, _warnings, builtins, sys)
    • _strptime, calendar, operator, os, re

dependent tests: (67 tests)

  • datetime: test_calendar test_email test_enum test_faulthandler test_fstring test_hash test_httpservers test_imaplib test_inspect test_logging test_plistlib test_pydoc test_sqlite3 test_str test_strptime test_sys test_tomllib test_tools test_unittest test_xmlrpc test_zipfile test_zoneinfo
    • calendar: test_strftime
      • http.cookiejar: test_http_cookiejar test_urllib2
      • mailbox: test_genericalias test_mailbox
      • ssl: test_asyncio test_ftplib test_httplib test_poplib test_ssl test_urllib test_urllib2_localnet test_venv
    • email.utils: test_email test_smtplib
      • logging.handlers: test_concurrent_futures test_pkgutil
      • smtplib: test_smtpnet
      • urllib.request: test_pathlib test_sax test_site test_urllib2net test_urllibnet
    • http.server: test_robotparser
      • wsgiref.simple_server: test_wsgiref
      • xmlrpc.server: test_docxmlrpc
    • plistlib:
      • platform: test__locale test__osx_support test_asyncio test_baseexception test_builtin test_cmath test_ctypes test_fcntl test_math test_mimetypes test_os test_platform test_posix test_regrtest test_shutil test_socket test_sysconfig test_time test_winreg

[ ] test: cpython/Lib/test/test_posix.py (TODO: 3)

dependencies:

dependent tests: (101 tests)

  • posix: test_pathlib test_posix test_posixpath test_shutil
    • importlib._bootstrap_external: test_importlib test_unittest
      • modulefinder: test_importlib test_modulefinder
      • py_compile: test_argparse test_cmd_line_script test_compileall test_importlib test_multiprocessing_main_handling test_py_compile test_pydoc test_runpy
      • pydoc: test_enum
    • pathlib._os: test_pathlib
    • posixpath: test_zipfile
      • fnmatch: test_embed test_fnmatch test_os
      • http.server: test_httpservers test_logging test_robotparser test_urllib2_localnet test_xmlrpc
      • mimetypes: test_mimetypes
      • wsgiref.util: test_wsgiref
      • zipfile._path: test_zipfile
    • shutil: test_bz2 test_ctypes test_filecmp test_glob test_importlib test_inspect test_largefile test_launcher test_peg_generator test_pkgutil test_reprlib test_sax test_site test_string_literals test_subprocess test_support test_sysconfig test_tarfile test_tempfile test_traceback test_unicode_file test_venv test_zoneinfo
      • ctypes.util: test_ctypes
      • ensurepip: test_ensurepip
      • multiprocessing.util: test_asyncio test_concurrent_futures
      • tempfile: test_ast test_asyncio test_bytes test_cmd_line test_compile test_concurrent_futures test_contextlib test_cprofile test_csv test_dis test_doctest test_faulthandler test_fileinput test_generated_cases test_genericalias test_hashlib test_importlib test_linecache test_mailbox test_ntpath test_pickle test_pkg test_pstats test_pyrepl test_regrtest test_selectors test_socket test_sys test_sys_settrace test_tabnanny test_termios test_threadedtempfile test_tokenize test_tomllib test_turtle test_urllib test_urllib2 test_urllib_response test_winconsoleio test_zipapp test_zipfile64 test_zstd
      • webbrowser: test_webbrowser
      • zipapp: test_pdb

[x] lib: cpython/Lib/pdb.py
[ ] test: cpython/Lib/test/test_pdb.py (TODO: 40)

dependencies:

  • pdb

dependent tests: (1 tests)

  • pdb: test_pdb

[x] test: cpython/Lib/test/test_format.py

dependencies:

dependent tests: (no tests depend on format)

[ ] lib: cpython/Lib/sqlite3
[ ] test: cpython/Lib/test/test_sqlite3 (TODO: 56)

dependencies:

  • sqlite3 (native: _sqlite3, collections.abc, readline, sqlite3.dbapi2, sys, time)
    • argparse, datetime, warnings
    • code, textwrap

dependent tests: (2 tests)

  • sqlite3: test_dbm_sqlite3 test_sqlite3

[x] test: cpython/Lib/test/test_range.py (TODO: 2)

dependencies:

dependent tests: (no tests depend on range)

[ ] test: cpython/Lib/test/test_mmap.py (TODO: 24)

dependencies:

dependent tests: (2 tests)

  • mmap: test_os
    • multiprocessing.shared_memory: test_genericalias

[x] test: cpython/Lib/test/test_marshal.py (TODO: 4)

dependencies:

dependent tests: (25 tests)

  • marshal: test_bool test_exceptions test_importlib test_inspect test_marshal test_zipimport
    • importlib._bootstrap_external: test_importlib test_unittest
      • modulefinder: test_importlib test_modulefinder
      • py_compile: test_argparse test_cmd_line_script test_compileall test_importlib test_multiprocessing_main_handling test_py_compile test_pydoc test_runpy
      • pydoc: test_enum
    • pkgutil: test_pkgutil test_pyrepl
    • profile: test_profile
    • pstats: test_pstats
    • zipimport: test_importlib test_zipimport_support

[ ] lib: cpython/Lib/socket.py
[ ] test: cpython/Lib/test/test_socket.py (TODO: 14)

dependencies:

  • socket (native: _socket, array, errno, sys)
    • io (native: _io, _thread, errno, msvcrt, sys)
    • enum, os, selectors

dependent tests: (101 tests)

  • socket: test_asyncio test_epoll test_exception_hierarchy test_external_inspection test_ftplib test_httplib test_httpservers test_imaplib test_kqueue test_largefile test_logging test_mailbox test_mmap test_os test_pathlib test_poplib test_pty test_selectors test_signal test_smtplib test_smtpnet test_socket test_socketserver test_ssl test_stat test_subprocess test_support test_sys test_timeout test_urllib test_urllib2 test_urllib2net test_urllib_response test_urllibnet test_xmlrpc
    • asyncio: test_asyncio test_inspect test_pdb test_unittest
    • email.utils: test_email
      • http.server: test_robotparser test_urllib2_localnet
      • logging.handlers: test_concurrent_futures test_pkgutil
      • urllib.request: test_http_cookiejar test_pydoc test_sax test_site
    • http.client: test_docxmlrpc test_hashlib test_ucn test_unicodedata test_wsgiref
    • http.server:
      • pydoc: test_enum
    • mailbox: test_genericalias
    • multiprocessing: test_compileall test_concurrent_futures test_fcntl test_memoryview test_multiprocessing_main_handling test_re
      • concurrent.futures.process: test_concurrent_futures
    • platform: test__locale test__osx_support test_baseexception test_builtin test_cmath test_ctypes test_math test_mimetypes test_platform test_posix test_regrtest test_shutil test_strptime test_sysconfig test_time test_winreg
    • ssl: test_venv
    • urllib.request:
      • pathlib: test_ast test_dbm_sqlite3 test_ensurepip test_importlib test_json test_launcher test_pathlib test_peg_generator test_pyrepl test_runpy test_tarfile test_tempfile test_tomllib test_tools test_traceback test_unparse test_winapi test_zipapp test_zipfile test_zoneinfo test_zstd
    • uuid:
      • wave: test_wave

[x] lib: cpython/Lib/bz2.py
[ ] test: cpython/Lib/test/test_bz2.py

dependencies:

  • bz2

dependent tests: (101 tests)

  • bz2: test_bz2 test_codecs test_fileinput test_tarfile
    • fileinput: test_genericalias
    • shutil: test_argparse test_compileall test_ctypes test_embed test_filecmp test_glob test_httpservers test_importlib test_inspect test_largefile test_launcher test_logging test_modulefinder test_os test_peg_generator test_pkgutil test_py_compile test_reprlib test_sax test_shutil test_site test_string_literals test_subprocess test_support test_sysconfig test_tempfile test_traceback test_unicode_file test_venv test_zoneinfo
      • ctypes.util: test_ctypes
      • ensurepip: test_ensurepip
      • http.server: test_robotparser test_urllib2_localnet test_xmlrpc
      • multiprocessing.util: test_asyncio test_concurrent_futures
      • pathlib: test_ast test_dbm_sqlite3 test_importlib test_json test_pathlib test_pyrepl test_runpy test_tomllib test_tools test_unparse test_winapi test_zipapp test_zipfile test_zstd
      • tempfile: test_asyncio test_bytes test_cmd_line test_compile test_concurrent_futures test_contextlib test_cprofile test_csv test_dis test_doctest test_faulthandler test_generated_cases test_hashlib test_importlib test_linecache test_mailbox test_ntpath test_pickle test_pkg test_posix test_pstats test_pydoc test_pyrepl test_regrtest test_selectors test_socket test_sys test_sys_settrace test_tabnanny test_termios test_threadedtempfile test_tokenize test_turtle test_urllib test_urllib2 test_urllib_response test_winconsoleio test_zipfile test_zipfile64
      • webbrowser: test_webbrowser
      • zipapp: test_pdb
      • zipfile: test_zipfile test_zipimport test_zipimport_support
    • zipfile:
      • importlib.metadata: test_importlib

[ ] test: cpython/Lib/test/test_extcall.py (TODO: 7)

dependencies:

dependent tests: (no tests depend on extcall)

[x] lib: cpython/Lib/hashlib.py
[x] test: cpython/Lib/test/test_hashlib.py (TODO: 6)

dependencies:

  • hashlib

dependent tests: (145 tests)

  • hashlib: test_hashlib test_hmac test_smtplib test_tarfile test_unicodedata test_urllib2_localnet
    • hmac:
      • imaplib: test_imaplib
      • secrets: test_secrets
      • smtplib: test_smtpnet
    • poplib: test_poplib
    • random: test_asyncio test_bisect test_buffer test_builtin test_bz2 test_collections test_complex test_context test_dbm_dumb test_decimal test_deque test_descr test_devpoll test_dict test_dummy_thread test_email test_float test_functools test_grp test_heapq test_importlib test_int test_io test_itertools test_logging test_long test_lzma test_math test_mmap test_numeric_tower test_ordered_dict test_poll test_posixpath test_pow test_pprint test_pwd test_queue test_random test_regrtest test_richcmp test_selectors test_set test_shutil test_signal test_socket test_sort test_statistics test_strtod test_struct test_sys test_thread test_threading test_tokenize test_traceback test_unparse test_uuid test_weakref test_zipfile test_zlib test_zstd
      • email.generator: test_email
      • email.utils: test_httpservers test_urllib2
      • tempfile: test_argparse test_ast test_asyncio test_bytes test_cmd_line test_compile test_compileall test_concurrent_futures test_contextlib test_cprofile test_csv test_ctypes test_dis test_doctest test_embed test_ensurepip test_faulthandler test_filecmp test_fileinput test_generated_cases test_genericalias test_importlib test_inspect test_launcher test_linecache test_mailbox test_modulefinder test_ntpath test_os test_pathlib test_peg_generator test_pickle test_pkg test_pkgutil test_posix test_pstats test_py_compile test_pydoc test_pyrepl test_runpy test_site test_string_literals test_subprocess test_support test_sys_settrace test_tabnanny test_tempfile test_termios test_threadedtempfile test_tomllib test_turtle test_urllib test_urllib_response test_venv test_winconsoleio test_zipapp test_zipfile64 test_zoneinfo
    • urllib.request: test_http_cookiejar test_sax test_ssl test_urllib2net test_urllibnet
      • pathlib: test_dbm_sqlite3 test_importlib test_json test_pathlib test_tomllib test_tools test_winapi test_zipfile
    • uuid:
      • wave: test_wave

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Tightens symbol validation (rejecting __debug__ in more contexts, forbidding _ as a pattern target, and enforcing global/nonlocal mutual exclusion), refactors parse-error translation into CPython-style diagnostics with centralized helpers, and routes parser errors through the AST/VM conversion path while preserving select message casing.

Changes

Symbol validation and parse error alignment

Layer / File(s) Summary
Symbol and name validation tightening
crates/codegen/src/compile.rs, crates/codegen/src/symboltable.rs
Function and class definitions now reject __debug__ in names and type parameters; except ... as NAME aliases run name checks before registration; _ is rejected as a pattern capture target; global/nonlocal declarations are mutually exclusive; type-parameter error context phrasing updated.
Parse error helper infrastructure
crates/compiler/src/lib.rs
Introduces adjusted_locations and many predicate/message helpers for indented-block normalization, missing defaults/arguments detection, incompatible string prefix messaging, parenthesized-param detection, starred/keyword/collection LHS detection, comprehension/import/match shape validators, and related helpers used to produce CPython-like diagnostics.
Parse error type translations
crates/compiler/src/lib.rs
Rewrites invalid-assignment-target, invalid-delete/augmented/named-target families and many Ruff-specific parse-error shapes to parse offending text, infer expression kinds, and emit CPython-style OtherError messages with adjusted ranges and targeted wording.
VM/AST error routing and minor runtime tweaks
crates/vm/src/stdlib/_ast.rs, crates/vm/src/vm/vm_new.rs, crates/vm/src/builtins/weakref.rs, crates/vm/src/stdlib/builtins.rs, example_projects/wasm32_without_js/...
parse() and parse_func_type() now route parser failures through CompileError::from_ruff_parse_error; SyntaxErrorInfo preserves select uppercase OtherError messages and drops two prior special-case arms; small changes include using zip for weakref comparisons, a clippy lint attribute tweak in pow, and a wasm import attribute addition.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • youknowone
  • ShaharNaveh

Poem

🐇 I hop through parse trees, nibbling errors small,
I banish naughty names—no debug at all.
Underscore captures? I toss them aside.
Ruff's clatters stitched to CPython, neat and wide.
Hop, hop — diagnostics tidy, now let's glide.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Align all remaining error messages with CPython' directly and clearly describes the main objective of the PR, which is to align parser error messages with CPython across multiple files and contexts.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
crates/codegen/src/compile.rs (1)

4718-4729: ⚡ Quick win

Extract duplicated type parameter validation into a helper method.

The type parameter __debug__ validation logic is duplicated identically in both function and class compilation paths. Extract this into a shared helper to improve maintainability and prevent future divergence.

♻️ Proposed refactor to eliminate duplication

Add a helper method to the compiler struct:

fn validate_type_params_no_debug(&self, type_params: Option<&ast::TypeParams>) -> CompileResult<()> {
    if let Some(params) = type_params {
        for tp in &params.type_params {
            let tp_name = match tp {
                ast::TypeParam::TypeVar(t) => &t.name,
                ast::TypeParam::TypeVarTuple(t) => &t.name,
                ast::TypeParam::ParamSpec(t) => &t.name,
            };
            if tp_name.as_str() == "__debug__" {
                return Err(self.error(CodegenErrorType::Assign("__debug__")));
            }
        }
    }
    Ok(())
}

Then replace the duplicated blocks with a single call in each location:

-        // Reject `def f[__debug__](): ...` type parameter (mirrors class defs).
-        if let Some(params) = type_params {
-            for tp in &params.type_params {
-                let tp_name = match tp {
-                    ast::TypeParam::TypeVar(t) => &t.name,
-                    ast::TypeParam::TypeVarTuple(t) => &t.name,
-                    ast::TypeParam::ParamSpec(t) => &t.name,
-                };
-                if tp_name.as_str() == "__debug__" {
-                    return Err(self.error(CodegenErrorType::Assign("__debug__")));
-                }
-            }
-        }
+        // Reject `def f[__debug__](): ...` type parameter (mirrors class defs).
+        self.validate_type_params_no_debug(type_params)?;

Apply the same simplification in the class compilation path (lines 5307-5318).

Also applies to: 5307-5318

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codegen/src/compile.rs` around lines 4718 - 4729, Extract the
duplicated "__debug__" type-parameter check into a new helper on the compiler
impl (e.g. fn validate_type_params_no_debug(&self, type_params:
Option<&ast::TypeParams>) -> CompileResult<()>) that iterates
params.type_params, extracts the name from ast::TypeParam variants, and returns
Err(self.error(CodegenErrorType::Assign("__debug__"))) if a name equals
"__debug__", otherwise Ok(()). Replace the duplicated validation blocks in the
function compilation path and the class compilation path with a single call to
this helper, keeping existing types (ast::TypeParams, CompileResult) and error
construction unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/compiler/src/lib.rs`:
- Around line 986-1025: The helper parenthesized_param_message currently only
inspects the current physical line, missing multiline cases; update it to scan
backwards from line_start through previous lines to find a preceding "def " or
"lambda" token and then evaluate parenthesis depth across that full span.
Specifically, for "def " (use def_idx search but on the concatenated slice from
the found def position to the error start) walk characters from that def
position counting '(' and ')' to determine if the error position is inside the
function parameter list (return the CPython message if inside), and for "lambda"
search backwards similarly to find a lambda token without a ':' between it and
the error (consider spanning newlines). Keep the existing return messages and
reuse parenthesized_param_message, but replace single-line checks with these
backward-scanning, depth-aware checks so multiline parameter lists are detected.
- Around line 1397-1419: chunk_has_bare_assignment currently treats any
alphanumeric LHS as an identifier; update it to reject literals/keywords by
first checking the trimmed lhs start and exact content: ensure lhs is non-empty
and its first byte is ASCII alphabetic or b'_' (reject if it starts with a
digit, quote, '(' '[' '{', etc.), then ensure the whole lhs matches the
identifier shape (bytes().all(...) as you already do) and is not one of the
Python literal/keyword tokens like "True", "False", or "None" (use a small
static set and compare lhs.eq_ignore_ascii_case or exact match as appropriate).
Return false for those cases so chunk_has_bare_assignment only returns true for
real identifiers.
- Around line 1682-1698: The current is_in_case_pattern only checks the current
line for a "case " header so it misses multi-line case headers; change the logic
to scan backwards from range.start() to find the nearest preceding non-empty
line whose trimmed start begins with "case " (use the existing start and
line_start variables as anchors) and treat the location as being in a case
pattern if such a "case " header exists with indentation (case_indent) less than
the current line and there is no '=' token between the header start and the
range.start(); update the code around is_in_case_pattern, keeping references to
start, line_start, rest, and case_indent, to iterate previous lines (instead of
only checking source[line_start..]) and ensure multiline headers are recognized
before falling back to the match-search logic.

---

Nitpick comments:
In `@crates/codegen/src/compile.rs`:
- Around line 4718-4729: Extract the duplicated "__debug__" type-parameter check
into a new helper on the compiler impl (e.g. fn
validate_type_params_no_debug(&self, type_params: Option<&ast::TypeParams>) ->
CompileResult<()>) that iterates params.type_params, extracts the name from
ast::TypeParam variants, and returns
Err(self.error(CodegenErrorType::Assign("__debug__"))) if a name equals
"__debug__", otherwise Ok(()). Replace the duplicated validation blocks in the
function compilation path and the class compilation path with a single call to
this helper, keeping existing types (ast::TypeParams, CompileResult) and error
construction unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: c64ba8a6-f09d-45ff-8156-2a8997556f5b

📥 Commits

Reviewing files that changed from the base of the PR and between 1385c4e and 4383c21.

⛔ Files ignored due to path filters (4)
  • Lib/test/test_genexps.py is excluded by !Lib/**
  • Lib/test/test_named_expressions.py is excluded by !Lib/**
  • Lib/test/test_patma.py is excluded by !Lib/**
  • Lib/test/test_syntax.py is excluded by !Lib/**
📒 Files selected for processing (5)
  • crates/codegen/src/compile.rs
  • crates/codegen/src/symboltable.rs
  • crates/compiler/src/lib.rs
  • crates/vm/src/stdlib/_ast.rs
  • crates/vm/src/vm/vm_new.rs

Comment thread crates/compiler/src/lib.rs Outdated
Comment thread crates/compiler/src/lib.rs Outdated
Comment thread crates/compiler/src/lib.rs Outdated
@JamesClarke7283
JamesClarke7283 force-pushed the align-aug-assign-delete-set-dict-errors branch from 4383c21 to a84b1a4 Compare May 29, 2026 09:47
@JamesClarke7283

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

The duplicated __debug__ type-parameter validation flagged in the nitpick (crates/codegen/src/compile.rs) has been extracted into a single validate_type_params_no_debug helper used by both the function and class compilation paths, in commit d23ca7e. The earlier reviews were on 4383c2195/a84b1a430, both before that fix.

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

@JamesClarke7283 Thanks for the update — good to know the validate_type_params_no_debug refactor has been applied. I'll kick off a fresh review of the current state of the PR.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ShaharNaveh ShaharNaveh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

tysm for working on this!

On one hand I can't argue with the value this brings, and on the other it feels a bit ridiculous because we are just reimplementing the lexer & parser atp.

I'll wait for @youknowone inputs

Comment thread crates/compiler/src/lib.rs Outdated
Comment thread crates/compiler/src/lib.rs Outdated
Comment thread crates/compiler/src/lib.rs Outdated
Comment thread crates/vm/src/stdlib/builtins.rs Outdated
Comment thread crates/vm/src/vm/vm_new.rs Outdated
@JamesClarke7283
JamesClarke7283 force-pushed the align-aug-assign-delete-set-dict-errors branch 2 times, most recently from 05843ed to 19195ed Compare May 30, 2026 13:41
@JamesClarke7283

Copy link
Copy Markdown
Contributor Author

@ShaharNaveh @youknowone is this okay to merge?

JamesClarke7283 and others added 7 commits June 3, 2026 18:51
Continues RustPython#7928/RustPython#7933/RustPython#7988. Translates many more ruff ParseErrorType
variants to CPython's exact wording in CompileError::from_ruff_parse_error,
and routes ast.parse() / compile(PyCF_ONLY_AST) through the same path so
those messages match too (previously they leaked raw ruff strings). Adds a
few codegen/symtable checks.

Covered: aug-assign/delete/set/dict/f-string/t-string targets; "cannot use
{kind} as import target"; string-prefix incompatibility and "invalid
character 'X' (U+XXXX)"; parenthesized def/lambda params; missing
default/argument value; dict ':' / value syntax; "'elif' block follows an
'else' block"; raise-from; comprehension 'if'; ternary statement keywords;
match "case ... as <target>" -> "cannot use {kind} as pattern target" and
"case ... as _"; __debug__ as def/class/type-param/except name; "name 'x'
is nonlocal and global"; generic type-parameter wording.

Lib/test: drop the now-passing "# TODO: RUSTPYTHON; Wrong error message"
doctest markers and @expectedfailure decorators.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`eval` calls the host `print` function, so rust-lld reported it as an
undefined symbol and the wasm32-unknown-unknown build failed to link
(`kv_get`/`kv_put` are unused, so they were GC'd and did not error).

Annotate the `extern "C"` block with `#[link(wasm_import_module = "env")]`
so the linker emits the host functions as wasm imports from the `env`
module, matching the wasmer host runtime in wasm-runtime/src/main.rs.
Verified: `cargo build` (the CI "check wasm32-unknown without js" step)
now links and produces the .wasm.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test_dictcomps.test_illegal_assignment, test_fstring.test_invalid_string_prefixes
and test_unicode_identifiers.test_invalid now pass thanks to this PR's
error-message alignment, so their @unittest.expectedFailure markers caused
"unexpected success" failures in CI. Remove the obsolete markers (same cleanup
already applied to test_syntax/test_genexps/test_named_expressions/test_patma).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The identical `__debug__` type-parameter rejection in compile_function_def
and compile_class_def is now a single shared helper, eliminating the
duplicated block (addresses a CodeRabbit review note). Behavior-preserving:
`def f[__debug__]`/`class C[__debug__]` still raise "cannot assign to
__debug__" and valid type params still compile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com>
- Report "Invalid star expression" for bare leading `*` in set/dict displays and non-call parenthesised groups (`{*}`, `(*,)`)
- Collapse double-comma in dict/set/list displays (`{1:2,, 3}`, `[1,, 2]`) to "invalid syntax"
- Add is_bare_star_first_in_group helper
- Drop stale "Is this intended to be part of the string?" uppercase-message entry
The bare-star helpers only checked that the error-adjacent token was `*`,
so a binary multiply with a missing operand was mislabeled "Invalid star
expression". `{1 *}`, `(1 *)`, `f(a *)` (and `f(g(a *))`, `{(x) *}`, …) now
correctly report "invalid syntax" like CPython. Require the `*` to start
its slot — mirroring `is_invalid_star_in_subscript` — and share the check
via `slot_starts_with_bare_star`.

The dict-value arm emitted "expression expected after dictionary key and
':'" for any unparseable value; CPython reserves that message for a
genuinely empty value and says "invalid syntax" otherwise. Restrict it to
empty values via `dict_value_is_empty`, so `{1: *}`, `{1: **}`, `{1: 2*}`,
`{1: +}`, `{1: not}`, … now match CPython.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JamesClarke7283
JamesClarke7283 force-pushed the align-aug-assign-delete-set-dict-errors branch from 19195ed to 669fc00 Compare June 3, 2026 17:53
@youknowone

Copy link
Copy Markdown
Member

I am sorry about late review of this. To be honest, I felt hard to review this changes and couldn't justify reimplementing the lexer & parser for this.
I eventually forked ruff parser and want to check how that affect this patch

…lete-set-dict-errors

Resolve conflicts following the review feedback from @youknowone and
@ShaharNaveh: do not reimplement the lexer/parser for error-message
translation. Upstream now aligns parse diagnostics through the
RustPython ruff fork (rustpython-ruff_python_parser 0.15.19-rustpython),
so this drops the source-slicing heuristic helpers this branch had added
to crates/compiler/src/lib.rs in favor of upstream's implementation:

- crates/compiler/src/lib.rs, crates/vm/src/stdlib/_ast.rs,
  crates/vm/src/vm/vm_new.rs, crates/vm/src/stdlib/builtins.rs,
  example_projects/wasm32_without_js: take upstream's versions; the
  string-heuristic error rewriting is superseded by the ruff fork.
- crates/codegen/src/compile.rs: take upstream's version; the
  __debug__ def/class/type-param checks, `case ... as _` rejection and
  "definition of a generic" wording already exist upstream (RustPython#8138),
  now enforced in the symboltable phase.
- Lib/test/test_dictcomps.py, test_fstring.py,
  test_named_expressions.py, test_unicode_identifiers.py: restore the
  expectedFailure markers; those message alignments came from the
  dropped heuristic layer and still differ upstream.

What remains of this PR on top of upstream:

- crates/codegen/src/symboltable.rs: reject global/nonlocal
  co-declaration of the same name with CPython's
  "name 'x' is nonlocal and global", reported at the first directive
  (verified against CPython 3.14.6 for both declaration orders).
- Lib/test/test_syntax.py: drop the now-passing EXPECTED_FAILURE
  doctest marker for the nonlocal/global case.
@JamesClarke7283

Copy link
Copy Markdown
Contributor Author

I am sorry about late review of this. To be honest, I felt hard to review this changes and couldn't justify reimplementing the lexer & parser for this. I eventually forked ruff parser and want to check how that affect this patch

now its not reimplementing the parser, just doing the error messages

@JamesClarke7283

Copy link
Copy Markdown
Contributor Author

Post-merge self-review of the remaining diff (symboltable.rs + test_syntax.py marker). Verified every case against CPython 3.14.6 and found two bugs in the global/nonlocal cross-check, fixed locally:

1. Error priority. The nonlocal and global cross-check was placed first in each match arm, but CPython runs it last, after the parameter/use/annotation/assignment checks. Reproducers:

def f():
    global x
    print(x)
    nonlocal x
# CPython: name 'x' is used prior to nonlocal declaration (line 4)
# before fix: name 'x' is nonlocal and global (line 2)

def f():
    x = 1
    def g():
        nonlocal x
        x = 2
        global x
# CPython: name 'x' is assigned to before global declaration
# before fix: name 'x' is nonlocal and global

Fix: moved the DEF_NONLOCAL/DEF_GLOBAL cross-check to the bottom of the Global/Nonlocal arms.

2. Directive location overwrite. symbol.location = location ran on every directive, so with a duplicated directive the error pointed at the last one; CPython reports the first:

def f():
    global x
    global x
    nonlocal x
# CPython: error at line 2; before fix: line 3

Fix: only store the location when symbol.location.is_none(). (This also makes the existing no binding for nonlocal errors report the first directive, matching CPython.)

Validation: 785/785 codegen unit tests pass (added tests for both behaviors), clippy/rustfmt clean, test_syntax/test_scope/test_global all pass, and message+line+offset now match CPython 3.14.6 across all 18 probed cases (both directive orders, duplicates, class scope, nested, use/assign/annot/param priority). The only remaining delta is end_lineno/end_offset being unset, which is pre-existing for all symboltable errors (SourceLocation is a point) and out of scope here.

The nonlocal-vs-global cross-check ran before the parameter, use,
annotation, and assignment checks in each symboltable arm, but CPython
3.14 runs it last:

- 'global x; print(x); nonlocal x' must report
  "name 'x' is used prior to nonlocal declaration", not
  "name 'x' is nonlocal and global"
- 'nonlocal x; x = 2; global x' must report
  "name 'x' is assigned to before global declaration"

Move the cross-check to the bottom of the Global/Nonlocal arms.

Also keep the location of the first global/nonlocal directive instead
of overwriting it on each one, so a duplicated directive still points
at the first occurrence, matching CPython (this also fixes the
"no binding for nonlocal" location for duplicate directives).

Assisted-by: Kimi Code CLI
@JamesClarke7283

Copy link
Copy Markdown
Contributor Author

The two fixes above are now pushed in d944e0a (codegen: match CPython priority/location for nonlocal-global conflicts), with unit tests covering both behaviors. All pre-commit hooks pass.

@ShaharNaveh ShaharNaveh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

tysm!

pdb's `_exec_in_closure` wraps the debugger input in a generated
`nonlocal <var>` scope, so a user's `global g` conflicts with it. CPython
rejects that with "name 'g' is nonlocal and global", which pdb catches to
fall back to a plain exec. Now that the symbol table raises the same
error, test_pdb_closure produces CPython's output and the
`+EXPECTED_FAILURE` marker inverts it into a failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JamesClarke7283

Copy link
Copy Markdown
Contributor Author

Why this touches test_pdb

The three failing Run snippets and cpython tests jobs (ubuntu/macos/windows) were all the same test: test_pdb_closure, which was marked

>>> with PdbTestInput([  # TODO: RUSTPYTHON # doctest: +NORMALIZE_WHITESPACE +EXPECTED_FAILURE

+EXPECTED_FAILURE inverts the doctest result (DocTestChecker.check_output in Lib/test/support/rustpython.py), so a doctest that starts passing gets reported as a failure. That's exactly what happened here — the symbol table change in this PR fixed the underlying behaviour, so the marker had to go.

It passes for the right reason

The command that exercises this is global g; (lambda: g)(). Because the lambda produces a nested code object, pdb._exec_in_closure doesn't take its early return and instead builds a wrapper of the form:

def __pdb_outer():
  x = None
  g = None
  def __pdb_scope():
    nonlocal x
    nonlocal g
    global g            # <- from the user's input
    (lambda: g)()
  return __pdb_scope.__code__

CPython rejects that generated source with SyntaxError: name 'g' is nonlocal and global, and pdb depends on it doing so: the except Exception: return False around that exec is what makes it fall back to a plain exec(code, globals, locals) in the real scope, printing 1 (the module global) rather than 3 (the function-local g).

Before this PR, RustPython accepted the wrapper, so the statement ran in the generated closure scope and produced the wrong value. With the nonlocal-vs-global cross-check added in symboltable.rs, we now raise the same error at the same point and the whole doctest matches CPython.

Result

The line is now byte-identical to CPython v3.14.5+NORMALIZE_WHITESPACE is upstream's own directive for this doctest, not a local patch, so nothing is being masked.

Verified locally against a release build: test_pdb passes in full (run=210 skipped=1), where it previously failed with 2 failures.

JamesClarke7283 and others added 2 commits August 14, 2026 06:32
All of this happens in the post-parse diagnostic layer; the parser itself
is an external pinned crate and is untouched.

- Reject incompatible string prefixes (`ub''`, `turf"..."`, ...) with
  CPython's message, using the same check order as
  `_PyLexer_check_string_prefixes` so a prefix with several conflicts
  names the same pair.
- Report the "here. Maybe you meant '==' instead of '='?" hint for set,
  dict, f-string and t-string assignment targets, and narrow the scanned
  span to the enclosing statement so an indented `x() = 1` is diagnosed
  like a top-level one. Narrowing is gated on the parser's own error
  offset so an earlier malformed header still wins.
- Consult the import- and match-target scanners before the generic
  "forgot a comma?" heuristic, and let them see `as` targets nested in
  parentheses.
- Skip statement-only diagnostics when compiling in `eval` mode, where
  CPython reports a plain "invalid syntax".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Clears every `TODO: RUSTPYTHON; Wrong error message` marker in
test_syntax.py. Each rule was checked against CPython 3.14.7 over the
valid as well as the invalid forms; the parser stays untouched.

- Reject `/` misplaced in a parameter list before the parser's own
  complaint about a later parameter, matching CPython's `"/" ','` rule.
- Diagnose `if x = 3:` and `if x.a = 3:` like `invalid_named_expression`:
  a bare name suggests `==` or `:=`, another `bitwise_or` names what
  cannot be assigned to, and anything else stays plain invalid syntax.
- Report "expected ':'" for a parenthesized `with` header, "Invalid star
  expression" for a `*` that starts a subscript or display element with
  no usable operand, and CPython's "invalid character" wording where the
  parser stopped.
- Treat `$`, `?` and a backtick as the tokenizer failure they are, so an
  earlier semantic diagnostic on the same line no longer wins.
- Collapse the parser diagnostics that have no CPython counterpart, and
  the dictionary-key wording inside a `case` pattern, to "invalid syntax".
- Extend the string-concatenation hint to any run of atoms between two
  string literals.
- Reject `class C(x for x in L)` in codegen: the parser accepts it, so it
  used to fail at runtime with a TypeError instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
JamesClarke7283 and others added 6 commits August 14, 2026 10:20
Follows the SyntaxError work with the non-syntax `wrong error message`
markers. Each message was compared against CPython 3.14.7 directly.

- structseq: accept CPython's second `dict` argument, reporting "got
  duplicate or unexpected field name(s)" when a key duplicates a
  positional field or names none, and raise "readonly attribute" from
  the field descriptors as member descriptors do.
- posix_spawn: validate `scheduler` in the body so a wrong type says
  "scheduler must be a tuple or None".
- socket.sendto: bind by hand to report "sendto() takes 2 or 3 arguments
  (N given)" and "socket.sendto() takes no keyword arguments".
- bz2: report libbzip2's "Invalid data stream", and make a decompressor
  unusable after a failure instead of resuming from inconsistent state.
- import: resolve `__import__` against the running frame's builtins, so
  `exec(code, {"__builtins__": {}})` raises ImportError, and pass None
  rather than () as the from-list of a plain import.
- symboltable: name the variable as written, not mangled, in
  "assignment expression cannot rebind comprehension iteration variable".
- _pydatetime: raise the message CPython's C _datetime uses when
  subtracting a naive and an aware datetime; the pure-Python module is
  the only implementation here.

test_hashlib and test_ast stay marked: both need the callee's name, or
non-string keyword keys, to reach argument binding, which is a change to
the calling convention rather than to a message.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The authorizer denied every statement. SQLite passes NULL for the
arguments an action does not use — all four are NULL for SQLITE_SELECT —
and `ptr_to_str` raised MemoryError on NULL, which the trampoline
swallowed as SQLITE_DENY before the callback ever ran. Those arguments
now reach the callback as None, matching CPython's callback trace.

That was also the reason the denial message differed: RustPython stopped
at the non-column SQLITE_SELECT check, which SQLite reports as the
generic "not authorized", where CPython reached the column check and got
"access to t2.c1 is prohibited".

Also:
- Bound the argument count before handing it to SQLite, so
  create_function and create_window_function report "'narg' must be
  between -1 and 1000, not -100" instead of a generic creation failure.
- Raise ValueError for every invalid `autocommit`, without the ", not X"
  suffix CPython does not use; a non-integer raised TypeError before.

A working authorizer makes the "concurrent mutation" tests reachable, and
they hang: they call back into the connection from inside a callback,
which deadlocks on the connection mutex. Skipped with that reason until
the locking is re-entrant; CI builds with `sqlite`, so leaving them
running would hang the suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CPython's clinic-generated signatures name the callee and the position of
`data`, and check a duplicated argument before an unknown keyword and an
unknown keyword before the data/string conflict. The generic binder knows
none of that, so the constructors bind by hand:

  hashlib.md5(b'', data=b'')  argument for openssl_md5() given by name
                              ('data') and position (1)
  hashlib.md5(_=None)         openssl_md5() got an unexpected keyword
                              argument '_'

`hashlib.blake2b` resolves to `_blake2.blake2b` rather than the openssl
constructor, so the two share an implementation that takes the name to
report.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Continues the runtime error-message alignment from 0e12017 across the
native stdlib modules: argument validation for the sqlite3, array,
binascii, csv, fcntl, json, locale, lzma, math, mmap, openssl, pystruct,
resource, select, socket, ssl, termios and zlib bindings, plus the shared
 %-formatting (cformat) and marshal format strings in compiler-core.

Each message was matched against the CPython 3.14.7 sources, including
argument-by-name wording and the order CPython validates in.

Assisted-by: Claude:Claude Opus 5
Completes the runtime error-message alignment for the VM, builtins and
protocol layers, all verified against CPython 3.14.7:

- Arity errors: new helpers in function::argument mirror CPython's
  three message styles (_PyArg_CheckPositional, METH_O/noargs wrappers
  and the clinic forms). Builtin functions (abs/chr/len/map/pow/round/
  format/filter/...) and methods across dict, list, set, tuple, str,
  bytes, bytearray, int, float, slice, range and property now report
  CPython's exact wording, including class-qualified names for keyword
  rejections but bare names for positional counts. Native-method arity
  errors no longer count the receiver, and the generic binder renders
  exact/singular forms when min == max.
- Constructor argument errors for str/bytes/bytearray/int/float/set/
  range/slice/complex/enumerate/type, including duplicate name-and-
  position and missing-required-argument wording.
- Semantic messages: concat errors use tp_name semantics (double quotes,
  module-qualified names), sequence-repeat reports "can't multiply
  sequence by non-int", str.join reports "can only join an iterable",
  __index__ conversions, attribute set/delete errors (read-only and
  no-__dict__ suffixes), NoneType immutability, raise vs gen.throw
  wording, unbound-method and wrapper-descriptor messages, and the
  str.translate table "must be" wording.
- Unraisable reports: __del__ failures report "Exception ignored while
  calling deallocator <repr>" and generator-close failures report
  "Exception ignored while closing generator <repr>" with the synthetic
  GeneratorExit error carrying a traceback.
- Format strings: "Single '{'/'}' encountered", "unmatched '{' in
  format spec", "Unknown conversion specifier" (validated at format
  time so _string.formatter_parser stays lenient), and
  "Invalid format specifier '<spec>' for object of type '<T>'".
- PEP 649: attached __annotate__ functions get the
  "<outer>.__annotate__" qualname (gh-137814).
- async for: GET_AITER validates __aiter__/__anext__ presence and
  GET_ANEXT awaits via _PyCoro_GetAwaitableIter with from-cause errors.

capi callers updated for the new method signatures.

Assisted-by: ZCode:GLM-5.3
Assisted-by: Claude:Claude Opus 5
Removes 37 stale TODO: RUSTPYTHON markers across 26 test files now that
the corresponding error messages match CPython 3.14: async-for errors
(test_coroutines), str()/Template/concat errors (test_str, test_tstring,
string_tests), unraisable reports (test_exceptions, test_generators),
__annotate__ qualnames (test_type_annotations), constructor arity
(test_range, test_posix, test_sqlite3, test_struct), find-family
messages (test_bytes), attribute errors (test_class, test_descr,
test_descrtut), exec/eval arguments (test_pdb, test_extcall), map
(test_itertools), marshal readers, lzma filter specs, enum, json
scanstring (C variant only; the pure-Python scanner still lacks the
OverflowError, so that variant keeps a scoped marker), mmap resize and
pdb's exec/eval doctests.

Assisted-by: ZCode:GLM-5.3
@JamesClarke7283

Copy link
Copy Markdown
Contributor Author

Runtime error-message alignment (d1aad65, 5605a25, 94c901e)

Following the parser-side alignment earlier in this PR, these commits complete the runtime error messages: VM, builtins, protocol layers, native stdlib modules, and the format-string machinery — all verified message-by-message against the CPython 3.14.7 sources, with no parser changes.

How it was verified

  • Differential harness: ~370 error-raising snippets executed side-by-side under CPython 3.14.7 and RustPython, comparing (exception type, exact message). Started at 177 mismatches, now 0.
  • Full CPython test suite: 41,051 tests, 398/402 modules pass. The 4 remaining failures are not error-message issues: test_future (REPL SyntaxError wording — parser territory, out of scope here), test_socket (UDP-LITE support), test_sysconfig (install paths), test_c_locale_coercion (locale coercion).
  • 37 stale TODO: RUSTPYTHON expectedFailure markers removed, each confirmed passing.

What changed

Arity errors — new helpers in function::argument mirror CPython's three distinct styles, because CPython itself uses three:

CPython mechanism Message form Example
_PyArg_CheckPositional X expected at least/at most N arguments, got M divmod expected 2 arguments, got 1
METH_O / noargs wrappers X() takes exactly one argument (N given) / X() takes no arguments set.add() takes exactly one argument (2 given)
Argument Clinic X() missing required argument 'y' (pos N) pow() missing required argument 'exp' (pos 2)

A CPython quirk reproduced faithfully: keyword rejections use class-qualified names (dict.get() takes no keyword arguments) while positional-arity errors use bare names (insert expected 2 arguments, got 0). Native-method arity errors no longer count the receiver, and the generic binder now renders exact/singular forms when min == max.

Constructor argument errors for str/bytes/bytearray/int/float/set/range/slice/complex/enumerate/type, including the duplicate name-and-position form (argument for str() given by name ('encoding') and position (2)).

Semantic messages — concat errors with tp_name semantics (double quotes, module-qualified: can only concatenate str (not "string.templatelib.Template") to str, can't concat int to bytes), can't multiply sequence by non-int of type 'str', can only join an iterable, attribute set/delete errors (read-only and no-__dict__ suffixes), NoneType immutability, the raise vs gen.throw wording split, unbound-method messages, and __index__ conversions.

Unraisable reports__del__ failures now report Exception ignored while calling deallocator <repr> and generator-close failures Exception ignored while closing generator <repr>, the latter carrying a traceback for the synthetic GeneratorExit error.

Format stringsSingle '{' encountered in format string, unmatched '{' in format spec, Unknown conversion specifier x (validated at format time so _string.formatter_parser stays lenient like CPython's), and Invalid format specifier '%z' for object of type 'int'.

PEP 649 — attached __annotate__ functions get the <outer>.__annotate__ qualname (gh-137814), matching f.__annotate__.__qualname__ == "f.__annotate__".

async forGET_AITER validates __aiter__/__anext__ presence with CPython's wording and GET_ANEXT awaits via _PyCoro_GetAwaitableIter, re-raising failures from cause.

The stdlib commit (d1aad65) continues the module-by-module alignment from 0e12017 across the native bindings (sqlite3, array, binascii, csv, fcntl, json, locale, lzma, math, mmap, openssl, pystruct, resource, select, socket, ssl, termios, zlib) plus the shared cformat/marshal strings.

Test impact

  • Markers removed: test_coroutines (async-for), test_str/test_tstring/string_tests, test_exceptions/test_generators (unraisable), test_type_annotations, test_range, test_posix, test_sqlite3, test_struct, test_bytes, test_class, test_descr, test_descrtut, test_pdb, test_extcall, test_itertools, test_marshal, test_lzma, test_enum, test_json (C variant), test_mmap.
  • One new scoped marker: TestPyScanstring.test_overflow (the pure-Python scanner lacks the OverflowError for out-of-range indices — behavior gap, not wording).
  • Rust workspace tests, capi tests (102/102), clippy and rustfmt all clean.

@ShaharNaveh

Copy link
Copy Markdown
Contributor

@JamesClarke7283 can you please fix the merge/rebase

…e aligned messages

Resolutions, verified against CPython 3.14.7 and the test suite:

- marshal: adopt upstream's placeholder/back-reference reader and writer
  (WriterRefEntry with incomplete bookkeeping, make_*_placeholder,
  set/insert item hooks) while keeping this branch's error paths - the
  0x7fff_ffff "too many objects" cap, negative size checks, NullIn*
  wrapping, readinto-based file reader, and read_error() mapping.
  Python errors raised while building containers now propagate through
  the pending-error cell instead of collapsing to "bad marshal data"
  (marshal.loads of a dict keyed by itself reports CPython's TypeError).
- structseq: upstream's hidden_field_names parameter and
  StructSequenceNewArgs, plus this branch's structseq_field() so field
  assignment still reports "readonly attribute".
- sqlite3: upstream's dynamic SQLITE_LIMIT_FUNCTION_ARG bound in
  check_num_params() (replacing the hardcoded 1000), dbconfig support
  and authorizer NULL-args fix, with this branch's plain autocommit
  message (CPython 3.14.7 has no type suffix there) and the
  create_window_function version guard.
- posix: upstream's collect_signals() refactored onto CPython's
  _Py_Sigset_Converter semantics (index conversion, saturating
  overflow, "signal number N out of range [1; 64]" naming the real
  platform bound).
- hash modules, lzma, float, set, str, time: upstream refactors with
  this branch's CPython-named checks kept in front.

Also fixes a gap the merge exposed: dict operations now report
"cannot use 'X' as a dict key (unhashable type: 'X')" for unhashable
keys in every context (get/del/contains/setdefault/fromkeys), matching
CPython's insertdict wording; sets unwrap to the plain inner hash
error for their "... as a set element (...)" form. Un-marks
test_dict.test_unhashable_key.

Assisted-by: ZCode:GLM-5.3
@JamesClarke7283

Copy link
Copy Markdown
Contributor Author

@JamesClarke7283 can you please fix the merge/rebase

On it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants