Repository navigation
Show the definitions of the aliases a refinement error uses - #489
Conversation
Codex review, round 1
Fixed in the latest commit: whitespace is now collapsed only outside string and char literals. Checked with Declined: none. |
Codex review, round 2
Fixed in the latest commit: invocations are now keyed by the invocation itself ( Declined: none. |
Codex review, round 3
|
A refinement error now records, for found and for expected, each alias invocation with its definition substituted by the arguments, the alias parameters, its definition as written and where it is declared, so a client can show what an alias means and link back to it. The CLI keeps the alias names in the message and adds one where line per alias the message uses, with the aliases their definitions use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ff6ee0b to
5731d46
Compare
#493) Closes #492 Variables in errors no longer carry a superscript counter (`box¹⁵`). That counter matched nothing in the code, and it mixed Latin-1 `¹²³` with the Superscripts block, which many fonts draw unevenly. Each error now names its variables within one scope: - A variable that no other variable in the scope is written like keeps its plain name. - When several are written alike, one keeps the plain name: the declared variable, or else the latest instance the message shows. Each other one gets the line its value comes from, `x@12`. - Two tagged variables from the same line (or with no line) are numbered `x@12#1`, `x@12#2`. Two different instances never print the same. The message, counterexample and Found steps of an error share the same names. The context view follows the same rule within each refinement. Verification, internal names, counters and the translation table are unchanged; this is presentation only. `toSuperscript` is removed. ### Before / after **Plain names** (A2_10 RandomFailureSupplier, the case reported in the issue): ``` before: Refinement Error: failures.size()⁸⁴² == size(this.failures) && failures.size()⁸⁴² >= 0 is not a subtype of failures.size()⁸⁴² > 0 --> Counterexample: failures.size()⁸⁴² == 0 after: Refinement Error: failures.size() == size(this.failures) && failures.size() >= 0 is not a subtype of failures.size() > 0 --> Counterexample: failures.size() == 0 ``` **A3_07 ScannerOutputStage** (the only study error with two instances of a name in the message): ``` before: found startTiling(preview¹¹¹) && state2(preview¹¹¹) == state2(preview¹⁰⁹) && compressionSet(preview¹¹⁶) && state1(preview¹¹⁶) == state1(preview¹¹¹) but expected tilingExplicit(preview¹¹⁶) || tilingSet(preview¹¹⁶) after: found startTiling(preview@51) && state2(preview@51) == state2(preview@50) && compressionSet(preview) && state1(preview) == state1(preview@51) but expected tilingExplicit(preview) || tilingSet(preview) ``` **A1_56 EditCommand**: the message, and the original Found the panel steps back to: ``` before: expected redoable(undoManager⁵⁵⁷) but could not prove it after: expected redoable(undoManager) but could not prove it before after ∀#getUndoManager_result⁵⁵⁵ true ∀#getUndoManager_result true ∀#operation_result⁵³⁷ true ∀#operation_result@41 true ∀#if_condition⁵³⁸ !#operation_result⁵³⁷ ∀#if_condition@41 !#operation_result@41 ∀#operation_result⁵⁵⁰ true ∀#operation_result@56 true ∀#operation_result⁵⁵¹ true ∀#operation_result true ∀#if_condition⁵⁵² !(#operation_result⁵⁵⁰ || …⁵⁵¹) ∀#if_condition !(#operation_result@56 || #operation_result) ∀undoManager⁵⁵⁷ undoManager⁵⁵⁷ == …⁵⁵⁵ ∀undoManager undoManager == #getUndoManager_result ``` Across the test suite, 11 of 437 errors still show a tag in the message. All of them are before-and-after values (`s--`, `b = -b`, a typestate object across calls). Counters remain only where values on one line must be told apart, e.g. `n@11#1` in `for (; n > 0; n -= 2);`. ### API for the extension (liquid-java/vscode-liquidjava#175) - `LJError.getDisplayNames()` returns the error's `DisplayNames` for a `RefinementError` or `StateRefinementError` (null for other errors). - Binder names, counterexample variables and translation-table keys: `names.get(internalName)`. - Expected and the predicate of each step: `ExpressionFormatter.format(expression, names)`. - Context view: `ExpressionFormatter.format(refinement.getExpression(), ContextHistory.getInstance()::lineOf)` for `VariableDTO.refinement` / `mainRefinement`. - `RefinementError.getFoundAliases()` / `getExpectedAliases()` (#489) use the same names: `invocation` and `expansion` are formatted with the error's `DisplayNames`, so an invocation reads as in Found/Expected, e.g. `Below(b@9, 10)` with expansion `b@9 < 10`. - `VariableFormatter.format` and `ExpressionFormatter.format(expression)` keep working without a scope. They write plain names and number clashes within the expression. - The webview tokenizers should treat `name@12#2` as one identifier and can render the tag as `<sub>`. ### Tests - `DisplayNamesTest`: line tags with the latest shown value plain; numbering for a shared line or no line, and the declared variable kept plain. - `TestFoundBinders` (new `Basket.negate` fixture): message, counterexample and original Found rows name `b@85`, `b@86` and `b` alike. - `ContextHistoryTest`: the refinement of `b` after `b = -b` reads `b == b@19 && b > 0`. - Existing assertions on superscripts are updated to plain names where they compare them; assertions that strip superscripts are left as they are. `mvn test` (rebased on main after #489): 712 tests, 0 failures. Diff vs main: +353/−113 in 23 files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Closes #295.
A refinement error now says what the aliases in Found and Expected mean, both as structured data for the IDE and as text in the CLI.
CLI
The message keeps the alias names. Under it, one
whereline per alias the message uses, in order of appearance (Found first, then Expected), each alias once. The definition is printed as the spec wrote it: parameter names and named constants unchanged, whitespace collapsed to one line.Nested aliases. When a definition uses another alias, that alias gets its own
whereline after the one that uses it, and so on down. Recursive aliases are not expanded again.Repro 1: minimal, aliases in both Found and Expected
Before:
After:
Repro 2: external spec with named constants
The
x_e_oss_a1_01_sliderecoderexercise, with theImageWriteModealias from the issue on thejavax.imageio.ImageWriteParamspec,setCompressionMode(@Refinement("ImageWriteMode(_)") int mode), andwParam.setCompressionMode(4);aftergetDefaultWriteParam().Before:
After:
Repro 3:
PortaliasThe
y_e_oss_a2_04_vmwarecimprobeexercise plusstatic InetSocketAddress agentAt(String host, int port) { return new InetSocketAddress(host, port); }.Before:
After:
The exercise's other error (
expected timeout >= 0 but could not prove it) uses no alias and is unchanged.Nested aliases and argument order
Structured data for the IDE
RefinementErrorhas two new accessors:getFoundAliases(): one entry per distinct alias invocation in the final simplified Found VC chain, the chain the extension renders as Found.getExpectedAliases(): the same for Expected.Each returns a
List<liquidjava.diagnostics.AliasExpansion>. Entries come in order of appearance, followed by the aliases their definitions use.AliasExpansionis a record:invocationNat(n)expansionn >= 0aliasNatparameters["x"]definitionx >= 0@RefinementAliasdeclarationPositionSourcePosition@RefinementAliasannotation value, for go-to-definitiondeclaration()givesNat(x) = x >= 0.getMessageNotes(), new onLJDiagnosticand empty by default, returns the CLIwherelines.The extension would need to add these to
RefinementErrorDTO, e.g.foundAliases/expectedAliasesbuilt from a smallAliasExpansionDTO, withdeclarationPositionconverted viaSourcePositionDTO.from. For repro 1 that gives:Positions are elided above; they are the
@RefinementAliasvalue range in the extension's 0-basedSourcePositionDTOform. With that DTO, the webview can show each invocation's expansion next to Found and Expected (hover or an expandable row) and linkaliastodeclarationPosition. The extension is not changed in this PR.Changes
AliasWrapperkeeps the definition text as written (whitespace collapsed to one line, except inside string and char literals) and the declaration position.AliasDTOexposes the raw text it already parsed.AliasExpansion(new) collects and substitutes the invocations.RefinementErrortakes the context aliases (fromVCChecker) and computes the expansions and thewherelines.LJDiagnostic.toStringprints the message notes under the message.The earlier derivation-based expansion from #184, removed in #261, attached alias bodies as origins in the expression derivation tree. The VC chain has no such per-node origins, so this PR records the expansions alongside the chain instead. It reuses the old approach of substituting the arguments into the alias body.
StateRefinementErroris not covered. Its Expected is states, and it has its own Found shape.Tests
TestAliasExpansions(2 tests, existing fixtures, no new ones):ErrorAlias: Found and Expected each record their substitutedInRangeinvocation, with parameters and declaration line.ErrorStaticFinalInAlias: onewhereline with the constants as written, printed right under the message.Full verifier module (rebased on main): 708 tests, 0 failures.
🤖 Generated with Claude Code