Skip to content

fix: follow every target when checking state reachability - #722

Open
tunglambk wants to merge 1 commit into
fgmacedo:mainfrom
tunglambk:fix/705-fork-reachability
Open

tunglambk wants to merge 1 commit into
fgmacedo:mainfrom
tunglambk:fix/705-fork-reachability

Conversation

@tunglambk

Copy link
Copy Markdown

Fixes #705

Why

The reachability walk in statemachine/graph.py followed only transition.target, so a UML fork written as a.to([x, y]) reported the second target as unreachable when nothing else pointed at it:

statemachine.exceptions.InvalidDefinition: There are unreachable states. The statemachine graph should have a single component. Disconnected states: ['y2']

Loaded documents turn the structural checks off (statemachine/io/interpreter.py), which is why the PSSM and SCXML cases never hit this.

What changes

visit_connected_states now queues every transition.targets. Both callers share that walk — the disconnected-state check and the path-to-final-state check — so a fork now counts in full for reachability. Single-target and target-less transitions are unchanged.

docs/transitions.md documents multi-target transitions as the UML fork, next to the other transition-declaration behaviour, with a doctest that enters two parallel regions in one step. _ToState.__call__ accepts list[State] so the documented spelling type-checks.

Evidence

tests/test_statemachine.py::test_fork_multi_target_transition_is_reachable fails on main with Disconnected states: ['y2'] and passes after the change. It also checks the fork leaves the machine in {active, r1, r2, x2, y2}.

Fork_001 and Fork_002 still xfail exactly as before (they load through the IO path), and the rest of the PSSM and SCXML suites are unchanged.

Serial uv run pytest: 2810 passed, 189 skipped, 126 xfailed. ruff check, ruff format --check, mypy statemachine/ tests/ and pyright statemachine/ are clean, and all pre-commit hooks passed. graph.py and state.py stay at 100% branch coverage; the serial coverage total reports 96.31% only because graphviz's dot binary is missing here, so the diagram tests skip (the misses are all in contrib/diagram/* plus one diagram-docstring helper in factory.py, none of them touched).

The reachability walk followed only the first target of a transition, so a UML
fork written as a.to([x, y]) reported the second target as unreachable when it
had no other incoming transition. Walk every target instead, which also fixes
the path-to-final-state check that shares the same walk.

Document multi-target transitions as the UML fork in docs/transitions.md, and
widen the to() type hint so the documented spelling type-checks.

Signed-off-by: Tung Lam <53996158+tunglambk@users.noreply.github.com>
@sonarqubecloud

Copy link
Copy Markdown

This branch has not been deployed

No deployments
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.

Fork: multi-target transitions fail the reachability check and are not documented as UML fork

1 participant