Skip to content

Commit 275ca17

Browse files
Show variables by their name, with a line only to tell instances apart
Errors and context refinements wrote every variable instance with its verifier-wide counter in superscript digits (box¹⁵), which matched nothing in the code and mixed two Unicode blocks that many fonts draw unevenly. Each error, and each context refinement, now names its variables in one scope: a variable written alike to no other keeps its plain name; when several are, the declared variable or the latest instance shown keeps it and each other one gets the line its value comes from (x@12), numbered only when they share a line or have none (x@12#1). The message, counterexample and Found steps share the error's names, exposed with getDisplayNames(); ContextHistory.format(refinement) does the same for the context view. Verification, internal names and the translation table are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent f44048f commit 275ca17

23 files changed

Lines changed: 372 additions & 113 deletions

‎liquidjava-example/src/main/java/testSuite/classes/found_binders_error/Basket.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,11 @@ static int count() {
7878
int pickByCount() {
7979
return random.nextInt(count()); // Expect: Refinement Error
8080
}
81+
82+
// the earlier values of a variable are written with the line they come from
83+
void negate() {
84+
@Refinement("_ > 0")
85+
int b = 8;
86+
b = -b; // Expect: Refinement Error
87+
}
8188
}

‎liquidjava-verifier/src/main/java/liquidjava/diagnostics/AliasExpansion.java‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import liquidjava.rj_language.ast.AliasInvocation;
1212
import liquidjava.rj_language.ast.Expression;
1313
import liquidjava.rj_language.ast.Var;
14+
import liquidjava.rj_language.ast.formatter.DisplayNames;
15+
import liquidjava.rj_language.ast.formatter.ExpressionFormatter;
1416
import spoon.reflect.cu.SourcePosition;
1517

1618
/**
@@ -40,19 +42,19 @@ public String declaration() {
4042

4143
/**
4244
* The expansions of the alias invocations in an expression, each invocation once in order of appearance, followed
43-
* by those of the aliases their definitions use
45+
* by those of the aliases their definitions use, naming the variables as the diagnostic does
4446
*/
45-
public static List<AliasExpansion> of(Expression expression, List<AliasWrapper> aliases) {
47+
public static List<AliasExpansion> of(Expression expression, List<AliasWrapper> aliases, DisplayNames names) {
4648
Map<String, AliasWrapper> byName = new LinkedHashMap<>();
4749
for (AliasWrapper alias : aliases)
4850
byName.put(alias.getName(), alias);
4951
Map<String, AliasExpansion> expansions = new LinkedHashMap<>();
50-
collect(expression, byName, expansions, new HashSet<>());
52+
collect(expression, byName, names, expansions, new HashSet<>());
5153
return new ArrayList<>(expansions.values());
5254
}
5355

5456
// Expands the invocations under expression, skipping aliases already being expanded so recursive ones end
55-
private static void collect(Expression expression, Map<String, AliasWrapper> aliases,
57+
private static void collect(Expression expression, Map<String, AliasWrapper> aliases, DisplayNames names,
5658
Map<String, AliasExpansion> expansions, Set<String> expanding) {
5759
List<AliasInvocation> invocations = new ArrayList<>();
5860
invocationsOf(expression, invocations);
@@ -64,14 +66,16 @@ private static void collect(Expression expression, Map<String, AliasWrapper> ali
6466
if (alias == null || expanding.contains(alias.getName()) || expansions.containsKey(key))
6567
continue;
6668
Expression body = substituteArguments(alias, invocation.getArgs());
67-
expansions.put(key, new AliasExpansion(invocation.toDisplayString(), body.toDisplayString(),
68-
alias.getName(), alias.getVarNames(), alias.getDefinition(), alias.getPosition()));
69+
expansions.put(key,
70+
new AliasExpansion(ExpressionFormatter.format(invocation, names),
71+
ExpressionFormatter.format(body, names), alias.getName(), alias.getVarNames(),
72+
alias.getDefinition(), alias.getPosition()));
6973
bodies.put(key, body);
7074
}
7175
for (Map.Entry<String, Expression> body : bodies.entrySet()) {
7276
Set<String> inner = new HashSet<>(expanding);
7377
inner.add(expansions.get(body.getKey()).alias());
74-
collect(body.getValue(), aliases, expansions, inner);
78+
collect(body.getValue(), aliases, names, expansions, inner);
7579
}
7680
}
7781

‎liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java‎

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import liquidjava.rj_language.ast.Enum;
2020
import liquidjava.rj_language.ast.Expression;
2121
import liquidjava.rj_language.ast.Var;
22-
import liquidjava.rj_language.ast.formatter.VariableFormatter;
22+
import liquidjava.processor.context.PlacementInCode;
23+
import liquidjava.rj_language.ast.formatter.DisplayNames;
24+
import liquidjava.rj_language.ast.formatter.ExpressionFormatter;
2325
import liquidjava.rj_language.opt.VCSimplificationResult;
2426
import liquidjava.smt.Counterexample;
2527
import liquidjava.utils.GeneratedNames;
@@ -48,24 +50,32 @@ public class RefinementError extends LJError {
4850
private final List<AliasExpansion> foundAliases;
4951
private final List<AliasExpansion> expectedAliases;
5052
private final List<String> aliasDeclarations;
53+
private final DisplayNames displayNames;
5154

5255
public RefinementError(SourcePosition position, SourcePosition declarationPosition, Predicate expected,
5356
VCSimplificationResult found, TranslationTable translationTable, Counterexample counterexample,
5457
String customMessage, List<AliasWrapper> aliases) {
55-
super("Refinement Error", message(expected, found), position, translationTable, customMessage);
58+
this(position, declarationPosition, expected, found, translationTable, counterexample, customMessage, aliases,
59+
displayNames(expected, found, translationTable));
60+
}
61+
62+
private RefinementError(SourcePosition position, SourcePosition declarationPosition, Predicate expected,
63+
VCSimplificationResult found, TranslationTable translationTable, Counterexample counterexample,
64+
String customMessage, List<AliasWrapper> aliases, DisplayNames displayNames) {
65+
super("Refinement Error", message(expected, found, displayNames), position, translationTable, customMessage);
5666
this.expected = expected;
5767
this.found = found;
5868
Expression foundExpression = found.getImplication().toPredicate().getExpression();
59-
this.foundAliases = AliasExpansion.of(foundExpression, aliases);
60-
this.expectedAliases = AliasExpansion.of(expected.getExpression(), aliases);
69+
this.foundAliases = AliasExpansion.of(foundExpression, aliases, displayNames);
70+
this.expectedAliases = AliasExpansion.of(expected.getExpression(), aliases, displayNames);
6171
this.aliasDeclarations = aliasDeclarations(linkedToExpected(foundExpression, expected.getExpression()),
62-
expected.getExpression(), aliases);
72+
expected.getExpression(), aliases, displayNames);
6373
this.counterexample = filterCounterexample(counterexample);
6474
this.declarationPosition = declarationPosition;
75+
this.displayNames = displayNames;
6576
if (!this.counterexample.isEmpty()) {
6677
String counterexampleString = this.counterexample.assignments().stream()
67-
.map(a -> VariableFormatter.format(a.first()) + " == " + a.second())
68-
.collect(Collectors.joining(" && "));
78+
.map(a -> displayNames.get(a.first()) + " == " + a.second()).collect(Collectors.joining(" && "));
6979
setCounterexampleStr("Counterexample: " + counterexampleString);
7080
}
7181
setCategory(categoryOf(expected, found));
@@ -76,21 +86,43 @@ public RefinementError(SourcePosition position, SourcePosition declarationPositi
7686

7787
// Only the facts of found about the values expected constrains; path conditions about other values stay in the
7888
// derivation
79-
private static String message(Predicate expected, VCSimplificationResult found) {
80-
Expression shown = linkedToExpected(found.getImplication().toPredicate().getExpression(),
81-
expected.getExpression());
89+
private static String message(Predicate expected, VCSimplificationResult found, DisplayNames names) {
90+
Expression shown = shownFound(expected, found);
91+
String expectedString = ExpressionFormatter.format(expected.getExpression(), names);
8292
if (isTrue(shown))
83-
return String.format("expected %s but could not prove it", expected.getExpression().toDisplayString());
84-
return String.format("%s is not a subtype of %s", shown.toDisplayString(),
85-
expected.getExpression().toDisplayString());
93+
return String.format("expected %s but could not prove it", expectedString);
94+
return String.format("%s is not a subtype of %s", ExpressionFormatter.format(shown, names), expectedString);
95+
}
96+
97+
/** The facts of found that the message shows */
98+
static Expression shownFound(Predicate expected, VCSimplificationResult found) {
99+
return linkedToExpected(found.getImplication().toPredicate().getExpression(), expected.getExpression());
100+
}
101+
102+
/**
103+
* The names of an error's variables: those of its message come first, then those only the steps of found show, each
104+
* with the line its value comes from
105+
*/
106+
static DisplayNames displayNames(Predicate expected, VCSimplificationResult found,
107+
TranslationTable translationTable) {
108+
List<String> shown = new ArrayList<>();
109+
shownFound(expected, found).getVariableNames(shown);
110+
expected.getExpression().getVariableNames(shown);
111+
VCSimplificationResult original = original(found);
112+
List<String> others = original.getBinders();
113+
original.getImplication().toPredicate().getExpression().getVariableNames(others);
114+
return DisplayNames.of(shown, others, name -> {
115+
PlacementInCode placement = translationTable == null ? null : translationTable.get(name);
116+
return placement == null ? null : placement.getLine();
117+
});
86118
}
87119

88120
// Each alias the message uses once, with those their definitions use, as declared
89121
private static List<String> aliasDeclarations(Expression shownFound, Expression expected,
90-
List<AliasWrapper> aliases) {
122+
List<AliasWrapper> aliases, DisplayNames names) {
91123
Map<String, String> declarations = new LinkedHashMap<>();
92124
for (Expression shown : List.of(shownFound, expected))
93-
for (AliasExpansion expansion : AliasExpansion.of(shown, aliases))
125+
for (AliasExpansion expansion : AliasExpansion.of(shown, aliases, names))
94126
declarations.putIfAbsent(expansion.alias(), expansion.declaration());
95127
return new ArrayList<>(declarations.values());
96128
}
@@ -135,6 +167,11 @@ public List<String> getMessageNotes() {
135167
return aliasDeclarations.stream().map(declaration -> "where " + declaration).toList();
136168
}
137169

170+
/** The names this error gives its variables, for every view of it to show them alike */
171+
public DisplayNames getDisplayNames() {
172+
return displayNames;
173+
}
174+
138175
// Keeps the counterexample assignments to the terms of expected and of the found facts linked to them, except
139176
// values found already states, so there is none when found fixes every value expected depends on. The terms of
140177
// expected come first, those found says nothing about before the rest, then the linked terms in the order of the
@@ -182,10 +219,14 @@ private static boolean states(Expression term, Expression value, Pair<String, St
182219

183220
// Binders of the VC chain before simplification, which keeps the variables in the order they were introduced
184221
private List<String> originalBinders() {
185-
VCSimplificationResult original = getFound();
222+
return original(getFound()).getBinders();
223+
}
224+
225+
private static VCSimplificationResult original(VCSimplificationResult found) {
226+
VCSimplificationResult original = found;
186227
while (original.getOrigin() != null)
187228
original = original.getOrigin();
188-
return original.getBinders();
229+
return original;
189230
}
190231

191232
// 0 for terms of expected found says nothing about, whose values alone break it, 1 for the other terms of

‎liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java‎

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package liquidjava.diagnostics.errors;
22

33
import static liquidjava.rj_language.opt.VCSimplificationUtils.isTrue;
4-
import static liquidjava.rj_language.opt.VCSimplificationUtils.linkedToExpected;
54

65
import java.util.List;
76

@@ -12,6 +11,8 @@
1211
import liquidjava.rj_language.Predicate;
1312
import liquidjava.rj_language.ast.Expression;
1413
import liquidjava.rj_language.ast.FunctionInvocation;
14+
import liquidjava.rj_language.ast.formatter.DisplayNames;
15+
import liquidjava.rj_language.ast.formatter.ExpressionFormatter;
1516
import liquidjava.rj_language.opt.VCSimplificationResult;
1617
import spoon.reflect.cu.SourcePosition;
1718

@@ -26,11 +27,21 @@ public class StateRefinementError extends LJError {
2627
private final VCSimplificationResult found;
2728
private final SourcePosition declarationPosition;
2829
private final StateErrorContext context;
30+
private final DisplayNames displayNames;
2931

3032
public StateRefinementError(SourcePosition position, SourcePosition declarationPosition, Predicate expected,
3133
VCSimplificationResult found, TranslationTable translationTable, String customMessage, List<Hint> hints,
3234
ErrorCategory category, StateErrorContext context) {
33-
super("State Refinement Error", message(expected, found), position, translationTable, customMessage);
35+
this(position, declarationPosition, expected, found, translationTable, customMessage, hints, category, context,
36+
RefinementError.displayNames(expected, found, translationTable));
37+
}
38+
39+
private StateRefinementError(SourcePosition position, SourcePosition declarationPosition, Predicate expected,
40+
VCSimplificationResult found, TranslationTable translationTable, String customMessage, List<Hint> hints,
41+
ErrorCategory category, StateErrorContext context, DisplayNames displayNames) {
42+
super("State Refinement Error", message(expected, found, displayNames), position, translationTable,
43+
customMessage);
44+
this.displayNames = displayNames;
3445
this.declarationPosition = declarationPosition;
3546
this.expected = expected;
3647
this.found = found;
@@ -44,13 +55,12 @@ public StateRefinementError(SourcePosition position, SourcePosition declarationP
4455

4556
// Only the facts of found about the objects of expected, and only when one of them tells a state: an alias or a
4657
// guard on another value says nothing about the state expected needs
47-
private static String message(Predicate expected, VCSimplificationResult found) {
48-
Expression shown = linkedToExpected(found.getImplication().toPredicate().getExpression(),
49-
expected.getExpression());
58+
private static String message(Predicate expected, VCSimplificationResult found, DisplayNames names) {
59+
Expression shown = RefinementError.shownFound(expected, found);
60+
String expectedString = ExpressionFormatter.format(expected.getExpression(), names);
5061
if (!appliesGhost(shown))
51-
return String.format("expected %s but could not prove it", expected.getExpression().toDisplayString());
52-
return String.format("found %s but expected %s", shown.toDisplayString(),
53-
expected.getExpression().toDisplayString());
62+
return String.format("expected %s but could not prove it", expectedString);
63+
return String.format("found %s but expected %s", ExpressionFormatter.format(shown, names), expectedString);
5464
}
5565

5666
private static boolean appliesGhost(Expression expression) {
@@ -75,6 +85,11 @@ public VCSimplificationResult getFoundSimplification() {
7585
return found;
7686
}
7787

88+
/** The names this error gives its variables, for every view of it to show them alike */
89+
public DisplayNames getDisplayNames() {
90+
return displayNames;
91+
}
92+
7893
/** What is known about the receiver's states, or null if they could not be computed */
7994
public StateErrorContext getContext() {
8095
return context;

‎liquidjava-verifier/src/main/java/liquidjava/processor/context/ContextHistory.java‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
import java.util.HashSet;
55
import java.util.Map;
66
import java.util.Set;
7+
import java.util.stream.Stream;
78

89
import liquidjava.api.CommandLineLauncher;
10+
import liquidjava.rj_language.Predicate;
11+
import liquidjava.rj_language.ast.formatter.DisplayNames;
12+
import liquidjava.rj_language.ast.formatter.ExpressionFormatter;
913
import liquidjava.utils.Utils;
1014
import spoon.reflect.cu.SourcePosition;
1115
import spoon.reflect.declaration.CtElement;
@@ -96,4 +100,19 @@ public Set<RefinedFunction> getMethods() {
96100
public Map<String, Set<String>> getFileScopes() {
97101
return fileScopes;
98102
}
103+
104+
/**
105+
* A refinement as the context shows it, its variables named as in {@link DisplayNames} with the lines of the
106+
* variables saved here
107+
*/
108+
public String format(Predicate refinement) {
109+
DisplayNames names = DisplayNames.of(refinement.getVariableNames(), Set.of(), this::lineOf);
110+
return ExpressionFormatter.format(refinement.getExpression(), names);
111+
}
112+
113+
private Integer lineOf(String name) {
114+
return Stream.concat(localVars.stream(), globalVars.stream()).filter(v -> v.getName().equals(name))
115+
.map(RefinedVariable::getPlacementInCode).filter(p -> p != null).map(PlacementInCode::getLine)
116+
.findFirst().orElse(null);
117+
}
99118
}

‎liquidjava-verifier/src/main/java/liquidjava/processor/context/PlacementInCode.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public SourcePosition getAnnotationPosition() {
3131
return annotationPosition;
3232
}
3333

34+
/** The line of this placement, or null when it is not in code */
35+
public Integer getLine() {
36+
return position == null || !position.isValidPosition() ? null : position.getLine();
37+
}
38+
3439
public static PlacementInCode createPlacement(CtElement elem) {
3540
CtElement elemCopy = elem.clone();
3641
// cleanup annotations

0 commit comments

Comments
 (0)