| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
PyType_Ready adds a slot wrapper for the forward and the reflected method of each number slot FixupSlots sets, when the type dict lacks them. A type declaring an operator in one direction only, like x + x, got a __radd__ wrapper that calls the very slot that dispatched to it, so 1.0 + x, 1 + x or sum([x]) recursed until RecursionError instead of raising TypeError. The same happened with [Flags] enums, whose bitwise operators are forward only (1 & flags). The wrappers without a CLR operator behind them are now removed after PyType_Ready, so the slot returns NotImplemented and Python raises its usual TypeError. A Python subclass can still define the missing method. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The cleanup now deletes a member only when it is a wrapper_descriptor, the object PyType_Ready adds, instead of anything that is not a managed object. It is best effort: a failed delete clears its error and any exception is swallowed, since a wrapper left behind only keeps the previous behavior. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
| Back | FazBrowse Home | New Git URL |
What
An operation a CLR type supports in one direction only now raises TypeError, like any unsupported Python operation. Before, it recursed until RecursionError.
The supported directions are unchanged. A Python subclass can still define the missing method, e.g. its own __radd__.
Why
InitClassBase fills the type dict with the CLR operators, e.g. __add__. Then FixupSlots installs the generic nb_add slot, and PyType_Ready adds a slot wrapper for each forward and reflected name the dict lacks, e.g. __radd__. With no CLR overload behind it, that wrapper calls back into the same slot that dispatched to it:
slot_nb_add → __radd__ wrapper → nb_add → slot_nb_add → …
So 1.0 + x loops until RecursionError. Code that catches TypeError to fall back (sum(), numeric helpers) gets a RecursionError instead. This happens on master 56b3b6e7 with a plain class that has no implicit conversion. It's not related to #150/#151, though I found it while reviewing #151.
How
After PyType_Ready, OperatorMethod.RemoveUnboundSlotWrappers goes through the slots FixupSlots installed for the type. It removes the forward and reflected entries that are CPython slot wrappers (wrapper_descriptor, the objects PyType_Ready adds). CLR operators, and any CLR member named like a dunder, are pythonnet objects and stay. With no __radd__ to find, the slot returns NotImplemented, so Python raises its usual TypeError.
Only operator slots this type installed are touched. Native slots implemented by pythonnet itself (nb_bool, in-place event slots) and comparison wrappers are left alone.
The cleanup is best effort and never fails the type creation: a failed delete clears its Python error, and any exception is swallowed. A wrapper left behind only keeps the previous behaviour.
Cost: about 1 µs per class, once when its Python type is created, which itself takes ~150–215 µs. There's no cost per operator call.
Where
Tests
New in TestOperator:
The first four fail on master and pass with the fix. The last one pins that only CPython's wrappers are removed.
Full embed suite: 998 passed, 0 failed (net10.0, Python 3.11).
Same wrappers removed after narrowing the rule: across 1,154 types from .NET's own assemblies, the same 79 types end up with an operator in one direction only, e.g. every [Flags] enum, DateTime, TimeSpan, Decimal, Int128 and the Vector/Matrix types.
Lean Python regression algorithms: 339/339 passed with this build dropped into Lean master 154e50720c, following .github/workflows/lean-python-regression-tests.yml.
🤖 Generated with Claude Code