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 the definitions of the aliases a refinement error uses by CatarinaGamboa · Pull Request #489 · liquid-java/liquidjava

Conversation

@CatarinaGamboa

@CatarinaGamboa CatarinaGamboa commented Nov 5, 2026 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Collaborator

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 where line 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 where line 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

@RefinementAlias("Positive(int x) { x > 0 }")
@RefinementAlias("Nat(int x) { x >= 0 }")
public class Probe {
    static void need(@Refinement("Positive(_)") int v) {}
    static void run(@Refinement("Nat(_)") int n) { need(n); }
}

Before:

Refinement Error: Nat(n) is not a subtype of Positive(n)

After:

Refinement Error: Nat(n) is not a subtype of Positive(n)
  where Nat(x) = x >= 0
  where Positive(x) = x > 0

Repro 2: external spec with named constants

The x_e_oss_a1_01_sliderecoder exercise, with the ImageWriteMode alias from the issue on the javax.imageio.ImageWriteParam spec, setCompressionMode(@Refinement("ImageWriteMode(_)") int mode), and wParam.setCompressionMode(4); after getDefaultWriteParam().

Before:

Refinement Error: mode³⁰⁶ == 4 is not a subtype of ImageWriteMode(mode³⁰⁶)

After:

Refinement Error: mode³⁰⁶ == 4 is not a subtype of ImageWriteMode(mode³⁰⁶)
  where ImageWriteMode(m) = m == ImageWriteParam.MODE_DISABLED || m == ImageWriteParam.MODE_DEFAULT || m == ImageWriteParam.MODE_EXPLICIT || m == ImageWriteParam.MODE_COPY_FROM_METADATA

Repro 3: Port alias

The y_e_oss_a2_04_vmwarecimprobe exercise plus static InetSocketAddress agentAt(String host, int port) { return new InetSocketAddress(host, port); }.

Before:

Refinement Error: expected Port(port⁶⁵) but could not prove it
 --> Counterexample: port⁶⁵ == 65536

After:

Refinement Error: expected Port(port⁶⁵) but could not prove it
  where Port(p) = 0 <= p && p <= 65535
 --> Counterexample: port⁶⁵ == 65536

The exercise's other error (expected timeout >= 0 but could not prove it) uses no alias and is unchanged.

Nested aliases and argument order

Refinement Error: expected Small(n) but could not prove it
  where Small(x) = Nat(x) && x < 10
  where Nat(x) = x >= 0

Refinement Error: Below(3, b) is not a subtype of Below(b, 5)
  where Below(a, b) = a < b

Structured data for the IDE

RefinementError has 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. AliasExpansion is a record:

field example meaning
invocation Nat(n) the invocation as the diagnostic displays it, so the client can find it in the Found/Expected text
expansion n >= 0 the definition with the parameters replaced by the arguments (all at once). Nested alias invocations are kept and have their own entry
alias Nat alias name
parameters ["x"] parameter names
definition x >= 0 definition as written in the @RefinementAlias
declarationPosition SourcePosition the @RefinementAlias annotation value, for go-to-definition

declaration() gives Nat(x) = x >= 0. getMessageNotes(), new on LJDiagnostic and empty by default, returns the CLI where lines.

The extension would need to add these to RefinementErrorDTO, e.g. foundAliases / expectedAliases built from a small AliasExpansionDTO, with declarationPosition converted via SourcePositionDTO.from. For repro 1 that gives:

"foundAliases": [
  { "invocation": "Nat(n)", "expansion": "n >= 0", "alias": "Nat", "parameters": ["x"],
    "definition": "x >= 0", "declarationPosition": { "file": ".../Probe.java", "lineStart": …, "colStart": …, "lineEnd": …, "colEnd": … } }
],
"expectedAliases": [
  { "invocation": "Positive(n)", "expansion": "n > 0", "alias": "Positive", "parameters": ["x"],
    "definition": "x > 0", "declarationPosition": { "file": ".../Probe.java", "lineStart": …, "colStart": …, "lineEnd": …, "colEnd": … } }
]

