Repository navigation
Suggest only calls allowed in any state when the receiver's state is unknown - #458
Conversation
|
[Codex review] The new needed-state hint can recommend an impossible constructor. Reproducer on this PR head: |
|
[Claude] Fixed in d7ce256: |
|
[Codex re-review] The abstract-class case is fixed, and the private-constructor case is now covered. One accessibility gap remains: package-private and protected constructors are also not callable from every site, but |
|
[Claude] Fixed in efa18bf:
New fixtures cover each case. The 84 existing state fixtures are unchanged from d7ce256. 600 tests, 0 failures. The PR body has the corrected claims and the merge notes for #464. |
…unknown With an unknown state, the hint listed every transition into the states the call needs, even when the transition needed a state that was just as unknown, and named those states in every combination with the state sets the call does not check. It now names the needed state in the checked state sets only, and suggests the shortest call sequences that are allowed in every state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… an unknown state - Name a set of states as the fewest groups that each fix the state in some state sets, so open(this) || loud(this) reads "open or loud" and a partly known state is named in its known state sets only. Every state hint uses this, for the current, needed and reached states. - Only call a state unknown when nothing mentions it. When it is mentioned only through another value, such as the result of an earlier check, say that what is known does not decide it. - Always name the needed state and the constructors, even when every state allows the call by a different state refinement. - Do not suggest creating a new object when the receiver is this. - Rename the hint kind REACHING_CALLS to NEEDED_STATE, and share the call sequence search between known and unknown states. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A state machine left out constructors only when it could not model them, so hints could suggest creating an abstract class, an interface, an enum or a class whose constructor is private. Constructors of such types are now left out of the machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…case - A state mentioned through another value counts as a check only when it is applied to the receiver's instance or to an object the solver proves equal to it. A check of another object leaves the state unknown. What the error shows as found is only computed when needed. - A join of every state across several state sets is no longer taken for a check, so it keeps the "No sequence of calls" hint. - Name the receiver's states in every state set the call checks, so a join that differs in a checked set still lists each state. - When every state allows the call, each by its own state refinement, name the needed state per refinement. - Search the smallest cover exactly up to 13 candidate groups. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
efa18bf to
b9683e9
Compare
- StateCover: search the smallest cover exactly, without the greedy fallback, keeping each group's states with it. - StateHints: take the needed states from one source, and give the set of checked state sets and the check flag distinct names. - StateErrorCategories: share the test for "every state possible and none mentioned" between categorize and checkedElsewhere. - StateMachine: derive statesAllowing from statesAllowingByRefinement. - AuxStateHandler: only look for a check when no argument decided the state. - Tests: merge the hint fixtures into one file per behaviour, each with several errors, so far fewer runs of the verifier are needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
[Codex review] Package-private constructors can still be suggested from unrelated packages. Example: |
|
[Claude] 878738a: clean-code pass and trimmed tests.
|
Part of #454 (part (a): what the hint suggests when any state is possible, and how hints name states). Part (b), suggesting a check such as
canRedo(), needs return-value queries modelled as guards. That is a separate change.Problem
When the receiver's state is unknown, the hint has two problems:
redo()needsredoable, and the hint suggestsundo(), which needsundoable. Following it only moves the error toundo().Before (the issue's JabRef exercise in VS Code):
Cause
StateHints.reachingCallslisted every transition from a state that does not allow the call into one that does (StateMachine.transitionsEnabling), whatever itsfrom. All hints printed a set of states by joining each state's names in every state set.Fix
States are named briefly, everywhere.
StateMachine.describe(newStateCover) names a set of states as the fewest groups that each fix the state in some state sets and allow any state in the others: a smallest cover by maximal sub-products of the state sets. It works on the machine's solver-built states, with no string matching. Every state hint uses it, for the current, needed and reached states. For example:redoable(this)with three state sets readsredoable;closed(this) && unlocked(this)readsclosed and unlocked;open(this) || loud(this)across two sets readsopen or loud;from = "undoable(this)"andfrom = "redoable(this)"readundoable or redoable;notRedoablereadsnotRedoable.The receiver's current state is named in every state set the call checks, plus any set its states agree on. So a join that differs in a checked set still lists each state (
closed and unlocked or opened and unlocked), and one that differs only in an unchecked set does not (closed). A single fully known state reads as before (closed and locked). When every state allows the call, each by its own refinement, the needed state is named per refinement (a or b, not every state).Only calls that work from any state. When any state is possible, the hint reuses the call sequence search of the known-state case, starting from every state. A call is suggested only if it is allowed in every state. That covers transitions with no
fromand sequences that start with one (reset(), then open()). Same limits as before: at most 3 sequences of at most 3 calls.The needed state is always named. The
NEEDED_STATEhint (renamed fromREACHING_CALLS, which nothing in vscode-liquidjava or liquidjava-mcp uses) always names the state the call needs, plus the constructors that create an object in it. That includes a call that every state allows, each by a different refinement (a toggle). Constructors are not suggested when the receiver isthis. The same applies to the existing "Create a new object instead" hint.Only constructors that can be called.
StateMachineBuilderleaves out the constructors of abstract classes and interfaces (abstractType, as in Give state hints when the state set or methods come from the receiver's supertypes #464), enums, and private constructors, so no hint suggestsnewfor them.A failed check is not "unknown". In
if (!it.hasNext()) it.next();, the receiver's own refinement says nothing, but the error's found ((#fresh == true --> hasMore(it)) && !#fresh) mentions its state through the check's result.StateErrorCategories.checkedElsewherelooks at what the error shows as found (TypeChecker.shownFound, the same premise chain the error prints). It only computes that when every state is possible and the receiver's own refinement mentions none. It counts a state only if it is applied to the receiver's instance, or to an object the solver proves equal to it (Scanner same = it; if (!it.hasNext()) same.next();). A check on another object (if (other.hasNext()) it.next();, or the "checked the wrong object"if (it.hasNext()) other.next();) leaves the state unknown. When it does count, the category is none instead of UNKNOWN, and the hint reads "What is known here does not decide the state of it, and this call needs it to be hasMore". Joins of every state across several state sets (if (p) d.open(); if (q) d.lock(); d.enter();) are not checks. They keep main's category (none) and main's "No sequence of calls moves d from … Create a new object instead" hint.What changes outside the unknown case:
iterator_observer_error,state_test_method_error).thisreceivers and for abstract classes, interfaces, enums and private constructors (in "Create a new object instead with …").StateMachineBuildernow leaves those constructors out of the machine.canModeland the methods it admits are unchanged.Extension point for part (b).
StateHints.anyStatebuilds the hints for "any state is possible": the needed state, then the call sequences from any state. It already tells the two causes apart: nothing known, or a check that did not decide the state. A guard hint goes between the two hints, once the machine records queries whose return value decides a state set. For example: "CheckundoManager.canRedo()before this call", or for a failed check, "Callit.next()only whenit.hasNext()returns true".Clean-code pass (878738a, no change in any hint: all 84 state fixtures and 30 reviewer reproducers print the same hints before and after):
StateCoversearches for the smallest cover exactly, with no greedy fallback, and keeps each group's states with it.StateHintstakes the needed states from one source, and names the checked state sets (checkedSets) and the check flag (checkedElsewhere) apart.StateErrorCategoriesshares its "every state possible, none mentioned" test.StateMachine.statesAllowingis derived fromstatesAllowingByRefinement.AuxStateHandleronly looks for a check when no argument decided the state.Hint output, before (main) and after
Issue reproducer (external
UndoManagerspec, two state sets, receiver from a method return):Three state sets (parameter receiver):
Disjunction across state sets (
from = "open(this) || loud(this)") and one refinement per set (undoOrRedo()):A failed check (
if (!it.hasNext()) it.next();):A call allowed in any state (
reset()has nofrom;open()needscreated):Partly known state (
thisknown to benotRedoable, three state sets):Toggle (a class whose
flip()goes fromuptodownand back):Known state, unchanged (after
open(); close();):Every other state fixture. I ran each
Error*state fixture and test-suite directory on main and on this branch and compared the hints. Apart from the cases above, the only changes are:iterator_observer_errorandresultset_forward_error(line 67) now get the needed-state hint. On main they got the generic "No state information is known…" hint, or no hint after a failedhasNext();state_test_method_error(if (edit.canUndo()) {} else edit.undo();) now reads "What is known here does not decide the state of edit…" instead of "unknown", and no longer suggestsedit.redo();ErrorLoopPostStateno longer suggestsnew ErrorLoopPostState()forthis;ErrorPathDependentOtherStateSetnow reads "from closed to opened" instead of two combinations each.Merging with #464. Both PRs touch
StateMachineBuilderandStateHints, and the conflicts are mechanical:abstractType: both PRs add an identical one;.distinct()on the constructor lists intoStateHints.newObjects, which replaces both lists here;typeName(inprivateConstructor) with Give state hints when the state set or methods come from the receiver's supertypes #464'sparameterTypes, which do the same thing.Tests
Each test pins one behaviour. To keep CI fast, the fixtures are grouped so that one verifier run checks several errors. Every new test fails without its fix: on the PR's base, or on the commit before the fix it pins.
TestStateHints)unknownStateOfAnExternalClassNamesOnlyTheCheckedStateSetclasses/state_hint_undo_manager_errorredoable,undo()not suggestedunknownStateDoesNotSuggestACallThatNeedsAnotherUnknownStateErrorStateHintUnknownState(existing, expectation changed)unknownStateSuggestsCallsAllowedInAnyStateErrorStateHintUnknownAnyStatereset(), then open()is suggested;open()alone is notstatesAreNamedOnlyInTheStateSetsThatMatterErrorStateHintNaming(3 errors)open or loudacross sets; a toggle on{a, b} × {x, y, z}readsa or b; a partly known state readsnotRedoablefailedCheckOfTheReceiverIsNotAnUnknownStateclasses/state_hint_checked_error(3 errors)hasNext()on the receiver, or on a variable holding it, "does not decide", with no category; a check of another scanner leaves it unknown (UNKNOWN)joinsAreNotTakenForAnUnknownStateErrorStateHintJoins(2 errors)onlyConstructorsThatCanBeCalledAreSuggestedErrorStateHintConstructors(4 errors)this, an abstract class or a private constructorStateMachineTesthas 10 unit tests fordescribeandstateSetsNamed. They run in milliseconds and cover the state naming: one set, cross-set and three-set disjunctions, every state, forced sets, and declaration order.Removed as overlapping: separate fixtures for three state sets, a one-set disjunction, one refinement per set, a two-set goal, a field receiver, several and long sequences, the one-set toggle, a known state, and the external abstract and interface specs. Each is covered by one of the tests above, by an existing test or by the unit tests. The interface spec never reached the guard, since an interface cannot declare a constructor refinement.
Test time (
mvn testfor the verifier, two interleaved runs of each on the same machine, surefire time summed over test cases):TestExamplesTestStateHintsmvn testfor the verifier: 569 tests, 0 failures, 0 errors.🤖 Generated with Claude Code