Repository navigation
Accept a state written as a value of its set: phase(this) == open - #510
Conversation
Codex review, round 1 (
|
Codex review, round 2
Fixed. Only the sets that apply to the argument's type are considered, that is, sets declared on its class or a supertype. The type comes from where the spec is written:
When the type is unknown, the state is read only if every set of that name has it. If only some do, the name stays an unknown variable and gets the existing "not found" error, never a different meaning.
Regression: |
Codex review, round 3
Declined: the shorthand does not support this either. No code change this round. |
Codex review, round 4 (soundness only, the shorthand as the baseline)
Fixed. The rule "every set of that name must have the state" is only needed when the argument's type is unknown. When it is known, the applicable sets are all in the argument's own class hierarchy, and the shorthand picks the state by name among them. So:
The round 2 case still holds: an unrelated class's Regression: |
Codex review, round 5
Declined, no valid program reaches it. Both halves were checked on main, before this PR:
So a field named like a state never had a meaning that the rewrite could change. Treating every field as a variable would instead break the new form in a class that has a field named like one of its states, where the shorthand works. No code change. |
Codex review, round 6Codex was asked for programs that are valid on main and change meaning or verdict. It found two, both the two
Kept, deliberately. In both, the shorthand ( No code change. With rounds 5 and 6 reporting nothing else on the rewrite, the review loop for this PR is done. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n the same place Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… type Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s the shorthand does Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0f94aaa to
90ddc55
Compare
…#511) Part of #502 (PR 2, messages). **Depends on #510**, which depends on #507 and #508. Only the last commit belongs here. ## What State errors show the states of **named** sets as values of their sets, whichever form the spec used. `ErrorStateHintMultipleSets` (sets `doorState` and `lockState`): ``` - found closed(d@26) && locked(d) && doorState(d) == doorState(d@26) but expected closed(d) && unlocked(d) + found doorState(d@26) == closed && lockState(d) == locked && doorState(d) == doorState(d@26) but expected doorState(d) == closed && lockState(d) == unlocked ``` It is now plain which set fails (only `lockState`), and the frame equality chains on its own: `doorState(d) == doorState(d@26)` and `doorState(d@26) == closed`. `!open(x)` is shown as `door(x) != open`. A state keeps the shorthand when its set has a generated name (`state2(x) == closed` reads worse than `closed(x)`), or when sets with different names have a state of that name. That way the set printed is never a guess. ## How `StateValues.toSetForm` rewrites expressions for display only. `StateRefinementError` receives it as `display`: - **The facts to show are still chosen on the expressions as given.** `linkedToExpected` and #495's relevance step run first. A shared state name such as `closed`, which is a variable in the rewritten form, would otherwise link facts about unrelated objects. - The message, `getExpected()` and every derivation step (`VCSimplificationResult.map`) hold the rewritten form, so the **IDE shows it without a server change**: the server formats those expressions as they are. Nothing the verifier decides changes: the VC, the verdict, the hints and the category are the same. Refinement errors (`RefinementError`) keep the shorthand for now. They also build alias expansions and counterexamples from the same expressions, so that would be its own change. `Formats.STATE_SET` names the generated `stateN`, which the display needs to recognise. ## Tests - `TestStateHints#foundKeepsANamedStateSetByItsName`: the message above. It fails without the change. The full suite passes locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…512) Part of #502 (PR 3, docs). **Depends on #511** (and so on #510, #508 and #507). Only the last commit belongs here. The README's typestate example now names its set and writes states as values of it: ```java @stateset(name="connection", value={"unconnected", "bound", "connected", "closed"}) ... @StateRefinement(from="connection(this) == unconnected", to="connection(this) == bound") public void bind(SocketAddress add); ... @StateRefinement(from="connection(this) != closed", to="connection(this) == closed") public void close(); ``` A short paragraph says that the shorthand `bound(this)` is still accepted, that a `to` keeps the sets it does not mention, and what the error looks like. That error was checked by running the README example on #511: ``` State Refinement Error: found connection(socket) == bound but expected connection(socket) == connected ``` The study exercises live in the study repository and are not changed here. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
) Found while converting the study exercises (proposal D) to the value form from #502. ```java @ExternalRefinementsFor("java.awt.Window") @stateset(name = "display", value = {"notDisplayable", "displayable"}) interface WindowRefinements { ... } @ExternalRefinementsFor("java.awt.Component") interface ComponentRefinements { @refinement("_ ? display(this) == displayable : display(this) == notDisplayable") boolean isDisplayable(); } ``` On main this reports `Not Found Error: Variable 'displayable' could not be found` at the call, while the shorthand `_ ? displayable(this) : notDisplayable(this)` is accepted. #510 reads the value form only against sets declared on the argument's type or its supertypes, and `display` is declared on `Window`, a subtype of `Component`. A `Component` can be a `Window`, so a set now also applies when it is declared on a subtype of the argument's type (`TypeChecker.isRelatedTo`). Sets of unrelated classes are still ignored, so the wrong-set and unrelated-same-name cases from #510 behave as before. Test: `classes/state_value_set_of_subtype_correct` (the study's Frame/Window case, with `dispose` requiring `display(this) == displayable`); it fails on main with the error above. Full suite: 726 tests pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of #502 (PR 1, the language). Depends on #507 and #508. Until they are merged, this diff also shows their commits; only the last commit belongs here.
What
A spec can write a state as a value of its set:
set(e) == statemeansstate(e), andset(e) != statemeans!state(e), in either order. The old shorthand stays valid, and the two can be mixed.How
StateValues.toShorthandrewrites the new form into the shorthand once, where the spec is parsed and in that spec's scope. Everything after that is unchanged, and that includes every part that reads states syntactically: the frame equalities, the state machine, the hints,DecidingArgument,StateErrorCategoriesand #495's relevance pass. The parse sites are:@Refinement, viagetRefinementFromAnnotation;createStatePredicate;to, viasetConstructorStates;Every set is already registered when specs are parsed (#508), so there is no late resolution, and a caller's variable can never capture a state name.
Scope rules (from the #502 design reviews):
_,thisandreturn, which are always variables. Specs that are valid today mean the same.State 'locked' is not a state of 'door' (its states are closed, opened).this,_, a parameter, a context variable,old(x), or an alias's declared parameter type. When the type is known, any applicable set may have the state, as with the shorthand. When it is unknown, every set of that name must have it, and otherwise the name stays an unknown variable. The rewritten state is named exactly as the shorthand written in the same place (a.Sub.door(this) == openbecomesa.Sub.open(this)), so it is resolved the same way.Two new checks on⚠️ breaking for specs that have this ambiguity today: they are now rejected even if the ambiguous state is never used), because
@StateSet(open(this)must say which set it means:State 'open' is in two state sets of X: state1 and state2. That was accepted before, and the shorthand picked one set silently.state1next to the unnamed setstate1is reported.Tests (each fails without the change)
CorrectStateValues, written only in the new form: two named sets, a constructorto, from/to,!=, a branch that keeps a set viaold, a parameter named like a state, an alias used next to a local variable named like its state, and states named_andreturn.ErrorStateValues: the wrong-set error, a state name shared by two sets, and a state named like a generated set.TestStateHints#queryWrittenWithItsStateSetIsOfferedAsAGuard: a query written in the new form is still offered as a guard, so the state machine reads it.The full suite passes locally. Found while testing: #509, an existing false error for a boolean query in an ordinary class, with either form.
🤖 Generated with Claude Code