Skip to content

Reject __abc_tpflags__ with both SEQUENCE and MAPPING bits#7700

Merged
youknowone merged 1 commit into
RustPython:mainfrom
changjoon-park:validate-abc-tpflags-conflict
Apr 28, 2026
Merged

Reject __abc_tpflags__ with both SEQUENCE and MAPPING bits#7700
youknowone merged 1 commit into
RustPython:mainfrom
changjoon-park:validate-abc-tpflags-conflict

Conversation

@changjoon-park

@changjoon-park changjoon-park commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Background

__abc_tpflags__ is the integer ABC pattern-matching flag set on a class so match statements can short-circuit Sequence vs Mapping dispatch. Setting both bits is meaningless for pattern matching and CPython rejects it in Modules/_abc.c::_abc_init:

TypeError: __abc_tpflags__ cannot be both Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING

RustPython's check_abc_tpflags (crates/vm/src/builtins/type.rs) copied the bits onto slots.flags without validating the conflict, silently producing an inconsistent type.

Repro

from collections.abc import Collection, Sequence, Mapping

class Both(Collection):
    __abc_tpflags__ = Sequence.__flags__ | Mapping.__flags__

# CPython 3.14: TypeError
# RustPython before this PR: class created without complaint

# Also: child overriding a valid parent
class Parent(Collection):
    __abc_tpflags__ = Sequence.__flags__

class Child(Parent):
    __abc_tpflags__ = Sequence.__flags__ | Mapping.__flags__

# CPython 3.14: TypeError
# RustPython before this PR: class created (parent's SEQUENCE bit
# triggered an early return that skipped Child's validation)

Fix

Two changes inside check_abc_tpflags:

  1. Mask abc_flags with COLLECTION_FLAGS (the SEQUENCE | MAPPING bits). If the masked value equals COLLECTION_FLAGS, return a String error — new_heap_inner already converts that to TypeError for the user.
  2. Move the "flags already inherited, skip" early return below the self-attrs branch. The previous order let a child class's conflicting __abc_tpflags__ slip through whenever the parent had already set one of the bits.

Tests unmasked

  • test_collections.TestCollectionABCs.test_illegal_patma_flags

Verification

  • 10 modules pass with no regressions: test_collections test_descr test_class test_typing test_abc test_dataclasses test_super test_call test_module test_patma (1,951 tests)
  • CPython 3.14.4 byte-identical wording for both conflict shapes (direct and child-override)
  • Edge cases probed: 3-level inheritance conflict (caught), diamond inheritance with no self flags (matches CPython — created OK), Sequence-only / Mapping-only / plain Collection subclasses (all OK), non-int __abc_tpflags__ value (silently ignored, matches CPython)

Summary by CodeRabbit

  • Refactor

    • Internal type validation function refactored to return explicit error results, enabling better error propagation and more robust inheritance handling.
  • Bug Fixes

    • Type validation now properly detects and rejects invalid configurations where both sequence and mapping collection behaviors are declared simultaneously, preventing runtime issues from contradictory type metadata.

CPython's _abc._abc_init validates that __abc_tpflags__ doesn't combine
Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING (Modules/_abc.c::_abc_init).
RustPython's check_abc_tpflags copied the bits onto slots.flags without
that validation, so a class declaring both produced an inconsistent
type silently.

Validate the masked __abc_tpflags__ value against COLLECTION_FLAGS and
return a String error (which the existing new_heap_inner caller turns
into a TypeError) when both bits are present. Move the inheritance-skip
check after the self-attrs branch so a child overriding __abc_tpflags__
is still validated even when the parent already set one of the bits.
@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The check_abc_tpflags function in the type module is refactored to return a Result<(), String> and now validates that both Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING flags cannot be set simultaneously. Custom __abc_tpflags__ attributes are always inspected when present, and the caller propagates errors appropriately.

Changes

Cohort / File(s) Summary
Type Flag Validation
crates/vm/src/builtins/type.rs
Refactored check_abc_tpflags to return Result<(), String>, added conflict validation for simultaneous SEQUENCE and MAPPING flags, improved handling of custom __abc_tpflags__ attributes, and updated caller (new_heap_inner) to propagate errors.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Flags now dance with careful grace,
Sequence and Mapping keep their place,
No conflicted types shall pass,
Custom checks make logic fast!

🚥 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 directly and accurately describes the main change: rejecting the invalid case where both SEQUENCE and MAPPING bits are set in abc_tpflags, which is the central bug fix in this PR.
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 and usage tips.

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

