Repository navigation
Fold a state error's Found under the constants its path conditions state - #529
CatarinaGamboa wants to merge 2 commits into
Conversation
The conditions of the conditional facts of a state error's Found are written with c for x wherever Found states x == c, for a literal or an enum constant, and folded; a branch is dropped only when its condition folds to a constant that rules it out. Found stays equivalent, and the fact x == c stays. A1_09's nested if-then-else becomes newMode == BORDERLESS && (previousMode != BORDERLESS ? ... : ...). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clean-code reviewI audited the diff with the clean-code-principles checklist. Fixed, while writing it:
Declined:
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex review, round 1
Soundness prompt. It found a real gap. Once a fold removes the last condition that mentions
The shorter headline is still a fact the verifier knew (S1), but the path condition that explains the branch disappears. That is not "folded under the shown path condition". Fixed in "Keep a state Found unfolded when folding would hide a fact it showed".
Tests: 730 run, 0 failures. |
Codex review, round 2Soundness prompt. Eight new programs, built and compared with
Declined for this PR. It is the round 1 fix working as designed: when keeping a fold would hide the path fact, we show the unfolded Found, which is sound and complete. Keeping a settled path fact visible needs This ends the Codex loop for this PR: nothing relevant is left. |
…fer to (#533) Closes #532. **Stacked on #530** (→ #528 → `main`). Retarget it to `main` before #530 is merged with `--delete-branch`. Every message that shows code now marks it with backticks, inside the message string: - headlines (`found … but expected …`, `expected … but could not prove it`); - hints; - origin notes; - the declared-here gloss (`where … means …`); - alias `where` lines; - counterexamples. ## How each client sees it - **Plain text** (`getMessage()`, `getMessageNotes()`, `Hint.message()`, `getCounterexampleStr()`, `getDeclarationNotes()`, and so the Problems tab and the hover): the string with backticks, as rustc writes its messages. - **CLI:** each span in blue (the existing `Colors.BLUE`), without backticks. The backticks stay when colour is off: `NO_COLOR` set (https://no-color.org) or no console (output piped or redirected). The colour switch applies to code spans only; the CLI's other colours are unchanged. - **IDE:** the same backticked strings, plus `LJDiagnostic.getReferences()`, a `List<SourceReference>` with one entry per line reference. See below. **Escaping:** `Code.of` fences code that holds backticks with a longer run, and pads it with one space on each side when it holds a backtick or starts or ends with a space: ``` `` c == '`' `` ```. `Code.render` strips exactly that padding, so it shows any non-empty code exactly as marked. ## Source references (for vscode-liquidjava#192) `record SourceReference(String message, String text, int start, String file, int line, int column)`: - `message`: the whole message line it belongs to, exactly as `getMessageNotes()` or `Hint.message()` gives it; - `text`: the words of `message` that name the place, e.g. `line 134` or `ArrayListRefinements.java:162`; - `start`: where `text` starts in `message`, as a UTF-16 index (a Java/JS string index), so `message.substring(start, start + text.length())` is `text`. Use it instead of searching, because the code of a call can hold the same words (`d.lock("line 18")` on line 18); - `file`: the absolute path; `line`, `column`: 1-based, from Spoon. Who carries one: - every origin note: the call, parameter, constructor or return-refinement declaration it names; - the deciding-argument hint (`… on line 20 decides …`), through the new `Hint.reference()`, which is null for other hints. `getReferences()` returns the notes' references first, then the hints'. ## Before → after (CLI piped, so backticks shown) ```text A1_38 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` - Call `buffered.mark(int)` before this call; afterwards `markState(buffered) == marked` where `marked(this)` means `markState(this) == marked`, `open(this)` means `stream(this) == open` A1_01 Origin: line 130, `writer.getDefaultWriteParam()` returns `compression(wParam) == compressionNotExplicit` - Call `wParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT)` before this call; afterwards `compression(wParam) == compressionExplicit` A2_04 Refinement Error: expected `timeout >= 0` but could not prove it Origin: line 94, `timeout` is a parameter of `checkCimReachable(String, int)` with no refinement --> Counterexample: `timeout == -1` A1_56 Origin: line 69, `frame.getUndoManager()` says nothing about `redoState(undoManager)` - Guard this call with `if (undoManager.canRedo())`; inside it, `redoState(undoManager) == redoable` A2_10 Refinement Error: found `failures.size() == size(this.failures) && failures.size() >= 0` but expected `failures.size() > 0` Origin: ArrayListRefinements.java:162, `failures.size()` guarantees only `failures.size() >= 0` ``` For all eight tasks (six study tasks and both practice tasks), the output with backticks removed is byte-identical to #530's. Each task still gives exactly one error, on its `reportedLine`. On a terminal the spans are blue and the backticks are gone. With `NO_COLOR=1` the backticks stay. ## Changes - `Code` (`of`, `render`), `SourceReference`, and `Colors.enabled()`. - `LJDiagnostic`: - `addMessageNote(SourceReference)` and `getReferences()`; - `toString` renders the message, notes, counterexample, hints and declaration notes. - Marked at the leaves where messages are built: - `ErrorMessage` (both headline frames); - `RefinementError` (counterexample, alias lines); - `AuxStateHandler.addMeanings` (gloss); - `StateHints`: each state name, call, receiver and constructor, with the prose words `or`, `and`, `then`, `with` left unmarked; - `OriginNotes`/`StateOrigin`, whose notes are now `SourceReference`s. - `NotFoundError` marks the missing name and the suggestion (``Alias `Positiv` could not be found``, ``Did you mean `Positive`?``). - Not marked: the other error kinds (state conflicts, syntax errors) and warnings, which the issue does not list. They are a follow-up if wanted. ## Tests - 59 existing expectations gained backticks. Each change was made by a script that accepts a new string only if removing its backticks gives exactly the old one. - `TestStateHints.assertHints` compares kind and message. - New tests, each failing without the change: - `CodeTest` (fence, colour on and off); - `TestOriginNotes.noteNamesTheLineOfTheCallForClientsToLinkTo`; - a reference check in the deciding-argument hint test. `mvn test`: 740 run, 0 failures. ## Conflicts - **#524 and #525 are merged**, and this branch sits on top of them (via #528's rebase), so it does not conflict with them. The lines of their code it changes are: - `StateHints.java`, on main: the hint templates at 116–119, 131–132, 182–184, 234, 239–240, 249, 252, 258, 260, 269, 295–297, 301, 303–305, 349, and `stateName` at 387–389; - `RefinementError.java`, on main: 71 (counterexample) and 123 (alias `where` lines); - `ErrorMessage.java` 39–41 (headline); - `AuxStateHandler.java` 865–866 (gloss). - With the #529 agent's work: both may touch `VCChecker.throwStateRefinementError`. This PR changes only the notes' parameter type there. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
Fixed in another stack |
Closes #522, part 5 of 5: A1_09's nested if-then-else Found, folded under the shown path condition. Design: #522 (comment)
What it does
A new pass,
VCPathFolding, runs once on the shown Found of a state error, afterVCRelevantFacts:It collects the facts
x == cthat the chain states.cis a literal (int, long, real, boolean, char), anEnumnode, or a Java enum constant. A Java enum constant enters the VC as a variable namedType.CONST; it counts as a constant only when the unsimplified chain binds it with an enum type that declares that constant.In the conditions of the chain's if-then-else facts, it writes
cforx. It then folds the condition with the existingVCFolding/VCLogicalSimplification, plusA == B/A != Bbetween two such enum constants (distinct values of a datatype).A branch is dropped only when its condition folds to
trueorfalse. Otherwise the ITE stays, with its simplified condition. No SMT check is used.VCCheckerkeeps the fold only if every fact of the unfolded shown Found that has no if-then-else is still shown. Otherwise, when a settled branch would make the headline drop the path fact that chose it (mode == A), it shows the unfolded Found.Why it is sound: every fact of the premise chain holds wherever the others are checked. Writing
cforxwherex == cis a premise keeps Found equivalent, and the factx == citself stays. The chain the pass gets has no Expected node (joinPredicatesclones it before appending Expected), so nothing is assumed from Expected.The pass is a normal simplification step ("Path Folding"), so the panel's steps can show it.
A1_09, before and after
otherwiseleft to the panel rows (extension side).reportedLine.Test
TestFoundBinders.stateFoundFoldsTheBranchesItsPathConditionRulesOut, on a new fixture shaped like A1_09:ErrorStatePathFolding.java, an enum mode with a two-arm if/else-if and a switch case. No existing fixture has a path-condition constant inside an ITE. The test fails without the change.mvn test: 730 run, 0 failures.Conflicts
VCChecker.throwStateRefinementError(whereshownis computed). The From-note PRs (Origin note: say where the wrong state of a receiver comes from #528) and Show a counterexample for state errors, restricted to the shown Found #527 change the lines just after it, so whichever merges second needs a trivial rebase.🤖 Generated with Claude Code