Skip to content

Align all remaining error messages with CPython - #7993

Open
JamesClarke7283 wants to merge 18 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 18 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_builtin.py (TODO: 14)

dependencies:

dependent tests: (no tests depend on builtin)

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

dependencies:

dependent tests: (no tests depend on tstring)

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

dependencies:

dependent tests: (no tests depend on bytes)

[ ] 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

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

dependencies:

dependent tests: (no tests depend on syntax)

[ ] 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_memoryview.py (TODO: 6)

dependencies:

dependent tests: (no tests depend on memoryview)

[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

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

dependencies:

dependent tests: (no tests depend on format)

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

dependencies:

dependent tests: (no tests depend on structseq)

[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

[ ] 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)

[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] 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

[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

[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_asyncgen.py (TODO: 3)

dependencies:

dependent tests: (no tests depend on asyncgen)

[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

[ ] 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] lib: cpython/Lib/threading.py
[x] lib: cpython/Lib/_threading_local.py
[ ] test: cpython/Lib/test/test_threading.py (TODO: 17)
[x] test: cpython/Lib/test/test_threadedtempfile.py
[x] test: cpython/Lib/test/test_threading_local.py (TODO: 1)

dependencies:

  • threading

dependent tests: (163 tests)

  • threading: test_android test_asyncio test_bytes test_bz2 test_code test_concurrent_futures test_context test_contextlib test_ctypes test_decimal test_docxmlrpc test_email test_enum test_external_inspection test_fork1 test_frame test_ftplib test_functools test_gc test_hashlib test_httplib test_httpservers test_imaplib test_importlib test_inspect test_io test_ioctl test_itertools test_largefile test_linecache test_logging test_memoryview test_opcache test_pathlib test_poll test_poplib test_pyrepl test_queue test_robotparser test_sched test_signal test_smtplib test_socket test_socketserver test_sqlite3 test_ssl test_subprocess test_super test_sys test_syslog test_termios test_threadedtempfile test_threading test_threading_local test_time test_urllib2_localnet test_weakref test_winreg test_wsgiref test_xmlrpc test_zstd
    • asyncio: test_asyncio test_os test_pdb test_unittest
    • bdb: test_bdb
    • concurrent.futures._base: test_concurrent_futures
    • concurrent.futures.process: test_compileall test_concurrent_futures
    • concurrent.futures.thread: test_genericalias
    • dummy_threading: test_dummy_threading
    • http.cookiejar: test_http_cookiejar test_urllib2
      • urllib.request: test_pathlib test_pydoc test_sax test_site test_urllib test_urllib2net test_urllibnet
    • importlib.util: test_asdl_parser test_ctypes test_doctest test_importlib test_pkgutil test_py_compile test_reprlib test_runpy test_zipfile test_zipimport
      • pkgutil: test_pyrepl
      • py_compile: test_argparse test_cmd_line_script test_importlib test_modulefinder test_multiprocessing_main_handling
      • pyclbr: test_pyclbr
      • sysconfig: test_c_locale_coercion test_cmd_line test_dtrace test_embed test_launcher test_osx_env test_peg_generator test_posix test_pyexpat test_regrtest test_support test_sysconfig test_tools test_venv
      • zipfile: test_shutil test_zipapp test_zipfile test_zipfile64 test_zipimport_support
    • logging: test_unittest
      • hashlib: test_hmac test_tarfile test_unicodedata
    • multiprocessing: test_fcntl test_re
    • queue: test_dummy_thread
    • subprocess: test_atexit test_audit test_ctypes test_faulthandler test_file_eintr test_gzip test_json test_msvcrt test_ntpath test_platform test_plistlib test_pyrepl test_quopri test_repl test_script_helper test_select test_sys_settrace test_tempfile test_traceback test_unittest test_utf8_mode test_wait3 test_webbrowser test_xpickle
      • ctypes.util: test_ctypes
      • ensurepip: test_ensurepip
      • platform: test__locale test__osx_support test_baseexception test_builtin test_cmath test_math test_mimetypes test_strptime
    • sysconfig:
      • trace: test_trace
    • zipfile:
      • shutil: test_filecmp test_glob test_string_literals test_unicode_file test_zoneinfo

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

dependencies:

dependent tests: (no tests depend on extcall)

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

dependencies:

dependent tests: (no tests depend on range)

[ ] 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/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

[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_mmap.py (TODO: 24)

dependencies:

dependent tests: (2 tests)

  • mmap: test_os
    • multiprocessing.shared_memory: test_genericalias

[ ] 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)

[ ] 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_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)

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
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

@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.

@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!

@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

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

@JamesClarke7283

Copy link
Copy Markdown
Contributor Author

@JamesClarke7283 can you please fix the merge/rebase

On it

@JamesClarke7283
JamesClarke7283 force-pushed the align-aug-assign-delete-set-dict-errors branch from b05af61 to fca3d8b Compare August 16, 2026 17:45
JamesClarke7283 and others added 8 commits August 16, 2026 18:56
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>
- 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
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>
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>
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>
JamesClarke7283 and others added 4 commits August 16, 2026 18:56
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
JamesClarke7283 force-pushed the align-aug-assign-delete-set-dict-errors branch from fca3d8b to 684929d Compare August 16, 2026 19:53
@JamesClarke7283

Copy link
Copy Markdown
Contributor Author

Rebased onto current main

History is now linear on top of upstream/main (no merge commits), and the conflicts against the recent landings were resolved as follows:

  • PEP 688 buffer rework (Implement PEP 688 and rework the buffer protocol around managed exports #8523): memoryview keeps upstream's export/restriction machinery; this branch's message layer was re-grafted on top — memoryview: a bytes-like object is required, not 'X' on non-buffer inputs, invalid type/value for format for __setitem__ (the TypeError/ValueError split now also classifies struct.error's "is not an integer" wording), and the cannot cast between two non-byte formats check. marshal.load streams through readinto again so a lying reader reports read() returned too much data: ... like CPython, while loads keeps marshal data too short / bad marshal data (...) per variant (Eof/EofObject/EofByte all surface as EOFError, as in r_object).
  • marshal back-reference reader: adopted upstream's placeholder/incomplete-entry design wholesale — its read_len/BadSize already renders CPython's "bad marshal data (tuple size out of range)" wording, subsuming this branch's per-arm checks.
  • struct/pystruct: took upstream's FormatSpec-based pack, whose get_int_or_index produces "required argument is not an integer" / "'B' format requires 0 <= number <= 255" directly.
  • socket: getaddrinfo reworked onto upstream's ArgStrOrBytesLike host path with this branch's port semantics — int ports short-circuit to their decimal string, string ports check surrogates, anything else raises OSError: Int or String expected (CPython's setipaddr).
  • select/array/_asyncio: upstream's streaming FD limit ("too many file descriptors in select()"), ArrayItem pre-conversion (array's __setitem__ still reports array assignment index out of range), and the awaited-by locking fix.

The only remaining test_socket failures are UDPLITE ones — this kernel has no IPPROTO_UDPLITE, CPython raises the same OSError: [Errno 93] Protocol not supported.

- posix: qualify the PyTuple path in the posix_spawn scheduler check so
  macOS (where the bare import is not in scope) compiles.
- coroutine: gate the Radium import on not(threading); without the
  feature lasti is a Cell and its load() comes from the trait. Fixes the
  wasm build and miri.
- marshal (compiler-core): drop the duplicate NullObject Display arm
  left by the conflict resolution and the now-unnecessary usize cast;
  both were -Dwarnings clippy errors.
- socket: getaddrinfo's IDNA path no longer clones the host str
  (redundant_clone), passing it by value.
- str: port upstream's char-aware count() body behind this branch's
  FuncArgs front - an empty needle is counted in characters (chars + 1),
  not encoded byte positions, so "가나다".count("") is 4 again. Also
  drops a duplicate #[inline] and a redundant #[must_use] that tripped
  -Dwarnings, and py_split_str takes an #[expect(too_many_arguments)]
  like its anystr counterpart.

Verified with the CI commands: workspace clippy -Dwarnings with the CI
feature set and excludes, the sandbox-mode checks, the snippets suite
(builtin_str green), and test_str/test_marshal/test_socket (only the
UDPLITE tests fail here - this kernel has no IPPROTO_UDPLITE).

Assisted-by: ZCode:GLM-5.3
- socket: getaddrinfo's port accepts str, bytes and bytearray service
  names like CPython's setipaddr; anything else (floats, lists) raises
  OSError "Int or String expected". The int shortcut (decimal string)
  stays. This un-breaks asyncio's create_connection, whose resolution
  path fed the service name through getaddrinfo on all three CI
  operating systems.
- socket: the PyInt import moved into the linux-only sendmsg_afalg, so
  macOS/Windows builds no longer see an unused import under -Dwarnings.
- mmap: the flags field is now cfg(linux/netbsd), matching its only
  reader (the mremap expansion check), so macOS clippy no longer flags
  it as never read.
- test_threading: test_join_daemon_thread_in_finalization stays an
  expected failure - it passed once on CI runners but fails
  deterministically here because daemon-thread shutdown ordering
  differs; the marker documents the dependency.

Assisted-by: ZCode:GLM-5.3
- mmap: the re-bound `flags` in py_new is only stored on linux/netbsd
  (the mremap expansion check is its only reader), so allow the unused
  variable on other unixes.
- os: dir_fd_and_fd_invalid is only called from the unix chown; allow
  dead_code off unix instead of gating the definition.

With these, clippy -Dwarnings passes with the CI feature set on every
platform: the previous run had only these two jobs failing.

Assisted-by: ZCode:GLM-5.3
- array: the intermediate int/unsigned-int range conversions are no-ops
  on Windows (c_long is i32, c_ulong is u32 there); allow the
  useless-conversion lint there while keeping CPython's two-step error
  order on LP64 platforms (verified against CPython for H/I overflow,
  negative, and 2**40 inputs).
- socket: PyIter moved from the module import list into the unix-only
  sendmsg, removing the unused import on Windows.

Assisted-by: ZCode:GLM-5.3
c_ulong is u32 there, which is the Self of the impl, so clippy's use_self
fires; on LP64 platforms it stays raw::c_ulong to keep CPython's
"Python int too large to convert to C unsigned long" step.

Assisted-by: ZCode:GLM-5.3
@ShaharNaveh

Copy link
Copy Markdown
Contributor

@JamesClarke7283 can you please run:

git fetch upstream
git rebase upstream/main

assuming that upstream is pointing to this project and not your fork

Reviewing the whole PR against CPython 3.14 surfaced a batch of
methods whose argument errors still went through the generic binder
("Expected type 'int' but 'float' found."), plus three re-entrancy
safety regressions that the new conversion order exposed:

- bytes/bytearray expandtabs, hex(bytes_per_sep), zfill (bytes) and
  the padding family (center/ljust/rjust width) now convert with
  PyNumber_AsSsize_t semantics ("'float' object cannot be
  interpreted as an integer", OverflowError at the ssize_t bounds).
- find-family start/end and startswith/endswith bounds convert as
  slice indices ("slice indices must be integers or None or have an
  __index__ method"), clamp like _PyEval_SliceIndex instead of
  erroring at the bounds, and - for bytes/bytearray - are validated
  before the needle so a bad index reports first, as CPython's
  parsing order does. The needle itself is also converted after the
  slice indices.
- bytes/bytearray decode() reports "decode() argument 'encoding'/
  'errors' must be str, not X" and join() reports "can only join an
  iterable".
- hex(sep) measures the separator with PyObject_Length semantics
  ("object of type 'float' has no len()", validated before the
  empty-input and bytes_per_sep==0 early returns), and the fillchar
  length error names type and length the way CPython's stringlib
  does ("center(): argument 2 must be a byte string of length 1,
  not a bytes object of length 2").
- gh-143195 / gh-142560 re-entrancy: bytearray's find/index/rfind/
  rindex/count/__contains__/split/rsplit/hex and memoryview's hex
  now run their argument conversions under an export guard, so a
  re-entrant __len__/__index__/__buffer__ that resizes the buffer
  raises BufferError ("Existing exports of data: object cannot be
  re-sized" / "memoryview has 1 exported buffer") as in CPython.
  memoryview.release() refuses while exports are live.

Un-marks the tests those fixes make pass (test_hex_use_after_free,
test_search_methods_reentrancy_raises_buffererror).

Assisted-by: ZCode:GLM-5.3
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