extmod/moductypes: Follow the attr protocol for unknown attributes.#19345
Open
cnadler86 wants to merge 1 commit into
Open
extmod/moductypes: Follow the attr protocol for unknown attributes.#19345cnadler86 wants to merge 1 commit into
cnadler86 wants to merge 1 commit into
Conversation
The struct attr handler raised KeyError for an attribute that is not a field, instead of leaving dest[0] as MP_OBJ_SENTINEL to signal "not handled" like every other native attr handler does (see mp_store_attr). This leaks a KeyError on a plain struct, and breaks subclassing of native types (micropython#18592), where a store must be offered to the native handler before falling back to the instance dict. Use a non-raising field lookup and return MP_OBJ_NULL when the attribute is not a field, so the standard machinery falls back or raises AttributeError. A missing attribute now raises AttributeError instead of KeyError, consistent with all other objects and CPython. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Christopher Nadler <christopher.nadler@gmail.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #19345 +/- ##
=======================================
Coverage 98.51% 98.51%
=======================================
Files 176 176
Lines 22903 22907 +4
=======================================
+ Hits 22562 22566 +4
Misses 341 341 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Code size report: |
Member
|
Do you think you could add a test for this? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
extmod/moductypes: Make the attr handler follow the native attr protocol
Summary
The
attrhandler ofuctypes.struct(uctypes_struct_attrinextmod/moductypes.c)doesn't follow the contract every other native
attrhandler honours: for an attributethat is not a field of the struct it raises
KeyError(viamp_obj_dict_get) insteadof leaving the result slot untouched so the caller can decide what to do.
This is invisible on a plain
uctypes.struct, but leaks out as soon as a Python classsubclasses a native type with an attr handler — the scenario behind #18592.
The native attr protocol
mp_store_attrandmp_load_method_maybe(py/runtime.c) define the contract. The handlergets a 2-element
destarray:dest = { MP_OBJ_SENTINEL, value }:dest[0] = MP_OBJ_NULL(success);dest[0] == MP_OBJ_SENTINEL(caller raisesAttributeError,or for a subclass falls back to instance
__dict__);dest[0] == MP_OBJ_NULL: found → setdest[0]; not found → leave itNULL.The key point: "attribute not recognised" is signalled in-band (sentinel / NULL), not via an
exception — exceptions are reserved for genuine errors.
mp_obj_exception_attr,complex_attr,range_attr, etc. all follow this.What uctypes does instead
For an unknown attribute this raises
KeyError, conflating "not one of my fields" with agenuine error.
Why it matters
Subclassing (Python subclass of native type with attr handler cannot properly store attributes #18592). Stores on a native subclass must be offered to the native
handler first (else native fields are shadowed by
__dict__entries). With a conforminghandler the generic code in
py/objtype.cjust checks the sentinel and falls through.Because uctypes raises, that code is otherwise forced to wrap the call in
nlr_push/nlr_popand guess from the exception type whether the attribute was unknown or a realfailure — which adds an
nlrframe to the hot path of every native-subclass store, is aheuristic (a setter legitimately failing with
KeyError/AttributeErrorgetsmisread as "unknown" and silently redirected to
__dict__), and is asymmetric with theload path (which never catches).
Inconsistent error type. A missing attribute on a
uctypes.structraisesKeyError,whereas every other object (and CPython) raises
AttributeError:Fix
Do a non-raising field lookup and return
MP_OBJ_NULLwhen the field is absent, so thestandard machinery falls back / raises
AttributeError:This lets
py/objtype.c(the #18592 fix) drop thenlr_pushworkaround and just honour thesentinel, makes store symmetric with load, lets genuine setter errors propagate as in
CPython, and works for any native type with an attr handler — not just uctypes.
Behaviour change
A missing attribute on a
uctypes.structnow raisesAttributeErrorinstead ofKeyError(load and store). This is more CPython-like and consistent with all other objects; no test
in the tree depends on the old
KeyError(checked againsttests/extmod/uctypes_*).