🧹 Nitpick comments (1)
crates/vm/src/builtins/type.rs (1)

612-660: Optional: factor out the duplicated __abc_tpflags__ parse/validate/apply logic.

The own-class branch (lines 615–633) and the legacy base-class branch (lines 643–659) repeat the same five steps: downcast to PyInt, convert via to_i64, build PyTypeFlags, mask with COLLECTION_FLAGS, reject the both-bits case, and OR into slots.flags. The error string is also duplicated verbatim, so any future tweak (wording, behavior on non-int, BigInt overflow handling) has to be kept in sync in two places.

A small helper would tighten this up without changing semantics:

♻️ Proposed extraction
+    /// Parse `__abc_tpflags__` (if it is an int), reject the
+    /// `SEQUENCE | MAPPING` combination, and return the masked
+    /// collection bits to be ORed into `slots.flags`. Returns
+    /// `Ok(None)` if the value is missing or not a `PyInt`.
+    fn parse_abc_tpflags(
+        value: Option<&PyObjectRef>,
+        collection_flags: PyTypeFlags,
+    ) -> Result<Option<PyTypeFlags>, String> {
+        let Some(obj) = value else { return Ok(None) };
+        let Some(int_obj) = obj.downcast_ref::<crate::builtins::int::PyInt>() else {
+            return Ok(None);
+        };
+        let flags_val = int_obj.as_bigint().to_i64().unwrap_or(0);
+        let masked = PyTypeFlags::from_bits_truncate(flags_val as u64) & collection_flags;
+        if masked == collection_flags {
+            return Err(
+                "__abc_tpflags__ cannot be both Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING"
+                    .to_owned(),
+            );
+        }
+        Ok(Some(masked))
+    }

Then check_abc_tpflags becomes a straight orchestration over attrs.get(...) and the base loop, each calling parse_abc_tpflags and applying slots.flags |= masked under the existing "don't override inherited" rule.

Pre-existing nit on the same surface: COLLECTION_FLAGS is also defined identically in inherit_patma_flags (lines 581–583) — pulling it up to a module-level const would let both functions share it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/builtins/type.rs` around lines 612 - 660, Extract the
duplicated parse/validate/apply logic into a small helper (e.g.
parse_and_validate_abc_tpflags) that takes a PyObjectRef (or Option<&PyObject>)
and returns Result<Option<PyTypeFlags>, String> or similar: it should downcast
to crate::builtins::int::PyInt, convert via to_i64().unwrap_or(0), build
PyTypeFlags::from_bits_truncate(...), mask with COLLECTION_FLAGS, and return
Err(...) if both sequence and mapping bits are set; then replace the two
duplicated blocks in the current function with calls to that helper and apply
slots.flags |= masked only under the existing inherited-bit check; also promote
COLLECTION_FLAGS to a module-level const so inherit_patma_flags and this new
helper share the same constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/vm/src/builtins/type.rs`:
- Around line 612-660: Extract the duplicated parse/validate/apply logic into a
small helper (e.g. parse_and_validate_abc_tpflags) that takes a PyObjectRef (or
Option<&PyObject>) and returns Result<Option<PyTypeFlags>, String> or similar:
it should downcast to crate::builtins::int::PyInt, convert via
to_i64().unwrap_or(0), build PyTypeFlags::from_bits_truncate(...), mask with
COLLECTION_FLAGS, and return Err(...) if both sequence and mapping bits are set;
then replace the two duplicated blocks in the current function with calls to
that helper and apply slots.flags |= masked only under the existing
inherited-bit check; also promote COLLECTION_FLAGS to a module-level const so
inherit_patma_flags and this new helper share the same constant.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: cae320f1-dd99-497e-b271-a266059af8a0

📥 Commits

Reviewing files that changed from the base of the PR and between 1d42ee5 and 6c9d598.

⛔ Files ignored due to path filters (1)
  • Lib/test/test_collections.py is excluded by !Lib/**
📒 Files selected for processing (1)
  • crates/vm/src/builtins/type.rs

@github-actions

Copy link
Copy Markdown
Contributor

📦 Library Dependencies

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

(module 'ast test_collections test_deque test_descr test_int test_reprlib' not found)

Legend:

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

@youknowone youknowone left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

:+1

@youknowone youknowone merged commit b8f7ae4 into RustPython:main Apr 28, 2026
21 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.

2 participants