GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Show a counterexample for state errors, restricted to the shown Found by CatarinaGamboa · Pull Request #527 · liquid-java/liquidjava

Conversation

@CatarinaGamboa

@CatarinaGamboa CatarinaGamboa commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator

Closes #522, part 4 of 5: counterexample for state errors. Design: #522 (comment)

State refinement errors had no counterexample. Now, when a state error is reported, one more solver call asks for a model of Found ∧ ¬Expected. It is the same VC, with the same context gathering, as the failed check. The model is shown restricted as S2 asks.

What is shown

Only the assignments to variables of the Found the headline shows. Left out:

  • every ghost or state application, such as display(host) = 1: a state is an unconstrained number (S3);
  • generated names (#…);
  • object values (T!val!n);
  • values that a Found fact x == c already states (RefinementError.isKnown, reused).

If the headline shows no Found (expected … but could not prove it), there is no counterexample. What is shown is a sub-assignment of a model of Found ∧ ¬Expected. If the solver fails, the error is reported without one.

Study tasks, before and after

A1_09:

 State Refinement Error: found newMode == BORDERLESS && (previousMode == BORDERLESS && newMode != BORDERLESS ? … : …) but expected display(host) == notDisplayable
 51 |       case BORDERLESS:
 52 |         host.setResizable(false);
 53 |         host.setUndecorated(true);
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^
 54 |         host.setExtendedState(Frame.MAXIMIZED_BOTH);
 55 |         host.setVisible(true);
+ --> Counterexample: previousMode == BORDERLESS

newMode is left out because Found states it.

  • A1_38, A1_01, A1_56: no counterexample (states only, or no Found shown), as the review asked.
  • A2_10, A2_04, practice, practice2: unchanged.

Each task still gives exactly one error, on its reportedLine.

Changes

  • TypeChecker.throwStateRefinementError asks for the model (best effort) and passes it through VCChecker.
  • StateRefinementError.showCounterexample filters the model.
  • RefinementError.isKnown becomes package-private so the filter can reuse it.
  • Test: TestCounterexamples, on the existing ErrorStateHintPathDependent.java, gives ready == false. It fails without the change.

Conflicts

  • Independent of the From-note PRs (From note: name an unrefined parameter as the unknown value's origin #526 and the stacked ones). They also add a parameter to throwStateRefinementError, so whichever merges second needs a one-line rebase.
  • May touch the same files as One headline frame: found … but expected … for refinement errors #520/Drop the general UNKNOWN hint and show every from in declared-here #521 (RefinementError) only at isKnown's modifier.

Tests: mvn test, 729 run, 0 failures.

🤖 Generated with Claude Code

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@CatarinaGamboa @claude
Show a counterexample for state errors, without state values
797608f
A state refinement error now shows the values a model of what is known
where the call fails gives the variables of the found the message
shows. Values found already states, generated names and ghost or state
values are left out, so A1_09 shows previousMode == BORDERLESS and an
error about states alone shows none.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@CatarinaGamboa CatarinaGamboa mentioned this pull request Nov 10, 2026
Origin note: say where the wrong state of a receiver comes from #528
Merged
@CatarinaGamboa @claude
Show no state counterexample when every value fails the call
8b74fa3
Co-Authored-By: Claude Opus 5.5 <[email protected]>
@CatarinaGamboa

CatarinaGamboa commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Codex review, round 1

codex exec review --base origin/main: "I found no actionable regressions in the diff."

Soundness prompt: a program whose model assignment is real but irrelevant.

State Refinement Error: found noise ? closed(f) : created(f) but expected open(f)
 --> Counterexample: noise == false

Both arms violate open(f), so noise says nothing about why the call fails. No ghost or state value was printed.

Fixed in "Show no state counterexample when every value fails the call". Before asking for a model, one solver check asks whether Found ⇒ ¬Expected. If it holds, no value lets the call through, and there is no counterexample.

  • That program now shows none.
  • ErrorStateHintPathDependent still gives ready == false.
  • A1_09 still gives previousMode == BORDERLESS.
  • A1_38, A1_01 and A1_56 still give none.

Tests: 729 run, 0 failures.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CatarinaGamboa

CatarinaGamboa commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Clean-code review

I audited the diff with the clean-code-principles checklist.

Fixed:

  • [core-dry] The filter reuses RefinementError.isKnown (now package-private) instead of a second "Found already states it" check.
  • [core-fail-fast / separation] The solver calls live in TypeChecker.counterexample, which is best effort. A solver failure logs under -d and the error is reported without a counterexample, as receiverStates does for hints.

Declined:

  • [solid-srp] StateRefinementError.showCounterexample both filters the model and formats it. Splitting it would make a one-use helper. RefinementError keeps filtering and formatting together too.
  • [func-few-arguments] VCChecker.throwStateRefinementError gains a tenth parameter. Bundling them is a refactor of an existing signature, outside this small PR.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CatarinaGamboa

CatarinaGamboa commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Codex review, round 2

codex exec review: "I found no actionable regression in the diff."

Soundness prompt: no case printed a state or ghost value, named a variable missing from Found, or gave a value outside a model of Found ∧ ¬Expected. It reported one redundant value:

State Refinement Error: found #if_condition == flag && #if_condition && (flag && choice ? open(f) : created(f)) but expected open(f)
 --> Counterexample: flag == true && choice == false

Found entails flag == true through the guard (#if_condition == flag && #if_condition), but does not state it as a direct fact.

Declined. The value is real and part of a model, so the line is sound. The filter drops only values Found states as x == c, which is the same syntactic isKnown refinement-error counterexamples use. Catching entailed values would need one solver call per shown variable, and it would make the two kinds of error filter differently. Found also shows the generated #if_condition there, which is a separate display issue.

This ends the Codex loop for this PR: nothing relevant is left.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

This was referenced Nov 10, 2026
Fold a state error's Found under the constants its path conditions state #529
Closed
Say where the wrong state or value comes from (From note), and counterexamples for state errors #522
Closed
@CatarinaGamboa CatarinaGamboa closed this Nov 10, 2026
CatarinaGamboa added a commit that referenced this pull request Nov 11, 2026
@CatarinaGamboa @claude
Origin note: say where the wrong state of a receiver comes from (#528)
070fb75
Closes #522, part 2: **origin notes F1 and F2**, where the wrong state
comes from. Design:
#522 (comment)

**Base: `main`.** #526 is merged. #530 is stacked on this PR, so
retarget it to `main` before merging this one with `--delete-branch`.

## The Origin note
The PM's review renamed the note from **"From:"** to **"Origin:"**,
because "From" read like `@StateRefinement(from = …)` in declared-here.
Every origin note now has one shape, `Origin: <where>, <call or
variable> <verb> <state or refinement>`, and only the verb changes. The
first commit renames the note #526 merged (`FromNotes` → `OriginNotes`,
`TestFromNotes` → `TestOriginNotes`). The soundness conditions are
unchanged.

| Case | Text |
|---|---|
| F1, the call that last set the sets | `Origin: line 134, new
BufferedInputStream(stream) makes markState(buffered) == unmarked` |
| F2, a returned value | `Origin: line 130,
writer.getDefaultWriteParam() returns compression(wParam) ==
compressionNotExplicit` |
| F2, nothing known | `Origin: line 69, frame.getUndoManager() says
nothing about redoState(undoManager)` |
| F1 fallback, creation | `Origin: line L, new T(…) creates x with
<state>` |
| F3 (#526) | `Origin: line 94, timeout is a parameter of
checkCimReachable(String, int) with no refinement` |

"Makes" is used only for the call the walk proves last set the shown
sets. The fallback says "creates … with", because a later call may have
set the state again. That is a fact about the constructor, not about the
current state.

## When there is no note at all
No origin note is given when the receiver may change state out of the
verifier's sight before the call. That covers three cases:
- **Another name may hold it.** A local, a parameter or `this` is used,
before the call, other than as the target of a call or field access or
in `==`/`!=`. That means copied, passed as an argument (including to the
failing call itself), stored in an array, returned, bound in a method
reference (`x::start`), or used in a lambda or a local class. "Before"
means earlier in the source, in the failing call's arguments, or in a
loop around the call, unless the use is inside a `return`/`throw`.
- **A method of the analyzed code with no state refinement is called on
it before.** The verifier assumes such a call keeps the state.
- **It is a field.**

Declared state contracts are trusted, as the verifier trusts them. A
body that breaks its own declared contract is a verifier issue, outside
the notes (see the round 3 reply).

## Which sets
The note covers the sets that the failing call's `from` checks. When the
state machine models the call, it narrows them to the sets where some
state the receiver may be in is the state of no allowed state. This only
selects what is shown (S1).

## F1, a state set by a call
Walk back from the receiver through `StateChange`/`previous()` to the
first change whose `to` names a shown set. The note is printed only if:
- every instance on the way has a `StateChange`, so the walk crosses no
join, loop head or unknown origin;
- every change crossed names none of the shown sets. Each `to` keeps the
sets it does not name, through `set(this) == set(old(this))`;
- `StateSolver.implies(<instance after that change>, <Found's facts on
the shown sets>)` holds.

Text: `Origin: line L, <call> makes <Found on the sets that change
names>`. With two wrong sets, a change to one is not given the other.

**Fallback, when the walk breaks.** The note gives the creation fact
only if all hold:
- the receiver is a local whose only write is its initializer, a
constructor call with a single state refinement;
- no other call on it is to a method that changes state, and no loop may
repeat the failing call;
- Found's facts on the shown sets imply what that constructor gives.

Text: `Origin: line L, new T(…) creates x with <state>`. It is a fact
about the constructor. The last condition stops it from speaking when
the state may have changed since (A1_09).

## F2, a state from a returned value
Printed only if the walk reaches, possibly after crossing changes to
other sets, an instance with no `StateChange` that holds the call result
it was assigned. Instances now record a `CallResult` when a local is
declared or assigned from a call to a refined method. A state-preserving
call keeps it, as it keeps the `StateChange`. Joins, loop heads and
transitions make new instances without one.
- `Origin: line L, <call> returns <facts>`: the conjuncts of the
instantiated return refinement on the shown sets. Printed only if they
imply Found's facts on those sets.
- `Origin: line L, <call> says nothing about <set>(x)`: printed only if
all hold:
  - Found has no fact on the sets;
- the method has no state refinement with a `to` (as `to =
"running(_)"`);
- the return refinement names no state of them, mentions no variable but
`_`, and uses no alias. This rules out `_ == this`-style returns;
- no shown set has a generated name (`state1`), which the message would
otherwise never show.

## DebugLog check for A1_38 (asked before coding F1)
With `-d`, the VC at `buffered.reset()` (line 157) has a single
`buffered` instance, `#buffered_259 : (stream == open) ∧ (markState ==
unmarked)`. That is the constructor's state, with no ITE and no
loop-head instance. The note also took the F1 path, which needs a
`StateChange` on that instance. So neither loop (lines 136 and 150)
creates a join for `buffered`.

A broken walk now logs `[WARN] Origin note: no known call gives #x_n its
state, e.g. a join of branches`. A1_09 logs it for `#host_95`.

## Study tasks, before (main) and after
```diff
 State Refinement Error: found stream(buffered) == open && markState(buffered) == unmarked but expected markState(buffered) == marked && stream(buffered) == open
+  Origin: line 134, new BufferedInputStream(stream) makes markState(buffered) == unmarked
```
```diff
 State Refinement Error: found compression(wParam) == compressionNotExplicit but expected compression(wParam) == compressionExplicit || compression(wParam) == compressionTypeSet
+  Origin: line 130, writer.getDefaultWriteParam() returns compression(wParam) == compressionNotExplicit
```
```diff
 State Refinement Error: expected redoState(undoManager) == redoable but could not prove it
+  Origin: line 69, frame.getUndoManager() says nothing about redoState(undoManager)
```
```diff
 Refinement Error: expected timeout >= 0 but could not prove it
-  From: timeout is a parameter of checkCimReachable(String, int) (line 94) with no refinement
+  Origin: line 94, timeout is a parameter of checkCimReachable(String, int) with no refinement
```
- A1_09: no origin note. Its receiver is a join, and Found does not say
the state `new JFrame(…)` gives.
- A2_10, practice and practice2: unchanged.

Each task still gives exactly one error, on its `reportedLine`. All
three notes are the review's mock-ups, word for word.

I checked every state-error fixture of the suite. Each note names the
creating or the last call. After the Codex rounds, the fallback fires in
none, and fixtures where the receiver is captured, a field, `this` in a
loop, or given an unspecified call get no note.

## Tests
`TestOriginNotes`, three tests that fail without the change:
- `ErrorStateHintMultipleSets` (existing): `Origin: line 27, d.lock()
makes lockState(d) == locked`. Only the wrong set is named, and the walk
stops at the change to it;
- `ErrorStateFromReturnedValue` (new, small; no existing fixture assigns
a state from a return refinement): `Origin: line 27,
FromReturnedTask.create() returns phase(task) == idle`.

- `ErrorDeferredCallbackState` (existing): the receiver captured by a
lambda gets no note.

`mvn test`: 736 run, 0 failures.

## Conflicts
- With #520/#521 (another agent: `AuxStateHandler` declared-here): the
one `throwStateRefinementError` call in `changeState`, which gets a
`messageNotes` argument. The rest is additive.
- With #518/#519 (`StateHints`): none.
- With #527 (state counterexample, mine): both add a parameter to
`throwStateRefinementError`, a one-line rebase.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Say where the wrong state or value comes from (From note), and counterexamples for state errors

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@CatarinaGamboa
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.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.