Repository navigation
fix: reject callback names colliding between nested state bodies - #674
Merged
Merged
Conversation
Callbacks declared inside a `State.Compound` or `State.Parallel` body are flattened into the class namespace keyed by name only, so the same name in two nested bodies, or in a nested body and the class body, kept only the last one and silently dropped the other. The metaclass now raises `InvalidDefinition` naming the callback and both owners. The same function object assigned under one name in several bodies is still accepted. Namespacing each nested body is tracked by #657. Fixes #653 Signed-off-by: Fernando Macedo <fgmacedo@gmail.com>
…ion check A bound method assigned in two nested bodies is a fresh object on each access, so identity rejected it although both bodies attach one callable. Compare by equality instead. A name that an event registered from a sibling body, or another attribute of the class, already uses was reported as a class-body declaration; name the event or the attribute instead. Signed-off-by: Fernando Macedo <fgmacedo@gmail.com>
`States.__eq__` iterates the other operand, so comparing a nested callback named `states` with the metaclass-provided attribute raised `TypeError` instead of `InvalidDefinition`. Signed-off-by: Fernando Macedo <fgmacedo@gmail.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #674 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 53 53
Lines 5714 5787 +73
Branches 900 918 +18
=========================================
+ Hits 5714 5787 +73
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
A callback attached by reference, through a decorator or by passing the function itself, keeps the function in its spec, so the owner is known. When its name is already taken, the metaclass stores it under a key unique to the owning state and the dispatcher resolves it through func.attr_name. Both sibling bodies now run instead of being rejected. Callbacks found by name (naming conventions and string references) still raise InvalidDefinition on a collision until #657 scopes names per body. Signed-off-by: Fernando Macedo <fgmacedo@gmail.com>
…ach it Holding a function by object does not make its name unreachable: a string reference, a naming convention or a self.<name>() call still resolves through the class attribute, and silently picked another body's function. A shared name now moves to per-state keys only when every claimant is held by object, the class attribute it would shadow is an event or another such function, and no name-resolved spec of the machine mentions it. Otherwise it raises, also against inherited attributes. The relocated key lives on CallbackSpec.lookup_name instead of the shared func.attr_name, which any later spec over the same function overwrote. The check runs after _setup so convention specs are known. Signed-off-by: Fernando Macedo <fgmacedo@gmail.com>
…ing-callback-collision Signed-off-by: Fernando Macedo <fgmacedo@gmail.com> # Conflicts: # docs/releases/3.2.2.md # statemachine/factory.py # tests/test_statechart_compound.py
…backs apart - A nested body overriding a method inherited from a base class works again, as on 3.2.1; only the class's own attributes count as a collision. - Rewrite lookup_name only on specs of the states this class declares: since #673 a subclass shares the base's states, and relocating there broke the base. - Collect names from guard expressions through the AST, so string literals no longer block storing a callback apart. - Report a plain callback named like a class-body event as an event clash. Signed-off-by: Fernando Macedo <fgmacedo@gmail.com>
|
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.



Fixes #653
Why
Callbacks declared inside
State.Compound/State.Parallelbodies are flattened into the class namespace keyed by name only, so the same name in two sibling regions kept only the last body. With the issue's example,send("go")moved both regions and ran 1 of the 2gobodies (log['r2'], measured onorigin/main). The same loss applied to plain callbacks such ason_enter_<state>in two bodies and to a nested body shadowing a method of the class body (log['nested']).What changes
InvalidDefinitionat class creation, naming the callback and both state bodies. The decorator form reports the name the user wrote (go, not the mangled_go_).TypeErrorforstates.functools.partial._callbacksis Subclassing a StateChart with callbacks in a State.Compound body raises AttributeError #646, handled by its own PR; this check does not misfire with eitherdelor= Nonethere.Evidence
Issue reproduction, before and after:
The seven raising tests fail on
origin/main(7 failed, 8 passed when the test class runs against agit archive origin/mainexport).Three adversarial review rounds by a fresh reviewer each. Round 1 found a bound-method false positive (
iscomparison) and misleading "class body" wording for events and metaclass attributes, both fixed in the second commit. Round 2 found aTypeErrorfromStates.__eq__when the clashing attribute iscls.states, fixed in the third commit. Round 3 verified the guard only.Not verified
__eq__returnsTrueacross instances pass the check and the last one wins. Not reachable with functions, methods or partials.Enums defined inside a nested body are classified as callbacks by pre-existing code, so two bodies definingclass Kind(Enum)raise with "Callback 'Kind'"; the collision is real, only the noun is off.state._callbacks = Nonevariant was simulated locally by patching the line.