Positions are elided above; they are the @RefinementAlias value range in the extension's 0-based SourcePositionDTO form. With that DTO, the webview can show each invocation's expansion next to Found and Expected (hover or an expandable row) and link alias to declarationPosition. The extension is not changed in this PR.

Changes

  • AliasWrapper keeps the definition text as written (whitespace collapsed to one line, except inside string and char literals) and the declaration position. AliasDTO exposes the raw text it already parsed.
  • AliasExpansion (new) collects and substitutes the invocations.
  • RefinementError takes the context aliases (from VCChecker) and computes the expansions and the where lines.
  • LJDiagnostic.toString prints 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.

StateRefinementError is 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 substituted InRange invocation, with parameters and declaration line.
  • ErrorStaticFinalInAlias: one where line with the constants as written, printed right under the message.

Full verifier module (rebased on main): 708 tests, 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

CatarinaGamboa commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

Codex review, round 1

codex exec review --base origin/main on 91b3dc2 found one issue:

  • [P2] Preserve whitespace inside alias string literals (AliasWrapper.java): the definition shown in where lines collapsed every whitespace run, so x == "a b" would print as x == "a b", which is a different predicate from the one checked.

Fixed in the latest commit: whitespace is now collapsed only outside string and char literals. Checked with @RefinementAlias("Pair(int x) { x == 2 && \"a b\" == \"a b\" }"), which now prints where Pair(x) = x == 2 && "a b" == "a b". Full verifier suite: 700 tests, 0 failures.

Declined: none.

All reactions

Sorry, something went wrong.

Uh oh!

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

@CatarinaGamboa

CatarinaGamboa commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

Codex review, round 2

codex exec review --base origin/main on 8ae2cf3 found one issue:

  • [P2] Deduplicate alias invocations by identity, not display text (AliasExpansion.java): invocations were deduplicated by their display string, so two invocations whose arguments differ only in qualifier, e.g. P(a.x) and P(b.x), could both display as P(x) and the second expansion would be dropped.

Fixed in the latest commit: invocations are now keyed by the invocation itself (toString(), which keeps full names). The invocation field still holds the display text. Full verifier suite: 700 tests, 0 failures.

Declined: none.

All reactions

Sorry, something went wrong.

Uh oh!

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

@CatarinaGamboa

CatarinaGamboa commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

Codex review, round 3

codex exec review --base origin/main on ff6ee0b found no actionable issues, and the targeted alias-expansion and refinement-error tests pass. Review loop closed.

All reactions

Sorry, something went wrong.

Uh oh!

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

@CatarinaGamboa CatarinaGamboa mentioned this pull request Nov 5, 2026
Alias arguments are substituted one at a time: Below(b, a) is expanded as a < a, giving a false "can never be true" warning #491
Open
CatarinaGamboa and others added 3 commits November 5, 2026 18:43
@CatarinaGamboa @claude
Show the definitions of the aliases a refinement error uses
98c26fd
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 <[email protected]>
@CatarinaGamboa @claude
Keep whitespace inside string literals of an alias definition
782d206
Co-Authored-By: Claude Opus 5.5 <[email protected]>
@CatarinaGamboa @claude
Tell apart alias invocations that display the same
5731d46
Co-Authored-By: Claude Opus 5.5 <[email protected]>
@CatarinaGamboa
CatarinaGamboa force-pushed the fix/295-alias-expansion branch from ff6ee0b to 5731d46 Compare November 5, 2026 18:58
@CatarinaGamboa
CatarinaGamboa merged commit f44048f into main Nov 5, 2026
1 check passed

Uh oh!

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

@CatarinaGamboa CatarinaGamboa mentioned this pull request Nov 5, 2026
Show variables by their name, with a line only to tell instances apart #493
Merged
CatarinaGamboa added a commit that referenced this pull request Nov 6, 2026
@CatarinaGamboa @claude
Show variables by their name, with a line only to tell instances apart (
ae278d2
#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 <[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.

Restore alias expansion in refinement diagnostic history

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.