diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java index 78b5e0aa..433e26ea 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java @@ -1,11 +1,10 @@ package liquidjava.diagnostics.errors; -import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import liquidjava.diagnostics.TranslationTable; -import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.formatter.VariableFormatter; @@ -38,38 +37,33 @@ public RefinementError(SourcePosition position, Predicate expected, VCSimplifica @Override public String getDetails() { - String counterexampleString = getCounterExampleString(); - if (counterexampleString == null) + Counterexample counterexamples = getCounterExamples(); + if (counterexamples == null) return ""; + + String counterexampleString = counterexamples.assignments().stream() + .map(a -> VariableFormatter.format(a.first()) + " == " + a.second()) + .collect(Collectors.joining(" && ")); return "Counterexample: " + counterexampleString; } - public String getCounterExampleString() { + // Filters counterexample assignments only in found VC and sorts them in the order of its binders + public Counterexample getCounterExamples() { if (counterexample == null || counterexample.assignments().isEmpty()) return null; - List foundVarNames = new ArrayList<>(); - Expression foundExpression = getFound().getImplication().toPredicate().getExpression(); - Expression expectedExpression = expected.getExpression(); - foundExpression.getVariableNames(foundVarNames); - // also keep resolved static-final constants (e.g. Integer.MAX_VALUE) referenced by either side of the - // subtyping check, so the counterexample maps the symbolic name back to its compile-time value - foundExpression.getResolvedConstantNames(foundVarNames); - expectedExpression.getResolvedConstantNames(foundVarNames); - List foundAssignments = foundExpression.getConjuncts().stream().map(Expression::toString).toList(); - String counterexampleString = counterexample.assignments().stream() - // only include variables that appear in the found value and are not already fixed there - .filter(a -> foundVarNames.contains(a.first()) - && !foundAssignments.contains(a.first() + " == " + a.second())) - // format as "var == value" - .map(a -> VariableFormatter.format(a.first()) + " == " + a.second()) - // join with "&&" - .collect(Collectors.joining(" && ")); + List binderNames = getFound().getBinders(); + Set knownAssignments = getFound().getImplication().toPredicate().getExpression().getConjuncts().stream() + .map(Expression::toString).collect(Collectors.toSet()); + var assignments = counterexample.assignments().stream().filter(a -> binderNames.contains(a.first())) + .filter(a -> !knownAssignments.contains(a.first() + " == " + a.second())) + .sorted((a, b) -> Integer.compare(binderNames.indexOf(a.first()), binderNames.indexOf(b.first()))) + .toList(); - if (counterexampleString.isEmpty()) + if (assignments.isEmpty()) return null; - return counterexampleString; + return new Counterexample(assignments); } public Counterexample getCounterexample() { diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationResult.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationResult.java index 30ef879d..2e368168 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationResult.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationResult.java @@ -1,5 +1,7 @@ package liquidjava.rj_language.opt; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import liquidjava.processor.VCImplication; @@ -35,6 +37,17 @@ public VCSimplificationResult getOrigin() { return origin; } + /** + * Returns the list of binder names in the simplified VC chain in order of appearance + */ + public List getBinders() { + ArrayList binderNames = new ArrayList<>(); + for (VCImplication current = getImplication(); current != null; current = current.getNext()) + if (current.hasBinder()) + binderNames.add(current.getName()); + return binderNames; + } + @Override public String toString() { if (origin == null)