Warn instead of aborting when a class namespace holds a non-string key - #8581
Warn instead of aborting when a class namespace holds a non-string key#8581luantaraschi wants to merge 2 commits into
Conversation
type() took the whole process down when the namespace it was handed had
a key that was not an exact str:
>>> type("MyClass", (), {1: 2})
thread 'main' panicked at crates/vm/src/builtins/dict.rs:781:65:
dict has non-string keys: [PyObject PyInt { value: 1 }]
to_attributes called expect() on downcast_exact, so a str subclass hit
the same line, and that one names an attribute as well as a str does.
PyAttributes is keyed by interned strings and can hold nothing else, so
a str subclass is now interned like any other name, and a key that is no
kind of string is skipped, with the caller deciding what the skip means.
type() warns once per class, the way CPython does, and
_thread.start_new_thread raises the TypeError an ordinary call already
raises for the same mapping.
The key itself still does not land in the class dict, which is what
test_type_lookup_mro_reference needs, so that one stays skipped with the
reason rewritten.
Assisted-by: Claude Code:claude-opus-5
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including ⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (4)
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthrough
ChangesDictionary attribute conversion
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to The change replaces interpreter panics with warning or TypeError behavior for invalid namespace keys while preserving existing supported behavior; no actionable merge-blocking risk remains after normal checks and review. Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant ClassConstruction as type.__new__
participant AttributeConversion as PyDict::to_attributes
participant WarningSystem as RuntimeWarning
participant ClassDictionary as class dictionary
ClassConstruction->>ClassDictionary: read namespace
ClassConstruction->>AttributeConversion: convert namespace
AttributeConversion->>WarningSystem: warn for non-string key
AttributeConversion-->>ClassConstruction: return PyResult<PyAttributes>
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
📦 Library DependenciesThe following Lib/ modules were modified. Here are their dependencies: [ ] test: cpython/Lib/test/test_descr.py (TODO: 31) dependencies: dependent tests: (no tests depend on descr) Legend:
|
|
|
||
| class MiscTests(unittest.TestCase): | ||
| @unittest.skip("TODO: RUSTPYTHON; rustpython panicked at 'dict has non-string keys: [PyObject PyBaseObject]'") | ||
| @unittest.skip("TODO: RUSTPYTHON; a class namespace drops keys that are not strings, so MyKey.__eq__ is never reached and __bases__ stays put") |
There was a problem hiding this comment.
if this no longer crash can we switch this to be expectedFailure instead of skip?
There was a problem hiding this comment.
Switched it. Five runs in a row report it as an expected failure, and it cannot turn into a pass while the class dict drops the key its lookup needs to reach MyKey.__eq__.
It aborted the runner before, which is why it was skipped. Now that it only fails, the run can carry it: five runs in a row report it as an expected failure, and it cannot turn into a pass while the class dict drops the key its lookup needs. Assisted-by: Claude Code:claude-opus-5
Summary
A class namespace holding a key that is not a string takes the interpreter down:
CPython builds the class and warns once:
A metaclass that writes into the namespace it is given reaches the same line, so
class Z(metaclass=M)aborts too whenM.__new__doesns[42] = "x".A
strsubclass hit that panic as well, and it names an attribute as well as astrdoes:_thread.start_new_threadshares the line through its kwargs dict, so it aborted where an ordinary call with the same mapping already raises:The change
Py<PyDict>::to_attributescalledexpect()ondowncast_exact.PyAttributesis anIndexMapkeyed by&'static PyStrInterned, so the two kinds of key now split there:strsubclass is interned like any other name, throughdowncast_ref::<PyStr>, which is the testcollect_ex_argsalready applies to keyword names;type()warns once per class with CPython's wording,_threadraisesTypeError: keywords must be strings.What this does not fix
The key still does not reach the class
__dict__, and interning gives up thestrsubclass:1 in type("K", (), {1: 2}).__dict__TrueFalse{S("z"): 9}SstrEither row would mean
PyAttributesgiving up its interned-string key, which reaches a lot further than the crash does.test_descr.MiscTests.test_type_lookup_mro_referencerests on the first row, because its lookup only reachesMyKey.__eq__if the key sits in the class dict, so it stays skipped and its reason now names that instead of the crash.Tests
Lib/test/test_descr.py::test_gh55664was skipped on this exact panic, quoting it in the reason, and is now unskipped. Without the change the module takes the runner down with it:extra_tests/snippets/builtin_type.pycovers thestrsubclass key, the single warning a class gets however many bad keys it holds, and that warning turning into an exception undersimplefilter("error"). It passes under CPython 3.14 too.Also run:
cargo test --workspace --exclude rustpython_wasm --exclude rustpython-venvlauncher --exclude rustpython-capi,cargo testincrates/capi, theextra_testssnippets,cargo fmt --check, the clippy invocation from CI, and-m testover test_descr, test_class, test_type_aliases, test_types, test_thread, test_threading, test_metaclass, test_dict, test_dictviews, test_super, test_abc, test_enum, test_dataclasses, test_functools and test_warnings. The one snippet failure isstdlib_sqlite, which wants a feature that is not in the default set.Summary by CodeRabbit
Bug Fixes
strsubclasses.TypeError.Warnings
RuntimeWarningwhen non-string attribute keys are encountered.