Repository navigation
Description
What happens
StateMachineParser.getTransitions (StateMachineParser.java:447) expands a @StateRefinement that has no from into one edge from every state:
List<TransitionSource> fromSources = from.isEmpty() ? allStateSources(states) : parsePrecondition(from, states);Example: an UndoManager spec where canUndo() works as a guard:
@StateSet({"start", "canEdit", "endDone", "endUndone"})
...
@StateRefinement(from="start(this)", to="!return && start(this)")
@StateRefinement(from="canEdit(this)")
@StateRefinement(from="endDone(this)")
@StateRefinement(from="endUndone(this)", to="!return && endUndone(this)")
@StateRefinement(to="return ? endDone(this) : true")
boolean canUndo();The machine gets four canUndo [return] edges into endDone: from start, canEdit, endDone and endUndone.
Why that is misleading
When the state is known, the verifier does not follow these edges. The matching from= alternative decides. Checked with verifier 0.0.36: addEdit(e); if (um.canUndo()) um.addEdit(e2); verifies, so canEdit does not move to endDone. Two of the drawn edges also contradict their own state's alternative (start and endUndone both say !return).
The from-less refinement only matters when the receiver's state is unknown: typestate is tracked within one method, so this happens with a field, a parameter, or the result of a getter (frame.getUndoManager().undo()). There it is how the object enters the protocol: if (um.canUndo()) um.undo(); verifies because a true answer puts um in endDone.
Proposal
Draw a from-less @StateRefinement as an extra entry arrow from [*], like a constructor's initial transition, labelled with the method and its condition:
stateDiagram-v2
[*] --> start: new UndoManager()
[*] --> endDone: canUndo() / canUndoOrRedo() is true
[*] --> endUndone: canRedo() is true
start --> canEdit: addEdit / undoableEditHappened
start --> endDone: end
canEdit --> endDone: end
endDone --> endUndone: undo / undoOrRedo
endUndone --> endDone: redo
For example, add them to initialTransitions with the method as label and the to condition (return), or add a separate entryTransitions list so that clients (the MCP's get_state_machine, the VS Code extension) can render them differently from constructors.
Optionally, when a from-less refinement sits beside from= alternatives, an edge it would add from a state that already has its own alternative could be dropped, since that alternative decides in a known state.
Context: found while building study exercises (UndoManager spec in the building-examples-liquidjava specs-v2 overlay).
Activity