Skip to content

Commit 0bb37b4

Browse files
Do not link Found facts through an enum constant they only compare with
An enum constant read in code is a variable named Type.CONST, so the relevance pass linked every fact that compared a value with the same constant. A constant is a fixed value, so such facts share no unknown; it now links facts only when a function or alias takes it or a value one takes is compared with it, where equal objects have equal ghost values. A1_09 no longer shows newMode == BORDERLESS once the ternary no longer depends on newMode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ceee6be commit 0bb37b4

4 files changed

Lines changed: 100 additions & 13 deletions

File tree

‎liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import liquidjava.rj_language.ast.Ite;
2323
import liquidjava.rj_language.ast.Var;
2424
import liquidjava.rj_language.opt.NeededVariables;
25+
import liquidjava.rj_language.opt.VCKnownEnumValues;
2526
import liquidjava.rj_language.opt.VCRelevantFacts;
2627
import liquidjava.rj_language.opt.VCSimplification;
2728
import liquidjava.rj_language.opt.VCSimplificationResult;
@@ -465,8 +466,8 @@ protected void throwStateRefinementError(SourcePosition position, List<SourcePos
465466
TranslationTable map = new TranslationTable();
466467
VCImplication foundState = buildPremiseChain(map, expected, found);
467468
VCSimplificationResult simplified = foundState.simplify(NeededVariables.forStateError(expected));
468-
VCSimplificationResult shown = VCSimplification.simplifyOnce(simplified,
469-
new VCRelevantFacts(expected.getExpression(), this::ghostFunctionsOf));
469+
VCSimplificationResult shown = VCSimplification.simplifyOnce(simplified, new VCRelevantFacts(
470+
expected.getExpression(), this::ghostFunctionsOf, VCKnownEnumValues.enumConstants(foundState)));
470471
StateRefinementError error = new StateRefinementError(position, declarationPositions, expected, shown, map,
471472
customMessage, hints, category, context,
472473
e -> StateValues.toSetForm(e, this.context.getGhostStates(), this.context.getGhosts()),

‎liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCKnownEnumValues.java‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import java.util.HashMap;
77
import java.util.Map;
8+
import java.util.Set;
89

910
import liquidjava.processor.VCImplication;
1011
import liquidjava.rj_language.Predicate;
@@ -32,14 +33,25 @@ private record EnumComparison(Expression expression, String variable, String con
3233
}
3334

3435
/** The enum type of each enum constant, by the name the solver translates the constant from */
35-
private final Map<String, String> enumTypes = new HashMap<>();
36+
private final Map<String, String> enumTypes;
3637

3738
public VCKnownEnumValues(VCImplication implication) {
39+
this.enumTypes = enumTypesOf(implication);
40+
}
41+
42+
/** The names the constants of the enum types of the chain's binders are translated from, which denote values */
43+
public static Set<String> enumConstants(VCImplication implication) {
44+
return enumTypesOf(implication).keySet();
45+
}
46+
47+
private static Map<String, String> enumTypesOf(VCImplication implication) {
48+
Map<String, String> enumTypes = new HashMap<>();
3849
for (VCImplication row = implication; row != null; row = row.getNext())
3950
if (row.getType() != null && row.getType().getDeclaration()instanceof CtEnum<?> enumDecl)
4051
for (CtEnumValue<?> value : enumDecl.getEnumValues())
4152
enumTypes.put(String.format(Formats.ENUM, enumDecl.getSimpleName(), value.getSimpleName()),
4253
enumDecl.getQualifiedName());
54+
return enumTypes;
4355
}
4456

4557
/**

‎liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCRelevantFacts.java‎

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import liquidjava.processor.VCImplication;
1717
import liquidjava.rj_language.Predicate;
18+
import liquidjava.rj_language.ast.AliasInvocation;
1819
import liquidjava.rj_language.ast.BinaryExpression;
1920
import liquidjava.rj_language.ast.Expression;
2021
import liquidjava.rj_language.ast.FunctionInvocation;
@@ -27,22 +28,28 @@
2728
* value it constrains. Different ghost functions, or the same one over different objects, are different solver terms,
2829
* so facts that share no symbol with expected, directly or through other facts, are independent of it and are left out.
2930
* An object whose value a fact constrains, such as {@code x == y}, or that an alias or unknown function takes, is one
30-
* symbol with all its ghost functions, so its facts are never split. Nothing changes when every fact or none is
31-
* relevant
31+
* symbol with all its ghost functions, so its facts are never split. A constant, such as an enum constant read in code,
32+
* is a fixed value, so facts that only compare values with it share no symbol through it; it stays one when a function
33+
* or alias takes it or a value one takes is compared with it, as equal objects have equal ghost values. Nothing changes
34+
* when every fact or none is relevant
3235
*/
3336
public class VCRelevantFacts implements VCSimplificationPass {
3437

3538
private final Expression expected;
3639
private final Function<String, Set<String>> ghostFunctionsOf;
40+
private final Set<String> constants;
3741

3842
/**
3943
* @param ghostFunctionsOf
4044
* the ghost functions an invocation name stands for: a state's state set function, or the ghost itself;
4145
* none for any other name
46+
* @param constants
47+
* the variable names that denote constants, such as enum constants
4248
*/
43-
public VCRelevantFacts(Expression expected, Function<String, Set<String>> ghostFunctionsOf) {
49+
public VCRelevantFacts(Expression expected, Function<String, Set<String>> ghostFunctionsOf, Set<String> constants) {
4450
this.expected = expected;
4551
this.ghostFunctionsOf = ghostFunctionsOf;
52+
this.constants = constants;
4653
}
4754

4855
@Override
@@ -60,8 +67,10 @@ public VCImplication apply(VCImplication implication) {
6067
addSymbols(expected, new HashMap<>(), values);
6168
allFacts.forEach(fact -> addSymbols(fact, new HashMap<>(), values));
6269

63-
Set<String> linked = symbolsOf(expected, values);
64-
List<Set<String>> unlinked = new ArrayList<>(allFacts.stream().map(fact -> symbolsOf(fact, values)).toList());
70+
Set<String> valueConstants = valueConstants(expected, allFacts);
71+
Set<String> linked = symbolsOf(expected, values, valueConstants);
72+
List<Set<String>> unlinked = new ArrayList<>(
73+
allFacts.stream().map(fact -> symbolsOf(fact, values, valueConstants)).toList());
6574
boolean changed = true;
6675
while (changed) {
6776
changed = false;
@@ -75,8 +84,11 @@ public VCImplication apply(VCImplication implication) {
7584
}
7685
}
7786

78-
List<List<Expression>> kept = rowFacts.stream().map(facts -> facts.stream()
79-
.filter(fact -> symbolsOf(fact, values).stream().anyMatch(linked::contains)).toList()).toList();
87+
List<List<Expression>> kept = rowFacts.stream()
88+
.map(facts -> facts.stream()
89+
.filter(fact -> symbolsOf(fact, values, valueConstants).stream().anyMatch(linked::contains))
90+
.toList())
91+
.toList();
8092
int relevant = kept.stream().mapToInt(List::size).sum();
8193
if (relevant == 0 || relevant == allFacts.size())
8294
return implication;
@@ -99,13 +111,45 @@ private VCImplication rebuild(VCImplication row, Iterator<List<Expression>> all,
99111
return result;
100112
}
101113

114+
// The constants no function or alias takes and that are compared with no value one takes
115+
private Set<String> valueConstants(Expression expected, List<Expression> facts) {
116+
Set<String> objects = new HashSet<>();
117+
addArguments(expected, objects);
118+
facts.forEach(fact -> addArguments(fact, objects));
119+
Set<String> valueConstants = new HashSet<>(constants);
120+
valueConstants.removeAll(objects);
121+
removeComparedWithObjects(expected, objects, valueConstants);
122+
facts.forEach(fact -> removeComparedWithObjects(fact, objects, valueConstants));
123+
return valueConstants;
124+
}
125+
126+
private static void addArguments(Expression expression, Set<String> objects) {
127+
if (expression instanceof FunctionInvocation invocation)
128+
invocation.getArgs().forEach(argument -> objects.addAll(variablesOf(argument)));
129+
else if (expression instanceof AliasInvocation alias)
130+
alias.getArgs().forEach(argument -> objects.addAll(variablesOf(argument)));
131+
expression.getChildren().forEach(child -> addArguments(child, objects));
132+
}
133+
134+
private static void removeComparedWithObjects(Expression expression, Set<String> objects,
135+
Set<String> valueConstants) {
136+
if (expression instanceof BinaryExpression binary
137+
&& ("==".equals(binary.getOperator()) || "!=".equals(binary.getOperator()))) {
138+
List<String> compared = variablesOf(binary);
139+
if (compared.stream().anyMatch(objects::contains))
140+
valueConstants.removeAll(compared);
141+
}
142+
expression.getChildren().forEach(child -> removeComparedWithObjects(child, objects, valueConstants));
143+
}
144+
102145
// The symbols an expression constrains: f(x) for a ghost function over an object, or the object itself when values
103-
// has it, and the variables whose value it constrains
104-
private Set<String> symbolsOf(Expression expression, Set<String> values) {
146+
// has it, and the variables whose value it constrains, except the value constants
147+
private Set<String> symbolsOf(Expression expression, Set<String> values, Set<String> valueConstants) {
105148
Map<String, String> applications = new HashMap<>();
106149
Set<String> symbols = new HashSet<>();
107150
addSymbols(expression, applications, symbols);
108151
applications.forEach((application, object) -> symbols.add(values.contains(object) ? object : application));
152+
symbols.removeAll(valueConstants);
109153
return symbols;
110154
}
111155

‎liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java‎

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
import org.junit.jupiter.api.AfterEach;
1010
import org.junit.jupiter.api.Test;
1111

12+
import liquidjava.processor.VCImplication;
1213
import liquidjava.processor.context.Context;
14+
import liquidjava.rj_language.Predicate;
15+
import liquidjava.rj_language.ast.BinaryExpression;
16+
import liquidjava.rj_language.ast.Ite;
17+
import liquidjava.rj_language.ast.Var;
1318
import liquidjava.rj_language.parsing.RefinementsParser;
1419
import liquidjava.utils.TestUtils;
1520

@@ -190,7 +195,7 @@ private static VCRelevantFacts relevantTo(String expected) {
190195
Map<String, String> stateSets = Map.of("startTiling", "state1", "tilingSet", "state1", "state1", "state1",
191196
"compressionSet", "state2", "state2", "state2");
192197
return new VCRelevantFacts(RefinementsParser.createAST(expected, ""),
193-
name -> stateSets.containsKey(name) ? Set.of(stateSets.get(name)) : Set.of());
198+
name -> stateSets.containsKey(name) ? Set.of(stateSets.get(name)) : Set.of(), Set.of("Mode.Photo"));
194199
}
195200

196201
@Test
@@ -200,6 +205,31 @@ void relevantStepKeepsEveryStateSetOfAnObjectComparedAsAValue() {
200205
step("compressionSet(q) && startTiling(p)", "p == q"));
201206
}
202207

208+
@Test
209+
void relevantStepDoesNotLinkFactsThroughAConstant() {
210+
// code reads an enum constant as a variable named after it
211+
Var photo = new Var("Mode.Photo");
212+
VCImplication implication = vc("∀n:Mode. true", "true");
213+
implication.setRefinement(new Predicate(new BinaryExpression(new Var("n"), "==", photo)));
214+
implication.getNext()
215+
.setRefinement(new Predicate(new Ite(new BinaryExpression(new Var("m"), "==", photo),
216+
RefinementsParser.createAST("startTiling(p)", ""),
217+
RefinementsParser.createAST("compressionSet(p)", ""))));
218+
assertSimplificationSteps(relevantTo("tilingSet(p)"), implication,
219+
step("m == Photo ? startTiling(p) : compressionSet(p)"));
220+
}
221+
222+
@Test
223+
void relevantStepLinksFactsThroughAConstantAnObjectEquals() {
224+
Var photo = new Var("Mode.Photo");
225+
VCImplication implication = vc("∀p:Mode. true", "true");
226+
implication.setRefinement(new Predicate(new BinaryExpression(new Var("p"), "==", photo)));
227+
implication.getNext().setRefinement(new Predicate(
228+
new BinaryExpression(RefinementsParser.createAST("startTiling(q)", ""), "&&", new Var("ready"))));
229+
implication.getNext().getRefinement().getExpression().getChildren().get(0).setChild(0, photo);
230+
assertSimplificationSteps(relevantTo("tilingSet(p)"), implication, step("p == Photo", "startTiling(Photo)"));
231+
}
232+
203233
@Test
204234
void relevantStepKeepsFactsAnUnknownFunctionTakesTheObjectOf() {
205235
assertSimplificationSteps(relevantTo("tilingSet(p)"), vc("compressionSet(p) && ready(p)", "startTiling(p)"),

0 commit comments

Comments
 (0)