Skip to content

Fix subclass right-op dispatch for Python classes#7462

Merged
youknowone merged 4 commits intoRustPython:mainfrom
rlaisqls:fix/subclass-right-ops
Mar 19, 2026
Merged

Fix subclass right-op dispatch for Python classes#7462
youknowone merged 4 commits intoRustPython:mainfrom
rlaisqls:fix/subclass-right-ops

Conversation

@rlaisqls
Copy link
Contributor

@rlaisqls rlaisqls commented Mar 18, 2026

When both operands are Python-defined classes, the inherited right-op was incorrectly given priority over the base class left-op, even when the subclass didn't override anything.

class A:
    def __floordiv__(self, other): return "A.__floordiv__"
    def __rfloordiv__(self, other): return "A.__rfloordiv__"

class B(A):
    pass

A() // B()
# Expected: "A.__floordiv__" (B didn't override, so base left-op wins)
# Got:      "A.__rfloordiv__" (inherited right-op was incorrectly prioritized)

The root cause is that binary_op1 and ternary_op compared left(A) vs
right(B) slot pointers to decide whether two types share the same implementation.

Since RustPython uses separate left/right slot functions (unlike CPython's single combined slot),
this comparison always returned "different" for Python classes, causing the
subclass right-op path to fire unconditionally.

Fix:

  • Compare left(A) vs left(B) instead, which correctly identifies same-implementation types
  • Add method_is_overloaded (similar to CPython method_is_overloaded) to check whether the subclass actually overrides the right-op at the Python method level
  • Add right_method_name to PyNumberBinaryOp / PyNumberTernaryOp for the op-to-dunder mapping needed by method_is_overloaded
  • Apply to both binary_op1 and ternary_op

Summary by CodeRabbit

  • New Features

    • Added support for right-side numeric operator methods so objects can provide r* implementations for binary and ternary numeric ops.
    • New accessors and helpers expose and identify right-side operator slots for use by extensions and tooling.
  • Bug Fixes

    • Improved operator dispatch so right-side methods and method overloading between related types are resolved more accurately.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • Lib/test/test_collections.py is excluded by !Lib/**

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 49b2cab1-b180-4b4b-99ac-fd894d59a06c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds explicit reflected ("right-side") numeric slots and helpers mapping ops to r* method names, and updates VM operator dispatch to consider right-side slots and subclass overloads when resolving binary and ternary operations.

Changes

Cohort / File(s) Summary
Number protocol: right-side slots & helpers
crates/vm/src/protocol/number.rs
Added right_method_name() for PyNumberBinaryOp and PyNumberTernaryOp; introduced public right_* AtomicCell fields on PyNumberSlots (e.g., right_add, right_subtract, right_multiply, right_power, right_matrix_multiply, etc.) and wired From<&PyNumberMethods> to populate them.
VM operator dispatch changes
crates/vm/src/vm/vm_ops.rs
Added method_is_overloaded() helper and updated binary/ternary dispatch to prefer right-side slots when operand classes differ and to check subclass overloads (aligning behavior with CPython's method resolution semantics).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Caller
    participant VM as VM (vm_ops)
    participant Left as LeftType.__op__
    participant Right as RightType.__rop__
    VM->>Left: lookup left slot (left_*)
    alt left slot exists and returns value
        Left-->>VM: result or NotImplemented
    else classes differ or left returned NotImplemented
        VM->>Right: lookup right slot (right_*)
        alt subclass and method_is_overloaded -> true
            Right-->>VM: result or NotImplemented
        else
            Right-->>VM: NotImplemented
        end
    end
    VM-->>Caller: final result or raise TypeError
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • youknowone

Poem

🐰 I hop from left to right with cheer,
New r doors open wide and clear.
VM listens, checks subclass and art,
Reflected slots play their part.
A rabbit applauds this numeric start.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix subclass right-op dispatch for Python classes' directly describes the main change: fixing incorrect dispatch of right-operator methods for Python-defined subclasses. It is specific, concise, and accurately reflects the primary objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 18, 2026

📦 Library Dependencies

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

[x] lib: cpython/Lib/opcode.py
[ ] lib: cpython/Lib/_opcode_metadata.py
[x] test: cpython/Lib/test/test__opcode.py (TODO: 2)
[x] test: cpython/Lib/test/test_opcodes.py

dependencies:

  • opcode (native: _opcode, builtins)
    • _opcode_metadata
    • _opcode_metadata

dependent tests: (44 tests)

  • opcode: test__opcode test_code test_compile test_dis test_peepholer
    • dis: test_ast test_compiler_assemble test_dtrace test_fstring test_inspect test_monitoring test_opcache test_patma test_positional_only_arg
      • bdb: test_bdb
      • inspect: test_abc test_argparse test_asyncgen test_buffer test_builtin test_collections test_coroutines test_decimal test_enum test_functools test_generators test_grammar test_ntpath test_operator test_posixpath test_pydoc test_signal test_sqlite3 test_traceback test_type_annotations test_types test_typing test_unittest test_yield_from test_zipimport test_zoneinfo
      • modulefinder: test_importlib test_modulefinder
      • trace: test_trace

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

dependencies:

dependent tests: (no tests depend on class)

[ ] lib: cpython/Lib/collections
[x] lib: cpython/Lib/_collections_abc.py
[x] test: cpython/Lib/test/test_collections.py (TODO: 2)
[x] test: cpython/Lib/test/test_deque.py (TODO: 3)
[x] test: cpython/Lib/test/test_defaultdict.py (TODO: 1)
[x] test: cpython/Lib/test/test_ordered_dict.py (TODO: 8)

dependencies:

  • collections (native: _collections, _weakref, itertools, sys)
    • _collections_abc
    • _collections_abc, abc, annotationlib, copy, heapq, keyword, operator, reprlib, warnings

dependent tests: (302 tests)

  • collections: test_annotationlib test_array test_asyncio test_bisect test_builtin test_c_locale_coercion test_call test_collections test_configparser test_contains test_copy test_csv test_ctypes test_defaultdict test_deque test_descr test_dict test_dictviews test_enum test_exception_group test_file test_fileinput test_fileio test_frame test_funcattrs test_functools test_genericalias test_hash test_httpservers test_inspect test_io test_ipaddress test_iter test_iterlen test_json test_logging test_math test_monitoring test_ordered_dict test_pathlib test_patma test_pickle test_plistlib test_pprint test_pydoc test_random test_reprlib test_richcmp test_set test_shelve test_sqlite3 test_statistics test_string test_struct test_sys test_traceback test_tuple test_types test_typing test_unittest test_urllib test_userdict test_userlist test_userstring test_weakref test_weakset test_with
    • ast: test_ast test_compile test_compiler_codegen test_dis test_fstring test_future_stmt test_site test_ssl test_type_comments test_ucn test_unparse
      • annotationlib: test_type_annotations test_type_params
      • dbm.dumb: test_dbm_dumb
      • inspect: test_abc test_argparse test_asyncgen test_buffer test_code test_coroutines test_decimal test_generators test_grammar test_ntpath test_operator test_posixpath test_signal test_yield_from test_zipimport test_zoneinfo
      • pyclbr: test_pyclbr
      • traceback: test_asyncio test_code_module test_contextlib test_contextlib_async test_dictcomps test_exceptions test_http_cookiejar test_importlib test_listcomps test_pyexpat test_setcomps test_socket test_subprocess test_threadedtempfile test_threading test_unittest
    • asyncio: test_asyncio test_os test_sys_settrace
    • concurrent.futures._base: test_concurrent_futures
    • dbm.sqlite3: test_dbm_sqlite3
    • difflib: test_difflib
    • dis: test__opcode test_compiler_assemble test_dtrace test_opcache test_peepholer test_positional_only_arg
      • bdb: test_bdb
      • modulefinder: test_importlib test_modulefinder
      • trace: test_trace
    • email.feedparser: test_email
    • http.client: test_docxmlrpc test_hashlib test_unicodedata test_urllib2 test_wsgiref test_xmlrpc
      • urllib.request: test_sax test_urllib2_localnet test_urllib2net test_urllibnet
    • importlib.metadata: test_importlib
    • inspect:
      • cmd: test_cmd
      • dataclasses: test__colorize test_ctypes test_regrtest
      • pkgutil: test_pkgutil test_runpy
      • rlcompleter: test_rlcompleter
    • logging: test_support
      • hashlib: test_hmac test_smtplib test_tarfile
      • multiprocessing.util: test_compileall test_concurrent_futures
      • venv: test_venv
    • multiprocessing: test_fcntl test_memoryview test_multiprocessing_main_handling test_re
    • platform: test__locale test__osx_support test_android test_baseexception test_cmath test_ctypes test_mimetypes test_platform test_posix test_shutil test_sysconfig test_time test_winreg
    • pprint: test_htmlparser test_sys_setprofile
      • pickle: test_bool test_bytes test_bz2 test_codecs test_concurrent_futures test_ctypes test_email test_enumerate test_fractions test_http_cookies test_itertools test_list test_lzma test_memoryio test_minidom test_picklebuffer test_pickletools test_range test_slice test_str test_type_aliases test_unittest test_uuid test_xml_dom_minicompat test_xml_etree test_zipfile test_zlib test_zoneinfo
    • queue: test_dummy_thread test_sched
    • selectors: test_selectors
      • socket: test_epoll test_exception_hierarchy test_ftplib test_httplib test_imaplib test_kqueue test_largefile test_mailbox test_mmap test_poplib test_pty test_smtpnet test_socketserver test_stat test_timeout test_urllib_response
      • subprocess: test_atexit test_audit test_cmd_line test_cmd_line_script test_ctypes test_faulthandler test_file_eintr test_gc test_gzip test_json test_launcher test_msvcrt test_osx_env test_poll test_py_compile test_quopri test_repl test_script_helper test_select test_tempfile test_unittest test_utf8_mode test_wait3 test_webbrowser test_zipfile
    • shlex: test_shlex
    • shutil: test_filecmp test_glob test_importlib test_string_literals test_unicode_file
      • ctypes.util: test_ctypes
      • ensurepip: test_ensurepip
      • pathlib: test_importlib test_pathlib test_tomllib test_tools test_winapi test_zipapp test_zstd
      • tempfile: test_doctest test_importlib test_linecache test_pkg test_tabnanny test_termios test_tokenize test_winconsoleio test_zipfile64
      • zipfile: test_zipfile
    • statistics:
      • random: test_complex test_context test_devpoll test_float test_heapq test_int test_long test_numeric_tower test_pow test_queue test_sort test_strtod test_thread
    • string: test_email test_fnmatch test_secrets test_string
    • threading: test_concurrent_futures test_ctypes test_fork1 test_importlib test_robotparser test_super test_syslog test_threading_local
      • dummy_threading: test_dummy_threading
      • sysconfig: test_tools
    • traceback:
      • timeit: test_timeit
    • urllib.parse: test_urlparse
    • wave: test_wave

[ ] test: cpython/Lib/test/test_descr.py (TODO: 43)
[ ] test: cpython/Lib/test/test_descrtut.py (TODO: 3)

dependencies:

dependent tests: (no tests depend on descr)

[x] lib: cpython/Lib/dis.py
[ ] test: cpython/Lib/test/test_dis.py (TODO: 37)

dependencies:

  • dis

dependent tests: (70 tests)

  • dis: test__opcode test_ast test_code test_compile test_compiler_assemble test_dis test_dtrace test_fstring test_inspect test_monitoring test_opcache test_patma test_peepholer test_positional_only_arg
    • bdb: test_bdb
    • inspect: test_abc test_argparse test_asyncgen test_buffer test_builtin test_collections test_coroutines test_decimal test_enum test_functools test_generators test_grammar test_ntpath test_operator test_posixpath test_pydoc test_signal test_sqlite3 test_traceback test_type_annotations test_types test_typing test_unittest test_yield_from test_zipimport test_zoneinfo
      • ast: test_compiler_codegen test_future_stmt test_site test_ssl test_type_comments test_ucn test_unparse
      • asyncio: test_asyncio test_contextlib_async test_logging test_os test_sys_settrace test_unittest
      • cmd: test_cmd
      • dataclasses: test__colorize test_copy test_ctypes test_genericalias test_pprint test_regrtest
      • importlib.metadata: test_importlib
      • pkgutil: test_pkgutil test_runpy
      • rlcompleter: test_rlcompleter
      • trace: test_trace
      • xmlrpc.server: test_docxmlrpc test_xmlrpc
    • modulefinder: test_importlib test_modulefinder

[x] test: cpython/Lib/test/test_format.py (TODO: 6)

dependencies:

dependent tests: (no tests depend on format)

[x] test: cpython/Lib/test/test_math.py
[x] test: cpython/Lib/test/test_math_property.py

dependencies:

dependent tests: (229 tests)

  • math: test_abstract_numbers test_asyncio test_builtin test_cmath test_compile test_complex test_ctypes test_decimal test_descr test_float test_fractions test_json test_long test_math test_math_property test_monitoring test_numeric_tower test_pow test_random test_socket test_statistics test_struct test_time test_zipfile
    • fractions: test_buffer test_compare test_itertools test_operator test_os test_string
      • statistics: test_signal
    • random: test_asyncio test_bisect test_bz2 test_collections test_context test_dbm_dumb test_deque test_devpoll test_dict test_dummy_thread test_email test_functools test_heapq test_hmac test_importlib test_int test_io test_logging test_lzma test_mmap test_ordered_dict test_poll test_posixpath test_pprint test_queue test_regrtest test_richcmp test_selectors test_set test_shutil test_sort test_strtod test_sys test_tarfile 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_smtplib test_urllib2
      • imaplib: test_imaplib
      • secrets: test_secrets
      • tempfile: test_argparse test_ast test_asyncio test_bytes test_cmd_line test_compileall test_concurrent_futures test_contextlib test_csv test_ctypes test_dis test_doctest test_ensurepip test_faulthandler test_filecmp test_fileinput test_genericalias test_hashlib test_importlib test_inspect test_launcher test_linecache test_mailbox test_modulefinder test_ntpath test_pathlib test_pickle test_pkg test_pkgutil test_posix test_py_compile test_pydoc test_runpy test_site test_string_literals test_subprocess test_support test_tabnanny test_tempfile test_termios test_threadedtempfile test_tomllib test_urllib test_urllib_response test_venv test_winconsoleio test_zipapp test_zipfile64 test_zoneinfo
    • reprlib: test_reprlib
      • bdb: test_bdb
      • collections: test_annotationlib test_array test_asyncio test_c_locale_coercion test_call test_configparser test_contains test_copy test_ctypes test_defaultdict test_dictviews test_enum test_exception_group test_file test_fileio test_frame test_funcattrs test_hash test_ipaddress test_iter test_iterlen test_json test_pathlib test_patma test_plistlib test_shelve test_sqlite3 test_tuple test_types test_typing test_unittest test_userdict test_userlist test_userstring test_weakset test_with
      • dataclasses: test__colorize test_ctypes
    • selectors: test_asyncio
      • socket: test_asyncio test_epoll test_exception_hierarchy test_ftplib test_httplib test_kqueue test_largefile test_poplib test_pty test_smtpnet test_socketserver test_ssl test_stat test_timeout test_urllib2net test_urllibnet test_xmlrpc
      • socketserver: test_wsgiref
      • subprocess: test_android test_asyncio test_atexit test_audit test_cmd_line_script test_ctypes test_dtrace test_file_eintr test_gc test_gzip test_json test_msvcrt test_osx_env test_platform test_quopri test_repl test_script_helper test_select test_sysconfig test_unittest test_utf8_mode test_wait3 test_webbrowser
    • urllib.parse: test_urllib2_localnet test_urlparse
      • http.client: test_docxmlrpc test_ucn test_unicodedata
      • http.cookiejar: test_http_cookiejar
      • http.server: test_robotparser
      • logging.handlers: test_concurrent_futures
      • mimetypes: test_mimetypes
      • pathlib: test_dbm_sqlite3 test_importlib test_pathlib test_tomllib test_tools test_winapi test_zipfile
      • xml.sax.saxutils: test_sax

[x] lib: cpython/Lib/types.py
[ ] test: cpython/Lib/test/test_types.py (TODO: 6)

dependencies:

  • types

dependent tests: (52 tests)

  • types: test_annotationlib test_ast test_asyncgen test_asyncio test_builtin test_call test_code test_collections test_compile test_compiler_assemble test_coroutines test_decorators test_descr test_dis test_doctest test_dtrace test_dynamicclassattribute test_email test_enum test_exception_group test_fstring test_funcattrs test_generators test_genericalias test_hmac test_importlib test_inspect test_listcomps test_marshal test_monitoring test_opcache test_os test_positional_only_arg test_pprint test_pyclbr test_pydoc test_raise test_string test_subclassinit test_subprocess test_tempfile test_threading test_trace test_traceback test_type_aliases test_type_annotations test_type_params test_types test_typing test_unittest test_xml_etree test_xml_etree_c

Legend:

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

@rlaisqls rlaisqls force-pushed the fix/subclass-right-ops branch from 216006b to 131e5aa Compare March 18, 2026 12:29
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/vm/src/vm/vm_ops.rs`:
- Around line 13-25: Change method_is_overloaded to return PyResult<bool>
instead of bool and propagate any error from vm.identical_or_equal instead of
silencing it; specifically, in method_is_overloaded (and places calling it such
as binary_op1 and ternary_op) replace the unwrap_or(false) with the ? operator
(or explicit match to return Err) so exceptions from identical_or_equal bubble
up as PyErrs, update callers binary_op1/ternary_op to accept the PyResult<bool>
and propagate errors (or handle them) accordingly, and ensure the initial
get_attr checks still return Ok(false) when attributes are missing.
- Around line 183-197: The code is incorrectly dropping slot_bb when the
left-side slots compare equal, which loses the reflected fallback (right_*
trampoline). Always preserve slot_bb by assigning slot_b = slot_bb when slot_bb
is present, and separate that from the priority decision: use
class_b.fast_issubclass(class_a) && let Some(rop_name) =
op_slot.right_method_name(self) && method_is_overloaded(class_a, class_b,
rop_name, self) only to decide whether the RHS should run before the LHS (i.e.,
adjust ordering/priority), not to decide whether to queue the reflected slot at
all; update the logic around slot_a, slot_b, slot_bb, class_a, class_b, op_slot,
right_method_name, method_is_overloaded and fast_issubclass accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 0b5c98b5-467c-45ff-92d6-5298b5f4eb29

📥 Commits

Reviewing files that changed from the base of the PR and between 216006b and 131e5aa.

⛔ Files ignored due to path filters (1)
  • Lib/test/test_descr.py is excluded by !Lib/**
📒 Files selected for processing (2)
  • crates/vm/src/protocol/number.rs
  • crates/vm/src/vm/vm_ops.rs

@rlaisqls rlaisqls force-pushed the fix/subclass-right-ops branch from 94c80c5 to b24d50e Compare March 18, 2026 13:23
Co-authored-by: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com>
Copy link
Contributor

@ShaharNaveh ShaharNaveh left a comment

Choose a reason for hiding this comment

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

great!
ty for your recent contributions:)

Copy link
Member

@youknowone youknowone left a comment

Choose a reason for hiding this comment

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

Thank you 👍 👍

@youknowone youknowone merged commit 79e17cb into RustPython:main Mar 19, 2026
16 checks passed
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