From e38d99d6bbfe6a39c17bb3846ce39f03f572604b Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Mon, 8 Jun 2026 11:19:11 +0100 Subject: [PATCH 01/41] Add SimplifiedExpression --- .../rj_language/ast/Expression.java | 5 + .../rj_language/ast/SimplifiedExpression.java | 124 ++++++++++++++++++ .../ast/formatter/ExpressionFormatter.java | 14 +- .../ast/formatter/ExpressionPrecedence.java | 3 + .../rj_language/ast/typing/TypeInfer.java | 3 + .../visitors/ExpressionVisitor.java | 5 +- .../liquidjava/smt/ExpressionToZ3Visitor.java | 6 + 7 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/SimplifiedExpression.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java index ee638262e..20c9fe84e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java @@ -82,6 +82,9 @@ public boolean isLiteral() { * @return true if it is a boolean expression, false otherwise */ public boolean isBooleanExpression() { + if (this instanceof SimplifiedExpression node) { + return node.getSimplifiedExpression().isBooleanExpression(); + } if (this instanceof LiteralBoolean || this instanceof Ite || this instanceof AliasInvocation || this instanceof FunctionInvocation) { return true; @@ -99,6 +102,8 @@ public boolean isBooleanExpression() { } public List getConjuncts() { + if (this instanceof SimplifiedExpression node) + return node.getSimplifiedExpression().getConjuncts(); if (this instanceof BinaryExpression binaryExpression && "&&".equals(binaryExpression.getOperator())) { List conjuncts = new ArrayList<>(); conjuncts.addAll(binaryExpression.getFirstOperand().getConjuncts()); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/SimplifiedExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/SimplifiedExpression.java new file mode 100644 index 000000000..e06bbc2dc --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/SimplifiedExpression.java @@ -0,0 +1,124 @@ +package liquidjava.rj_language.ast; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import liquidjava.diagnostics.errors.LJError; +import liquidjava.rj_language.visitors.ExpressionVisitor; +import spoon.reflect.reference.CtTypeReference; + +public class SimplifiedExpression extends Expression { + + private final Expression origin; + private final List binders; + + public SimplifiedExpression(Expression simplified, Expression origin) { + this(simplified, origin, List.of()); + } + + public SimplifiedExpression(Expression simplified, Expression origin, List binders) { + addChild(simplified); + this.origin = origin; + this.binders = new ArrayList<>(binders); + } + + public Expression getSimplifiedExpression() { + return children.get(0); + } + + public Expression getOrigin() { + return origin; + } + + public List getBinders() { + return binders; + } + + @Override + public T accept(ExpressionVisitor visitor) throws LJError { + return visitor.visitSimplifiedNode(this); + } + + @Override + public void getVariableNames(List toAdd) { + getSimplifiedExpression().getVariableNames(toAdd); + } + + @Override + public void getStateInvocations(List toAdd, List all) { + getSimplifiedExpression().getStateInvocations(toAdd, all); + } + + @Override + public boolean isBooleanTrue() { + return getSimplifiedExpression().isBooleanTrue(); + } + + @Override + public Expression clone() { + return new SimplifiedExpression(getSimplifiedExpression().clone(), origin.clone(), binders); + } + + @Override + public String toString() { + return getSimplifiedExpression().toString(); + } + + @Override + public int hashCode() { + return Objects.hash(getSimplifiedExpression(), origin, binders); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SimplifiedExpression other = (SimplifiedExpression) obj; + return getSimplifiedExpression().equals(other.getSimplifiedExpression()) && origin.equals(other.origin) + && binders.equals(other.binders); + } + + public static class Binder { + private final String name; + private final String type; + + public Binder(String name, String type) { + this.name = name; + this.type = type; + } + + public Binder(String name, CtTypeReference type) { + this(name, type.getQualifiedName()); + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + @Override + public int hashCode() { + return Objects.hash(name, type); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Binder other = (Binder) obj; + return name.equals(other.name) && type.equals(other.type); + } + } +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java index cf6e4fdce..34d71a1d2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java @@ -17,6 +17,7 @@ import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.LiteralString; import liquidjava.rj_language.ast.Enum; +import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.ast.UnaryExpression; import liquidjava.rj_language.ast.Var; import liquidjava.rj_language.visitors.ExpressionVisitor; @@ -61,8 +62,12 @@ private String formatArguments(List args) { } private Expression unwrapGroup(Expression expression) { - while (expression instanceof GroupExpression group) - expression = group.getExpression(); + while (expression instanceof GroupExpression || expression instanceof SimplifiedExpression) { + if (expression instanceof GroupExpression group) + expression = group.getExpression(); + else if (expression instanceof SimplifiedExpression node) + expression = node.getSimplifiedExpression(); + } return expression; } @@ -161,6 +166,11 @@ public String visitLiteralString(LiteralString lit) { return lit.toString(); } + @Override + public String visitSimplifiedNode(SimplifiedExpression node) { + return formatExpression(node.getSimplifiedExpression()); + } + @Override public String visitUnaryExpression(UnaryExpression exp) { return exp.getOp() + formatOperand(exp, exp.getExpression(), true); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionPrecedence.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionPrecedence.java index fbaa95cbe..ce9678c52 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionPrecedence.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionPrecedence.java @@ -4,6 +4,7 @@ import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.GroupExpression; import liquidjava.rj_language.ast.Ite; +import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.ast.UnaryExpression; public enum ExpressionPrecedence { @@ -16,6 +17,8 @@ public boolean isLowerThan(ExpressionPrecedence other) { public static ExpressionPrecedence of(Expression expression) { if (expression instanceof GroupExpression group) return of(group.getExpression()); + if (expression instanceof SimplifiedExpression node) + return of(node.getSimplifiedExpression()); if (expression instanceof Ite) return TERNARY; if (expression instanceof UnaryExpression) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java index 0fa965cde..140c7ed88 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java @@ -15,6 +15,7 @@ import liquidjava.rj_language.ast.LiteralLong; import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.LiteralString; +import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.ast.UnaryExpression; import liquidjava.rj_language.ast.Var; import liquidjava.utils.Utils; @@ -57,6 +58,8 @@ else if (e instanceof FunctionInvocation) return functionType(ctx, (FunctionInvocation) e); else if (e instanceof AliasInvocation) return boolType(factory); + else if (e instanceof SimplifiedExpression) + return getType(ctx, factory, ((SimplifiedExpression) e).getSimplifiedExpression()); return Optional.empty(); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java index 904690a79..cd2e9e384 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java @@ -13,6 +13,7 @@ import liquidjava.rj_language.ast.LiteralLong; import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.LiteralString; +import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.ast.UnaryExpression; import liquidjava.rj_language.ast.Var; @@ -39,9 +40,11 @@ public interface ExpressionVisitor { T visitLiteralString(LiteralString lit) throws LJError; + T visitSimplifiedNode(SimplifiedExpression node) throws LJError; + T visitUnaryExpression(UnaryExpression exp) throws LJError; T visitEnum(Enum en) throws LJError; T visitVar(Var var) throws LJError; -} \ No newline at end of file +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java b/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java index 464cd9898..edf4ba9be 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java @@ -15,6 +15,7 @@ import liquidjava.rj_language.ast.LiteralLong; import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.LiteralString; +import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.ast.UnaryExpression; import liquidjava.rj_language.ast.Var; import liquidjava.rj_language.visitors.ExpressionVisitor; @@ -113,6 +114,11 @@ public Expr visitLiteralString(LiteralString lit) { return ctx.makeString(lit.toString()); } + @Override + public Expr visitSimplifiedNode(SimplifiedExpression node) throws LJError { + return node.getSimplifiedExpression().accept(this); + } + @Override public Expr visitUnaryExpression(UnaryExpression exp) throws LJError { return switch (exp.getOp()) { From 208249f68a2616f435c14be618cd2a531541e7ef Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Mon, 8 Jun 2026 18:23:48 +0100 Subject: [PATCH 02/41] Change SimplifiedExpression to SimplifiedPredicate --- .../rj_language/ast/Expression.java | 5 - .../rj_language/ast/SimplifiedExpression.java | 124 ------------------ .../ast/formatter/ExpressionFormatter.java | 10 +- .../ast/formatter/ExpressionPrecedence.java | 3 - .../rj_language/ast/typing/TypeInfer.java | 4 - .../visitors/ExpressionVisitor.java | 3 - .../liquidjava/smt/ExpressionToZ3Visitor.java | 6 - 7 files changed, 1 insertion(+), 154 deletions(-) delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/SimplifiedExpression.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java index 20c9fe84e..ee638262e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java @@ -82,9 +82,6 @@ public boolean isLiteral() { * @return true if it is a boolean expression, false otherwise */ public boolean isBooleanExpression() { - if (this instanceof SimplifiedExpression node) { - return node.getSimplifiedExpression().isBooleanExpression(); - } if (this instanceof LiteralBoolean || this instanceof Ite || this instanceof AliasInvocation || this instanceof FunctionInvocation) { return true; @@ -102,8 +99,6 @@ public boolean isBooleanExpression() { } public List getConjuncts() { - if (this instanceof SimplifiedExpression node) - return node.getSimplifiedExpression().getConjuncts(); if (this instanceof BinaryExpression binaryExpression && "&&".equals(binaryExpression.getOperator())) { List conjuncts = new ArrayList<>(); conjuncts.addAll(binaryExpression.getFirstOperand().getConjuncts()); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/SimplifiedExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/SimplifiedExpression.java deleted file mode 100644 index e06bbc2dc..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/SimplifiedExpression.java +++ /dev/null @@ -1,124 +0,0 @@ -package liquidjava.rj_language.ast; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import liquidjava.diagnostics.errors.LJError; -import liquidjava.rj_language.visitors.ExpressionVisitor; -import spoon.reflect.reference.CtTypeReference; - -public class SimplifiedExpression extends Expression { - - private final Expression origin; - private final List binders; - - public SimplifiedExpression(Expression simplified, Expression origin) { - this(simplified, origin, List.of()); - } - - public SimplifiedExpression(Expression simplified, Expression origin, List binders) { - addChild(simplified); - this.origin = origin; - this.binders = new ArrayList<>(binders); - } - - public Expression getSimplifiedExpression() { - return children.get(0); - } - - public Expression getOrigin() { - return origin; - } - - public List getBinders() { - return binders; - } - - @Override - public T accept(ExpressionVisitor visitor) throws LJError { - return visitor.visitSimplifiedNode(this); - } - - @Override - public void getVariableNames(List toAdd) { - getSimplifiedExpression().getVariableNames(toAdd); - } - - @Override - public void getStateInvocations(List toAdd, List all) { - getSimplifiedExpression().getStateInvocations(toAdd, all); - } - - @Override - public boolean isBooleanTrue() { - return getSimplifiedExpression().isBooleanTrue(); - } - - @Override - public Expression clone() { - return new SimplifiedExpression(getSimplifiedExpression().clone(), origin.clone(), binders); - } - - @Override - public String toString() { - return getSimplifiedExpression().toString(); - } - - @Override - public int hashCode() { - return Objects.hash(getSimplifiedExpression(), origin, binders); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - SimplifiedExpression other = (SimplifiedExpression) obj; - return getSimplifiedExpression().equals(other.getSimplifiedExpression()) && origin.equals(other.origin) - && binders.equals(other.binders); - } - - public static class Binder { - private final String name; - private final String type; - - public Binder(String name, String type) { - this.name = name; - this.type = type; - } - - public Binder(String name, CtTypeReference type) { - this(name, type.getQualifiedName()); - } - - public String getName() { - return name; - } - - public String getType() { - return type; - } - - @Override - public int hashCode() { - return Objects.hash(name, type); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Binder other = (Binder) obj; - return name.equals(other.name) && type.equals(other.type); - } - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java index 34d71a1d2..2135f41d4 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java @@ -17,7 +17,6 @@ import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.LiteralString; import liquidjava.rj_language.ast.Enum; -import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.ast.UnaryExpression; import liquidjava.rj_language.ast.Var; import liquidjava.rj_language.visitors.ExpressionVisitor; @@ -62,11 +61,9 @@ private String formatArguments(List args) { } private Expression unwrapGroup(Expression expression) { - while (expression instanceof GroupExpression || expression instanceof SimplifiedExpression) { + while (expression instanceof GroupExpression) { if (expression instanceof GroupExpression group) expression = group.getExpression(); - else if (expression instanceof SimplifiedExpression node) - expression = node.getSimplifiedExpression(); } return expression; } @@ -166,11 +163,6 @@ public String visitLiteralString(LiteralString lit) { return lit.toString(); } - @Override - public String visitSimplifiedNode(SimplifiedExpression node) { - return formatExpression(node.getSimplifiedExpression()); - } - @Override public String visitUnaryExpression(UnaryExpression exp) { return exp.getOp() + formatOperand(exp, exp.getExpression(), true); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionPrecedence.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionPrecedence.java index ce9678c52..fbaa95cbe 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionPrecedence.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionPrecedence.java @@ -4,7 +4,6 @@ import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.GroupExpression; import liquidjava.rj_language.ast.Ite; -import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.ast.UnaryExpression; public enum ExpressionPrecedence { @@ -17,8 +16,6 @@ public boolean isLowerThan(ExpressionPrecedence other) { public static ExpressionPrecedence of(Expression expression) { if (expression instanceof GroupExpression group) return of(group.getExpression()); - if (expression instanceof SimplifiedExpression node) - return of(node.getSimplifiedExpression()); if (expression instanceof Ite) return TERNARY; if (expression instanceof UnaryExpression) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java index 140c7ed88..59cd6a0f2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java @@ -15,7 +15,6 @@ import liquidjava.rj_language.ast.LiteralLong; import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.LiteralString; -import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.ast.UnaryExpression; import liquidjava.rj_language.ast.Var; import liquidjava.utils.Utils; @@ -58,9 +57,6 @@ else if (e instanceof FunctionInvocation) return functionType(ctx, (FunctionInvocation) e); else if (e instanceof AliasInvocation) return boolType(factory); - else if (e instanceof SimplifiedExpression) - return getType(ctx, factory, ((SimplifiedExpression) e).getSimplifiedExpression()); - return Optional.empty(); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java index cd2e9e384..24d030fc9 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java @@ -13,7 +13,6 @@ import liquidjava.rj_language.ast.LiteralLong; import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.LiteralString; -import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.ast.UnaryExpression; import liquidjava.rj_language.ast.Var; @@ -40,8 +39,6 @@ public interface ExpressionVisitor { T visitLiteralString(LiteralString lit) throws LJError; - T visitSimplifiedNode(SimplifiedExpression node) throws LJError; - T visitUnaryExpression(UnaryExpression exp) throws LJError; T visitEnum(Enum en) throws LJError; diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java b/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java index edf4ba9be..464cd9898 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java @@ -15,7 +15,6 @@ import liquidjava.rj_language.ast.LiteralLong; import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.LiteralString; -import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.ast.UnaryExpression; import liquidjava.rj_language.ast.Var; import liquidjava.rj_language.visitors.ExpressionVisitor; @@ -114,11 +113,6 @@ public Expr visitLiteralString(LiteralString lit) { return ctx.makeString(lit.toString()); } - @Override - public Expr visitSimplifiedNode(SimplifiedExpression node) throws LJError { - return node.getSimplifiedExpression().accept(this); - } - @Override public Expr visitUnaryExpression(UnaryExpression exp) throws LJError { return switch (exp.getOp()) { From 92359f3dcdfead9db79cac14c530c8efb9c82f6c Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 13:48:42 +0100 Subject: [PATCH 03/41] Requested Changes --- .../rj_language/ast/formatter/ExpressionFormatter.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java index 2135f41d4..b26026f6c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java @@ -61,9 +61,8 @@ private String formatArguments(List args) { } private Expression unwrapGroup(Expression expression) { - while (expression instanceof GroupExpression) { - if (expression instanceof GroupExpression group) - expression = group.getExpression(); + while (expression instanceof GroupExpression group) { + expression = group.getExpression(); } return expression; } From de68c470cf5997137b6bf99d0dded7e576c595b6 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 14:08:10 +0100 Subject: [PATCH 04/41] Formatting --- .../rj_language/ast/formatter/ExpressionFormatter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java index b26026f6c..cf6e4fdce 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/formatter/ExpressionFormatter.java @@ -61,9 +61,8 @@ private String formatArguments(List args) { } private Expression unwrapGroup(Expression expression) { - while (expression instanceof GroupExpression group) { + while (expression instanceof GroupExpression group) expression = group.getExpression(); - } return expression; } From 8bb6fb4d420ef5146b1f4cd183db2ef8ff923eb4 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 14:44:29 +0100 Subject: [PATCH 05/41] Minor Changes --- .../main/java/liquidjava/rj_language/ast/typing/TypeInfer.java | 1 + .../java/liquidjava/rj_language/visitors/ExpressionVisitor.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java index 59cd6a0f2..0fa965cde 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java @@ -57,6 +57,7 @@ else if (e instanceof FunctionInvocation) return functionType(ctx, (FunctionInvocation) e); else if (e instanceof AliasInvocation) return boolType(factory); + return Optional.empty(); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java index 24d030fc9..904690a79 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java @@ -44,4 +44,4 @@ public interface ExpressionVisitor { T visitEnum(Enum en) throws LJError; T visitVar(Var var) throws LJError; -} +} \ No newline at end of file From ba977dc6cbcb1c6a1bcd526bcba9859da70a74a0 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Mon, 8 Jun 2026 16:51:53 +0100 Subject: [PATCH 06/41] Add VC Substitution --- .../rj_language/opt/VCSimplifier.java | 10 ++ .../rj_language/opt/VCSubstitution.java | 105 ++++++++++++++---- .../rj_language/opt/VCSubstitutionTest.java | 68 ++---------- .../java/liquidjava/utils/VCTestUtils.java | 67 ++++++----- 4 files changed, 139 insertions(+), 111 deletions(-) create mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java new file mode 100644 index 000000000..ef501bf39 --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java @@ -0,0 +1,10 @@ +package liquidjava.rj_language.opt; + +import liquidjava.processor.VCImplication; + +public class VCSimplifier { + + public static VCImplication simplifyOnce(VCImplication implication) { + return VCSubstitution.apply(implication); + } +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java index 37dd16bf9..7d6d74916 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java @@ -4,11 +4,11 @@ import java.util.List; import java.util.Optional; -import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.BinaryExpression; import liquidjava.rj_language.ast.Expression; +import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.ast.Var; /** @@ -19,11 +19,11 @@ public class VCSubstitution { /** * A substitution discovered from an implication node */ - private record Substitution(VCImplication node, Expression replacement) { + private record Substitution(VCImplication source, Expression value) { } /** - * Applies one substitution in a VC chain + * Applies all available binder equality substitutions in a VC chain */ public static VCImplication apply(VCImplication implication) { if (implication == null) @@ -32,10 +32,29 @@ public static VCImplication apply(VCImplication implication) { VCImplication result = implication.clone(); Optional substitutionOpt = VCSubstitution.findSubstitution(result); + // keep applying substitutions until there are no more substitutions available + while (substitutionOpt.isPresent()) { + VCSubstitution.Substitution substitution = substitutionOpt.get(); + result = VCSubstitution.substitute(result, substitution.source(), substitution.value()); + substitutionOpt = VCSubstitution.findSubstitution(result); + } + return result; + } + + /** + * Applies one substitution in a VC chain + */ + public static VCImplication applyOnce(VCImplication implication) { + if (implication == null) + return null; + + VCImplication result = implication.clone(); + Optional substitutionOpt = VCSubstitution.findSubstitution(result); + // apply only the first available substitution if (substitutionOpt.isPresent()) { VCSubstitution.Substitution substitution = substitutionOpt.get(); - result = VCSubstitution.substitute(result, substitution.node(), substitution.replacement()); + result = VCSubstitution.substitute(result, substitution.source(), substitution.value()); } return result; } @@ -43,30 +62,61 @@ public static VCImplication apply(VCImplication implication) { /** * Rewrites one VC chain with a single substitution and removes its source node */ - private static VCImplication substitute(VCImplication implication, VCImplication node, Expression replacement) { + private static VCImplication substitute(VCImplication implication, VCImplication source, Expression value) { if (implication == null) return null; // skip the source node to remove it from the chain and start substitution from the next node - if (implication == node) - return substitute(implication.getNext(), node, replacement); + if (implication == source) + return substitute(implication.getNext(), source, value); - VCImplication result = substituteNode(implication, node, replacement); - result.setNext(substitute(implication.getNext(), node, replacement)); + Predicate refinement = substituteRefinement(implication.getRefinement(), source, value); + VCImplication result = copyWithRefinement(implication, refinement); + result.setNext(substitute(implication.getNext(), source, value)); return result; } /** - * Substitutes a source binder inside one VC node while preserving simplification metadata + * Substitutes a source binder inside one predicate while preserving simplification metadata */ - private static VCImplication substituteNode(VCImplication implication, VCImplication node, Expression replacement) { - Expression exp = implication.getRefinement().getExpression().clone(); - if (!containsVar(exp, node.getName())) - return implication.copyWithRefinement(new Predicate(exp)); - - Expression substituted = exp.substitute(new Var(node.getName()), replacement.clone()); - VCImplication origin = new VCImplication(node.getName(), node.getType(), implication.getOriginRefinement()); - return new SimplifiedVCImplication(implication, new Predicate(substituted), origin); + private static Predicate substituteRefinement(Predicate refinement, VCImplication source, Expression value) { + Expression expression = refinement.getExpression(); + Expression active = activeExpression(expression); + SimplifiedExpression.Binder binder = new SimplifiedExpression.Binder(source.getName(), source.getType()); + Expression substituted = active.substitute(new Var(binder.getName()), value.clone()); + + return new Predicate(new SimplifiedExpression(substituted, originExpression(expression), + bindersAfterSubstitution(expression, active, binder))); + } + + /** + * Copies an implication node with a replacement refinement + */ + private static VCImplication copyWithRefinement(VCImplication implication, Predicate refinement) { + if (implication.hasBinder()) + return new VCImplication(implication.getName(), implication.getType(), refinement); + return new VCImplication(refinement); + } + + /** + * Returns the expression that should be shown as the original formula + */ + private static Expression originExpression(Expression expression) { + if (expression instanceof SimplifiedExpression simplified) + return simplified.getOrigin().clone(); + return expression.clone(); + } + + /** + * Builds the binder metadata after one substitution + */ + private static List bindersAfterSubstitution(Expression expression, Expression active, + SimplifiedExpression.Binder binder) { + List binders = expression instanceof SimplifiedExpression previous + ? new ArrayList<>(previous.getBinders()) : new ArrayList<>(); + if (containsVariable(active, binder.getName()) && !binders.contains(binder)) + binders.add(binder); + return binders; } /** @@ -90,7 +140,7 @@ private static Optional getSubstitution(VCImplication implication) if (!implication.hasBinder()) return Optional.empty(); - Expression refinement = implication.getRefinement().getExpression().clone(); + Expression refinement = activeExpression(implication.getRefinement().getExpression()); if (!(refinement instanceof BinaryExpression binary) || !"==".equals(binary.getOperator())) return Optional.empty(); @@ -98,9 +148,9 @@ private static Optional getSubstitution(VCImplication implication) Expression left = binary.getFirstOperand(); Expression right = binary.getSecondOperand(); - if (isVar(left, name) && !containsVar(right, name)) + if (isVar(left, name) && !containsVariable(right, name)) return Optional.of(new Substitution(implication, right.clone())); - if (isVar(right, name) && !containsVar(left, name)) + if (isVar(right, name) && !containsVariable(left, name)) return Optional.of(new Substitution(implication, left.clone())); return Optional.empty(); @@ -109,16 +159,25 @@ private static Optional getSubstitution(VCImplication implication) /** * Checks whether an expression is a variable with a given name */ - public static boolean isVar(Expression expression, String name) { + private static boolean isVar(Expression expression, String name) { return expression instanceof Var var && name.equals(var.getName()); } /** * Checks whether an expression contains a variable name */ - public static boolean containsVar(Expression expression, String name) { + private static boolean containsVariable(Expression expression, String name) { List names = new ArrayList<>(); expression.getVariableNames(names); return names.contains(name); } + + /** + * Returns the expression used for matching and substitution + */ + private static Expression activeExpression(Expression expression) { + if (expression instanceof SimplifiedExpression simplified) + return simplified.getSimplifiedExpression().clone(); + return expression.clone(); + } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java index aef90ff77..a62b7810b 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java @@ -1,7 +1,6 @@ package liquidjava.rj_language.opt; import static liquidjava.utils.VCTestUtils.*; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; @@ -21,7 +20,7 @@ void substitutesBinderEqualityIntoWholeChain() { VCImplication result = VCSubstitution.apply(implication); - assertSimplifiedVC(result, simplified("3 > 0", "∀x:int. x > 0")); + assertSimplifiedVC(result, simplified("3 > 0", "x > 0", "x:int")); } @Test @@ -30,7 +29,7 @@ void substitutesReverseBinderEquality() { VCImplication result = VCSubstitution.apply(implication); - assertSimplifiedVC(result, simplified("3 > 0", "∀x:int. x > 0")); + assertSimplifiedVC(result, simplified("3 > 0", "x > 0", "x:int")); } @Test @@ -39,45 +38,7 @@ void substitutesCompoundKnownValue() { VCImplication result = VCSubstitution.apply(implication); - assertSimplifiedVC(result, simplified("y + 1 > y", "∀x:int. x > y")); - } - - @Test - void substitutesOnlyWholeVariableReferences() { - VCImplication implication = vc("∀x:int. x == 3", "xx > x"); - - VCImplication result = VCSubstitution.apply(implication); - - assertSimplifiedVC(result, simplified("xx > 3", "∀x:int. xx > x")); - } - - @Test - void substitutesEveryOccurrenceInPredicate() { - VCImplication implication = vc("∀x:int. x == 2", "x + x > 0"); - - VCImplication result = VCSubstitution.apply(implication); - - assertSimplifiedVC(result, simplified("2 + 2 > 0", "∀x:int. x + x > 0")); - } - - @Test - void preservesRemainingBinderAfterSubstitution() { - VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y > x", "y > 0"); - - VCImplication result = VCSubstitution.apply(implication); - - assertEquals("y", result.getName()); - assertEquals("y > 3", result.getRefinement().toString()); - assertVC(result.getNext(), "y > 0"); - } - - @Test - void removesSourceNodeWhenItIsLastInChain() { - VCImplication implication = vc("x > 0", "∀y:int. y == 1"); - - VCImplication result = VCSubstitution.apply(implication); - - assertVC(result, "x > 0"); + assertSimplifiedVC(result, simplified("y + 1 > y", "x > y", "x:int")); } @Test @@ -86,9 +47,7 @@ void usesFirstSubstitutionFoundInChain() { VCImplication result = VCSubstitution.apply(implication); - assertVC(result, "x > 0", "x + 4 > 0"); - assertEquals(VCImplication.class, result.getClass()); - assertSimplifiedVC(result.getNext(), simplified("x + 4 > 0", "∀y:int. x + y > 0")); + assertSimplifiedVC(result, simplified("x > 0", "x > 0", ""), simplified("x + 4 > 0", "x + y > 0", "y:int")); } @Test @@ -97,10 +56,8 @@ void substitutesInnerKnownValueAcrossNestedImplications() { VCImplication result = VCSubstitution.apply(implication); - assertVC(result, "true", "z > 1", "1 + z > 0"); - assertEquals(VCImplication.class, result.getClass()); - assertSimplifiedVC(result.getNext(), simplified("z > 1", "∀y:int. z > y"), - simplified("1 + z > 0", "∀y:int. y + z > 0")); + assertSimplifiedVC(result, simplified("true", "true", ""), simplified("z > 1", "z > y", "y:int"), + simplified("1 + z > 0", "y + z > 0", "y:int")); } @Test @@ -109,8 +66,7 @@ void substitutesOuterKnownValueIntoNestedBinderRefinements() { VCImplication result = VCSubstitution.apply(implication); - assertSimplifiedVC(result, simplified("y == 3 + 1", "∀x:int. y == x + 1"), - simplified("y > 3", "∀x:int. y > x")); + assertSimplifiedVC(result, simplified("3 + 1 > 3", "y > x", "x:int, y:int")); } @Test @@ -133,16 +89,6 @@ void ignoresNonEqualityBinderRefinement() { assertVC(result, "x > 3", "x > 0"); } - @Test - void ignoresDerivedBinderEquality() { - VCImplication implication = vc("∀x:int. x + 1 == 3", "x > 0"); - - VCImplication result = VCSubstitution.apply(implication); - - assertNotSame(implication, result); - assertVC(result, "x + 1 == 3", "x > 0"); - } - @Test void ignoresEqualityWithoutBinder() { VCImplication implication = vc("x == 3", "x > 0"); diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java index 046e4f9b7..334712fae 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -4,10 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; -import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.Expression; +import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.parsing.RefinementsParser; import spoon.Launcher; import spoon.reflect.reference.CtTypeReference; @@ -52,34 +52,39 @@ private static CtTypeReference type(String name) { } public static void assertSimplifiedVC(VCImplication implication, String... expected) { - ExpectedSimplifiedVCImplication[] predicates = java.util.Arrays.stream(expected) - .map(VCTestUtils::parseExpectedSimplifiedVCImplication).toArray(ExpectedSimplifiedVCImplication[]::new); - assertSimplifiedVC(implication, predicates); + ExpectedSimplifiedExpression[] expressions = java.util.Arrays.stream(expected) + .map(VCTestUtils::parseExpectedSimplifiedExpression).toArray(ExpectedSimplifiedExpression[]::new); + assertSimplifiedVC(implication, expressions); } - public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplifiedVCImplication... expected) { + public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplifiedExpression... expected) { VCImplication current = implication; for (int i = 0; i < expected.length; i++) { - ExpectedSimplifiedVCImplication expectedPredicate = expected[i]; - SimplifiedVCImplication simplified = simplifiedImplication(current, i); - assertEquals(Predicate.class, simplified.getRefinement().getClass(), - "Expected simplified refinement at implication " + i + " to be a plain Predicate"); - assertEquals(expectedPredicate.simplified(), simplified.getRefinement().toString(), + ExpectedSimplifiedExpression expectedExpression = expected[i]; + SimplifiedExpression expression = simplifiedExpression(current, i); + assertEquals(expectedExpression.simplified(), expression.getSimplifiedExpression().toString(), "Unexpected simplified expression at implication " + i); - if (expectedPredicate.origin() != null) - assertEquals(expectedPredicate.origin(), formatOrigin(simplified.getOrigin()), - "Unexpected origin VC at implication " + i); + if (expectedExpression.origin() != null) + assertEquals(expectedExpression.origin(), expression.getOrigin().toString(), + "Unexpected origin expression at implication " + i); + if (expectedExpression.binders() != null) + assertEquals(expectedExpression.binders(), formatBinders(expression), + "Unexpected binders at implication " + i); current = current.getNext(); } assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); } - public static ExpectedSimplifiedVCImplication simplified(String simplified) { - return new ExpectedSimplifiedVCImplication(simplified, null); + public static ExpectedSimplifiedExpression simplified(String simplified) { + return new ExpectedSimplifiedExpression(simplified, null, null); } - public static ExpectedSimplifiedVCImplication simplified(String simplified, String origin) { - return new ExpectedSimplifiedVCImplication(simplified, origin); + public static ExpectedSimplifiedExpression simplified(String simplified, String origin) { + return new ExpectedSimplifiedExpression(simplified, origin, null); + } + + public static ExpectedSimplifiedExpression simplified(String simplified, String origin, String binders) { + return new ExpectedSimplifiedExpression(simplified, origin, binders); } public static void assertVC(VCImplication implication, String... expected) { @@ -92,25 +97,33 @@ public static void assertVC(VCImplication implication, String... expected) { assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); } - public static SimplifiedVCImplication simplifiedImplication(VCImplication implication, int index) { - return assertInstanceOf(SimplifiedVCImplication.class, implication, - "Expected implication " + index + " to be a SimplifiedVCImplication"); + public static SimplifiedExpression simplifiedExpression(VCImplication implication, int index) { + assertInstanceOf(SimplifiedExpression.class, implication.getRefinement().getExpression(), + "Expected implication " + index + " to contain a SimplifiedExpression"); + return (SimplifiedExpression) implication.getRefinement().getExpression(); } - private static String formatOrigin(VCImplication origin) { - if (!origin.hasBinder()) - return origin.getRefinement().toString(); - return "∀" + origin.getName() + ":" + origin.getType().getQualifiedName() + ". " + origin.getRefinement(); + private static String formatBinders(SimplifiedExpression expression) { + return expression.getBinders().stream().map(binder -> binder.getName() + ":" + binder.getType()) + .collect(java.util.stream.Collectors.joining(", ")); } - private static ExpectedSimplifiedVCImplication parseExpectedSimplifiedVCImplication(String expected) { + private static ExpectedSimplifiedExpression parseExpectedSimplifiedExpression(String expected) { + String binders = null; String expression = expected.trim(); + int binderStart = expression.lastIndexOf('['); + if (binderStart >= 0) { + int binderEnd = expression.lastIndexOf(']'); + binders = expression.substring(binderStart + 1, binderEnd).trim(); + expression = expression.substring(0, binderStart).trim(); + } + String[] parts = expression.split("<-", 2); String simplified = parts[0].trim(); String origin = parts.length > 1 ? parts[1].trim() : null; - return new ExpectedSimplifiedVCImplication(simplified, origin); + return new ExpectedSimplifiedExpression(simplified, origin, binders); } - public record ExpectedSimplifiedVCImplication(String simplified, String origin) { + public record ExpectedSimplifiedExpression(String simplified, String origin, String binders) { } } From 82eb3beabc732a378143b201eab57f16dcc1e098 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Mon, 8 Jun 2026 17:12:50 +0100 Subject: [PATCH 07/41] Add Comments --- .../main/java/liquidjava/rj_language/opt/VCSimplifier.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java index ef501bf39..a970174e1 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java @@ -4,7 +4,11 @@ public class VCSimplifier { - public static VCImplication simplifyOnce(VCImplication implication) { + /** + * Applies all available simplification steps to a VC chain + */ + public static VCImplication simplify(VCImplication implication) { + // TODO: implement remaining simplification steps return VCSubstitution.apply(implication); } } From 63a1c216b6191aff71cb5d9614a770ceac93a493 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Mon, 8 Jun 2026 18:32:56 +0100 Subject: [PATCH 08/41] SimplifiedPredicate Follow-Up --- .../rj_language/opt/VCSubstitution.java | 33 ++++++------ .../java/liquidjava/utils/VCTestUtils.java | 54 +++++++++---------- 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java index 7d6d74916..5e0a3dd99 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java @@ -6,9 +6,9 @@ import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.SimplifiedPredicate; import liquidjava.rj_language.ast.BinaryExpression; import liquidjava.rj_language.ast.Expression; -import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.ast.Var; /** @@ -80,13 +80,12 @@ private static VCImplication substitute(VCImplication implication, VCImplication * Substitutes a source binder inside one predicate while preserving simplification metadata */ private static Predicate substituteRefinement(Predicate refinement, VCImplication source, Expression value) { - Expression expression = refinement.getExpression(); - Expression active = activeExpression(expression); - SimplifiedExpression.Binder binder = new SimplifiedExpression.Binder(source.getName(), source.getType()); + Expression active = activeExpression(refinement); + SimplifiedPredicate.Binder binder = new SimplifiedPredicate.Binder(source.getName(), source.getType()); Expression substituted = active.substitute(new Var(binder.getName()), value.clone()); - return new Predicate(new SimplifiedExpression(substituted, originExpression(expression), - bindersAfterSubstitution(expression, active, binder))); + return new SimplifiedPredicate(new Predicate(substituted), originPredicate(refinement), + bindersAfterSubstitution(refinement, active, binder)); } /** @@ -101,18 +100,18 @@ private static VCImplication copyWithRefinement(VCImplication implication, Predi /** * Returns the expression that should be shown as the original formula */ - private static Expression originExpression(Expression expression) { - if (expression instanceof SimplifiedExpression simplified) + private static Predicate originPredicate(Predicate refinement) { + if (refinement instanceof SimplifiedPredicate simplified) return simplified.getOrigin().clone(); - return expression.clone(); + return refinement.clone(); } /** * Builds the binder metadata after one substitution */ - private static List bindersAfterSubstitution(Expression expression, Expression active, - SimplifiedExpression.Binder binder) { - List binders = expression instanceof SimplifiedExpression previous + private static List bindersAfterSubstitution(Predicate refinement, Expression active, + SimplifiedPredicate.Binder binder) { + List binders = refinement instanceof SimplifiedPredicate previous ? new ArrayList<>(previous.getBinders()) : new ArrayList<>(); if (containsVariable(active, binder.getName()) && !binders.contains(binder)) binders.add(binder); @@ -140,7 +139,7 @@ private static Optional getSubstitution(VCImplication implication) if (!implication.hasBinder()) return Optional.empty(); - Expression refinement = activeExpression(implication.getRefinement().getExpression()); + Expression refinement = activeExpression(implication.getRefinement()); if (!(refinement instanceof BinaryExpression binary) || !"==".equals(binary.getOperator())) return Optional.empty(); @@ -175,9 +174,9 @@ private static boolean containsVariable(Expression expression, String name) { /** * Returns the expression used for matching and substitution */ - private static Expression activeExpression(Expression expression) { - if (expression instanceof SimplifiedExpression simplified) - return simplified.getSimplifiedExpression().clone(); - return expression.clone(); + private static Expression activeExpression(Predicate refinement) { + if (refinement instanceof SimplifiedPredicate simplified) + return simplified.getSimplifiedPredicate().getExpression().clone(); + return refinement.getExpression().clone(); } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java index 334712fae..c526d132d 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -6,8 +6,8 @@ import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.SimplifiedPredicate; import liquidjava.rj_language.ast.Expression; -import liquidjava.rj_language.ast.SimplifiedExpression; import liquidjava.rj_language.parsing.RefinementsParser; import spoon.Launcher; import spoon.reflect.reference.CtTypeReference; @@ -52,39 +52,39 @@ private static CtTypeReference type(String name) { } public static void assertSimplifiedVC(VCImplication implication, String... expected) { - ExpectedSimplifiedExpression[] expressions = java.util.Arrays.stream(expected) - .map(VCTestUtils::parseExpectedSimplifiedExpression).toArray(ExpectedSimplifiedExpression[]::new); - assertSimplifiedVC(implication, expressions); + ExpectedSimplifiedPredicate[] predicates = java.util.Arrays.stream(expected) + .map(VCTestUtils::parseExpectedSimplifiedPredicate).toArray(ExpectedSimplifiedPredicate[]::new); + assertSimplifiedVC(implication, predicates); } - public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplifiedExpression... expected) { + public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplifiedPredicate... expected) { VCImplication current = implication; for (int i = 0; i < expected.length; i++) { - ExpectedSimplifiedExpression expectedExpression = expected[i]; - SimplifiedExpression expression = simplifiedExpression(current, i); - assertEquals(expectedExpression.simplified(), expression.getSimplifiedExpression().toString(), + ExpectedSimplifiedPredicate expectedPredicate = expected[i]; + SimplifiedPredicate predicate = simplifiedPredicate(current, i); + assertEquals(expectedPredicate.simplified(), predicate.getSimplifiedPredicate().toString(), "Unexpected simplified expression at implication " + i); - if (expectedExpression.origin() != null) - assertEquals(expectedExpression.origin(), expression.getOrigin().toString(), + if (expectedPredicate.origin() != null) + assertEquals(expectedPredicate.origin(), predicate.getOrigin().toString(), "Unexpected origin expression at implication " + i); - if (expectedExpression.binders() != null) - assertEquals(expectedExpression.binders(), formatBinders(expression), + if (expectedPredicate.binders() != null) + assertEquals(expectedPredicate.binders(), formatBinders(predicate), "Unexpected binders at implication " + i); current = current.getNext(); } assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); } - public static ExpectedSimplifiedExpression simplified(String simplified) { - return new ExpectedSimplifiedExpression(simplified, null, null); + public static ExpectedSimplifiedPredicate simplified(String simplified) { + return new ExpectedSimplifiedPredicate(simplified, null, null); } - public static ExpectedSimplifiedExpression simplified(String simplified, String origin) { - return new ExpectedSimplifiedExpression(simplified, origin, null); + public static ExpectedSimplifiedPredicate simplified(String simplified, String origin) { + return new ExpectedSimplifiedPredicate(simplified, origin, null); } - public static ExpectedSimplifiedExpression simplified(String simplified, String origin, String binders) { - return new ExpectedSimplifiedExpression(simplified, origin, binders); + public static ExpectedSimplifiedPredicate simplified(String simplified, String origin, String binders) { + return new ExpectedSimplifiedPredicate(simplified, origin, binders); } public static void assertVC(VCImplication implication, String... expected) { @@ -97,18 +97,18 @@ public static void assertVC(VCImplication implication, String... expected) { assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); } - public static SimplifiedExpression simplifiedExpression(VCImplication implication, int index) { - assertInstanceOf(SimplifiedExpression.class, implication.getRefinement().getExpression(), - "Expected implication " + index + " to contain a SimplifiedExpression"); - return (SimplifiedExpression) implication.getRefinement().getExpression(); + public static SimplifiedPredicate simplifiedPredicate(VCImplication implication, int index) { + assertInstanceOf(SimplifiedPredicate.class, implication.getRefinement(), + "Expected implication " + index + " to contain a SimplifiedPredicate"); + return (SimplifiedPredicate) implication.getRefinement(); } - private static String formatBinders(SimplifiedExpression expression) { - return expression.getBinders().stream().map(binder -> binder.getName() + ":" + binder.getType()) + private static String formatBinders(SimplifiedPredicate predicate) { + return predicate.getBinders().stream().map(binder -> binder.getName() + ":" + binder.getType()) .collect(java.util.stream.Collectors.joining(", ")); } - private static ExpectedSimplifiedExpression parseExpectedSimplifiedExpression(String expected) { + private static ExpectedSimplifiedPredicate parseExpectedSimplifiedPredicate(String expected) { String binders = null; String expression = expected.trim(); int binderStart = expression.lastIndexOf('['); @@ -121,9 +121,9 @@ private static ExpectedSimplifiedExpression parseExpectedSimplifiedExpression(St String[] parts = expression.split("<-", 2); String simplified = parts[0].trim(); String origin = parts.length > 1 ? parts[1].trim() : null; - return new ExpectedSimplifiedExpression(simplified, origin, binders); + return new ExpectedSimplifiedPredicate(simplified, origin, binders); } - public record ExpectedSimplifiedExpression(String simplified, String origin, String binders) { + public record ExpectedSimplifiedPredicate(String simplified, String origin, String binders) { } } From 36fd1fde439e2cded7ba0b53e92f44cc12ff7266 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Mon, 8 Jun 2026 18:34:59 +0100 Subject: [PATCH 09/41] Add `simplifyOnce` --- .../java/liquidjava/rj_language/opt/VCSimplifier.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java index a970174e1..84be7a270 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java @@ -11,4 +11,12 @@ public static VCImplication simplify(VCImplication implication) { // TODO: implement remaining simplification steps return VCSubstitution.apply(implication); } + + /** + * Applies one simplification step to a VC chain + */ + public static VCImplication simplifyOnce(VCImplication implication) { + // TODO: implement remaining simplification steps + return VCSubstitution.applyOnce(implication); + } } From e8e07d54202aa08cf3e7792ff717a6c5a820de99 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 14:34:51 +0100 Subject: [PATCH 10/41] Code Refactoring --- .../liquidjava/rj_language/opt/VCSubstitution.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java index 5e0a3dd99..fb152da68 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java @@ -7,6 +7,7 @@ import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.SimplifiedPredicate; +import liquidjava.rj_language.SimplifiedPredicate.Binder; import liquidjava.rj_language.ast.BinaryExpression; import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.Var; @@ -81,7 +82,7 @@ private static VCImplication substitute(VCImplication implication, VCImplication */ private static Predicate substituteRefinement(Predicate refinement, VCImplication source, Expression value) { Expression active = activeExpression(refinement); - SimplifiedPredicate.Binder binder = new SimplifiedPredicate.Binder(source.getName(), source.getType()); + Binder binder = new Binder(source.getName(), source.getType()); Expression substituted = active.substitute(new Var(binder.getName()), value.clone()); return new SimplifiedPredicate(new Predicate(substituted), originPredicate(refinement), @@ -109,7 +110,7 @@ private static Predicate originPredicate(Predicate refinement) { /** * Builds the binder metadata after one substitution */ - private static List bindersAfterSubstitution(Predicate refinement, Expression active, + private static List bindersAfterSubstitution(Predicate refinement, Expression active, SimplifiedPredicate.Binder binder) { List binders = refinement instanceof SimplifiedPredicate previous ? new ArrayList<>(previous.getBinders()) : new ArrayList<>(); @@ -158,14 +159,14 @@ private static Optional getSubstitution(VCImplication implication) /** * Checks whether an expression is a variable with a given name */ - private static boolean isVar(Expression expression, String name) { + public static boolean isVar(Expression expression, String name) { return expression instanceof Var var && name.equals(var.getName()); } /** * Checks whether an expression contains a variable name */ - private static boolean containsVariable(Expression expression, String name) { + public static boolean containsVariable(Expression expression, String name) { List names = new ArrayList<>(); expression.getVariableNames(names); return names.contains(name); @@ -174,7 +175,7 @@ private static boolean containsVariable(Expression expression, String name) { /** * Returns the expression used for matching and substitution */ - private static Expression activeExpression(Predicate refinement) { + public static Expression activeExpression(Predicate refinement) { if (refinement instanceof SimplifiedPredicate simplified) return simplified.getSimplifiedPredicate().getExpression().clone(); return refinement.getExpression().clone(); From 27061e9242f20cef490980ff70fd57ab1daefac5 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 14:42:41 +0100 Subject: [PATCH 11/41] Add Comment --- .../src/main/java/liquidjava/rj_language/opt/VCSimplifier.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java index 84be7a270..082d48df2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java @@ -2,6 +2,9 @@ import liquidjava.processor.VCImplication; +/** + * Simplifies VCImplication chains by applying various simplification steps + */ public class VCSimplifier { /** From 47c1844eef8f7c4a9708668e2b2469530f8f50a5 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 16:00:05 +0100 Subject: [PATCH 12/41] Code Refactoring --- .../opt/VCSimplificationUtils.java | 46 +++++++++++++++++++ .../rj_language/opt/VCSimplifier.java | 10 +--- .../rj_language/opt/VCSubstitution.java | 45 ++---------------- .../rj_language/opt/VCSubstitutionTest.java | 25 +++++----- 4 files changed, 63 insertions(+), 63 deletions(-) create mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationUtils.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationUtils.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationUtils.java new file mode 100644 index 000000000..e5ce9fa74 --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationUtils.java @@ -0,0 +1,46 @@ +package liquidjava.rj_language.opt; + +import java.util.ArrayList; +import java.util.List; + +import liquidjava.processor.VCImplication; +import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.SimplifiedPredicate; +import liquidjava.rj_language.SimplifiedPredicate.Binder; +import liquidjava.rj_language.ast.Expression; + +class VCSimplificationUtils { + + private VCSimplificationUtils() { + } + + static Expression activeExpression(Predicate refinement) { + if (refinement instanceof SimplifiedPredicate simplified) + return simplified.getSimplifiedPredicate().getExpression().clone(); + return refinement.getExpression().clone(); + } + + static Predicate originPredicate(Predicate refinement) { + if (refinement instanceof SimplifiedPredicate simplified) + return simplified.getOrigin().clone(); + return refinement.clone(); + } + + static List binders(Predicate refinement) { + if (refinement instanceof SimplifiedPredicate simplified) + return new ArrayList<>(simplified.getBinders()); + return new ArrayList<>(); + } + + static VCImplication copyWithRefinement(VCImplication implication, Predicate refinement) { + if (implication.hasBinder()) + return new VCImplication(implication.getName(), implication.getType(), refinement); + return new VCImplication(refinement); + } + + static boolean sameVc(VCImplication left, VCImplication right) { + if (left == null || right == null) + return left == right; + return left.toString().equals(right.toString()); + } +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java index 082d48df2..9beb9571c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java @@ -11,15 +11,7 @@ public class VCSimplifier { * Applies all available simplification steps to a VC chain */ public static VCImplication simplify(VCImplication implication) { - // TODO: implement remaining simplification steps - return VCSubstitution.apply(implication); - } - - /** - * Applies one simplification step to a VC chain - */ - public static VCImplication simplifyOnce(VCImplication implication) { - // TODO: implement remaining simplification steps + // TODO: implement remaining simplification steps with fixed point iteration return VCSubstitution.applyOnce(implication); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java index fb152da68..2888b3d2e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java @@ -23,25 +23,6 @@ public class VCSubstitution { private record Substitution(VCImplication source, Expression value) { } - /** - * Applies all available binder equality substitutions in a VC chain - */ - public static VCImplication apply(VCImplication implication) { - if (implication == null) - return null; - - VCImplication result = implication.clone(); - Optional substitutionOpt = VCSubstitution.findSubstitution(result); - - // keep applying substitutions until there are no more substitutions available - while (substitutionOpt.isPresent()) { - VCSubstitution.Substitution substitution = substitutionOpt.get(); - result = VCSubstitution.substitute(result, substitution.source(), substitution.value()); - substitutionOpt = VCSubstitution.findSubstitution(result); - } - return result; - } - /** * Applies one substitution in a VC chain */ @@ -72,7 +53,7 @@ private static VCImplication substitute(VCImplication implication, VCImplication return substitute(implication.getNext(), source, value); Predicate refinement = substituteRefinement(implication.getRefinement(), source, value); - VCImplication result = copyWithRefinement(implication, refinement); + VCImplication result = VCSimplificationUtils.copyWithRefinement(implication, refinement); result.setNext(substitute(implication.getNext(), source, value)); return result; } @@ -85,28 +66,10 @@ private static Predicate substituteRefinement(Predicate refinement, VCImplicatio Binder binder = new Binder(source.getName(), source.getType()); Expression substituted = active.substitute(new Var(binder.getName()), value.clone()); - return new SimplifiedPredicate(new Predicate(substituted), originPredicate(refinement), + return new SimplifiedPredicate(new Predicate(substituted), VCSimplificationUtils.originPredicate(refinement), bindersAfterSubstitution(refinement, active, binder)); } - /** - * Copies an implication node with a replacement refinement - */ - private static VCImplication copyWithRefinement(VCImplication implication, Predicate refinement) { - if (implication.hasBinder()) - return new VCImplication(implication.getName(), implication.getType(), refinement); - return new VCImplication(refinement); - } - - /** - * Returns the expression that should be shown as the original formula - */ - private static Predicate originPredicate(Predicate refinement) { - if (refinement instanceof SimplifiedPredicate simplified) - return simplified.getOrigin().clone(); - return refinement.clone(); - } - /** * Builds the binder metadata after one substitution */ @@ -176,8 +139,6 @@ public static boolean containsVariable(Expression expression, String name) { * Returns the expression used for matching and substitution */ public static Expression activeExpression(Predicate refinement) { - if (refinement instanceof SimplifiedPredicate simplified) - return simplified.getSimplifiedPredicate().getExpression().clone(); - return refinement.getExpression().clone(); + return VCSimplificationUtils.activeExpression(refinement); } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java index a62b7810b..938dc0f63 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java @@ -10,15 +10,15 @@ class VCSubstitutionTest { @Test - void applyReturnsNullForNullImplication() { - assertNull(VCSubstitution.apply(null)); + void applyOnceReturnsNullForNullImplication() { + assertNull(VCSubstitution.applyOnce(null)); } @Test void substitutesBinderEqualityIntoWholeChain() { VCImplication implication = vc("∀x:int. x == 3", "x > 0"); - VCImplication result = VCSubstitution.apply(implication); + VCImplication result = VCSubstitution.applyOnce(implication); assertSimplifiedVC(result, simplified("3 > 0", "x > 0", "x:int")); } @@ -27,7 +27,7 @@ void substitutesBinderEqualityIntoWholeChain() { void substitutesReverseBinderEquality() { VCImplication implication = vc("∀x:int. 3 == x", "x > 0"); - VCImplication result = VCSubstitution.apply(implication); + VCImplication result = VCSubstitution.applyOnce(implication); assertSimplifiedVC(result, simplified("3 > 0", "x > 0", "x:int")); } @@ -36,7 +36,7 @@ void substitutesReverseBinderEquality() { void substitutesCompoundKnownValue() { VCImplication implication = vc("∀x:int. x == y + 1", "x > y"); - VCImplication result = VCSubstitution.apply(implication); + VCImplication result = VCSubstitution.applyOnce(implication); assertSimplifiedVC(result, simplified("y + 1 > y", "x > y", "x:int")); } @@ -45,7 +45,7 @@ void substitutesCompoundKnownValue() { void usesFirstSubstitutionFoundInChain() { VCImplication implication = vc("∀x:int. x > 0", "∀y:int. y == 4", "x + y > 0"); - VCImplication result = VCSubstitution.apply(implication); + VCImplication result = VCSubstitution.applyOnce(implication); assertSimplifiedVC(result, simplified("x > 0", "x > 0", ""), simplified("x + 4 > 0", "x + y > 0", "y:int")); } @@ -54,7 +54,7 @@ void usesFirstSubstitutionFoundInChain() { void substitutesInnerKnownValueAcrossNestedImplications() { VCImplication implication = vc("∀x:int. true", "∀y:int. y == 1", "∀z:int. z > y", "y + z > 0"); - VCImplication result = VCSubstitution.apply(implication); + VCImplication result = VCSubstitution.applyOnce(implication); assertSimplifiedVC(result, simplified("true", "true", ""), simplified("z > 1", "z > y", "y:int"), simplified("1 + z > 0", "y + z > 0", "y:int")); @@ -64,16 +64,17 @@ void substitutesInnerKnownValueAcrossNestedImplications() { void substitutesOuterKnownValueIntoNestedBinderRefinements() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); - VCImplication result = VCSubstitution.apply(implication); + VCImplication result = VCSubstitution.applyOnce(implication); - assertSimplifiedVC(result, simplified("3 + 1 > 3", "y > x", "x:int, y:int")); + assertSimplifiedVC(result, simplified("y == 3 + 1", "y == x + 1", "x:int"), + simplified("y > 3", "y > x", "x:int")); } @Test void ignoresRecursiveBinderEquality() { VCImplication implication = vc("∀x:int. x == x + 1", "x > 0"); - VCImplication result = VCSubstitution.apply(implication); + VCImplication result = VCSubstitution.applyOnce(implication); assertNotSame(implication, result); assertVC(result, "x == x + 1", "x > 0"); @@ -83,7 +84,7 @@ void ignoresRecursiveBinderEquality() { void ignoresNonEqualityBinderRefinement() { VCImplication implication = vc("∀x:int. x > 3", "x > 0"); - VCImplication result = VCSubstitution.apply(implication); + VCImplication result = VCSubstitution.applyOnce(implication); assertNotSame(implication, result); assertVC(result, "x > 3", "x > 0"); @@ -93,7 +94,7 @@ void ignoresNonEqualityBinderRefinement() { void ignoresEqualityWithoutBinder() { VCImplication implication = vc("x == 3", "x > 0"); - VCImplication result = VCSubstitution.apply(implication); + VCImplication result = VCSubstitution.applyOnce(implication); assertVC(result, "x == 3", "x > 0"); } From a3b4f29634f572f847a662fc3d105cf6dc64061c Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 16:14:11 +0100 Subject: [PATCH 13/41] Add Fixed Point Iteration --- .../rj_language/opt/VCSimplificationUtils.java | 17 ++++------------- .../rj_language/opt/VCSimplifier.java | 17 ----------------- .../rj_language/opt/VCSubstitution.java | 9 ++------- 3 files changed, 6 insertions(+), 37 deletions(-) delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationUtils.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationUtils.java index e5ce9fa74..ee79633a2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationUtils.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationUtils.java @@ -11,34 +11,25 @@ class VCSimplificationUtils { - private VCSimplificationUtils() { - } - - static Expression activeExpression(Predicate refinement) { + public static Expression activeExpression(Predicate refinement) { if (refinement instanceof SimplifiedPredicate simplified) return simplified.getSimplifiedPredicate().getExpression().clone(); return refinement.getExpression().clone(); } - static Predicate originPredicate(Predicate refinement) { + public static Predicate originPredicate(Predicate refinement) { if (refinement instanceof SimplifiedPredicate simplified) return simplified.getOrigin().clone(); return refinement.clone(); } - static List binders(Predicate refinement) { - if (refinement instanceof SimplifiedPredicate simplified) - return new ArrayList<>(simplified.getBinders()); - return new ArrayList<>(); - } - - static VCImplication copyWithRefinement(VCImplication implication, Predicate refinement) { + public static VCImplication copyWithRefinement(VCImplication implication, Predicate refinement) { if (implication.hasBinder()) return new VCImplication(implication.getName(), implication.getType(), refinement); return new VCImplication(refinement); } - static boolean sameVc(VCImplication left, VCImplication right) { + public static boolean sameVc(VCImplication left, VCImplication right) { if (left == null || right == null) return left == right; return left.toString().equals(right.toString()); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java deleted file mode 100644 index 9beb9571c..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplifier.java +++ /dev/null @@ -1,17 +0,0 @@ -package liquidjava.rj_language.opt; - -import liquidjava.processor.VCImplication; - -/** - * Simplifies VCImplication chains by applying various simplification steps - */ -public class VCSimplifier { - - /** - * Applies all available simplification steps to a VC chain - */ - public static VCImplication simplify(VCImplication implication) { - // TODO: implement remaining simplification steps with fixed point iteration - return VCSubstitution.applyOnce(implication); - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java index 2888b3d2e..a42c36a5f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java @@ -12,6 +12,8 @@ import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.Var; +import static liquidjava.rj_language.opt.VCSimplificationUtils.*; + /** * Simplifies VCImplication chains by replacing binder equalities with their known values */ @@ -134,11 +136,4 @@ public static boolean containsVariable(Expression expression, String name) { expression.getVariableNames(names); return names.contains(name); } - - /** - * Returns the expression used for matching and substitution - */ - public static Expression activeExpression(Predicate refinement) { - return VCSimplificationUtils.activeExpression(refinement); - } } From 659a1396a3d2dff2661fd41bcc99d06db6facafb Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 16:57:05 +0100 Subject: [PATCH 14/41] Requested Changes --- .../opt/VCSimplificationUtils.java | 37 ------------------- .../rj_language/opt/VCSubstitution.java | 10 ++--- 2 files changed, 4 insertions(+), 43 deletions(-) delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationUtils.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationUtils.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationUtils.java deleted file mode 100644 index ee79633a2..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -package liquidjava.rj_language.opt; - -import java.util.ArrayList; -import java.util.List; - -import liquidjava.processor.VCImplication; -import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.SimplifiedPredicate; -import liquidjava.rj_language.SimplifiedPredicate.Binder; -import liquidjava.rj_language.ast.Expression; - -class VCSimplificationUtils { - - public static Expression activeExpression(Predicate refinement) { - if (refinement instanceof SimplifiedPredicate simplified) - return simplified.getSimplifiedPredicate().getExpression().clone(); - return refinement.getExpression().clone(); - } - - public static Predicate originPredicate(Predicate refinement) { - if (refinement instanceof SimplifiedPredicate simplified) - return simplified.getOrigin().clone(); - return refinement.clone(); - } - - public static VCImplication copyWithRefinement(VCImplication implication, Predicate refinement) { - if (implication.hasBinder()) - return new VCImplication(implication.getName(), implication.getType(), refinement); - return new VCImplication(refinement); - } - - public static boolean sameVc(VCImplication left, VCImplication right) { - if (left == null || right == null) - return left == right; - return left.toString().equals(right.toString()); - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java index a42c36a5f..dbc3e2be1 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java @@ -12,8 +12,6 @@ import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.Var; -import static liquidjava.rj_language.opt.VCSimplificationUtils.*; - /** * Simplifies VCImplication chains by replacing binder equalities with their known values */ @@ -55,7 +53,7 @@ private static VCImplication substitute(VCImplication implication, VCImplication return substitute(implication.getNext(), source, value); Predicate refinement = substituteRefinement(implication.getRefinement(), source, value); - VCImplication result = VCSimplificationUtils.copyWithRefinement(implication, refinement); + VCImplication result = new VCImplication(implication, refinement); result.setNext(substitute(implication.getNext(), source, value)); return result; } @@ -64,11 +62,11 @@ private static VCImplication substitute(VCImplication implication, VCImplication * Substitutes a source binder inside one predicate while preserving simplification metadata */ private static Predicate substituteRefinement(Predicate refinement, VCImplication source, Expression value) { - Expression active = activeExpression(refinement); + Expression active = refinement.getExpression().clone(); Binder binder = new Binder(source.getName(), source.getType()); Expression substituted = active.substitute(new Var(binder.getName()), value.clone()); - return new SimplifiedPredicate(new Predicate(substituted), VCSimplificationUtils.originPredicate(refinement), + return new SimplifiedPredicate(new Predicate(substituted), refinement.getOrigin().clone(), bindersAfterSubstitution(refinement, active, binder)); } @@ -105,7 +103,7 @@ private static Optional getSubstitution(VCImplication implication) if (!implication.hasBinder()) return Optional.empty(); - Expression refinement = activeExpression(implication.getRefinement()); + Expression refinement = implication.getRefinement().getExpression().clone(); if (!(refinement instanceof BinaryExpression binary) || !"==".equals(binary.getOperator())) return Optional.empty(); From 7f6f9d21893252aa2c951fb396b7aabfd8d1797a Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 17:19:22 +0100 Subject: [PATCH 15/41] Rename --- .../liquidjava/rj_language/opt/VCSubstitution.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java index dbc3e2be1..97100a87c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java @@ -62,22 +62,22 @@ private static VCImplication substitute(VCImplication implication, VCImplication * Substitutes a source binder inside one predicate while preserving simplification metadata */ private static Predicate substituteRefinement(Predicate refinement, VCImplication source, Expression value) { - Expression active = refinement.getExpression().clone(); + Expression exp = refinement.getExpression().clone(); Binder binder = new Binder(source.getName(), source.getType()); - Expression substituted = active.substitute(new Var(binder.getName()), value.clone()); + Expression substituted = exp.substitute(new Var(binder.getName()), value.clone()); return new SimplifiedPredicate(new Predicate(substituted), refinement.getOrigin().clone(), - bindersAfterSubstitution(refinement, active, binder)); + bindersAfterSubstitution(refinement, exp, binder)); } /** * Builds the binder metadata after one substitution */ - private static List bindersAfterSubstitution(Predicate refinement, Expression active, + private static List bindersAfterSubstitution(Predicate refinement, Expression exp, SimplifiedPredicate.Binder binder) { List binders = refinement instanceof SimplifiedPredicate previous ? new ArrayList<>(previous.getBinders()) : new ArrayList<>(); - if (containsVariable(active, binder.getName()) && !binders.contains(binder)) + if (containsVariable(exp, binder.getName()) && !binders.contains(binder)) binders.add(binder); return binders; } From 2e145d336fe5512fad4a772eff771f51ecf8d247 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 18:59:01 +0100 Subject: [PATCH 16/41] Replace `SimplifiedPredicate` with `SimplifiedVCImplication` --- .../rj_language/opt/VCSubstitution.java | 54 ++++++++-------- .../rj_language/opt/VCSubstitutionTest.java | 21 ++++--- .../java/liquidjava/utils/VCTestUtils.java | 63 ++++++++----------- 3 files changed, 63 insertions(+), 75 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java index 97100a87c..022a81c32 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java @@ -4,10 +4,9 @@ import java.util.List; import java.util.Optional; +import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.SimplifiedPredicate; -import liquidjava.rj_language.SimplifiedPredicate.Binder; import liquidjava.rj_language.ast.BinaryExpression; import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.Var; @@ -20,7 +19,7 @@ public class VCSubstitution { /** * A substitution discovered from an implication node */ - private record Substitution(VCImplication source, Expression value) { + private record Substitution(VCImplication node, Expression replacement) { } /** @@ -36,7 +35,7 @@ public static VCImplication applyOnce(VCImplication implication) { // apply only the first available substitution if (substitutionOpt.isPresent()) { VCSubstitution.Substitution substitution = substitutionOpt.get(); - result = VCSubstitution.substitute(result, substitution.source(), substitution.value()); + result = VCSubstitution.substitute(result, substitution.node(), substitution.replacement()); } return result; } @@ -44,42 +43,39 @@ public static VCImplication applyOnce(VCImplication implication) { /** * Rewrites one VC chain with a single substitution and removes its source node */ - private static VCImplication substitute(VCImplication implication, VCImplication source, Expression value) { + private static VCImplication substitute(VCImplication implication, VCImplication node, Expression replacement) { if (implication == null) return null; // skip the source node to remove it from the chain and start substitution from the next node - if (implication == source) - return substitute(implication.getNext(), source, value); + if (implication == node) + return substitute(implication.getNext(), node, replacement); - Predicate refinement = substituteRefinement(implication.getRefinement(), source, value); - VCImplication result = new VCImplication(implication, refinement); - result.setNext(substitute(implication.getNext(), source, value)); + VCImplication result = substituteNode(implication, node, replacement); + result.setNext(substitute(implication.getNext(), node, replacement)); return result; } /** - * Substitutes a source binder inside one predicate while preserving simplification metadata + * Substitutes a source binder inside one VC node while preserving simplification metadata */ - private static Predicate substituteRefinement(Predicate refinement, VCImplication source, Expression value) { - Expression exp = refinement.getExpression().clone(); - Binder binder = new Binder(source.getName(), source.getType()); - Expression substituted = exp.substitute(new Var(binder.getName()), value.clone()); - - return new SimplifiedPredicate(new Predicate(substituted), refinement.getOrigin().clone(), - bindersAfterSubstitution(refinement, exp, binder)); + private static VCImplication substituteNode(VCImplication implication, VCImplication node, Expression replacement) { + Expression exp = implication.getRefinement().getExpression().clone(); + if (!containsVar(exp, node.getName())) + return implication.copyWithRefinement(new Predicate(exp)); + + Expression substituted = exp.substitute(new Var(node.getName()), replacement.clone()); + VCImplication origin = new VCImplication(node.getName(), node.getType(), origin(implication)); + return new SimplifiedVCImplication(implication, new Predicate(substituted), origin); } /** - * Builds the binder metadata after one substitution + * Uses the earliest original predicate available when simplifying an already-simplified node */ - private static List bindersAfterSubstitution(Predicate refinement, Expression exp, - SimplifiedPredicate.Binder binder) { - List binders = refinement instanceof SimplifiedPredicate previous - ? new ArrayList<>(previous.getBinders()) : new ArrayList<>(); - if (containsVariable(exp, binder.getName()) && !binders.contains(binder)) - binders.add(binder); - return binders; + private static Predicate origin(VCImplication implication) { + if (implication instanceof SimplifiedVCImplication simplified) + return simplified.getOrigin().getRefinement().clone(); + return implication.getRefinement().clone(); } /** @@ -111,9 +107,9 @@ private static Optional getSubstitution(VCImplication implication) Expression left = binary.getFirstOperand(); Expression right = binary.getSecondOperand(); - if (isVar(left, name) && !containsVariable(right, name)) + if (isVar(left, name) && !containsVar(right, name)) return Optional.of(new Substitution(implication, right.clone())); - if (isVar(right, name) && !containsVariable(left, name)) + if (isVar(right, name) && !containsVar(left, name)) return Optional.of(new Substitution(implication, left.clone())); return Optional.empty(); @@ -129,7 +125,7 @@ public static boolean isVar(Expression expression, String name) { /** * Checks whether an expression contains a variable name */ - public static boolean containsVariable(Expression expression, String name) { + public static boolean containsVar(Expression expression, String name) { List names = new ArrayList<>(); expression.getVariableNames(names); return names.contains(name); diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java index 938dc0f63..a19dd374d 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java @@ -1,6 +1,7 @@ package liquidjava.rj_language.opt; import static liquidjava.utils.VCTestUtils.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; @@ -20,7 +21,7 @@ void substitutesBinderEqualityIntoWholeChain() { VCImplication result = VCSubstitution.applyOnce(implication); - assertSimplifiedVC(result, simplified("3 > 0", "x > 0", "x:int")); + assertSimplifiedVC(result, simplified("3 > 0", "∀x:int. x > 0")); } @Test @@ -29,7 +30,7 @@ void substitutesReverseBinderEquality() { VCImplication result = VCSubstitution.applyOnce(implication); - assertSimplifiedVC(result, simplified("3 > 0", "x > 0", "x:int")); + assertSimplifiedVC(result, simplified("3 > 0", "∀x:int. x > 0")); } @Test @@ -38,7 +39,7 @@ void substitutesCompoundKnownValue() { VCImplication result = VCSubstitution.applyOnce(implication); - assertSimplifiedVC(result, simplified("y + 1 > y", "x > y", "x:int")); + assertSimplifiedVC(result, simplified("y + 1 > y", "∀x:int. x > y")); } @Test @@ -47,7 +48,9 @@ void usesFirstSubstitutionFoundInChain() { VCImplication result = VCSubstitution.applyOnce(implication); - assertSimplifiedVC(result, simplified("x > 0", "x > 0", ""), simplified("x + 4 > 0", "x + y > 0", "y:int")); + assertVC(result, "x > 0", "x + 4 > 0"); + assertEquals(VCImplication.class, result.getClass()); + assertSimplifiedVC(result.getNext(), simplified("x + 4 > 0", "∀y:int. x + y > 0")); } @Test @@ -56,8 +59,10 @@ void substitutesInnerKnownValueAcrossNestedImplications() { VCImplication result = VCSubstitution.applyOnce(implication); - assertSimplifiedVC(result, simplified("true", "true", ""), simplified("z > 1", "z > y", "y:int"), - simplified("1 + z > 0", "y + z > 0", "y:int")); + assertVC(result, "true", "z > 1", "1 + z > 0"); + assertEquals(VCImplication.class, result.getClass()); + assertSimplifiedVC(result.getNext(), simplified("z > 1", "∀y:int. z > y"), + simplified("1 + z > 0", "∀y:int. y + z > 0")); } @Test @@ -66,8 +71,8 @@ void substitutesOuterKnownValueIntoNestedBinderRefinements() { VCImplication result = VCSubstitution.applyOnce(implication); - assertSimplifiedVC(result, simplified("y == 3 + 1", "y == x + 1", "x:int"), - simplified("y > 3", "y > x", "x:int")); + assertSimplifiedVC(result, simplified("y == 3 + 1", "∀x:int. y == x + 1"), + simplified("y > 3", "∀x:int. y > x")); } @Test diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java index c526d132d..046e4f9b7 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -4,9 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; +import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.SimplifiedPredicate; import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.parsing.RefinementsParser; import spoon.Launcher; @@ -52,39 +52,34 @@ private static CtTypeReference type(String name) { } public static void assertSimplifiedVC(VCImplication implication, String... expected) { - ExpectedSimplifiedPredicate[] predicates = java.util.Arrays.stream(expected) - .map(VCTestUtils::parseExpectedSimplifiedPredicate).toArray(ExpectedSimplifiedPredicate[]::new); + ExpectedSimplifiedVCImplication[] predicates = java.util.Arrays.stream(expected) + .map(VCTestUtils::parseExpectedSimplifiedVCImplication).toArray(ExpectedSimplifiedVCImplication[]::new); assertSimplifiedVC(implication, predicates); } - public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplifiedPredicate... expected) { + public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplifiedVCImplication... expected) { VCImplication current = implication; for (int i = 0; i < expected.length; i++) { - ExpectedSimplifiedPredicate expectedPredicate = expected[i]; - SimplifiedPredicate predicate = simplifiedPredicate(current, i); - assertEquals(expectedPredicate.simplified(), predicate.getSimplifiedPredicate().toString(), + ExpectedSimplifiedVCImplication expectedPredicate = expected[i]; + SimplifiedVCImplication simplified = simplifiedImplication(current, i); + assertEquals(Predicate.class, simplified.getRefinement().getClass(), + "Expected simplified refinement at implication " + i + " to be a plain Predicate"); + assertEquals(expectedPredicate.simplified(), simplified.getRefinement().toString(), "Unexpected simplified expression at implication " + i); if (expectedPredicate.origin() != null) - assertEquals(expectedPredicate.origin(), predicate.getOrigin().toString(), - "Unexpected origin expression at implication " + i); - if (expectedPredicate.binders() != null) - assertEquals(expectedPredicate.binders(), formatBinders(predicate), - "Unexpected binders at implication " + i); + assertEquals(expectedPredicate.origin(), formatOrigin(simplified.getOrigin()), + "Unexpected origin VC at implication " + i); current = current.getNext(); } assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); } - public static ExpectedSimplifiedPredicate simplified(String simplified) { - return new ExpectedSimplifiedPredicate(simplified, null, null); + public static ExpectedSimplifiedVCImplication simplified(String simplified) { + return new ExpectedSimplifiedVCImplication(simplified, null); } - public static ExpectedSimplifiedPredicate simplified(String simplified, String origin) { - return new ExpectedSimplifiedPredicate(simplified, origin, null); - } - - public static ExpectedSimplifiedPredicate simplified(String simplified, String origin, String binders) { - return new ExpectedSimplifiedPredicate(simplified, origin, binders); + public static ExpectedSimplifiedVCImplication simplified(String simplified, String origin) { + return new ExpectedSimplifiedVCImplication(simplified, origin); } public static void assertVC(VCImplication implication, String... expected) { @@ -97,33 +92,25 @@ public static void assertVC(VCImplication implication, String... expected) { assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); } - public static SimplifiedPredicate simplifiedPredicate(VCImplication implication, int index) { - assertInstanceOf(SimplifiedPredicate.class, implication.getRefinement(), - "Expected implication " + index + " to contain a SimplifiedPredicate"); - return (SimplifiedPredicate) implication.getRefinement(); + public static SimplifiedVCImplication simplifiedImplication(VCImplication implication, int index) { + return assertInstanceOf(SimplifiedVCImplication.class, implication, + "Expected implication " + index + " to be a SimplifiedVCImplication"); } - private static String formatBinders(SimplifiedPredicate predicate) { - return predicate.getBinders().stream().map(binder -> binder.getName() + ":" + binder.getType()) - .collect(java.util.stream.Collectors.joining(", ")); + private static String formatOrigin(VCImplication origin) { + if (!origin.hasBinder()) + return origin.getRefinement().toString(); + return "∀" + origin.getName() + ":" + origin.getType().getQualifiedName() + ". " + origin.getRefinement(); } - private static ExpectedSimplifiedPredicate parseExpectedSimplifiedPredicate(String expected) { - String binders = null; + private static ExpectedSimplifiedVCImplication parseExpectedSimplifiedVCImplication(String expected) { String expression = expected.trim(); - int binderStart = expression.lastIndexOf('['); - if (binderStart >= 0) { - int binderEnd = expression.lastIndexOf(']'); - binders = expression.substring(binderStart + 1, binderEnd).trim(); - expression = expression.substring(0, binderStart).trim(); - } - String[] parts = expression.split("<-", 2); String simplified = parts[0].trim(); String origin = parts.length > 1 ? parts[1].trim() : null; - return new ExpectedSimplifiedPredicate(simplified, origin, binders); + return new ExpectedSimplifiedVCImplication(simplified, origin); } - public record ExpectedSimplifiedPredicate(String simplified, String origin, String binders) { + public record ExpectedSimplifiedVCImplication(String simplified, String origin) { } } From ba81c3a4b147eab51d0a2ff86f0d4006eba33ebe Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 21:34:34 +0100 Subject: [PATCH 17/41] Refactoring --- .../processor/SimplifiedVCImplication.java | 5 +++++ .../rj_language/opt/VCSubstitution.java | 13 ++---------- .../rj_language/opt/VCSubstitutionTest.java | 20 +++++++++---------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java b/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java index 7c741cdea..5e3240191 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java @@ -21,6 +21,11 @@ public VCImplication getOrigin() { return origin; } + @Override + public Predicate getOriginRefinement() { + return origin.getRefinement().clone(); + } + @Override public VCImplication copyWithRefinement(Predicate refinement) { return new SimplifiedVCImplication(this, refinement, origin); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java index 022a81c32..37dd16bf9 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java @@ -25,7 +25,7 @@ private record Substitution(VCImplication node, Expression replacement) { /** * Applies one substitution in a VC chain */ - public static VCImplication applyOnce(VCImplication implication) { + public static VCImplication apply(VCImplication implication) { if (implication == null) return null; @@ -65,19 +65,10 @@ private static VCImplication substituteNode(VCImplication implication, VCImplica return implication.copyWithRefinement(new Predicate(exp)); Expression substituted = exp.substitute(new Var(node.getName()), replacement.clone()); - VCImplication origin = new VCImplication(node.getName(), node.getType(), origin(implication)); + VCImplication origin = new VCImplication(node.getName(), node.getType(), implication.getOriginRefinement()); return new SimplifiedVCImplication(implication, new Predicate(substituted), origin); } - /** - * Uses the earliest original predicate available when simplifying an already-simplified node - */ - private static Predicate origin(VCImplication implication) { - if (implication instanceof SimplifiedVCImplication simplified) - return simplified.getOrigin().getRefinement().clone(); - return implication.getRefinement().clone(); - } - /** * Finds the first substitution candidate in the VC chain */ diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java index a19dd374d..cc0e4612f 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java @@ -12,14 +12,14 @@ class VCSubstitutionTest { @Test void applyOnceReturnsNullForNullImplication() { - assertNull(VCSubstitution.applyOnce(null)); + assertNull(VCSubstitution.apply(null)); } @Test void substitutesBinderEqualityIntoWholeChain() { VCImplication implication = vc("∀x:int. x == 3", "x > 0"); - VCImplication result = VCSubstitution.applyOnce(implication); + VCImplication result = VCSubstitution.apply(implication); assertSimplifiedVC(result, simplified("3 > 0", "∀x:int. x > 0")); } @@ -28,7 +28,7 @@ void substitutesBinderEqualityIntoWholeChain() { void substitutesReverseBinderEquality() { VCImplication implication = vc("∀x:int. 3 == x", "x > 0"); - VCImplication result = VCSubstitution.applyOnce(implication); + VCImplication result = VCSubstitution.apply(implication); assertSimplifiedVC(result, simplified("3 > 0", "∀x:int. x > 0")); } @@ -37,7 +37,7 @@ void substitutesReverseBinderEquality() { void substitutesCompoundKnownValue() { VCImplication implication = vc("∀x:int. x == y + 1", "x > y"); - VCImplication result = VCSubstitution.applyOnce(implication); + VCImplication result = VCSubstitution.apply(implication); assertSimplifiedVC(result, simplified("y + 1 > y", "∀x:int. x > y")); } @@ -46,7 +46,7 @@ void substitutesCompoundKnownValue() { void usesFirstSubstitutionFoundInChain() { VCImplication implication = vc("∀x:int. x > 0", "∀y:int. y == 4", "x + y > 0"); - VCImplication result = VCSubstitution.applyOnce(implication); + VCImplication result = VCSubstitution.apply(implication); assertVC(result, "x > 0", "x + 4 > 0"); assertEquals(VCImplication.class, result.getClass()); @@ -57,7 +57,7 @@ void usesFirstSubstitutionFoundInChain() { void substitutesInnerKnownValueAcrossNestedImplications() { VCImplication implication = vc("∀x:int. true", "∀y:int. y == 1", "∀z:int. z > y", "y + z > 0"); - VCImplication result = VCSubstitution.applyOnce(implication); + VCImplication result = VCSubstitution.apply(implication); assertVC(result, "true", "z > 1", "1 + z > 0"); assertEquals(VCImplication.class, result.getClass()); @@ -69,7 +69,7 @@ void substitutesInnerKnownValueAcrossNestedImplications() { void substitutesOuterKnownValueIntoNestedBinderRefinements() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); - VCImplication result = VCSubstitution.applyOnce(implication); + VCImplication result = VCSubstitution.apply(implication); assertSimplifiedVC(result, simplified("y == 3 + 1", "∀x:int. y == x + 1"), simplified("y > 3", "∀x:int. y > x")); @@ -79,7 +79,7 @@ void substitutesOuterKnownValueIntoNestedBinderRefinements() { void ignoresRecursiveBinderEquality() { VCImplication implication = vc("∀x:int. x == x + 1", "x > 0"); - VCImplication result = VCSubstitution.applyOnce(implication); + VCImplication result = VCSubstitution.apply(implication); assertNotSame(implication, result); assertVC(result, "x == x + 1", "x > 0"); @@ -89,7 +89,7 @@ void ignoresRecursiveBinderEquality() { void ignoresNonEqualityBinderRefinement() { VCImplication implication = vc("∀x:int. x > 3", "x > 0"); - VCImplication result = VCSubstitution.applyOnce(implication); + VCImplication result = VCSubstitution.apply(implication); assertNotSame(implication, result); assertVC(result, "x > 3", "x > 0"); @@ -99,7 +99,7 @@ void ignoresNonEqualityBinderRefinement() { void ignoresEqualityWithoutBinder() { VCImplication implication = vc("x == 3", "x > 0"); - VCImplication result = VCSubstitution.applyOnce(implication); + VCImplication result = VCSubstitution.apply(implication); assertVC(result, "x == 3", "x > 0"); } From 43a0befcce5c617251fc7405c57fd55abee7d7b3 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 21:40:44 +0100 Subject: [PATCH 18/41] Minor Change --- .../java/liquidjava/processor/SimplifiedVCImplication.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java b/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java index 5e3240191..7c741cdea 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java @@ -21,11 +21,6 @@ public VCImplication getOrigin() { return origin; } - @Override - public Predicate getOriginRefinement() { - return origin.getRefinement().clone(); - } - @Override public VCImplication copyWithRefinement(Predicate refinement) { return new SimplifiedVCImplication(this, refinement, origin); From fa7eff5c0861f40929674c77629f7c9ac5040469 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 11 Jun 2026 12:46:39 +0100 Subject: [PATCH 19/41] Add Tests --- .../rj_language/opt/VCSubstitutionTest.java | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java index cc0e4612f..aef90ff77 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java @@ -11,7 +11,7 @@ class VCSubstitutionTest { @Test - void applyOnceReturnsNullForNullImplication() { + void applyReturnsNullForNullImplication() { assertNull(VCSubstitution.apply(null)); } @@ -42,6 +42,44 @@ void substitutesCompoundKnownValue() { assertSimplifiedVC(result, simplified("y + 1 > y", "∀x:int. x > y")); } + @Test + void substitutesOnlyWholeVariableReferences() { + VCImplication implication = vc("∀x:int. x == 3", "xx > x"); + + VCImplication result = VCSubstitution.apply(implication); + + assertSimplifiedVC(result, simplified("xx > 3", "∀x:int. xx > x")); + } + + @Test + void substitutesEveryOccurrenceInPredicate() { + VCImplication implication = vc("∀x:int. x == 2", "x + x > 0"); + + VCImplication result = VCSubstitution.apply(implication); + + assertSimplifiedVC(result, simplified("2 + 2 > 0", "∀x:int. x + x > 0")); + } + + @Test + void preservesRemainingBinderAfterSubstitution() { + VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y > x", "y > 0"); + + VCImplication result = VCSubstitution.apply(implication); + + assertEquals("y", result.getName()); + assertEquals("y > 3", result.getRefinement().toString()); + assertVC(result.getNext(), "y > 0"); + } + + @Test + void removesSourceNodeWhenItIsLastInChain() { + VCImplication implication = vc("x > 0", "∀y:int. y == 1"); + + VCImplication result = VCSubstitution.apply(implication); + + assertVC(result, "x > 0"); + } + @Test void usesFirstSubstitutionFoundInChain() { VCImplication implication = vc("∀x:int. x > 0", "∀y:int. y == 4", "x + y > 0"); @@ -95,6 +133,16 @@ void ignoresNonEqualityBinderRefinement() { assertVC(result, "x > 3", "x > 0"); } + @Test + void ignoresDerivedBinderEquality() { + VCImplication implication = vc("∀x:int. x + 1 == 3", "x > 0"); + + VCImplication result = VCSubstitution.apply(implication); + + assertNotSame(implication, result); + assertVC(result, "x + 1 == 3", "x > 0"); + } + @Test void ignoresEqualityWithoutBinder() { VCImplication implication = vc("x == 3", "x > 0"); From 607e1b583d34626ea8973ec0c206722f123dd12c Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Mon, 8 Jun 2026 18:23:48 +0100 Subject: [PATCH 20/41] Change SimplifiedExpression to SimplifiedPredicate --- .../main/java/liquidjava/rj_language/ast/typing/TypeInfer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java index 0fa965cde..59cd6a0f2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java @@ -57,7 +57,6 @@ else if (e instanceof FunctionInvocation) return functionType(ctx, (FunctionInvocation) e); else if (e instanceof AliasInvocation) return boolType(factory); - return Optional.empty(); } From 0b060bb4c73d51e7f974f9b0110211e46cfb6f33 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 16:03:13 +0100 Subject: [PATCH 21/41] Add VC Folding --- .../liquidjava/rj_language/opt/VCFolding.java | 252 ++++++++++++++++++ .../rj_language/opt/VCFoldingTest.java | 107 ++++++++ .../opt/VCImplicationGenerator.java | 46 +++- .../VCSimplificationPropertyBasedTest.java | 12 +- .../rj_language/opt/VCSimplificationTest.java | 68 +++++ 5 files changed, 477 insertions(+), 8 deletions(-) create mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java create mode 100644 liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java create mode 100644 liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java new file mode 100644 index 000000000..fb6f01265 --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java @@ -0,0 +1,252 @@ +package liquidjava.rj_language.opt; + +import java.util.Optional; + +import liquidjava.processor.VCImplication; +import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.SimplifiedPredicate; +import liquidjava.rj_language.ast.BinaryExpression; +import liquidjava.rj_language.ast.Enum; +import liquidjava.rj_language.ast.Expression; +import liquidjava.rj_language.ast.GroupExpression; +import liquidjava.rj_language.ast.Ite; +import liquidjava.rj_language.ast.LiteralBoolean; +import liquidjava.rj_language.ast.LiteralInt; +import liquidjava.rj_language.ast.LiteralReal; +import liquidjava.rj_language.ast.UnaryExpression; + +/** + * Simplifies VCImplication chains by folding constant expressions inside active predicates. + */ +public class VCFolding { + + /** + * Applies folding to the first foldable predicate in a VC chain. + */ + public static VCImplication applyOnce(VCImplication implication) { + return applyOnceChanged(implication).orElseGet(() -> implication == null ? null : implication.clone()); + } + + private static Optional applyOnceChanged(VCImplication implication) { + if (implication == null) + return Optional.empty(); + + Optional folded = fold(VCSimplificationUtils.activeExpression(implication.getRefinement())); + if (folded.isPresent()) { + Predicate refinement = foldedPredicate(implication.getRefinement(), folded.get()); + VCImplication result = VCSimplificationUtils.copyWithRefinement(implication, refinement); + result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); + return Optional.of(result); + } + + Optional next = applyOnceChanged(implication.getNext()); + if (next.isEmpty()) + return Optional.empty(); + + VCImplication result = VCSimplificationUtils.copyWithRefinement(implication, + implication.getRefinement().clone()); + result.setNext(next.get()); + return Optional.of(result); + } + + private static Predicate foldedPredicate(Predicate refinement, Expression folded) { + return new SimplifiedPredicate(new Predicate(folded), VCSimplificationUtils.originPredicate(refinement), + VCSimplificationUtils.binders(refinement)); + } + + private static Optional fold(Expression expression) { + if (expression instanceof BinaryExpression binary) + return foldBinary(binary); + if (expression instanceof UnaryExpression unary) + return foldUnary(unary); + if (expression instanceof Ite ite) + return foldIte(ite); + if (expression instanceof GroupExpression group && group.getChildren().size() == 1) { + Optional child = fold(group.getExpression()); + return Optional.of(child.orElseGet(() -> group.getExpression().clone())); + } + return Optional.empty(); + } + + private static Optional foldBinary(BinaryExpression binary) { + Optional leftFolded = fold(binary.getFirstOperand()); + Optional rightFolded = fold(binary.getSecondOperand()); + + Expression leftExpression = leftFolded.orElseGet(() -> binary.getFirstOperand().clone()); + Expression rightExpression = rightFolded.orElseGet(() -> binary.getSecondOperand().clone()); + Expression left = resolvedLiteral(leftExpression); + Expression right = resolvedLiteral(rightExpression); + boolean childChanged = leftFolded.isPresent() || rightFolded.isPresent() || left != leftExpression + || right != rightExpression; + String op = binary.getOperator(); + + Expression folded = foldLiteralBinary(left, right, op); + if (folded != null) + return Optional.of(folded); + + Optional adjacentConstants = foldAdjacentIntegerConstants(left, right, op); + if (adjacentConstants.isPresent()) + return adjacentConstants; + + if (childChanged) + return Optional.of(new BinaryExpression(left, op, right)); + return Optional.empty(); + } + + private static Expression resolvedLiteral(Expression expression) { + if (expression instanceof Enum en && en.getResolvedLiteral() != null) + return en.getResolvedLiteral().clone(); + return expression; + } + + private static Expression foldLiteralBinary(Expression left, Expression right, String op) { + if (left instanceof LiteralInt leftInt && right instanceof LiteralInt rightInt) + return foldInts(leftInt.getValue(), rightInt.getValue(), op); + + if (left instanceof LiteralReal leftReal && right instanceof LiteralReal rightReal) + return foldReals(leftReal.getValue(), rightReal.getValue(), op); + + if (isMixedNumeric(left, right)) { + double l = numericValue(left); + double r = numericValue(right); + return foldReals(l, r, op); + } + + if (left instanceof LiteralBoolean leftBool && right instanceof LiteralBoolean rightBool) + return foldBooleans(leftBool.isBooleanTrue(), rightBool.isBooleanTrue(), op); + + if (left instanceof Enum leftEnum && right instanceof Enum rightEnum + && leftEnum.getTypeName().equals(rightEnum.getTypeName())) { + boolean equal = leftEnum.getConstName().equals(rightEnum.getConstName()); + return switch (op) { + case "==" -> new LiteralBoolean(equal); + case "!=" -> new LiteralBoolean(!equal); + default -> null; + }; + } + + return null; + } + + private static Expression foldInts(int left, int right, String op) { + return switch (op) { + case "+" -> new LiteralInt(left + right); + case "-" -> new LiteralInt(left - right); + case "*" -> new LiteralInt(left * right); + case "/" -> right != 0 ? new LiteralInt(left / right) : null; + case "%" -> right != 0 ? new LiteralInt(left % right) : null; + case "<" -> new LiteralBoolean(left < right); + case "<=" -> new LiteralBoolean(left <= right); + case ">" -> new LiteralBoolean(left > right); + case ">=" -> new LiteralBoolean(left >= right); + case "==" -> new LiteralBoolean(left == right); + case "!=" -> new LiteralBoolean(left != right); + default -> null; + }; + } + + private static Expression foldReals(double left, double right, String op) { + return switch (op) { + case "+" -> new LiteralReal(left + right); + case "-" -> new LiteralReal(left - right); + case "*" -> new LiteralReal(left * right); + case "/" -> right != 0.0 ? new LiteralReal(left / right) : null; + case "%" -> right != 0.0 ? new LiteralReal(left % right) : null; + case "<" -> new LiteralBoolean(left < right); + case "<=" -> new LiteralBoolean(left <= right); + case ">" -> new LiteralBoolean(left > right); + case ">=" -> new LiteralBoolean(left >= right); + case "==" -> new LiteralBoolean(left == right); + case "!=" -> new LiteralBoolean(left != right); + default -> null; + }; + } + + private static boolean isMixedNumeric(Expression left, Expression right) { + return left instanceof LiteralInt && right instanceof LiteralReal + || left instanceof LiteralReal && right instanceof LiteralInt; + } + + private static double numericValue(Expression expression) { + if (expression instanceof LiteralInt literal) + return literal.getValue(); + return ((LiteralReal) expression).getValue(); + } + + private static Expression foldBooleans(boolean left, boolean right, String op) { + return switch (op) { + case "&&" -> new LiteralBoolean(left && right); + case "||" -> new LiteralBoolean(left || right); + case "-->" -> new LiteralBoolean(!left || right); + case "==" -> new LiteralBoolean(left == right); + case "!=" -> new LiteralBoolean(left != right); + default -> null; + }; + } + + private static Optional foldUnary(UnaryExpression unary) { + Optional operandFolded = fold(unary.getExpression()); + Expression operand = operandFolded.orElseGet(() -> unary.getExpression().clone()); + String op = unary.getOp(); + + if ("!".equals(op) && operand instanceof LiteralBoolean literal) + return Optional.of(new LiteralBoolean(!literal.isBooleanTrue())); + + if ("-".equals(op)) { + if (operand instanceof LiteralInt literal) + return Optional.of(new LiteralInt(-literal.getValue())); + if (operand instanceof LiteralReal literal) + return Optional.of(new LiteralReal(-literal.getValue())); + } + + if (operandFolded.isPresent()) + return Optional.of(new UnaryExpression(op, operand)); + return Optional.empty(); + } + + private static Optional foldIte(Ite ite) { + Optional conditionFolded = fold(ite.getCondition()); + Optional thenFolded = fold(ite.getThen()); + Optional elseFolded = fold(ite.getElse()); + + Expression condition = conditionFolded.orElseGet(() -> ite.getCondition().clone()); + Expression thenExpression = thenFolded.orElseGet(() -> ite.getThen().clone()); + Expression elseExpression = elseFolded.orElseGet(() -> ite.getElse().clone()); + + if (condition instanceof LiteralBoolean literal) + return Optional.of(literal.isBooleanTrue() ? thenExpression : elseExpression); + + if (thenExpression.equals(elseExpression)) + return Optional.of(thenExpression); + + if (conditionFolded.isPresent() || thenFolded.isPresent() || elseFolded.isPresent()) + return Optional.of(new Ite(condition, thenExpression, elseExpression)); + return Optional.empty(); + } + + private static Optional foldAdjacentIntegerConstants(Expression left, Expression right, String op) { + if (!"+".equals(op) && !"-".equals(op)) + return Optional.empty(); + if (!(right instanceof LiteralInt rightLiteral)) + return Optional.empty(); + if (!(left instanceof BinaryExpression leftBinary)) + return Optional.empty(); + if (!"+".equals(leftBinary.getOperator()) && !"-".equals(leftBinary.getOperator())) + return Optional.empty(); + if (!(leftBinary.getSecondOperand()instanceof LiteralInt leftLiteral)) + return Optional.empty(); + + int signedLeft = "+".equals(leftBinary.getOperator()) ? leftLiteral.getValue() : -leftLiteral.getValue(); + int signedRight = "+".equals(op) ? rightLiteral.getValue() : -rightLiteral.getValue(); + Expression folded = expressionWithConstant(leftBinary.getFirstOperand(), signedLeft + signedRight); + return Optional.of(folded); + } + + private static Expression expressionWithConstant(Expression base, int constant) { + if (constant == 0) + return base.clone(); + if (constant > 0) + return new BinaryExpression(base.clone(), "+", new LiteralInt(constant)); + return new BinaryExpression(base.clone(), "-", new LiteralInt(-constant)); + } +} diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java new file mode 100644 index 000000000..378f75356 --- /dev/null +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java @@ -0,0 +1,107 @@ +package liquidjava.rj_language.opt; + +import static liquidjava.utils.VCTestUtils.assertSimplifiedVC; +import static liquidjava.utils.VCTestUtils.assertVC; +import static liquidjava.utils.VCTestUtils.simplified; +import static liquidjava.utils.VCTestUtils.vc; +import static org.junit.jupiter.api.Assertions.assertNull; + +import liquidjava.processor.VCImplication; +import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.ast.BinaryExpression; +import liquidjava.rj_language.ast.Enum; +import liquidjava.rj_language.ast.LiteralInt; +import org.junit.jupiter.api.Test; + +class VCFoldingTest { + + @Test + void applyOnceReturnsNullForNullImplication() { + assertNull(VCFolding.applyOnce(null)); + } + + @Test + void foldsIntegerArithmeticAndComparisons() { + assertFolded("1 + 2 == 3", "true"); + assertFolded("4 > 7", "false"); + } + + @Test + void foldsRealAndMixedNumericExpressions() { + assertFolded("1.5 + 2.0 == 3.5", "true"); + assertFolded("2 + 0.5 > 2", "true"); + } + + @Test + void leavesDivisionAndModuloByZeroUnchanged() { + assertUnchanged("4 / 0 == 0"); + assertUnchanged("4 % 0 == 0"); + } + + @Test + void foldsBooleanBinaryExpressions() { + assertFolded("true && false", "false"); + assertFolded("false --> true", "true"); + assertFolded("true != false", "true"); + } + + @Test + void foldsUnaryExpressions() { + assertFolded("!true", "false"); + assertFolded("-3 < 0", "true"); + } + + @Test + void foldsIteExpressions() { + assertFolded("true ? a : b", "a"); + assertFolded("false ? a : b", "b"); + assertFolded("cond ? b : b", "b"); + } + + @Test + void foldsAdjacentIntegerConstants() { + assertFolded("x + 1 - 2", "x - 1"); + assertFolded("x - 1 + 2", "x + 1"); + assertFolded("x + 1 + 2", "x + 3"); + assertFolded("x + 1 - 1", "x"); + } + + @Test + void foldsEnumEqualityAndInequality() { + assertFolded("Mode.Photo == Mode.Photo", "true"); + assertFolded("Mode.Photo != Mode.Video", "true"); + } + + @Test + void foldsResolvedEnumLiterals() { + Enum limit = new Enum("Config", "LIMIT"); + limit.setResolvedLiteral(new LiteralInt(3)); + VCImplication implication = new VCImplication( + new Predicate(new BinaryExpression(limit, "==", new LiteralInt(3)))); + + VCImplication result = VCFolding.applyOnce(implication); + + assertSimplifiedVC(result, simplified("true", "Config.LIMIT == 3")); + } + + @Test + void preservesOriginAndBindersFromExistingSimplifiedPredicate() { + VCImplication substituted = VCSubstitution.applyOnce(vc("∀x:int. x == 1", "x + 1 + 2 > 0")); + + VCImplication result = VCFolding.applyOnce(substituted); + + assertSimplifiedVC(result, simplified("true", "x + 1 + 2 > 0", "x:int")); + } + + private static void assertFolded(String original, String folded) { + VCImplication result = VCFolding.applyOnce(vc(original)); + + assertSimplifiedVC(result, simplified(folded, original)); + } + + private static void assertUnchanged(String original) { + VCImplication result = VCFolding.applyOnce(vc(original)); + + assertVC(result, original); + } +} diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCImplicationGenerator.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCImplicationGenerator.java index c39e9dc6d..4f3a79422 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCImplicationGenerator.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCImplicationGenerator.java @@ -19,13 +19,17 @@ public VCImplicationGenerator() { @Override public VCImplication generate(SourceOfRandomness random, GenerationStatus status) { - return switch (random.nextInt(0, 5)) { + return switch (random.nextInt(0, 9)) { case 0 -> vc(substitution(random, "x"), comparison(random, "x")); case 1 -> vc(reverseSubstitution(random, "x"), comparison(random, "x")); case 2 -> vc(nonSubstitution(random, "x"), substitution(random, "y"), comparison(random, "y")); case 3 -> vc(substitution(random, "x"), dependentSubstitution(random), comparison(random, "y")); case 4 -> vc("∀y:int. true", "∀x:int. x == y + 1", comparison(random, "x")); - default -> vc(substitution(random, "x"), substitution(random, "y"), comparison(random, "z")); + case 5 -> vc(foldableComparison(random)); + case 6 -> vc(foldableBoolean(random), comparison(random, "x")); + case 7 -> vc(foldableIte(random)); + case 8 -> vc(adjacentConstants(random) + " " + comparisonOperator(random) + " " + intLiteral(random)); + default -> vc(substitution(random, "x"), substitution(random, "y"), foldableComparison(random)); }; } @@ -55,7 +59,43 @@ private static String comparison(SourceOfRandomness random, String preferredVar) String left = random.nextBoolean() ? preferredVar : arithmetic(random, preferredVar); String right = random.nextBoolean() ? intLiteral(random) : arithmetic(random, FREE_VARS[random.nextInt(0, FREE_VARS.length - 1)]); - return left + " " + COMPARISON_OPS[random.nextInt(0, COMPARISON_OPS.length - 1)] + " " + right; + return left + " " + comparisonOperator(random) + " " + right; + } + + private static String foldableComparison(SourceOfRandomness random) { + return literalArithmetic(random) + " " + comparisonOperator(random) + " " + literalArithmetic(random); + } + + private static String foldableBoolean(SourceOfRandomness random) { + String left = random.nextBoolean() ? "true" : "false"; + String right = random.nextBoolean() ? "true" : "false"; + String[] ops = { "&&", "||", "-->", "==", "!=" }; + return left + " " + ops[random.nextInt(0, ops.length - 1)] + " " + right; + } + + private static String foldableIte(SourceOfRandomness random) { + String condition = random.nextBoolean() ? foldableBoolean(random) : foldableComparison(random); + String thenBranch = comparison(random, "x"); + String elseBranch = random.nextBoolean() ? thenBranch : comparison(random, "y"); + return condition + " ? " + thenBranch + " : " + elseBranch; + } + + private static String literalArithmetic(SourceOfRandomness random) { + String left = intLiteral(random); + String right = Integer.toString(random.nextInt(1, 7)); + String[] ops = { "+", "-", "*" }; + return left + " " + ops[random.nextInt(0, ops.length - 1)] + " " + right; + } + + private static String adjacentConstants(SourceOfRandomness random) { + String variable = FREE_VARS[random.nextInt(0, FREE_VARS.length - 1)]; + int left = random.nextInt(-3, 3); + int right = random.nextInt(-3, 3); + return variable + " " + signed(left) + " " + signed(right); + } + + private static String comparisonOperator(SourceOfRandomness random) { + return COMPARISON_OPS[random.nextInt(0, COMPARISON_OPS.length - 1)]; } private static String value(SourceOfRandomness random) { diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationPropertyBasedTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationPropertyBasedTest.java index d6e3ecc36..3c4c919a2 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationPropertyBasedTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationPropertyBasedTest.java @@ -2,6 +2,7 @@ import static liquidjava.rj_language.opt.VCSubstitution.containsVar; import static liquidjava.rj_language.opt.VCSubstitution.isVar; +import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.assertTrue; import com.pholser.junit.quickcheck.From; @@ -20,7 +21,8 @@ @RunWith(JUnitQuickcheck.class) public class VCSimplificationPropertyBasedTest { - private static final int TRIALS = 500; + private static final int TRIALS = 500; // number of random VCs to test + private static final int MAX_STEPS = 20; // to prevent infinite loops in case of non-termination @Property(trials = TRIALS) public void eachSimplificationStepPreservesVcSemantics(@From(VCImplicationGenerator.class) VCImplication vc) { @@ -35,7 +37,7 @@ public void eachSimplificationStepPreservesVcSemantics(@From(VCImplicationGenera assertEquivalent(current, simplified, step); current = simplified; } - // System.out.println("---------------------------------------------------------"); + fail("VC simplification did not reach a fixed point within " + MAX_STEPS + " steps: " + current); } private static void setUpContext() { @@ -50,9 +52,9 @@ private static void assertEquivalent(VCImplication unsimplified, VCImplication s Predicate premises = substitutionPremises(unsimplified); Predicate unsimplifiedFormula = Predicate.createConjunction(premises, new Predicate(vcFormula(unsimplified))); Predicate simplifiedFormula = Predicate.createConjunction(premises, new Predicate(vcFormula(simplified))); - // System.out.println(unsimplifiedFormula); - // System.out.println("=>"); - // System.out.println(simplifiedFormula); + System.out.println(unsimplifiedFormula); + System.out.println("=>"); + System.out.println(simplifiedFormula); assertImplies(unsimplifiedFormula, simplifiedFormula, unsimplified, simplified, step, "unsimplified => simplified"); assertImplies(simplifiedFormula, unsimplifiedFormula, unsimplified, simplified, step, diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java new file mode 100644 index 000000000..c05508604 --- /dev/null +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java @@ -0,0 +1,68 @@ +package liquidjava.rj_language.opt; + +import static liquidjava.utils.VCTestUtils.assertSimplifiedVC; +import static liquidjava.utils.VCTestUtils.assertVC; +import static liquidjava.utils.VCTestUtils.simplified; +import static liquidjava.utils.VCTestUtils.vc; +import static org.junit.jupiter.api.Assertions.assertNull; + +import liquidjava.processor.VCImplication; +import org.junit.jupiter.api.Test; + +class VCSimplificationTest { + + @Test + void simplifyReturnsNullForNullImplication() { + assertNull(VCSimplification.simplify(null)); + } + + @Test + void simplifyOnceReturnsNullForNullImplication() { + assertNull(VCSimplification.simplifyOnce(null)); + } + + @Test + void simplifyOnceAppliesSubstitutionBeforeFolding() { + VCImplication implication = vc("∀x:int. x == 1 + 2", "x > 2"); + + VCImplication result = VCSimplification.simplifyOnce(implication); + + assertSimplifiedVC(result, simplified("1 + 2 > 2", "x > 2", "x:int")); + } + + @Test + void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { + VCImplication implication = vc("1 + 2 > 2"); + + VCImplication result = VCSimplification.simplifyOnce(implication); + + assertSimplifiedVC(result, simplified("true", "1 + 2 > 2")); + } + + @Test + void simplifyKeepsApplyingStepsUntilFixedPoint() { + VCImplication implication = vc("∀x:int. x == 1 + 2", "x + 1 > 3"); + + VCImplication result = VCSimplification.simplify(implication); + + assertSimplifiedVC(result, simplified("true", "x + 1 > 3", "x:int")); + } + + @Test + void simplifyAppliesMultipleSubstitutionsBeforeReachingFixedPoint() { + VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); + + VCImplication result = VCSimplification.simplify(implication); + + assertSimplifiedVC(result, simplified("true", "y > x", "x:int, y:int")); + } + + @Test + void simplifyLeavesUnchangedVcAsPlainPredicates() { + VCImplication implication = vc("x > 0", "y > x"); + + VCImplication result = VCSimplification.simplify(implication); + + assertVC(result, "x > 0", "y > x"); + } +} From 7bb0632b3daff48af7a99a7490ab5ea917619d26 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 16:14:11 +0100 Subject: [PATCH 22/41] Add Fixed Point Iteration --- .../main/java/liquidjava/rj_language/opt/VCSimplification.java | 2 ++ .../main/java/liquidjava/rj_language/opt/VCSubstitution.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplification.java index 1c2f3fd64..87a19a1e2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplification.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplification.java @@ -2,6 +2,8 @@ import liquidjava.processor.VCImplication; +import static liquidjava.rj_language.opt.VCSimplificationUtils.*; + /** * Simplifies VCImplication chains by applying various simplification steps */ diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java index 37dd16bf9..73249d64b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java @@ -11,6 +11,8 @@ import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.Var; +import static liquidjava.rj_language.opt.VCSimplificationUtils.*; + /** * Simplifies VCImplication chains by replacing binder equalities with their known values */ From 4c39689395b6e44ede6ffbbf7ffa9728da6b185d Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 19:08:17 +0100 Subject: [PATCH 23/41] Use SimplifiedVCImplication --- .../liquidjava/rj_language/opt/VCFolding.java | 18 +++++++++--------- .../rj_language/opt/VCFoldingTest.java | 4 ++-- .../rj_language/opt/VCSimplificationTest.java | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java index fb6f01265..7a4db6d49 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java @@ -2,9 +2,9 @@ import java.util.Optional; +import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.SimplifiedPredicate; import liquidjava.rj_language.ast.BinaryExpression; import liquidjava.rj_language.ast.Enum; import liquidjava.rj_language.ast.Expression; @@ -31,10 +31,10 @@ private static Optional applyOnceChanged(VCImplication implicatio if (implication == null) return Optional.empty(); - Optional folded = fold(VCSimplificationUtils.activeExpression(implication.getRefinement())); + Optional folded = fold(implication.getRefinement().getExpression()); if (folded.isPresent()) { - Predicate refinement = foldedPredicate(implication.getRefinement(), folded.get()); - VCImplication result = VCSimplificationUtils.copyWithRefinement(implication, refinement); + VCImplication result = new SimplifiedVCImplication(implication, new Predicate(folded.get()), + originFor(implication)); result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); return Optional.of(result); } @@ -43,15 +43,15 @@ private static Optional applyOnceChanged(VCImplication implicatio if (next.isEmpty()) return Optional.empty(); - VCImplication result = VCSimplificationUtils.copyWithRefinement(implication, - implication.getRefinement().clone()); + VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); result.setNext(next.get()); return Optional.of(result); } - private static Predicate foldedPredicate(Predicate refinement, Expression folded) { - return new SimplifiedPredicate(new Predicate(folded), VCSimplificationUtils.originPredicate(refinement), - VCSimplificationUtils.binders(refinement)); + private static VCImplication originFor(VCImplication implication) { + if (implication instanceof SimplifiedVCImplication simplified) + return simplified.getOrigin().clone(); + return new VCImplication(implication, implication.getRefinement().clone()); } private static Optional fold(Expression expression) { diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java index 378f75356..3403134f6 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java @@ -85,12 +85,12 @@ void foldsResolvedEnumLiterals() { } @Test - void preservesOriginAndBindersFromExistingSimplifiedPredicate() { + void preservesOriginFromExistingSimplifiedImplication() { VCImplication substituted = VCSubstitution.applyOnce(vc("∀x:int. x == 1", "x + 1 + 2 > 0")); VCImplication result = VCFolding.applyOnce(substituted); - assertSimplifiedVC(result, simplified("true", "x + 1 + 2 > 0", "x:int")); + assertSimplifiedVC(result, simplified("true", "∀x:int. x + 1 + 2 > 0")); } private static void assertFolded(String original, String folded) { diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java index c05508604..53c2900e4 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java @@ -27,7 +27,7 @@ void simplifyOnceAppliesSubstitutionBeforeFolding() { VCImplication result = VCSimplification.simplifyOnce(implication); - assertSimplifiedVC(result, simplified("1 + 2 > 2", "x > 2", "x:int")); + assertSimplifiedVC(result, simplified("1 + 2 > 2", "∀x:int. x > 2")); } @Test @@ -45,7 +45,7 @@ void simplifyKeepsApplyingStepsUntilFixedPoint() { VCImplication result = VCSimplification.simplify(implication); - assertSimplifiedVC(result, simplified("true", "x + 1 > 3", "x:int")); + assertSimplifiedVC(result, simplified("true", "∀x:int. x + 1 > 3")); } @Test @@ -54,7 +54,7 @@ void simplifyAppliesMultipleSubstitutionsBeforeReachingFixedPoint() { VCImplication result = VCSimplification.simplify(implication); - assertSimplifiedVC(result, simplified("true", "y > x", "x:int, y:int")); + assertSimplifiedVC(result, simplified("true", "∀y:int. y > x")); } @Test From 981c63f6584c8b9180f3fc8b1bcd2b54d7549b5e Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 21:34:34 +0100 Subject: [PATCH 24/41] Refactoring --- .../java/liquidjava/processor/SimplifiedVCImplication.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java b/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java index 7c741cdea..5e3240191 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java @@ -21,6 +21,11 @@ public VCImplication getOrigin() { return origin; } + @Override + public Predicate getOriginRefinement() { + return origin.getRefinement().clone(); + } + @Override public VCImplication copyWithRefinement(Predicate refinement) { return new SimplifiedVCImplication(this, refinement, origin); From b374c66143c8886b8fd8864017ded1dd9036dbe9 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 21:41:11 +0100 Subject: [PATCH 25/41] Fix --- .../main/java/liquidjava/rj_language/opt/VCFolding.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java index 7a4db6d49..f439b4159 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java @@ -34,7 +34,7 @@ private static Optional applyOnceChanged(VCImplication implicatio Optional folded = fold(implication.getRefinement().getExpression()); if (folded.isPresent()) { VCImplication result = new SimplifiedVCImplication(implication, new Predicate(folded.get()), - originFor(implication)); + implication.getOrigin()); result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); return Optional.of(result); } @@ -48,12 +48,6 @@ private static Optional applyOnceChanged(VCImplication implicatio return Optional.of(result); } - private static VCImplication originFor(VCImplication implication) { - if (implication instanceof SimplifiedVCImplication simplified) - return simplified.getOrigin().clone(); - return new VCImplication(implication, implication.getRefinement().clone()); - } - private static Optional fold(Expression expression) { if (expression instanceof BinaryExpression binary) return foldBinary(binary); From 67b05cece98a0f07b9f99ff04a687c365a1fe39f Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Tue, 9 Jun 2026 22:30:00 +0100 Subject: [PATCH 26/41] Refactoring --- .../liquidjava/rj_language/opt/VCFolding.java | 22 ++++++++----------- .../rj_language/opt/VCFoldingTest.java | 14 ++++++------ .../rj_language/opt/VCSimplificationTest.java | 8 +++---- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java index f439b4159..b8597ed04 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java @@ -16,36 +16,32 @@ import liquidjava.rj_language.ast.UnaryExpression; /** - * Simplifies VCImplication chains by folding constant expressions inside active predicates. + * Simplifies VCImplication chains by folding constant expressions and other foldable patterns inside refinements */ public class VCFolding { /** * Applies folding to the first foldable predicate in a VC chain. */ - public static VCImplication applyOnce(VCImplication implication) { - return applyOnceChanged(implication).orElseGet(() -> implication == null ? null : implication.clone()); - } - - private static Optional applyOnceChanged(VCImplication implication) { + public static VCImplication apply(VCImplication implication) { if (implication == null) - return Optional.empty(); + return null; Optional folded = fold(implication.getRefinement().getExpression()); if (folded.isPresent()) { VCImplication result = new SimplifiedVCImplication(implication, new Predicate(folded.get()), implication.getOrigin()); result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); - return Optional.of(result); + return result; } - Optional next = applyOnceChanged(implication.getNext()); - if (next.isEmpty()) - return Optional.empty(); + VCImplication next = apply(implication.getNext()); + if (implication.getNext() == null || implication.getNext().equals(next)) + return implication.clone(); VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); - result.setNext(next.get()); - return Optional.of(result); + result.setNext(next); + return result; } private static Optional fold(Expression expression) { diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java index 3403134f6..db439c5cc 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java @@ -16,8 +16,8 @@ class VCFoldingTest { @Test - void applyOnceReturnsNullForNullImplication() { - assertNull(VCFolding.applyOnce(null)); + void applyReturnsNullForNullImplication() { + assertNull(VCFolding.apply(null)); } @Test @@ -79,28 +79,28 @@ void foldsResolvedEnumLiterals() { VCImplication implication = new VCImplication( new Predicate(new BinaryExpression(limit, "==", new LiteralInt(3)))); - VCImplication result = VCFolding.applyOnce(implication); + VCImplication result = VCFolding.apply(implication); assertSimplifiedVC(result, simplified("true", "Config.LIMIT == 3")); } @Test void preservesOriginFromExistingSimplifiedImplication() { - VCImplication substituted = VCSubstitution.applyOnce(vc("∀x:int. x == 1", "x + 1 + 2 > 0")); + VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == 1", "x + 1 + 2 > 0")); - VCImplication result = VCFolding.applyOnce(substituted); + VCImplication result = VCFolding.apply(substituted); assertSimplifiedVC(result, simplified("true", "∀x:int. x + 1 + 2 > 0")); } private static void assertFolded(String original, String folded) { - VCImplication result = VCFolding.applyOnce(vc(original)); + VCImplication result = VCFolding.apply(vc(original)); assertSimplifiedVC(result, simplified(folded, original)); } private static void assertUnchanged(String original) { - VCImplication result = VCFolding.applyOnce(vc(original)); + VCImplication result = VCFolding.apply(vc(original)); assertVC(result, original); } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java index 53c2900e4..d21da2451 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java @@ -13,7 +13,7 @@ class VCSimplificationTest { @Test void simplifyReturnsNullForNullImplication() { - assertNull(VCSimplification.simplify(null)); + assertNull(VCSimplification.simplifyToFixedPoint(null)); } @Test @@ -43,7 +43,7 @@ void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { void simplifyKeepsApplyingStepsUntilFixedPoint() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x + 1 > 3"); - VCImplication result = VCSimplification.simplify(implication); + VCImplication result = VCSimplification.simplifyToFixedPoint(implication); assertSimplifiedVC(result, simplified("true", "∀x:int. x + 1 > 3")); } @@ -52,7 +52,7 @@ void simplifyKeepsApplyingStepsUntilFixedPoint() { void simplifyAppliesMultipleSubstitutionsBeforeReachingFixedPoint() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); - VCImplication result = VCSimplification.simplify(implication); + VCImplication result = VCSimplification.simplifyToFixedPoint(implication); assertSimplifiedVC(result, simplified("true", "∀y:int. y > x")); } @@ -61,7 +61,7 @@ void simplifyAppliesMultipleSubstitutionsBeforeReachingFixedPoint() { void simplifyLeavesUnchangedVcAsPlainPredicates() { VCImplication implication = vc("x > 0", "y > x"); - VCImplication result = VCSimplification.simplify(implication); + VCImplication result = VCSimplification.simplifyToFixedPoint(implication); assertVC(result, "x > 0", "y > x"); } From 9ffbe14fa02b1b51999fffb906e2ad2339a457bf Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 10 Jun 2026 19:09:45 +0100 Subject: [PATCH 27/41] Add Comments --- .../liquidjava/rj_language/opt/VCFolding.java | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java index b8597ed04..0ea8e838c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java @@ -21,7 +21,7 @@ public class VCFolding { /** - * Applies folding to the first foldable predicate in a VC chain. + * Applies folding to the first foldable predicate in a VC chain */ public static VCImplication apply(VCImplication implication) { if (implication == null) @@ -44,6 +44,9 @@ public static VCImplication apply(VCImplication implication) { return result; } + /** + * Folds an expression + */ private static Optional fold(Expression expression) { if (expression instanceof BinaryExpression binary) return foldBinary(binary); @@ -58,6 +61,9 @@ private static Optional fold(Expression expression) { return Optional.empty(); } + /** + * Folds a binary expression and its operands + */ private static Optional foldBinary(BinaryExpression binary) { Optional leftFolded = fold(binary.getFirstOperand()); Optional rightFolded = fold(binary.getSecondOperand()); @@ -83,12 +89,18 @@ private static Optional foldBinary(BinaryExpression binary) { return Optional.empty(); } + /** + * Replaces a resolved enum constant with its literal value + */ private static Expression resolvedLiteral(Expression expression) { if (expression instanceof Enum en && en.getResolvedLiteral() != null) return en.getResolvedLiteral().clone(); return expression; } + /** + * Folds a binary expression whose operands are both literals + */ private static Expression foldLiteralBinary(Expression left, Expression right, String op) { if (left instanceof LiteralInt leftInt && right instanceof LiteralInt rightInt) return foldInts(leftInt.getValue(), rightInt.getValue(), op); @@ -118,6 +130,9 @@ private static Expression foldLiteralBinary(Expression left, Expression right, S return null; } + /** + * Folds integer operations + */ private static Expression foldInts(int left, int right, String op) { return switch (op) { case "+" -> new LiteralInt(left + right); @@ -135,6 +150,9 @@ private static Expression foldInts(int left, int right, String op) { }; } + /** + * Folds real number operations + */ private static Expression foldReals(double left, double right, String op) { return switch (op) { case "+" -> new LiteralReal(left + right); @@ -152,17 +170,26 @@ private static Expression foldReals(double left, double right, String op) { }; } + /** + * Checks whether two expressions mix integer and real literals + */ private static boolean isMixedNumeric(Expression left, Expression right) { return left instanceof LiteralInt && right instanceof LiteralReal || left instanceof LiteralReal && right instanceof LiteralInt; } + /** + * Reads a numeric literal as a double + */ private static double numericValue(Expression expression) { if (expression instanceof LiteralInt literal) return literal.getValue(); return ((LiteralReal) expression).getValue(); } + /** + * Folds boolean operations + */ private static Expression foldBooleans(boolean left, boolean right, String op) { return switch (op) { case "&&" -> new LiteralBoolean(left && right); @@ -174,6 +201,9 @@ private static Expression foldBooleans(boolean left, boolean right, String op) { }; } + /** + * Folds a unary expression and its operand + */ private static Optional foldUnary(UnaryExpression unary) { Optional operandFolded = fold(unary.getExpression()); Expression operand = operandFolded.orElseGet(() -> unary.getExpression().clone()); @@ -194,6 +224,9 @@ private static Optional foldUnary(UnaryExpression unary) { return Optional.empty(); } + /** + * Folds a conditional expression and its branches + */ private static Optional foldIte(Ite ite) { Optional conditionFolded = fold(ite.getCondition()); Optional thenFolded = fold(ite.getThen()); @@ -214,6 +247,9 @@ private static Optional foldIte(Ite ite) { return Optional.empty(); } + /** + * Combines adjacent integer constants in additions and subtractions + */ private static Optional foldAdjacentIntegerConstants(Expression left, Expression right, String op) { if (!"+".equals(op) && !"-".equals(op)) return Optional.empty(); @@ -226,6 +262,7 @@ private static Optional foldAdjacentIntegerConstants(Expression left if (!(leftBinary.getSecondOperand()instanceof LiteralInt leftLiteral)) return Optional.empty(); + // treat subtraction as adding a negative constant and then add the two int signedLeft = "+".equals(leftBinary.getOperator()) ? leftLiteral.getValue() : -leftLiteral.getValue(); int signedRight = "+".equals(op) ? rightLiteral.getValue() : -rightLiteral.getValue(); Expression folded = expressionWithConstant(leftBinary.getFirstOperand(), signedLeft + signedRight); From 68c3b426411369ae89e58cb1ff1dc1db69f73396 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 10 Jun 2026 19:18:32 +0100 Subject: [PATCH 28/41] Refactoring --- .../liquidjava/rj_language/opt/VCFolding.java | 181 +++++++++--------- 1 file changed, 89 insertions(+), 92 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java index 0ea8e838c..728e6225d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java @@ -76,13 +76,13 @@ private static Optional foldBinary(BinaryExpression binary) { || right != rightExpression; String op = binary.getOperator(); - Expression folded = foldLiteralBinary(left, right, op); - if (folded != null) - return Optional.of(folded); + Expression foldedBinary = foldLiteralBinary(left, right, op); + if (foldedBinary != null) + return Optional.of(foldedBinary); - Optional adjacentConstants = foldAdjacentIntegerConstants(left, right, op); - if (adjacentConstants.isPresent()) - return adjacentConstants; + Optional foldedAdjacentInts = foldAdjacentInts(left, right, op); + if (foldedAdjacentInts.isPresent()) + return foldedAdjacentInts; if (childChanged) return Optional.of(new BinaryExpression(left, op, right)); @@ -90,12 +90,49 @@ private static Optional foldBinary(BinaryExpression binary) { } /** - * Replaces a resolved enum constant with its literal value + * Folds a unary expression and its operand */ - private static Expression resolvedLiteral(Expression expression) { - if (expression instanceof Enum en && en.getResolvedLiteral() != null) - return en.getResolvedLiteral().clone(); - return expression; + private static Optional foldUnary(UnaryExpression unary) { + Optional operandFolded = fold(unary.getExpression()); + Expression operand = operandFolded.orElseGet(() -> unary.getExpression().clone()); + String op = unary.getOp(); + + if ("!".equals(op) && operand instanceof LiteralBoolean literal) + return Optional.of(new LiteralBoolean(!literal.isBooleanTrue())); + + if ("-".equals(op)) { + if (operand instanceof LiteralInt literal) + return Optional.of(new LiteralInt(-literal.getValue())); + if (operand instanceof LiteralReal literal) + return Optional.of(new LiteralReal(-literal.getValue())); + } + + if (operandFolded.isPresent()) + return Optional.of(new UnaryExpression(op, operand)); + return Optional.empty(); + } + + /** + * Folds a conditional expression and its branches + */ + private static Optional foldIte(Ite ite) { + Optional conditionFolded = fold(ite.getCondition()); + Optional thenFolded = fold(ite.getThen()); + Optional elseFolded = fold(ite.getElse()); + + Expression condition = conditionFolded.orElseGet(() -> ite.getCondition().clone()); + Expression thenExpression = thenFolded.orElseGet(() -> ite.getThen().clone()); + Expression elseExpression = elseFolded.orElseGet(() -> ite.getElse().clone()); + + if (condition instanceof LiteralBoolean literal) + return Optional.of(literal.isBooleanTrue() ? thenExpression : elseExpression); + + if (thenExpression.equals(elseExpression)) + return Optional.of(thenExpression); + + if (conditionFolded.isPresent() || thenFolded.isPresent() || elseFolded.isPresent()) + return Optional.of(new Ite(condition, thenExpression, elseExpression)); + return Optional.empty(); } /** @@ -130,6 +167,33 @@ private static Expression foldLiteralBinary(Expression left, Expression right, S return null; } + /** + * Combines adjacent integer constants in additions and subtractions + */ + private static Optional foldAdjacentInts(Expression left, Expression right, String op) { + if (!"+".equals(op) && !"-".equals(op)) + return Optional.empty(); + if (!(right instanceof LiteralInt rightLiteral)) + return Optional.empty(); + if (!(left instanceof BinaryExpression leftBinary)) + return Optional.empty(); + if (!"+".equals(leftBinary.getOperator()) && !"-".equals(leftBinary.getOperator())) + return Optional.empty(); + if (!(leftBinary.getSecondOperand()instanceof LiteralInt leftLiteral)) + return Optional.empty(); + + // treat subtraction as adding a negative constant and then add the two + int signedLeft = "+".equals(leftBinary.getOperator()) ? leftLiteral.getValue() : -leftLiteral.getValue(); + int signedRight = "+".equals(op) ? rightLiteral.getValue() : -rightLiteral.getValue(); + int constant = signedLeft + signedRight; + Expression base = leftBinary.getFirstOperand().clone(); + if (constant == 0) + return Optional.of(base); + if (constant > 0) + return Optional.of(new BinaryExpression(base, "+", new LiteralInt(constant))); + return Optional.of(new BinaryExpression(base, "-", new LiteralInt(-constant))); + } + /** * Folds integer operations */ @@ -170,23 +234,6 @@ private static Expression foldReals(double left, double right, String op) { }; } - /** - * Checks whether two expressions mix integer and real literals - */ - private static boolean isMixedNumeric(Expression left, Expression right) { - return left instanceof LiteralInt && right instanceof LiteralReal - || left instanceof LiteralReal && right instanceof LiteralInt; - } - - /** - * Reads a numeric literal as a double - */ - private static double numericValue(Expression expression) { - if (expression instanceof LiteralInt literal) - return literal.getValue(); - return ((LiteralReal) expression).getValue(); - } - /** * Folds boolean operations */ @@ -202,78 +249,28 @@ private static Expression foldBooleans(boolean left, boolean right, String op) { } /** - * Folds a unary expression and its operand + * Replaces a resolved enum constant with its literal value */ - private static Optional foldUnary(UnaryExpression unary) { - Optional operandFolded = fold(unary.getExpression()); - Expression operand = operandFolded.orElseGet(() -> unary.getExpression().clone()); - String op = unary.getOp(); - - if ("!".equals(op) && operand instanceof LiteralBoolean literal) - return Optional.of(new LiteralBoolean(!literal.isBooleanTrue())); - - if ("-".equals(op)) { - if (operand instanceof LiteralInt literal) - return Optional.of(new LiteralInt(-literal.getValue())); - if (operand instanceof LiteralReal literal) - return Optional.of(new LiteralReal(-literal.getValue())); - } - - if (operandFolded.isPresent()) - return Optional.of(new UnaryExpression(op, operand)); - return Optional.empty(); + private static Expression resolvedLiteral(Expression expression) { + if (expression instanceof Enum en && en.getResolvedLiteral() != null) + return en.getResolvedLiteral().clone(); + return expression; } /** - * Folds a conditional expression and its branches + * Checks whether two expressions mix integer and real literals */ - private static Optional foldIte(Ite ite) { - Optional conditionFolded = fold(ite.getCondition()); - Optional thenFolded = fold(ite.getThen()); - Optional elseFolded = fold(ite.getElse()); - - Expression condition = conditionFolded.orElseGet(() -> ite.getCondition().clone()); - Expression thenExpression = thenFolded.orElseGet(() -> ite.getThen().clone()); - Expression elseExpression = elseFolded.orElseGet(() -> ite.getElse().clone()); - - if (condition instanceof LiteralBoolean literal) - return Optional.of(literal.isBooleanTrue() ? thenExpression : elseExpression); - - if (thenExpression.equals(elseExpression)) - return Optional.of(thenExpression); - - if (conditionFolded.isPresent() || thenFolded.isPresent() || elseFolded.isPresent()) - return Optional.of(new Ite(condition, thenExpression, elseExpression)); - return Optional.empty(); + private static boolean isMixedNumeric(Expression left, Expression right) { + return left instanceof LiteralInt && right instanceof LiteralReal + || left instanceof LiteralReal && right instanceof LiteralInt; } /** - * Combines adjacent integer constants in additions and subtractions + * Reads a numeric literal as a double */ - private static Optional foldAdjacentIntegerConstants(Expression left, Expression right, String op) { - if (!"+".equals(op) && !"-".equals(op)) - return Optional.empty(); - if (!(right instanceof LiteralInt rightLiteral)) - return Optional.empty(); - if (!(left instanceof BinaryExpression leftBinary)) - return Optional.empty(); - if (!"+".equals(leftBinary.getOperator()) && !"-".equals(leftBinary.getOperator())) - return Optional.empty(); - if (!(leftBinary.getSecondOperand()instanceof LiteralInt leftLiteral)) - return Optional.empty(); - - // treat subtraction as adding a negative constant and then add the two - int signedLeft = "+".equals(leftBinary.getOperator()) ? leftLiteral.getValue() : -leftLiteral.getValue(); - int signedRight = "+".equals(op) ? rightLiteral.getValue() : -rightLiteral.getValue(); - Expression folded = expressionWithConstant(leftBinary.getFirstOperand(), signedLeft + signedRight); - return Optional.of(folded); - } - - private static Expression expressionWithConstant(Expression base, int constant) { - if (constant == 0) - return base.clone(); - if (constant > 0) - return new BinaryExpression(base.clone(), "+", new LiteralInt(constant)); - return new BinaryExpression(base.clone(), "-", new LiteralInt(-constant)); + private static double numericValue(Expression expression) { + if (expression instanceof LiteralInt literal) + return literal.getValue(); + return ((LiteralReal) expression).getValue(); } } From eab59f7fa7e2dff66ce02f90ece1964bb7be2f50 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 10 Jun 2026 19:32:39 +0100 Subject: [PATCH 29/41] Refactoring --- .../liquidjava/rj_language/opt/VCFolding.java | 106 +++++++++--------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java index 728e6225d..7408611a6 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java @@ -1,7 +1,5 @@ package liquidjava.rj_language.opt; -import java.util.Optional; - import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; @@ -20,6 +18,12 @@ */ public class VCFolding { + /** + * A folded expression and whether the fold changed the original expression + */ + private record Folding(Expression folded, boolean changed) { + } + /** * Applies folding to the first foldable predicate in a VC chain */ @@ -27,9 +31,9 @@ public static VCImplication apply(VCImplication implication) { if (implication == null) return null; - Optional folded = fold(implication.getRefinement().getExpression()); - if (folded.isPresent()) { - VCImplication result = new SimplifiedVCImplication(implication, new Predicate(folded.get()), + Folding folding = fold(implication.getRefinement().getExpression()); + if (folding.changed()) { + VCImplication result = new SimplifiedVCImplication(implication, new Predicate(folding.folded()), implication.getOrigin()); result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); return result; @@ -47,7 +51,7 @@ public static VCImplication apply(VCImplication implication) { /** * Folds an expression */ - private static Optional fold(Expression expression) { + private static Folding fold(Expression expression) { if (expression instanceof BinaryExpression binary) return foldBinary(binary); if (expression instanceof UnaryExpression unary) @@ -55,84 +59,84 @@ private static Optional fold(Expression expression) { if (expression instanceof Ite ite) return foldIte(ite); if (expression instanceof GroupExpression group && group.getChildren().size() == 1) { - Optional child = fold(group.getExpression()); - return Optional.of(child.orElseGet(() -> group.getExpression().clone())); + Folding child = fold(group.getExpression()); + return new Folding(child.folded(), true); } - return Optional.empty(); + return new Folding(expression.clone(), false); } /** * Folds a binary expression and its operands */ - private static Optional foldBinary(BinaryExpression binary) { - Optional leftFolded = fold(binary.getFirstOperand()); - Optional rightFolded = fold(binary.getSecondOperand()); + private static Folding foldBinary(BinaryExpression binary) { + Folding leftFolded = fold(binary.getFirstOperand()); + Folding rightFolded = fold(binary.getSecondOperand()); - Expression leftExpression = leftFolded.orElseGet(() -> binary.getFirstOperand().clone()); - Expression rightExpression = rightFolded.orElseGet(() -> binary.getSecondOperand().clone()); + Expression leftExpression = leftFolded.folded(); + Expression rightExpression = rightFolded.folded(); Expression left = resolvedLiteral(leftExpression); Expression right = resolvedLiteral(rightExpression); - boolean childChanged = leftFolded.isPresent() || rightFolded.isPresent() || left != leftExpression + boolean childChanged = leftFolded.changed() || rightFolded.changed() || left != leftExpression || right != rightExpression; String op = binary.getOperator(); Expression foldedBinary = foldLiteralBinary(left, right, op); if (foldedBinary != null) - return Optional.of(foldedBinary); + return new Folding(foldedBinary, true); - Optional foldedAdjacentInts = foldAdjacentInts(left, right, op); - if (foldedAdjacentInts.isPresent()) - return foldedAdjacentInts; + Expression foldedAdjacentInts = foldAdjacentInts(left, right, op); + if (foldedAdjacentInts != null) + return new Folding(foldedAdjacentInts, true); if (childChanged) - return Optional.of(new BinaryExpression(left, op, right)); - return Optional.empty(); + return new Folding(new BinaryExpression(left, op, right), true); + return new Folding(binary.clone(), false); } /** * Folds a unary expression and its operand */ - private static Optional foldUnary(UnaryExpression unary) { - Optional operandFolded = fold(unary.getExpression()); - Expression operand = operandFolded.orElseGet(() -> unary.getExpression().clone()); + private static Folding foldUnary(UnaryExpression unary) { + Folding operandFolded = fold(unary.getExpression()); + Expression operand = operandFolded.folded(); String op = unary.getOp(); if ("!".equals(op) && operand instanceof LiteralBoolean literal) - return Optional.of(new LiteralBoolean(!literal.isBooleanTrue())); + return new Folding(new LiteralBoolean(!literal.isBooleanTrue()), true); if ("-".equals(op)) { if (operand instanceof LiteralInt literal) - return Optional.of(new LiteralInt(-literal.getValue())); + return new Folding(new LiteralInt(-literal.getValue()), true); if (operand instanceof LiteralReal literal) - return Optional.of(new LiteralReal(-literal.getValue())); + return new Folding(new LiteralReal(-literal.getValue()), true); } - if (operandFolded.isPresent()) - return Optional.of(new UnaryExpression(op, operand)); - return Optional.empty(); + if (operandFolded.changed()) + return new Folding(new UnaryExpression(op, operand), true); + return new Folding(unary.clone(), false); } /** * Folds a conditional expression and its branches */ - private static Optional foldIte(Ite ite) { - Optional conditionFolded = fold(ite.getCondition()); - Optional thenFolded = fold(ite.getThen()); - Optional elseFolded = fold(ite.getElse()); + private static Folding foldIte(Ite ite) { + Folding conditionFolded = fold(ite.getCondition()); + Folding thenFolded = fold(ite.getThen()); + Folding elseFolded = fold(ite.getElse()); - Expression condition = conditionFolded.orElseGet(() -> ite.getCondition().clone()); - Expression thenExpression = thenFolded.orElseGet(() -> ite.getThen().clone()); - Expression elseExpression = elseFolded.orElseGet(() -> ite.getElse().clone()); + Expression condition = conditionFolded.folded(); + Expression thenExpression = thenFolded.folded(); + Expression elseExpression = elseFolded.folded(); if (condition instanceof LiteralBoolean literal) - return Optional.of(literal.isBooleanTrue() ? thenExpression : elseExpression); + return new Folding(literal.isBooleanTrue() ? thenExpression : elseExpression, true); if (thenExpression.equals(elseExpression)) - return Optional.of(thenExpression); + return new Folding(thenExpression, true); - if (conditionFolded.isPresent() || thenFolded.isPresent() || elseFolded.isPresent()) - return Optional.of(new Ite(condition, thenExpression, elseExpression)); - return Optional.empty(); + if (conditionFolded.changed() || thenFolded.changed() || elseFolded.changed()) + return new Folding(new Ite(condition, thenExpression, elseExpression), true); + return new Folding(ite.clone(), false); } /** @@ -170,17 +174,17 @@ private static Expression foldLiteralBinary(Expression left, Expression right, S /** * Combines adjacent integer constants in additions and subtractions */ - private static Optional foldAdjacentInts(Expression left, Expression right, String op) { + private static Expression foldAdjacentInts(Expression left, Expression right, String op) { if (!"+".equals(op) && !"-".equals(op)) - return Optional.empty(); + return null; if (!(right instanceof LiteralInt rightLiteral)) - return Optional.empty(); + return null; if (!(left instanceof BinaryExpression leftBinary)) - return Optional.empty(); + return null; if (!"+".equals(leftBinary.getOperator()) && !"-".equals(leftBinary.getOperator())) - return Optional.empty(); + return null; if (!(leftBinary.getSecondOperand()instanceof LiteralInt leftLiteral)) - return Optional.empty(); + return null; // treat subtraction as adding a negative constant and then add the two int signedLeft = "+".equals(leftBinary.getOperator()) ? leftLiteral.getValue() : -leftLiteral.getValue(); @@ -188,10 +192,10 @@ private static Optional foldAdjacentInts(Expression left, Expression int constant = signedLeft + signedRight; Expression base = leftBinary.getFirstOperand().clone(); if (constant == 0) - return Optional.of(base); + return base; if (constant > 0) - return Optional.of(new BinaryExpression(base, "+", new LiteralInt(constant))); - return Optional.of(new BinaryExpression(base, "-", new LiteralInt(-constant))); + return new BinaryExpression(base, "+", new LiteralInt(constant)); + return new BinaryExpression(base, "-", new LiteralInt(-constant)); } /** From 11be88b9ad1b2986d2ed4172afe097d6896d672b Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 10 Jun 2026 20:15:40 +0100 Subject: [PATCH 30/41] Simplify VCFolding --- .../liquidjava/rj_language/opt/VCFolding.java | 77 +++++++------------ .../rj_language/opt/VCFoldingTest.java | 28 +++++++ 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java index 7408611a6..764a3af34 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java @@ -18,12 +18,6 @@ */ public class VCFolding { - /** - * A folded expression and whether the fold changed the original expression - */ - private record Folding(Expression folded, boolean changed) { - } - /** * Applies folding to the first foldable predicate in a VC chain */ @@ -31,9 +25,10 @@ public static VCImplication apply(VCImplication implication) { if (implication == null) return null; - Folding folding = fold(implication.getRefinement().getExpression()); - if (folding.changed()) { - VCImplication result = new SimplifiedVCImplication(implication, new Predicate(folding.folded()), + Expression expression = implication.getRefinement().getExpression(); + Expression folded = fold(expression); + if (!expression.equals(folded)) { + VCImplication result = new SimplifiedVCImplication(implication, new Predicate(folded), implication.getOrigin()); result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); return result; @@ -51,92 +46,74 @@ public static VCImplication apply(VCImplication implication) { /** * Folds an expression */ - private static Folding fold(Expression expression) { + private static Expression fold(Expression expression) { if (expression instanceof BinaryExpression binary) return foldBinary(binary); if (expression instanceof UnaryExpression unary) return foldUnary(unary); if (expression instanceof Ite ite) return foldIte(ite); - if (expression instanceof GroupExpression group && group.getChildren().size() == 1) { - Folding child = fold(group.getExpression()); - return new Folding(child.folded(), true); - } - return new Folding(expression.clone(), false); + if (expression instanceof GroupExpression group && group.getChildren().size() == 1) + return fold(group.getExpression()); + return expression.clone(); } /** * Folds a binary expression and its operands */ - private static Folding foldBinary(BinaryExpression binary) { - Folding leftFolded = fold(binary.getFirstOperand()); - Folding rightFolded = fold(binary.getSecondOperand()); - - Expression leftExpression = leftFolded.folded(); - Expression rightExpression = rightFolded.folded(); + private static Expression foldBinary(BinaryExpression binary) { + Expression leftExpression = fold(binary.getFirstOperand()); + Expression rightExpression = fold(binary.getSecondOperand()); Expression left = resolvedLiteral(leftExpression); Expression right = resolvedLiteral(rightExpression); - boolean childChanged = leftFolded.changed() || rightFolded.changed() || left != leftExpression - || right != rightExpression; String op = binary.getOperator(); Expression foldedBinary = foldLiteralBinary(left, right, op); if (foldedBinary != null) - return new Folding(foldedBinary, true); + return foldedBinary; Expression foldedAdjacentInts = foldAdjacentInts(left, right, op); if (foldedAdjacentInts != null) - return new Folding(foldedAdjacentInts, true); + return foldedAdjacentInts; - if (childChanged) - return new Folding(new BinaryExpression(left, op, right), true); - return new Folding(binary.clone(), false); + return new BinaryExpression(left, op, right); } /** * Folds a unary expression and its operand */ - private static Folding foldUnary(UnaryExpression unary) { - Folding operandFolded = fold(unary.getExpression()); - Expression operand = operandFolded.folded(); + private static Expression foldUnary(UnaryExpression unary) { + Expression operand = fold(unary.getExpression()); String op = unary.getOp(); if ("!".equals(op) && operand instanceof LiteralBoolean literal) - return new Folding(new LiteralBoolean(!literal.isBooleanTrue()), true); + return new LiteralBoolean(!literal.isBooleanTrue()); if ("-".equals(op)) { if (operand instanceof LiteralInt literal) - return new Folding(new LiteralInt(-literal.getValue()), true); + return new LiteralInt(-literal.getValue()); if (operand instanceof LiteralReal literal) - return new Folding(new LiteralReal(-literal.getValue()), true); + return new LiteralReal(-literal.getValue()); } - if (operandFolded.changed()) - return new Folding(new UnaryExpression(op, operand), true); - return new Folding(unary.clone(), false); + return new UnaryExpression(op, operand); } /** * Folds a conditional expression and its branches */ - private static Folding foldIte(Ite ite) { - Folding conditionFolded = fold(ite.getCondition()); - Folding thenFolded = fold(ite.getThen()); - Folding elseFolded = fold(ite.getElse()); - - Expression condition = conditionFolded.folded(); - Expression thenExpression = thenFolded.folded(); - Expression elseExpression = elseFolded.folded(); + private static Expression foldIte(Ite ite) { + Expression condition = fold(ite.getCondition()); + Expression thenExpression = fold(ite.getThen()); + Expression elseExpression = fold(ite.getElse()); if (condition instanceof LiteralBoolean literal) - return new Folding(literal.isBooleanTrue() ? thenExpression : elseExpression, true); + return literal.isBooleanTrue() ? thenExpression : elseExpression; if (thenExpression.equals(elseExpression)) - return new Folding(thenExpression, true); + return thenExpression; - if (conditionFolded.changed() || thenFolded.changed() || elseFolded.changed()) - return new Folding(new Ite(condition, thenExpression, elseExpression), true); - return new Folding(ite.clone(), false); + return new Ite(condition, thenExpression, elseExpression); } /** diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java index db439c5cc..eaf5689ab 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java @@ -2,14 +2,19 @@ import static liquidjava.utils.VCTestUtils.assertSimplifiedVC; import static liquidjava.utils.VCTestUtils.assertVC; +import static liquidjava.utils.VCTestUtils.parse; import static liquidjava.utils.VCTestUtils.simplified; import static liquidjava.utils.VCTestUtils.vc; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; +import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.BinaryExpression; import liquidjava.rj_language.ast.Enum; +import liquidjava.rj_language.ast.GroupExpression; import liquidjava.rj_language.ast.LiteralInt; import org.junit.jupiter.api.Test; @@ -93,6 +98,29 @@ void preservesOriginFromExistingSimplifiedImplication() { assertSimplifiedVC(result, simplified("true", "∀x:int. x + 1 + 2 > 0")); } + @Test + void recordsOriginWhenOnlyGroupIsUnwrapped() { + VCImplication implication = new VCImplication(new Predicate(new GroupExpression(parse("x > 0")))); + + VCImplication result = VCFolding.apply(implication); + + SimplifiedVCImplication simplified = assertInstanceOf(SimplifiedVCImplication.class, result); + assertEquals("x > 0", simplified.getRefinement().toString()); + assertInstanceOf(GroupExpression.class, simplified.getOrigin().getRefinement().getExpression()); + } + + @Test + void recordsOriginWhenFoldingLaterImplication() { + VCImplication implication = vc("x > 0", "1 + 2 > 0"); + + VCImplication result = VCFolding.apply(implication); + + assertEquals("x > 0", result.getRefinement().toString()); + SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); + assertEquals("true", simplifiedNext.getRefinement().toString()); + assertEquals("1 + 2 > 0", simplifiedNext.getOrigin().getRefinement().toString()); + } + private static void assertFolded(String original, String folded) { VCImplication result = VCFolding.apply(vc(original)); From 84f9727760f656be6f3fa2f1b14fd88281f9db9f Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 11 Jun 2026 12:55:47 +0100 Subject: [PATCH 31/41] Add Tests --- .../rj_language/opt/VCFoldingTest.java | 40 +++++++++++++++++++ .../rj_language/opt/VCSimplificationTest.java | 27 +++++++++++++ 2 files changed, 67 insertions(+) diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java index eaf5689ab..224cc0c95 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java @@ -43,6 +43,12 @@ void leavesDivisionAndModuloByZeroUnchanged() { assertUnchanged("4 % 0 == 0"); } + @Test + void leavesRealDivisionAndModuloByZeroUnchanged() { + assertUnchanged("4.0 / 0.0 == 0.0"); + assertUnchanged("4.0 % 0.0 == 0.0"); + } + @Test void foldsBooleanBinaryExpressions() { assertFolded("true && false", "false"); @@ -50,6 +56,22 @@ void foldsBooleanBinaryExpressions() { assertFolded("true != false", "true"); } + @Test + void foldsBooleanSubexpressionsInsideLargerExpression() { + assertFolded("true && false || ok", "false || ok"); + } + + @Test + void foldsNestedConstantsInsideLargerExpression() { + assertFolded("x > 1 + 2", "x > 3"); + assertFolded("x + 1 + 2 > 4", "x + 3 > 4"); + } + + @Test + void foldsPartialComparisonsWithoutDroppingSymbolicTerms() { + assertFolded("1 + 2 < x + 4", "3 < x + 4"); + } + @Test void foldsUnaryExpressions() { assertFolded("!true", "false"); @@ -63,6 +85,11 @@ void foldsIteExpressions() { assertFolded("cond ? b : b", "b"); } + @Test + void foldsIteBranchesBeforeComparingThem() { + assertFolded("cond ? 1 + 2 : 3", "3"); + } + @Test void foldsAdjacentIntegerConstants() { assertFolded("x + 1 - 2", "x - 1"); @@ -89,6 +116,19 @@ void foldsResolvedEnumLiterals() { assertSimplifiedVC(result, simplified("true", "Config.LIMIT == 3")); } + @Test + void foldsResolvedEnumLiteralsInsideLargerExpression() { + Enum limit = new Enum("Config", "LIMIT"); + limit.setResolvedLiteral(new LiteralInt(3)); + BinaryExpression arithmetic = new BinaryExpression(limit, "+", new LiteralInt(2)); + VCImplication implication = new VCImplication( + new Predicate(new BinaryExpression(arithmetic, "==", new LiteralInt(5)))); + + VCImplication result = VCFolding.apply(implication); + + assertSimplifiedVC(result, simplified("true", "Config.LIMIT + 2 == 5")); + } + @Test void preservesOriginFromExistingSimplifiedImplication() { VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == 1", "x + 1 + 2 > 0")); diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java index d21da2451..aecb7b184 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java @@ -30,6 +30,15 @@ void simplifyOnceAppliesSubstitutionBeforeFolding() { assertSimplifiedVC(result, simplified("1 + 2 > 2", "∀x:int. x > 2")); } + @Test + void simplifyOnceDoesNotFoldAfterSubstitutionInSameStep() { + VCImplication implication = vc("∀x:int. x == 1 + 2", "x == 3"); + + VCImplication result = VCSimplification.simplifyOnce(implication); + + assertSimplifiedVC(result, simplified("1 + 2 == 3", "∀x:int. x == 3")); + } + @Test void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { VCImplication implication = vc("1 + 2 > 2"); @@ -57,6 +66,24 @@ void simplifyAppliesMultipleSubstitutionsBeforeReachingFixedPoint() { assertSimplifiedVC(result, simplified("true", "∀y:int. y > x")); } + @Test + void simplifyAppliesLongSubstitutionChainBeforeReachingFixedPoint() { + VCImplication implication = vc("∀x:int. x == 1", "∀y:int. y == x + 1", "∀z:int. z == y + 1", "z == 3"); + + VCImplication result = VCSimplification.simplifyToFixedPoint(implication); + + assertSimplifiedVC(result, simplified("true", "∀z:int. z == 3")); + } + + @Test + void simplifyCombinesSubstitutionAndNestedFoldingAcrossFixedPoint() { + VCImplication implication = vc("∀x:int. x == 1", "∀y:int. y == x + 2", "y - 1 == 2"); + + VCImplication result = VCSimplification.simplifyToFixedPoint(implication); + + assertSimplifiedVC(result, simplified("true", "∀y:int. y - 1 == 2")); + } + @Test void simplifyLeavesUnchangedVcAsPlainPredicates() { VCImplication implication = vc("x > 0", "y > x"); From 99131d4c9314c8279657092dd07225f587200903 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 11 Jun 2026 14:31:33 +0100 Subject: [PATCH 32/41] Fixes --- .../liquidjava/rj_language/opt/VCFolding.java | 54 ++++++++++++------- .../rj_language/opt/VCSimplification.java | 5 +- .../rj_language/opt/VCSubstitution.java | 2 - .../rj_language/opt/VCFoldingTest.java | 33 +++++++----- .../VCSimplificationPropertyBasedTest.java | 14 ++--- .../rj_language/opt/VCSimplificationTest.java | 26 +++++---- .../rj_language/opt/VCSubstitutionTest.java | 11 ++-- .../java/liquidjava/utils/VCTestUtils.java | 19 +++++++ 8 files changed, 102 insertions(+), 62 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java index 764a3af34..9c5cf0cdf 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java @@ -36,7 +36,7 @@ public static VCImplication apply(VCImplication implication) { VCImplication next = apply(implication.getNext()); if (implication.getNext() == null || implication.getNext().equals(next)) - return implication.clone(); + return implication; VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); result.setNext(next); @@ -44,9 +44,11 @@ public static VCImplication apply(VCImplication implication) { } /** - * Folds an expression + * Folds the first foldable expression found */ private static Expression fold(Expression expression) { + if (expression instanceof Enum en && en.getResolvedLiteral() != null) + return en.getResolvedLiteral().clone(); if (expression instanceof BinaryExpression binary) return foldBinary(binary); if (expression instanceof UnaryExpression unary) @@ -54,7 +56,7 @@ private static Expression fold(Expression expression) { if (expression instanceof Ite ite) return foldIte(ite); if (expression instanceof GroupExpression group && group.getChildren().size() == 1) - return fold(group.getExpression()); + return group.getExpression().clone(); return expression.clone(); } @@ -62,10 +64,16 @@ private static Expression fold(Expression expression) { * Folds a binary expression and its operands */ private static Expression foldBinary(BinaryExpression binary) { - Expression leftExpression = fold(binary.getFirstOperand()); - Expression rightExpression = fold(binary.getSecondOperand()); - Expression left = resolvedLiteral(leftExpression); - Expression right = resolvedLiteral(rightExpression); + Expression left = binary.getFirstOperand(); + Expression foldedLeft = fold(left); + if (!left.equals(foldedLeft)) + return new BinaryExpression(foldedLeft, binary.getOperator(), binary.getSecondOperand().clone()); + + Expression right = binary.getSecondOperand(); + Expression foldedRight = fold(right); + if (!right.equals(foldedRight)) + return new BinaryExpression(left.clone(), binary.getOperator(), foldedRight); + String op = binary.getOperator(); Expression foldedBinary = foldLiteralBinary(left, right, op); @@ -83,7 +91,11 @@ private static Expression foldBinary(BinaryExpression binary) { * Folds a unary expression and its operand */ private static Expression foldUnary(UnaryExpression unary) { - Expression operand = fold(unary.getExpression()); + Expression operand = unary.getExpression(); + Expression foldedOperand = fold(operand); + if (!operand.equals(foldedOperand)) + return new UnaryExpression(unary.getOp(), foldedOperand); + String op = unary.getOp(); if ("!".equals(op) && operand instanceof LiteralBoolean literal) @@ -103,9 +115,20 @@ private static Expression foldUnary(UnaryExpression unary) { * Folds a conditional expression and its branches */ private static Expression foldIte(Ite ite) { - Expression condition = fold(ite.getCondition()); - Expression thenExpression = fold(ite.getThen()); - Expression elseExpression = fold(ite.getElse()); + Expression condition = ite.getCondition(); + Expression foldedCondition = fold(condition); + if (!condition.equals(foldedCondition)) + return new Ite(foldedCondition, ite.getThen().clone(), ite.getElse().clone()); + + Expression thenExpression = ite.getThen(); + Expression foldedThen = fold(thenExpression); + if (!thenExpression.equals(foldedThen)) + return new Ite(condition.clone(), foldedThen, ite.getElse().clone()); + + Expression elseExpression = ite.getElse(); + Expression foldedElse = fold(elseExpression); + if (!elseExpression.equals(foldedElse)) + return new Ite(condition.clone(), thenExpression.clone(), foldedElse); if (condition instanceof LiteralBoolean literal) return literal.isBooleanTrue() ? thenExpression : elseExpression; @@ -229,15 +252,6 @@ private static Expression foldBooleans(boolean left, boolean right, String op) { }; } - /** - * Replaces a resolved enum constant with its literal value - */ - private static Expression resolvedLiteral(Expression expression) { - if (expression instanceof Enum en && en.getResolvedLiteral() != null) - return en.getResolvedLiteral().clone(); - return expression; - } - /** * Checks whether two expressions mix integer and real literals */ diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplification.java index 87a19a1e2..a9837f5ce 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplification.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplification.java @@ -2,8 +2,6 @@ import liquidjava.processor.VCImplication; -import static liquidjava.rj_language.opt.VCSimplificationUtils.*; - /** * Simplifies VCImplication chains by applying various simplification steps */ @@ -38,7 +36,6 @@ public static VCImplication simplifyOnce(VCImplication implication) { if (!implication.equals(substituted)) return substituted; - // TODO: add more simplification steps here (e.g., folding) - return substituted; + return VCFolding.apply(implication); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java index 73249d64b..37dd16bf9 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java @@ -11,8 +11,6 @@ import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.Var; -import static liquidjava.rj_language.opt.VCSimplificationUtils.*; - /** * Simplifies VCImplication chains by replacing binder equalities with their known values */ diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java index 224cc0c95..5d4db1632 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java @@ -1,6 +1,7 @@ package liquidjava.rj_language.opt; import static liquidjava.utils.VCTestUtils.assertSimplifiedVC; +import static liquidjava.utils.VCTestUtils.assertSimplificationSteps; import static liquidjava.utils.VCTestUtils.assertVC; import static liquidjava.utils.VCTestUtils.parse; import static liquidjava.utils.VCTestUtils.simplified; @@ -27,14 +28,14 @@ void applyReturnsNullForNullImplication() { @Test void foldsIntegerArithmeticAndComparisons() { - assertFolded("1 + 2 == 3", "true"); + assertSimplificationSteps(vc("1 + 2 == 3"), VCFolding::apply, "1 + 2 == 3", "3 == 3", "true"); assertFolded("4 > 7", "false"); } @Test void foldsRealAndMixedNumericExpressions() { - assertFolded("1.5 + 2.0 == 3.5", "true"); - assertFolded("2 + 0.5 > 2", "true"); + assertSimplificationSteps(vc("1.5 + 2.0 == 3.5"), VCFolding::apply, "1.5 + 2.0 == 3.5", "3.5 == 3.5", "true"); + assertSimplificationSteps(vc("2 + 0.5 > 2"), VCFolding::apply, "2 + 0.5 > 2", "2.5 > 2", "true"); } @Test @@ -75,7 +76,7 @@ void foldsPartialComparisonsWithoutDroppingSymbolicTerms() { @Test void foldsUnaryExpressions() { assertFolded("!true", "false"); - assertFolded("-3 < 0", "true"); + assertSimplificationSteps(vc("-3 < 0"), VCFolding::apply, "-3 < 0", "-3 < 0", "true"); } @Test @@ -87,7 +88,7 @@ void foldsIteExpressions() { @Test void foldsIteBranchesBeforeComparingThem() { - assertFolded("cond ? 1 + 2 : 3", "3"); + assertSimplificationSteps(vc("cond ? 1 + 2 : 3"), VCFolding::apply, "cond ? 1 + 2 : 3", "cond ? 3 : 3", "3"); } @Test @@ -111,9 +112,8 @@ void foldsResolvedEnumLiterals() { VCImplication implication = new VCImplication( new Predicate(new BinaryExpression(limit, "==", new LiteralInt(3)))); - VCImplication result = VCFolding.apply(implication); - - assertSimplifiedVC(result, simplified("true", "Config.LIMIT == 3")); + assertSimplificationSteps(implication, VCFolding::apply, simplified("3 == 3", "Config.LIMIT == 3"), + simplified("true", "Config.LIMIT == 3")); } @Test @@ -124,18 +124,16 @@ void foldsResolvedEnumLiteralsInsideLargerExpression() { VCImplication implication = new VCImplication( new Predicate(new BinaryExpression(arithmetic, "==", new LiteralInt(5)))); - VCImplication result = VCFolding.apply(implication); - - assertSimplifiedVC(result, simplified("true", "Config.LIMIT + 2 == 5")); + assertSimplificationSteps(implication, VCFolding::apply, simplified("3 + 2 == 5", "Config.LIMIT + 2 == 5"), + simplified("5 == 5", "Config.LIMIT + 2 == 5"), simplified("true", "Config.LIMIT + 2 == 5")); } @Test void preservesOriginFromExistingSimplifiedImplication() { VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == 1", "x + 1 + 2 > 0")); - VCImplication result = VCFolding.apply(substituted); - - assertSimplifiedVC(result, simplified("true", "∀x:int. x + 1 + 2 > 0")); + assertSimplificationSteps(substituted, VCFolding::apply, simplified("2 + 2 > 0", "∀x:int. x + 1 + 2 > 0"), + simplified("4 > 0", "∀x:int. x + 1 + 2 > 0"), simplified("true", "∀x:int. x + 1 + 2 > 0")); } @Test @@ -157,6 +155,13 @@ void recordsOriginWhenFoldingLaterImplication() { assertEquals("x > 0", result.getRefinement().toString()); SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); + assertEquals("3 > 0", simplifiedNext.getRefinement().toString()); + assertEquals("1 + 2 > 0", simplifiedNext.getOrigin().getRefinement().toString()); + + result = VCFolding.apply(result); + + assertEquals("x > 0", result.getRefinement().toString()); + simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); assertEquals("true", simplifiedNext.getRefinement().toString()); assertEquals("1 + 2 > 0", simplifiedNext.getOrigin().getRefinement().toString()); } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationPropertyBasedTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationPropertyBasedTest.java index 3c4c919a2..ea12f536f 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationPropertyBasedTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationPropertyBasedTest.java @@ -2,8 +2,8 @@ import static liquidjava.rj_language.opt.VCSubstitution.containsVar; import static liquidjava.rj_language.opt.VCSubstitution.isVar; -import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import com.pholser.junit.quickcheck.From; import com.pholser.junit.quickcheck.Property; @@ -21,7 +21,7 @@ @RunWith(JUnitQuickcheck.class) public class VCSimplificationPropertyBasedTest { - private static final int TRIALS = 500; // number of random VCs to test + private static final int TRIALS = 100; // number of random VCs to test private static final int MAX_STEPS = 20; // to prevent infinite loops in case of non-termination @Property(trials = TRIALS) @@ -29,11 +29,10 @@ public void eachSimplificationStepPreservesVcSemantics(@From(VCImplicationGenera setUpContext(); VCImplication current = vc; - for (int step = 0; step < VCImplicationGenerator.BINDERS.length; step++) { - VCImplication simplified = VCSimplification.simplifyToFixedPoint(current); + for (int step = 0; step < MAX_STEPS; step++) { + VCImplication simplified = VCSimplification.simplifyOnce(current); if (current.equals(simplified)) - break; - + return; assertEquivalent(current, simplified, step); current = simplified; } @@ -52,9 +51,6 @@ private static void assertEquivalent(VCImplication unsimplified, VCImplication s Predicate premises = substitutionPremises(unsimplified); Predicate unsimplifiedFormula = Predicate.createConjunction(premises, new Predicate(vcFormula(unsimplified))); Predicate simplifiedFormula = Predicate.createConjunction(premises, new Predicate(vcFormula(simplified))); - System.out.println(unsimplifiedFormula); - System.out.println("=>"); - System.out.println(simplifiedFormula); assertImplies(unsimplifiedFormula, simplifiedFormula, unsimplified, simplified, step, "unsimplified => simplified"); assertImplies(simplifiedFormula, unsimplifiedFormula, unsimplified, simplified, step, diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java index aecb7b184..491626b5c 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java @@ -1,6 +1,7 @@ package liquidjava.rj_language.opt; import static liquidjava.utils.VCTestUtils.assertSimplifiedVC; +import static liquidjava.utils.VCTestUtils.assertSimplificationSteps; import static liquidjava.utils.VCTestUtils.assertVC; import static liquidjava.utils.VCTestUtils.simplified; import static liquidjava.utils.VCTestUtils.vc; @@ -25,27 +26,25 @@ void simplifyOnceReturnsNullForNullImplication() { void simplifyOnceAppliesSubstitutionBeforeFolding() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x > 2"); - VCImplication result = VCSimplification.simplifyOnce(implication); - - assertSimplifiedVC(result, simplified("1 + 2 > 2", "∀x:int. x > 2")); + assertSimplificationSteps(implication, VCSimplification::simplifyOnce, simplified("1 + 2 > 2", "∀x:int. x > 2"), + simplified("3 > 2", "∀x:int. x > 2"), simplified("true", "∀x:int. x > 2")); } @Test void simplifyOnceDoesNotFoldAfterSubstitutionInSameStep() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x == 3"); - VCImplication result = VCSimplification.simplifyOnce(implication); - - assertSimplifiedVC(result, simplified("1 + 2 == 3", "∀x:int. x == 3")); + assertSimplificationSteps(implication, VCSimplification::simplifyOnce, + simplified("1 + 2 == 3", "∀x:int. x == 3"), simplified("3 == 3", "∀x:int. x == 3"), + simplified("true", "∀x:int. x == 3")); } @Test void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { VCImplication implication = vc("1 + 2 > 2"); - VCImplication result = VCSimplification.simplifyOnce(implication); - - assertSimplifiedVC(result, simplified("true", "1 + 2 > 2")); + assertSimplificationSteps(implication, VCSimplification::simplifyOnce, simplified("3 > 2", "1 + 2 > 2"), + simplified("true", "1 + 2 > 2")); } @Test @@ -84,6 +83,15 @@ void simplifyCombinesSubstitutionAndNestedFoldingAcrossFixedPoint() { assertSimplifiedVC(result, simplified("true", "∀y:int. y - 1 == 2")); } + @Test + void simplifyStopsAfterSubstitutionWhenOnlyNegativeLiteralShapeChanges() { + VCImplication implication = vc("∀x:int. x == a + 0", "x >= -3"); + + VCImplication result = VCSimplification.simplifyToFixedPoint(implication); + + assertSimplifiedVC(result, simplified("a + 0 >= -3", "∀x:int. x >= -3")); + } + @Test void simplifyLeavesUnchangedVcAsPlainPredicates() { VCImplication implication = vc("x > 0", "y > x"); diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java index aef90ff77..f757273e1 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java @@ -2,7 +2,6 @@ import static liquidjava.utils.VCTestUtils.*; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import liquidjava.processor.VCImplication; @@ -22,6 +21,7 @@ void substitutesBinderEqualityIntoWholeChain() { VCImplication result = VCSubstitution.apply(implication); assertSimplifiedVC(result, simplified("3 > 0", "∀x:int. x > 0")); + assertSimplificationSteps(result, VCSimplification::simplifyOnce, simplified("true", "∀x:int. x > 0")); } @Test @@ -31,6 +31,7 @@ void substitutesReverseBinderEquality() { VCImplication result = VCSubstitution.apply(implication); assertSimplifiedVC(result, simplified("3 > 0", "∀x:int. x > 0")); + assertSimplificationSteps(result, VCSimplification::simplifyOnce, simplified("true", "∀x:int. x > 0")); } @Test @@ -58,6 +59,8 @@ void substitutesEveryOccurrenceInPredicate() { VCImplication result = VCSubstitution.apply(implication); assertSimplifiedVC(result, simplified("2 + 2 > 0", "∀x:int. x + x > 0")); + assertSimplificationSteps(result, VCSimplification::simplifyOnce, simplified("4 > 0", "∀x:int. x + x > 0"), + simplified("true", "∀x:int. x + x > 0")); } @Test @@ -111,6 +114,8 @@ void substitutesOuterKnownValueIntoNestedBinderRefinements() { assertSimplifiedVC(result, simplified("y == 3 + 1", "∀x:int. y == x + 1"), simplified("y > 3", "∀x:int. y > x")); + assertSimplificationSteps(result, VCSimplification::simplifyOnce, simplified("3 + 1 > 3", "∀y:int. y > x"), + simplified("4 > 3", "∀y:int. y > x"), simplified("true", "∀y:int. y > x")); } @Test @@ -119,7 +124,6 @@ void ignoresRecursiveBinderEquality() { VCImplication result = VCSubstitution.apply(implication); - assertNotSame(implication, result); assertVC(result, "x == x + 1", "x > 0"); } @@ -129,7 +133,6 @@ void ignoresNonEqualityBinderRefinement() { VCImplication result = VCSubstitution.apply(implication); - assertNotSame(implication, result); assertVC(result, "x > 3", "x > 0"); } @@ -139,7 +142,6 @@ void ignoresDerivedBinderEquality() { VCImplication result = VCSubstitution.apply(implication); - assertNotSame(implication, result); assertVC(result, "x + 1 == 3", "x > 0"); } @@ -151,4 +153,5 @@ void ignoresEqualityWithoutBinder() { assertVC(result, "x == 3", "x > 0"); } + } diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java index 046e4f9b7..285284dbd 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -4,6 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; +import java.util.function.UnaryOperator; + import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; @@ -92,6 +94,23 @@ public static void assertVC(VCImplication implication, String... expected) { assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); } + public static VCImplication assertSimplificationSteps(VCImplication implication, + UnaryOperator simplifier, ExpectedSimplifiedVCImplication... expectedSteps) { + VCImplication current = implication; + for (int i = 0; i < expectedSteps.length; i++) { + current = simplifier.apply(current); + assertSimplifiedVC(current, expectedSteps[i]); + } + return current; + } + + public static VCImplication assertSimplificationSteps(VCImplication implication, + UnaryOperator simplifier, String origin, String... simplifiedSteps) { + ExpectedSimplifiedVCImplication[] expectedSteps = java.util.Arrays.stream(simplifiedSteps) + .map(step -> simplified(step, origin)).toArray(ExpectedSimplifiedVCImplication[]::new); + return assertSimplificationSteps(implication, simplifier, expectedSteps); + } + public static SimplifiedVCImplication simplifiedImplication(VCImplication implication, int index) { return assertInstanceOf(SimplifiedVCImplication.class, implication, "Expected implication " + index + " to be a SimplifiedVCImplication"); From 8cde2d28cb4423032dd39fa7ca4acff417f4cdea Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 11 Jun 2026 14:31:52 +0100 Subject: [PATCH 33/41] Update Tests --- .../rj_language/opt/VCFoldingTest.java | 21 ++++++++++++------- .../rj_language/opt/VCSimplificationTest.java | 6 +++--- .../rj_language/opt/VCSubstitutionTest.java | 8 +++---- .../java/liquidjava/utils/VCTestUtils.java | 11 ++-------- 4 files changed, 22 insertions(+), 24 deletions(-) diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java index 5d4db1632..cfed03994 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java @@ -28,14 +28,17 @@ void applyReturnsNullForNullImplication() { @Test void foldsIntegerArithmeticAndComparisons() { - assertSimplificationSteps(vc("1 + 2 == 3"), VCFolding::apply, "1 + 2 == 3", "3 == 3", "true"); + assertSimplificationSteps(VCFolding::apply, vc("1 + 2 == 3"), simplified("3 == 3", "1 + 2 == 3"), + simplified("true", "1 + 2 == 3")); assertFolded("4 > 7", "false"); } @Test void foldsRealAndMixedNumericExpressions() { - assertSimplificationSteps(vc("1.5 + 2.0 == 3.5"), VCFolding::apply, "1.5 + 2.0 == 3.5", "3.5 == 3.5", "true"); - assertSimplificationSteps(vc("2 + 0.5 > 2"), VCFolding::apply, "2 + 0.5 > 2", "2.5 > 2", "true"); + assertSimplificationSteps(VCFolding::apply, vc("1.5 + 2.0 == 3.5"), + simplified("3.5 == 3.5", "1.5 + 2.0 == 3.5"), simplified("true", "1.5 + 2.0 == 3.5")); + assertSimplificationSteps(VCFolding::apply, vc("2 + 0.5 > 2"), simplified("2.5 > 2", "2 + 0.5 > 2"), + simplified("true", "2 + 0.5 > 2")); } @Test @@ -76,7 +79,8 @@ void foldsPartialComparisonsWithoutDroppingSymbolicTerms() { @Test void foldsUnaryExpressions() { assertFolded("!true", "false"); - assertSimplificationSteps(vc("-3 < 0"), VCFolding::apply, "-3 < 0", "-3 < 0", "true"); + assertSimplificationSteps(VCFolding::apply, vc("-3 < 0"), simplified("-3 < 0", "-3 < 0"), + simplified("true", "-3 < 0")); } @Test @@ -88,7 +92,8 @@ void foldsIteExpressions() { @Test void foldsIteBranchesBeforeComparingThem() { - assertSimplificationSteps(vc("cond ? 1 + 2 : 3"), VCFolding::apply, "cond ? 1 + 2 : 3", "cond ? 3 : 3", "3"); + assertSimplificationSteps(VCFolding::apply, vc("cond ? 1 + 2 : 3"), + simplified("cond ? 3 : 3", "cond ? 1 + 2 : 3"), simplified("3", "cond ? 1 + 2 : 3")); } @Test @@ -112,7 +117,7 @@ void foldsResolvedEnumLiterals() { VCImplication implication = new VCImplication( new Predicate(new BinaryExpression(limit, "==", new LiteralInt(3)))); - assertSimplificationSteps(implication, VCFolding::apply, simplified("3 == 3", "Config.LIMIT == 3"), + assertSimplificationSteps(VCFolding::apply, implication, simplified("3 == 3", "Config.LIMIT == 3"), simplified("true", "Config.LIMIT == 3")); } @@ -124,7 +129,7 @@ void foldsResolvedEnumLiteralsInsideLargerExpression() { VCImplication implication = new VCImplication( new Predicate(new BinaryExpression(arithmetic, "==", new LiteralInt(5)))); - assertSimplificationSteps(implication, VCFolding::apply, simplified("3 + 2 == 5", "Config.LIMIT + 2 == 5"), + assertSimplificationSteps(VCFolding::apply, implication, simplified("3 + 2 == 5", "Config.LIMIT + 2 == 5"), simplified("5 == 5", "Config.LIMIT + 2 == 5"), simplified("true", "Config.LIMIT + 2 == 5")); } @@ -132,7 +137,7 @@ void foldsResolvedEnumLiteralsInsideLargerExpression() { void preservesOriginFromExistingSimplifiedImplication() { VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == 1", "x + 1 + 2 > 0")); - assertSimplificationSteps(substituted, VCFolding::apply, simplified("2 + 2 > 0", "∀x:int. x + 1 + 2 > 0"), + assertSimplificationSteps(VCFolding::apply, substituted, simplified("2 + 2 > 0", "∀x:int. x + 1 + 2 > 0"), simplified("4 > 0", "∀x:int. x + 1 + 2 > 0"), simplified("true", "∀x:int. x + 1 + 2 > 0")); } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java index 491626b5c..7231aad11 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java @@ -26,7 +26,7 @@ void simplifyOnceReturnsNullForNullImplication() { void simplifyOnceAppliesSubstitutionBeforeFolding() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x > 2"); - assertSimplificationSteps(implication, VCSimplification::simplifyOnce, simplified("1 + 2 > 2", "∀x:int. x > 2"), + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, simplified("1 + 2 > 2", "∀x:int. x > 2"), simplified("3 > 2", "∀x:int. x > 2"), simplified("true", "∀x:int. x > 2")); } @@ -34,7 +34,7 @@ void simplifyOnceAppliesSubstitutionBeforeFolding() { void simplifyOnceDoesNotFoldAfterSubstitutionInSameStep() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x == 3"); - assertSimplificationSteps(implication, VCSimplification::simplifyOnce, + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, simplified("1 + 2 == 3", "∀x:int. x == 3"), simplified("3 == 3", "∀x:int. x == 3"), simplified("true", "∀x:int. x == 3")); } @@ -43,7 +43,7 @@ void simplifyOnceDoesNotFoldAfterSubstitutionInSameStep() { void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { VCImplication implication = vc("1 + 2 > 2"); - assertSimplificationSteps(implication, VCSimplification::simplifyOnce, simplified("3 > 2", "1 + 2 > 2"), + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, simplified("3 > 2", "1 + 2 > 2"), simplified("true", "1 + 2 > 2")); } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java index f757273e1..d540cc9e0 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java @@ -21,7 +21,7 @@ void substitutesBinderEqualityIntoWholeChain() { VCImplication result = VCSubstitution.apply(implication); assertSimplifiedVC(result, simplified("3 > 0", "∀x:int. x > 0")); - assertSimplificationSteps(result, VCSimplification::simplifyOnce, simplified("true", "∀x:int. x > 0")); + assertSimplificationSteps(VCSimplification::simplifyOnce, result, simplified("true", "∀x:int. x > 0")); } @Test @@ -31,7 +31,7 @@ void substitutesReverseBinderEquality() { VCImplication result = VCSubstitution.apply(implication); assertSimplifiedVC(result, simplified("3 > 0", "∀x:int. x > 0")); - assertSimplificationSteps(result, VCSimplification::simplifyOnce, simplified("true", "∀x:int. x > 0")); + assertSimplificationSteps(VCSimplification::simplifyOnce, result, simplified("true", "∀x:int. x > 0")); } @Test @@ -59,7 +59,7 @@ void substitutesEveryOccurrenceInPredicate() { VCImplication result = VCSubstitution.apply(implication); assertSimplifiedVC(result, simplified("2 + 2 > 0", "∀x:int. x + x > 0")); - assertSimplificationSteps(result, VCSimplification::simplifyOnce, simplified("4 > 0", "∀x:int. x + x > 0"), + assertSimplificationSteps(VCSimplification::simplifyOnce, result, simplified("4 > 0", "∀x:int. x + x > 0"), simplified("true", "∀x:int. x + x > 0")); } @@ -114,7 +114,7 @@ void substitutesOuterKnownValueIntoNestedBinderRefinements() { assertSimplifiedVC(result, simplified("y == 3 + 1", "∀x:int. y == x + 1"), simplified("y > 3", "∀x:int. y > x")); - assertSimplificationSteps(result, VCSimplification::simplifyOnce, simplified("3 + 1 > 3", "∀y:int. y > x"), + assertSimplificationSteps(VCSimplification::simplifyOnce, result, simplified("3 + 1 > 3", "∀y:int. y > x"), simplified("4 > 3", "∀y:int. y > x"), simplified("true", "∀y:int. y > x")); } diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java index 285284dbd..7950c8662 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -94,8 +94,8 @@ public static void assertVC(VCImplication implication, String... expected) { assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); } - public static VCImplication assertSimplificationSteps(VCImplication implication, - UnaryOperator simplifier, ExpectedSimplifiedVCImplication... expectedSteps) { + public static VCImplication assertSimplificationSteps(UnaryOperator simplifier, + VCImplication implication, ExpectedSimplifiedVCImplication... expectedSteps) { VCImplication current = implication; for (int i = 0; i < expectedSteps.length; i++) { current = simplifier.apply(current); @@ -104,13 +104,6 @@ public static VCImplication assertSimplificationSteps(VCImplication implication, return current; } - public static VCImplication assertSimplificationSteps(VCImplication implication, - UnaryOperator simplifier, String origin, String... simplifiedSteps) { - ExpectedSimplifiedVCImplication[] expectedSteps = java.util.Arrays.stream(simplifiedSteps) - .map(step -> simplified(step, origin)).toArray(ExpectedSimplifiedVCImplication[]::new); - return assertSimplificationSteps(implication, simplifier, expectedSteps); - } - public static SimplifiedVCImplication simplifiedImplication(VCImplication implication, int index) { return assertInstanceOf(SimplifiedVCImplication.class, implication, "Expected implication " + index + " to be a SimplifiedVCImplication"); From ee3919cc71850b9c24f71822866e593be6b8ac30 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 11 Jun 2026 16:13:29 +0100 Subject: [PATCH 34/41] Refactor Tests --- .../rj_language/opt/VCFoldingTest.java | 58 +++++++-------- .../rj_language/opt/VCSimplificationTest.java | 42 ++++------- .../rj_language/opt/VCSubstitutionTest.java | 72 ++++--------------- .../java/liquidjava/utils/VCTestUtils.java | 63 ++++++++-------- 4 files changed, 81 insertions(+), 154 deletions(-) diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java index cfed03994..340578dc0 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java @@ -1,10 +1,6 @@ package liquidjava.rj_language.opt; -import static liquidjava.utils.VCTestUtils.assertSimplifiedVC; import static liquidjava.utils.VCTestUtils.assertSimplificationSteps; -import static liquidjava.utils.VCTestUtils.assertVC; -import static liquidjava.utils.VCTestUtils.parse; -import static liquidjava.utils.VCTestUtils.simplified; import static liquidjava.utils.VCTestUtils.vc; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; @@ -28,17 +24,19 @@ void applyReturnsNullForNullImplication() { @Test void foldsIntegerArithmeticAndComparisons() { - assertSimplificationSteps(VCFolding::apply, vc("1 + 2 == 3"), simplified("3 == 3", "1 + 2 == 3"), - simplified("true", "1 + 2 == 3")); + VCImplication implication = vc("1 + 2 == 3"); + + assertSimplificationSteps(VCFolding::apply, implication, "3 == 3", "true"); assertFolded("4 > 7", "false"); } @Test void foldsRealAndMixedNumericExpressions() { - assertSimplificationSteps(VCFolding::apply, vc("1.5 + 2.0 == 3.5"), - simplified("3.5 == 3.5", "1.5 + 2.0 == 3.5"), simplified("true", "1.5 + 2.0 == 3.5")); - assertSimplificationSteps(VCFolding::apply, vc("2 + 0.5 > 2"), simplified("2.5 > 2", "2 + 0.5 > 2"), - simplified("true", "2 + 0.5 > 2")); + VCImplication realArithmetic = vc("1.5 + 2.0 == 3.5"); + VCImplication mixedArithmetic = vc("2 + 0.5 > 2"); + + assertSimplificationSteps(VCFolding::apply, realArithmetic, "3.5 == 3.5", "true"); + assertSimplificationSteps(VCFolding::apply, mixedArithmetic, "2.5 > 2", "true"); } @Test @@ -79,8 +77,9 @@ void foldsPartialComparisonsWithoutDroppingSymbolicTerms() { @Test void foldsUnaryExpressions() { assertFolded("!true", "false"); - assertSimplificationSteps(VCFolding::apply, vc("-3 < 0"), simplified("-3 < 0", "-3 < 0"), - simplified("true", "-3 < 0")); + VCImplication implication = vc("-3 < 0"); + + assertSimplificationSteps(VCFolding::apply, implication, "-3 < 0", "true"); } @Test @@ -92,8 +91,9 @@ void foldsIteExpressions() { @Test void foldsIteBranchesBeforeComparingThem() { - assertSimplificationSteps(VCFolding::apply, vc("cond ? 1 + 2 : 3"), - simplified("cond ? 3 : 3", "cond ? 1 + 2 : 3"), simplified("3", "cond ? 1 + 2 : 3")); + VCImplication implication = vc("cond ? 1 + 2 : 3"); + + assertSimplificationSteps(VCFolding::apply, implication, "cond ? 3 : 3", "3"); } @Test @@ -117,8 +117,7 @@ void foldsResolvedEnumLiterals() { VCImplication implication = new VCImplication( new Predicate(new BinaryExpression(limit, "==", new LiteralInt(3)))); - assertSimplificationSteps(VCFolding::apply, implication, simplified("3 == 3", "Config.LIMIT == 3"), - simplified("true", "Config.LIMIT == 3")); + assertSimplificationSteps(VCFolding::apply, implication, "3 == 3", "true"); } @Test @@ -129,23 +128,20 @@ void foldsResolvedEnumLiteralsInsideLargerExpression() { VCImplication implication = new VCImplication( new Predicate(new BinaryExpression(arithmetic, "==", new LiteralInt(5)))); - assertSimplificationSteps(VCFolding::apply, implication, simplified("3 + 2 == 5", "Config.LIMIT + 2 == 5"), - simplified("5 == 5", "Config.LIMIT + 2 == 5"), simplified("true", "Config.LIMIT + 2 == 5")); + assertSimplificationSteps(VCFolding::apply, implication, "3 + 2 == 5", "5 == 5", "true"); } @Test void preservesOriginFromExistingSimplifiedImplication() { VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == 1", "x + 1 + 2 > 0")); - assertSimplificationSteps(VCFolding::apply, substituted, simplified("2 + 2 > 0", "∀x:int. x + 1 + 2 > 0"), - simplified("4 > 0", "∀x:int. x + 1 + 2 > 0"), simplified("true", "∀x:int. x + 1 + 2 > 0")); + assertSimplificationSteps(VCFolding::apply, substituted, "2 + 2 > 0", "4 > 0", "true"); } @Test void recordsOriginWhenOnlyGroupIsUnwrapped() { - VCImplication implication = new VCImplication(new Predicate(new GroupExpression(parse("x > 0")))); - - VCImplication result = VCFolding.apply(implication); + VCImplication implication = vc("(x > 0)"); + VCImplication result = assertSimplificationSteps(VCFolding::apply, implication, "x > 0"); SimplifiedVCImplication simplified = assertInstanceOf(SimplifiedVCImplication.class, result); assertEquals("x > 0", simplified.getRefinement().toString()); @@ -156,30 +152,26 @@ void recordsOriginWhenOnlyGroupIsUnwrapped() { void recordsOriginWhenFoldingLaterImplication() { VCImplication implication = vc("x > 0", "1 + 2 > 0"); - VCImplication result = VCFolding.apply(implication); + VCImplication result = assertSimplificationSteps(VCFolding::apply, implication, "x > 0 -> 3 > 0"); - assertEquals("x > 0", result.getRefinement().toString()); SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); - assertEquals("3 > 0", simplifiedNext.getRefinement().toString()); assertEquals("1 + 2 > 0", simplifiedNext.getOrigin().getRefinement().toString()); - result = VCFolding.apply(result); + result = assertSimplificationSteps(VCFolding::apply, result, "x > 0 -> true"); - assertEquals("x > 0", result.getRefinement().toString()); simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); - assertEquals("true", simplifiedNext.getRefinement().toString()); assertEquals("1 + 2 > 0", simplifiedNext.getOrigin().getRefinement().toString()); } private static void assertFolded(String original, String folded) { - VCImplication result = VCFolding.apply(vc(original)); + VCImplication implication = vc(original); - assertSimplifiedVC(result, simplified(folded, original)); + assertSimplificationSteps(VCFolding::apply, implication, folded); } private static void assertUnchanged(String original) { - VCImplication result = VCFolding.apply(vc(original)); + VCImplication implication = vc(original); - assertVC(result, original); + assertSimplificationSteps(VCFolding::apply, implication, original); } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java index 7231aad11..12f5bbd71 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java @@ -1,9 +1,6 @@ package liquidjava.rj_language.opt; -import static liquidjava.utils.VCTestUtils.assertSimplifiedVC; import static liquidjava.utils.VCTestUtils.assertSimplificationSteps; -import static liquidjava.utils.VCTestUtils.assertVC; -import static liquidjava.utils.VCTestUtils.simplified; import static liquidjava.utils.VCTestUtils.vc; import static org.junit.jupiter.api.Assertions.assertNull; @@ -26,78 +23,67 @@ void simplifyOnceReturnsNullForNullImplication() { void simplifyOnceAppliesSubstitutionBeforeFolding() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x > 2"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, simplified("1 + 2 > 2", "∀x:int. x > 2"), - simplified("3 > 2", "∀x:int. x > 2"), simplified("true", "∀x:int. x > 2")); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "1 + 2 > 2", "3 > 2", "true"); } @Test void simplifyOnceDoesNotFoldAfterSubstitutionInSameStep() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x == 3"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, - simplified("1 + 2 == 3", "∀x:int. x == 3"), simplified("3 == 3", "∀x:int. x == 3"), - simplified("true", "∀x:int. x == 3")); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "1 + 2 == 3", "3 == 3", "true"); } @Test void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { VCImplication implication = vc("1 + 2 > 2"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, simplified("3 > 2", "1 + 2 > 2"), - simplified("true", "1 + 2 > 2")); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "3 > 2", "true"); } @Test void simplifyKeepsApplyingStepsUntilFixedPoint() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x + 1 > 3"); - VCImplication result = VCSimplification.simplifyToFixedPoint(implication); - - assertSimplifiedVC(result, simplified("true", "∀x:int. x + 1 > 3")); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "1 + 2 + 1 > 3", "3 + 1 > 3", "4 > 3", + "true"); } @Test void simplifyAppliesMultipleSubstitutionsBeforeReachingFixedPoint() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); - VCImplication result = VCSimplification.simplifyToFixedPoint(implication); - - assertSimplifiedVC(result, simplified("true", "∀y:int. y > x")); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "∀y:int. y == 3 + 1 -> y > 3", + "3 + 1 > 3", "4 > 3", "true"); } @Test void simplifyAppliesLongSubstitutionChainBeforeReachingFixedPoint() { VCImplication implication = vc("∀x:int. x == 1", "∀y:int. y == x + 1", "∀z:int. z == y + 1", "z == 3"); - VCImplication result = VCSimplification.simplifyToFixedPoint(implication); - - assertSimplifiedVC(result, simplified("true", "∀z:int. z == 3")); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + "∀y:int. y == 1 + 1 -> ∀z:int. z == y + 1 -> z == 3", "∀z:int. z == 1 + 1 + 1 -> z == 3", + "1 + 1 + 1 == 3", "2 + 1 == 3", "3 == 3", "true"); } @Test void simplifyCombinesSubstitutionAndNestedFoldingAcrossFixedPoint() { VCImplication implication = vc("∀x:int. x == 1", "∀y:int. y == x + 2", "y - 1 == 2"); - VCImplication result = VCSimplification.simplifyToFixedPoint(implication); - - assertSimplifiedVC(result, simplified("true", "∀y:int. y - 1 == 2")); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "∀y:int. y == 1 + 2 -> y - 1 == 2", + "1 + 2 - 1 == 2", "3 - 1 == 2", "2 == 2", "true"); } @Test void simplifyStopsAfterSubstitutionWhenOnlyNegativeLiteralShapeChanges() { VCImplication implication = vc("∀x:int. x == a + 0", "x >= -3"); - VCImplication result = VCSimplification.simplifyToFixedPoint(implication); - - assertSimplifiedVC(result, simplified("a + 0 >= -3", "∀x:int. x >= -3")); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "a + 0 >= -3"); } @Test void simplifyLeavesUnchangedVcAsPlainPredicates() { VCImplication implication = vc("x > 0", "y > x"); - VCImplication result = VCSimplification.simplifyToFixedPoint(implication); - - assertVC(result, "x > 0", "y > x"); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "x > 0 -> y > x"); } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java index d540cc9e0..3c77a6dcb 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java @@ -1,7 +1,6 @@ package liquidjava.rj_language.opt; import static liquidjava.utils.VCTestUtils.*; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import liquidjava.processor.VCImplication; @@ -18,140 +17,97 @@ void applyReturnsNullForNullImplication() { void substitutesBinderEqualityIntoWholeChain() { VCImplication implication = vc("∀x:int. x == 3", "x > 0"); - VCImplication result = VCSubstitution.apply(implication); - - assertSimplifiedVC(result, simplified("3 > 0", "∀x:int. x > 0")); - assertSimplificationSteps(VCSimplification::simplifyOnce, result, simplified("true", "∀x:int. x > 0")); + assertSimplificationSteps(VCSubstitution::apply, implication, "3 > 0"); } @Test void substitutesReverseBinderEquality() { VCImplication implication = vc("∀x:int. 3 == x", "x > 0"); - VCImplication result = VCSubstitution.apply(implication); - - assertSimplifiedVC(result, simplified("3 > 0", "∀x:int. x > 0")); - assertSimplificationSteps(VCSimplification::simplifyOnce, result, simplified("true", "∀x:int. x > 0")); + assertSimplificationSteps(VCSubstitution::apply, implication, "3 > 0"); } @Test void substitutesCompoundKnownValue() { VCImplication implication = vc("∀x:int. x == y + 1", "x > y"); - VCImplication result = VCSubstitution.apply(implication); - - assertSimplifiedVC(result, simplified("y + 1 > y", "∀x:int. x > y")); + assertSimplificationSteps(VCSubstitution::apply, implication, "y + 1 > y"); } @Test void substitutesOnlyWholeVariableReferences() { VCImplication implication = vc("∀x:int. x == 3", "xx > x"); - VCImplication result = VCSubstitution.apply(implication); - - assertSimplifiedVC(result, simplified("xx > 3", "∀x:int. xx > x")); + assertSimplificationSteps(VCSubstitution::apply, implication, "xx > 3"); } @Test void substitutesEveryOccurrenceInPredicate() { VCImplication implication = vc("∀x:int. x == 2", "x + x > 0"); - VCImplication result = VCSubstitution.apply(implication); - - assertSimplifiedVC(result, simplified("2 + 2 > 0", "∀x:int. x + x > 0")); - assertSimplificationSteps(VCSimplification::simplifyOnce, result, simplified("4 > 0", "∀x:int. x + x > 0"), - simplified("true", "∀x:int. x + x > 0")); + assertSimplificationSteps(VCSubstitution::apply, implication, "2 + 2 > 0"); } @Test void preservesRemainingBinderAfterSubstitution() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y > x", "y > 0"); - VCImplication result = VCSubstitution.apply(implication); - - assertEquals("y", result.getName()); - assertEquals("y > 3", result.getRefinement().toString()); - assertVC(result.getNext(), "y > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, "∀y:int. y > 3 -> y > 0"); } @Test void removesSourceNodeWhenItIsLastInChain() { VCImplication implication = vc("x > 0", "∀y:int. y == 1"); - VCImplication result = VCSubstitution.apply(implication); - - assertVC(result, "x > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, "x > 0"); } @Test void usesFirstSubstitutionFoundInChain() { VCImplication implication = vc("∀x:int. x > 0", "∀y:int. y == 4", "x + y > 0"); - VCImplication result = VCSubstitution.apply(implication); - - assertVC(result, "x > 0", "x + 4 > 0"); - assertEquals(VCImplication.class, result.getClass()); - assertSimplifiedVC(result.getNext(), simplified("x + 4 > 0", "∀y:int. x + y > 0")); + assertSimplificationSteps(VCSubstitution::apply, implication, "∀x:int. x > 0 -> x + 4 > 0"); } @Test void substitutesInnerKnownValueAcrossNestedImplications() { VCImplication implication = vc("∀x:int. true", "∀y:int. y == 1", "∀z:int. z > y", "y + z > 0"); - VCImplication result = VCSubstitution.apply(implication); - - assertVC(result, "true", "z > 1", "1 + z > 0"); - assertEquals(VCImplication.class, result.getClass()); - assertSimplifiedVC(result.getNext(), simplified("z > 1", "∀y:int. z > y"), - simplified("1 + z > 0", "∀y:int. y + z > 0")); + assertSimplificationSteps(VCSubstitution::apply, implication, "∀x:int. true -> ∀z:int. z > 1 -> 1 + z > 0"); } @Test void substitutesOuterKnownValueIntoNestedBinderRefinements() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); - VCImplication result = VCSubstitution.apply(implication); - - assertSimplifiedVC(result, simplified("y == 3 + 1", "∀x:int. y == x + 1"), - simplified("y > 3", "∀x:int. y > x")); - assertSimplificationSteps(VCSimplification::simplifyOnce, result, simplified("3 + 1 > 3", "∀y:int. y > x"), - simplified("4 > 3", "∀y:int. y > x"), simplified("true", "∀y:int. y > x")); + assertSimplificationSteps(VCSubstitution::apply, implication, "∀y:int. y == 3 + 1 -> y > 3", "3 + 1 > 3"); } @Test void ignoresRecursiveBinderEquality() { VCImplication implication = vc("∀x:int. x == x + 1", "x > 0"); - VCImplication result = VCSubstitution.apply(implication); - - assertVC(result, "x == x + 1", "x > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, "∀x:int. x == x + 1 -> x > 0"); } @Test void ignoresNonEqualityBinderRefinement() { VCImplication implication = vc("∀x:int. x > 3", "x > 0"); - VCImplication result = VCSubstitution.apply(implication); - - assertVC(result, "x > 3", "x > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, "∀x:int. x > 3 -> x > 0"); } @Test void ignoresDerivedBinderEquality() { VCImplication implication = vc("∀x:int. x + 1 == 3", "x > 0"); - VCImplication result = VCSubstitution.apply(implication); - - assertVC(result, "x + 1 == 3", "x > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, "∀x:int. x + 1 == 3 -> x > 0"); } @Test void ignoresEqualityWithoutBinder() { VCImplication implication = vc("x == 3", "x > 0"); - VCImplication result = VCSubstitution.apply(implication); - - assertVC(result, "x == 3", "x > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, "x == 3 -> x > 0"); } - } diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java index 7950c8662..c9a0b9d0f 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -9,7 +9,6 @@ import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.parsing.RefinementsParser; import spoon.Launcher; import spoon.reflect.reference.CtTypeReference; @@ -18,10 +17,6 @@ public class VCTestUtils { private static final CtTypeReference INT = new Launcher().getFactory().Type().INTEGER_PRIMITIVE; - public static Expression parse(String refinement) { - return RefinementsParser.createAST(refinement, ""); - } - public static VCImplication vc(String... implications) { VCImplication first = null; VCImplication last = null; @@ -38,13 +33,14 @@ public static VCImplication vc(String... implications) { private static VCImplication parseImplication(String implication) { if (!implication.startsWith("∀")) - return new VCImplication(new Predicate(parse(implication))); + return new VCImplication(new Predicate(RefinementsParser.createAST(implication, ""))); int refinementStart = implication.indexOf('.'); String binder = implication.substring(1, refinementStart).trim(); String refinement = implication.substring(refinementStart + 1).trim(); String[] parts = binder.split(":"); - return new VCImplication(parts[0].trim(), type(parts[1].trim()), new Predicate(parse(refinement))); + return new VCImplication(parts[0].trim(), type(parts[1].trim()), + new Predicate(RefinementsParser.createAST(refinement, ""))); } private static CtTypeReference type(String name) { @@ -53,12 +49,6 @@ private static CtTypeReference type(String name) { throw new IllegalArgumentException("Unsupported test type: " + name); } - public static void assertSimplifiedVC(VCImplication implication, String... expected) { - ExpectedSimplifiedVCImplication[] predicates = java.util.Arrays.stream(expected) - .map(VCTestUtils::parseExpectedSimplifiedVCImplication).toArray(ExpectedSimplifiedVCImplication[]::new); - assertSimplifiedVC(implication, predicates); - } - public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplifiedVCImplication... expected) { VCImplication current = implication; for (int i = 0; i < expected.length; i++) { @@ -76,30 +66,22 @@ public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplif assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); } - public static ExpectedSimplifiedVCImplication simplified(String simplified) { - return new ExpectedSimplifiedVCImplication(simplified, null); - } - - public static ExpectedSimplifiedVCImplication simplified(String simplified, String origin) { - return new ExpectedSimplifiedVCImplication(simplified, origin); - } - - public static void assertVC(VCImplication implication, String... expected) { + public static VCImplication assertSimplificationSteps(UnaryOperator simplifier, + VCImplication implication, ExpectedSimplifiedVCImplication... expectedSteps) { VCImplication current = implication; - for (int i = 0; i < expected.length; i++) { - assertEquals(expected[i], current.getRefinement().getExpression().toString(), - "Unexpected expression at implication " + i); - current = current.getNext(); + for (ExpectedSimplifiedVCImplication expectedStep : expectedSteps) { + current = simplifier.apply(current); + assertSimplifiedVC(current, expectedStep); } - assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); + return current; } public static VCImplication assertSimplificationSteps(UnaryOperator simplifier, - VCImplication implication, ExpectedSimplifiedVCImplication... expectedSteps) { + VCImplication implication, String... expectedSteps) { VCImplication current = implication; for (int i = 0; i < expectedSteps.length; i++) { current = simplifier.apply(current); - assertSimplifiedVC(current, expectedSteps[i]); + assertExpectedVCChain(current, expectedSteps[i], i); } return current; } @@ -109,18 +91,29 @@ public static SimplifiedVCImplication simplifiedImplication(VCImplication implic "Expected implication " + index + " to be a SimplifiedVCImplication"); } + private static void assertExpectedVCChain(VCImplication implication, String expectedStep, int step) { + VCImplication current = implication; + String[] expected = expectedStep.trim().split("\\s*->\\s*"); + for (int i = 0; i < expected.length; i++) { + assertEquals(expected[i], formatImplication(current), + "Unexpected expression at simplification step " + step + ", implication " + i); + current = current.getNext(); + } + assertNull(current, + "Expected simplification step " + step + " to end after " + expected.length + " implications"); + } + private static String formatOrigin(VCImplication origin) { if (!origin.hasBinder()) return origin.getRefinement().toString(); return "∀" + origin.getName() + ":" + origin.getType().getQualifiedName() + ". " + origin.getRefinement(); } - private static ExpectedSimplifiedVCImplication parseExpectedSimplifiedVCImplication(String expected) { - String expression = expected.trim(); - String[] parts = expression.split("<-", 2); - String simplified = parts[0].trim(); - String origin = parts.length > 1 ? parts[1].trim() : null; - return new ExpectedSimplifiedVCImplication(simplified, origin); + private static String formatImplication(VCImplication implication) { + if (!implication.hasBinder()) + return implication.getRefinement().toString(); + return "∀" + implication.getName() + ":" + implication.getType().getQualifiedName() + ". " + + implication.getRefinement(); } public record ExpectedSimplifiedVCImplication(String simplified, String origin) { From cb0cb2ef393555ca4e4d4141a96cb27e19bb299e Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 11 Jun 2026 16:35:23 +0100 Subject: [PATCH 35/41] Update VCImplicationGenerator --- .../rj_language/opt/VCImplicationGenerator.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCImplicationGenerator.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCImplicationGenerator.java index 4f3a79422..0ebe0e420 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCImplicationGenerator.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCImplicationGenerator.java @@ -9,9 +9,11 @@ public class VCImplicationGenerator extends Generator { - static final String[] BINDERS = { "x", "y", "z" }; - static final String[] FREE_VARS = { "a", "b", "c" }; + public static final String[] BINDERS = { "x", "y", "z", "w" }; + public static final String[] FREE_VARS = { "a", "b", "c", "d" }; private static final String[] COMPARISON_OPS = { "==", "!=", ">=", ">", "<=", "<" }; + private static final String[] BOOLEAN_OPS = { "&&", "||", "-->", "==", "!=" }; + private static final String[] ARITHMETIC_OPS = { "+", "-", "*" }; public VCImplicationGenerator() { super(VCImplication.class); @@ -69,8 +71,7 @@ private static String foldableComparison(SourceOfRandomness random) { private static String foldableBoolean(SourceOfRandomness random) { String left = random.nextBoolean() ? "true" : "false"; String right = random.nextBoolean() ? "true" : "false"; - String[] ops = { "&&", "||", "-->", "==", "!=" }; - return left + " " + ops[random.nextInt(0, ops.length - 1)] + " " + right; + return left + " " + BOOLEAN_OPS[random.nextInt(0, BOOLEAN_OPS.length - 1)] + " " + right; } private static String foldableIte(SourceOfRandomness random) { @@ -83,8 +84,7 @@ private static String foldableIte(SourceOfRandomness random) { private static String literalArithmetic(SourceOfRandomness random) { String left = intLiteral(random); String right = Integer.toString(random.nextInt(1, 7)); - String[] ops = { "+", "-", "*" }; - return left + " " + ops[random.nextInt(0, ops.length - 1)] + " " + right; + return left + " " + ARITHMETIC_OPS[random.nextInt(0, ARITHMETIC_OPS.length - 1)] + " " + right; } private static String adjacentConstants(SourceOfRandomness random) { From 35895a378ecba4969bba082e808bb3c77838bf23 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 11 Jun 2026 22:15:06 +0100 Subject: [PATCH 36/41] Minor Changes --- .../java/liquidjava/processor/SimplifiedVCImplication.java | 5 ----- .../rj_language/opt/VCSimplificationPropertyBasedTest.java | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java b/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java index 5e3240191..7c741cdea 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java @@ -21,11 +21,6 @@ public VCImplication getOrigin() { return origin; } - @Override - public Predicate getOriginRefinement() { - return origin.getRefinement().clone(); - } - @Override public VCImplication copyWithRefinement(Predicate refinement) { return new SimplifiedVCImplication(this, refinement, origin); diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationPropertyBasedTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationPropertyBasedTest.java index ea12f536f..c20fc1061 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationPropertyBasedTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationPropertyBasedTest.java @@ -21,7 +21,7 @@ @RunWith(JUnitQuickcheck.class) public class VCSimplificationPropertyBasedTest { - private static final int TRIALS = 100; // number of random VCs to test + private static final int TRIALS = 50; // number of random VCs to test private static final int MAX_STEPS = 20; // to prevent infinite loops in case of non-termination @Property(trials = TRIALS) From e4258aa6b5501175c6e5a74e0f04ab74286bb7de Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Fri, 12 Jun 2026 13:43:48 +0100 Subject: [PATCH 37/41] Minor Changes --- .../src/main/java/liquidjava/processor/VCImplication.java | 5 +++++ .../java/liquidjava/rj_language/opt/VCSimplification.java | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java b/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java index 88f6811b8..d3febea64 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java @@ -3,6 +3,7 @@ import java.util.Objects; import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.opt.VCSimplification; import liquidjava.utils.Utils; import spoon.reflect.reference.CtTypeReference; @@ -85,6 +86,10 @@ public String toString() { return String.format("%-20s %s", "", refinement.toString()); } + public VCImplication simplify() { + return VCSimplification.simplifyToFixedPoint(this); + } + public Predicate toConjunctions() { Predicate c = new Predicate(); if (name == null && type == null && next == null) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplification.java index a9837f5ce..f87b1081f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplification.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplification.java @@ -8,7 +8,7 @@ public class VCSimplification { /** - * Applies all available simplification steps to a VC chain + * Applies all available simplification steps to a VC chain until a fixed point is reached */ public static VCImplication simplifyToFixedPoint(VCImplication implication) { if (implication == null) @@ -18,8 +18,8 @@ public static VCImplication simplifyToFixedPoint(VCImplication implication) { VCImplication current = implication.clone(); while (true) { VCImplication simplified = simplifyOnce(current); - if (current.equals(simplified)) // fixed point reached - return simplified; + if (current.equals(simplified)) + return simplified; // fixed point reached current = simplified; } } From 804c7a6e3b8a54986d87ae5396bf229235808cad Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Fri, 12 Jun 2026 13:51:44 +0100 Subject: [PATCH 38/41] Update Tests --- .../rj_language/opt/VCFoldingTest.java | 55 ++++++++----------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java index 340578dc0..45e94f090 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java @@ -27,7 +27,7 @@ void foldsIntegerArithmeticAndComparisons() { VCImplication implication = vc("1 + 2 == 3"); assertSimplificationSteps(VCFolding::apply, implication, "3 == 3", "true"); - assertFolded("4 > 7", "false"); + assertSimplificationSteps(VCFolding::apply, vc("4 > 7"), "false"); } @Test @@ -41,42 +41,42 @@ void foldsRealAndMixedNumericExpressions() { @Test void leavesDivisionAndModuloByZeroUnchanged() { - assertUnchanged("4 / 0 == 0"); - assertUnchanged("4 % 0 == 0"); + assertSimplificationSteps(VCFolding::apply, vc("4 / 0 == 0"), "4 / 0 == 0"); + assertSimplificationSteps(VCFolding::apply, vc("4 % 0 == 0"), "4 % 0 == 0"); } @Test void leavesRealDivisionAndModuloByZeroUnchanged() { - assertUnchanged("4.0 / 0.0 == 0.0"); - assertUnchanged("4.0 % 0.0 == 0.0"); + assertSimplificationSteps(VCFolding::apply, vc("4.0 / 0.0 == 0.0"), "4.0 / 0.0 == 0.0"); + assertSimplificationSteps(VCFolding::apply, vc("4.0 % 0.0 == 0.0"), "4.0 % 0.0 == 0.0"); } @Test void foldsBooleanBinaryExpressions() { - assertFolded("true && false", "false"); - assertFolded("false --> true", "true"); - assertFolded("true != false", "true"); + assertSimplificationSteps(VCFolding::apply, vc("true && false"), "false"); + assertSimplificationSteps(VCFolding::apply, vc("false --> true"), "true"); + assertSimplificationSteps(VCFolding::apply, vc("true != false"), "true"); } @Test void foldsBooleanSubexpressionsInsideLargerExpression() { - assertFolded("true && false || ok", "false || ok"); + assertSimplificationSteps(VCFolding::apply, vc("true && false || ok"), "false || ok"); } @Test void foldsNestedConstantsInsideLargerExpression() { - assertFolded("x > 1 + 2", "x > 3"); - assertFolded("x + 1 + 2 > 4", "x + 3 > 4"); + assertSimplificationSteps(VCFolding::apply, vc("x > 1 + 2"), "x > 3"); + assertSimplificationSteps(VCFolding::apply, vc("x + 1 + 2 > 4"), "x + 3 > 4"); } @Test void foldsPartialComparisonsWithoutDroppingSymbolicTerms() { - assertFolded("1 + 2 < x + 4", "3 < x + 4"); + assertSimplificationSteps(VCFolding::apply, vc("1 + 2 < x + 4"), "3 < x + 4"); } @Test void foldsUnaryExpressions() { - assertFolded("!true", "false"); + assertSimplificationSteps(VCFolding::apply, vc("!true"), "false"); VCImplication implication = vc("-3 < 0"); assertSimplificationSteps(VCFolding::apply, implication, "-3 < 0", "true"); @@ -84,9 +84,9 @@ void foldsUnaryExpressions() { @Test void foldsIteExpressions() { - assertFolded("true ? a : b", "a"); - assertFolded("false ? a : b", "b"); - assertFolded("cond ? b : b", "b"); + assertSimplificationSteps(VCFolding::apply, vc("true ? a : b"), "a"); + assertSimplificationSteps(VCFolding::apply, vc("false ? a : b"), "b"); + assertSimplificationSteps(VCFolding::apply, vc("cond ? b : b"), "b"); } @Test @@ -98,16 +98,16 @@ void foldsIteBranchesBeforeComparingThem() { @Test void foldsAdjacentIntegerConstants() { - assertFolded("x + 1 - 2", "x - 1"); - assertFolded("x - 1 + 2", "x + 1"); - assertFolded("x + 1 + 2", "x + 3"); - assertFolded("x + 1 - 1", "x"); + assertSimplificationSteps(VCFolding::apply, vc("x + 1 - 2"), "x - 1"); + assertSimplificationSteps(VCFolding::apply, vc("x - 1 + 2"), "x + 1"); + assertSimplificationSteps(VCFolding::apply, vc("x + 1 + 2"), "x + 3"); + assertSimplificationSteps(VCFolding::apply, vc("x + 1 - 1"), "x"); } @Test void foldsEnumEqualityAndInequality() { - assertFolded("Mode.Photo == Mode.Photo", "true"); - assertFolded("Mode.Photo != Mode.Video", "true"); + assertSimplificationSteps(VCFolding::apply, vc("Mode.Photo == Mode.Photo"), "true"); + assertSimplificationSteps(VCFolding::apply, vc("Mode.Photo != Mode.Video"), "true"); } @Test @@ -163,15 +163,4 @@ void recordsOriginWhenFoldingLaterImplication() { assertEquals("1 + 2 > 0", simplifiedNext.getOrigin().getRefinement().toString()); } - private static void assertFolded(String original, String folded) { - VCImplication implication = vc(original); - - assertSimplificationSteps(VCFolding::apply, implication, folded); - } - - private static void assertUnchanged(String original) { - VCImplication implication = vc(original); - - assertSimplificationSteps(VCFolding::apply, implication, original); - } } From 1043d1ac5c01385116f5a0185b228f88a4f7ae5b Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Fri, 12 Jun 2026 14:11:07 +0100 Subject: [PATCH 39/41] Refactor Tests --- .../rj_language/opt/VCFoldingTest.java | 85 +++++++++++-------- .../rj_language/opt/VCSimplificationTest.java | 45 ++++++---- .../rj_language/opt/VCSubstitutionTest.java | 37 +++++--- .../java/liquidjava/utils/VCTestUtils.java | 50 +++-------- 4 files changed, 115 insertions(+), 102 deletions(-) diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java index 45e94f090..53f51404e 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java @@ -1,7 +1,6 @@ package liquidjava.rj_language.opt; -import static liquidjava.utils.VCTestUtils.assertSimplificationSteps; -import static liquidjava.utils.VCTestUtils.vc; +import static liquidjava.utils.VCTestUtils.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; @@ -26,8 +25,9 @@ void applyReturnsNullForNullImplication() { void foldsIntegerArithmeticAndComparisons() { VCImplication implication = vc("1 + 2 == 3"); - assertSimplificationSteps(VCFolding::apply, implication, "3 == 3", "true"); - assertSimplificationSteps(VCFolding::apply, vc("4 > 7"), "false"); + assertSimplificationSteps(VCFolding::apply, implication, chain(expect("3 == 3", "1 + 2 == 3")), + chain(expect("true", "1 + 2 == 3"))); + assertSimplificationSteps(VCFolding::apply, vc("4 > 7"), chain(expect("false", "4 > 7"))); } @Test @@ -35,79 +35,88 @@ void foldsRealAndMixedNumericExpressions() { VCImplication realArithmetic = vc("1.5 + 2.0 == 3.5"); VCImplication mixedArithmetic = vc("2 + 0.5 > 2"); - assertSimplificationSteps(VCFolding::apply, realArithmetic, "3.5 == 3.5", "true"); - assertSimplificationSteps(VCFolding::apply, mixedArithmetic, "2.5 > 2", "true"); + assertSimplificationSteps(VCFolding::apply, realArithmetic, chain(expect("3.5 == 3.5", "1.5 + 2.0 == 3.5")), + chain(expect("true", "1.5 + 2.0 == 3.5"))); + assertSimplificationSteps(VCFolding::apply, mixedArithmetic, chain(expect("2.5 > 2", "2 + 0.5 > 2")), + chain(expect("true", "2 + 0.5 > 2"))); } @Test void leavesDivisionAndModuloByZeroUnchanged() { - assertSimplificationSteps(VCFolding::apply, vc("4 / 0 == 0"), "4 / 0 == 0"); - assertSimplificationSteps(VCFolding::apply, vc("4 % 0 == 0"), "4 % 0 == 0"); + assertSimplificationSteps(VCFolding::apply, vc("4 / 0 == 0"), chain(expect("4 / 0 == 0", "4 / 0 == 0"))); + assertSimplificationSteps(VCFolding::apply, vc("4 % 0 == 0"), chain(expect("4 % 0 == 0", "4 % 0 == 0"))); } @Test void leavesRealDivisionAndModuloByZeroUnchanged() { - assertSimplificationSteps(VCFolding::apply, vc("4.0 / 0.0 == 0.0"), "4.0 / 0.0 == 0.0"); - assertSimplificationSteps(VCFolding::apply, vc("4.0 % 0.0 == 0.0"), "4.0 % 0.0 == 0.0"); + assertSimplificationSteps(VCFolding::apply, vc("4.0 / 0.0 == 0.0"), + chain(expect("4.0 / 0.0 == 0.0", "4.0 / 0.0 == 0.0"))); + assertSimplificationSteps(VCFolding::apply, vc("4.0 % 0.0 == 0.0"), + chain(expect("4.0 % 0.0 == 0.0", "4.0 % 0.0 == 0.0"))); } @Test void foldsBooleanBinaryExpressions() { - assertSimplificationSteps(VCFolding::apply, vc("true && false"), "false"); - assertSimplificationSteps(VCFolding::apply, vc("false --> true"), "true"); - assertSimplificationSteps(VCFolding::apply, vc("true != false"), "true"); + assertSimplificationSteps(VCFolding::apply, vc("true && false"), chain(expect("false", "true && false"))); + assertSimplificationSteps(VCFolding::apply, vc("false --> true"), chain(expect("true", "false --> true"))); + assertSimplificationSteps(VCFolding::apply, vc("true != false"), chain(expect("true", "true != false"))); } @Test void foldsBooleanSubexpressionsInsideLargerExpression() { - assertSimplificationSteps(VCFolding::apply, vc("true && false || ok"), "false || ok"); + assertSimplificationSteps(VCFolding::apply, vc("true && false || ok"), + chain(expect("false || ok", "true && false || ok"))); } @Test void foldsNestedConstantsInsideLargerExpression() { - assertSimplificationSteps(VCFolding::apply, vc("x > 1 + 2"), "x > 3"); - assertSimplificationSteps(VCFolding::apply, vc("x + 1 + 2 > 4"), "x + 3 > 4"); + assertSimplificationSteps(VCFolding::apply, vc("x > 1 + 2"), chain(expect("x > 3", "x > 1 + 2"))); + assertSimplificationSteps(VCFolding::apply, vc("x + 1 + 2 > 4"), chain(expect("x + 3 > 4", "x + 1 + 2 > 4"))); } @Test void foldsPartialComparisonsWithoutDroppingSymbolicTerms() { - assertSimplificationSteps(VCFolding::apply, vc("1 + 2 < x + 4"), "3 < x + 4"); + assertSimplificationSteps(VCFolding::apply, vc("1 + 2 < x + 4"), chain(expect("3 < x + 4", "1 + 2 < x + 4"))); } @Test void foldsUnaryExpressions() { - assertSimplificationSteps(VCFolding::apply, vc("!true"), "false"); + assertSimplificationSteps(VCFolding::apply, vc("!true"), chain(expect("false", "!true"))); VCImplication implication = vc("-3 < 0"); - assertSimplificationSteps(VCFolding::apply, implication, "-3 < 0", "true"); + assertSimplificationSteps(VCFolding::apply, implication, chain(expect("-3 < 0", "-3 < 0")), + chain(expect("true", "-3 < 0"))); } @Test void foldsIteExpressions() { - assertSimplificationSteps(VCFolding::apply, vc("true ? a : b"), "a"); - assertSimplificationSteps(VCFolding::apply, vc("false ? a : b"), "b"); - assertSimplificationSteps(VCFolding::apply, vc("cond ? b : b"), "b"); + assertSimplificationSteps(VCFolding::apply, vc("true ? a : b"), chain(expect("a", "true ? a : b"))); + assertSimplificationSteps(VCFolding::apply, vc("false ? a : b"), chain(expect("b", "false ? a : b"))); + assertSimplificationSteps(VCFolding::apply, vc("cond ? b : b"), chain(expect("b", "cond ? b : b"))); } @Test void foldsIteBranchesBeforeComparingThem() { VCImplication implication = vc("cond ? 1 + 2 : 3"); - assertSimplificationSteps(VCFolding::apply, implication, "cond ? 3 : 3", "3"); + assertSimplificationSteps(VCFolding::apply, implication, chain(expect("cond ? 3 : 3", "cond ? 1 + 2 : 3")), + chain(expect("3", "cond ? 1 + 2 : 3"))); } @Test void foldsAdjacentIntegerConstants() { - assertSimplificationSteps(VCFolding::apply, vc("x + 1 - 2"), "x - 1"); - assertSimplificationSteps(VCFolding::apply, vc("x - 1 + 2"), "x + 1"); - assertSimplificationSteps(VCFolding::apply, vc("x + 1 + 2"), "x + 3"); - assertSimplificationSteps(VCFolding::apply, vc("x + 1 - 1"), "x"); + assertSimplificationSteps(VCFolding::apply, vc("x + 1 - 2"), chain(expect("x - 1", "x + 1 - 2"))); + assertSimplificationSteps(VCFolding::apply, vc("x - 1 + 2"), chain(expect("x + 1", "x - 1 + 2"))); + assertSimplificationSteps(VCFolding::apply, vc("x + 1 + 2"), chain(expect("x + 3", "x + 1 + 2"))); + assertSimplificationSteps(VCFolding::apply, vc("x + 1 - 1"), chain(expect("x", "x + 1 - 1"))); } @Test void foldsEnumEqualityAndInequality() { - assertSimplificationSteps(VCFolding::apply, vc("Mode.Photo == Mode.Photo"), "true"); - assertSimplificationSteps(VCFolding::apply, vc("Mode.Photo != Mode.Video"), "true"); + assertSimplificationSteps(VCFolding::apply, vc("Mode.Photo == Mode.Photo"), + chain(expect("true", "Mode.Photo == Mode.Photo"))); + assertSimplificationSteps(VCFolding::apply, vc("Mode.Photo != Mode.Video"), + chain(expect("true", "Mode.Photo != Mode.Video"))); } @Test @@ -117,7 +126,8 @@ void foldsResolvedEnumLiterals() { VCImplication implication = new VCImplication( new Predicate(new BinaryExpression(limit, "==", new LiteralInt(3)))); - assertSimplificationSteps(VCFolding::apply, implication, "3 == 3", "true"); + assertSimplificationSteps(VCFolding::apply, implication, chain(expect("3 == 3", "Config.LIMIT == 3")), + chain(expect("true", "Config.LIMIT == 3"))); } @Test @@ -128,20 +138,23 @@ void foldsResolvedEnumLiteralsInsideLargerExpression() { VCImplication implication = new VCImplication( new Predicate(new BinaryExpression(arithmetic, "==", new LiteralInt(5)))); - assertSimplificationSteps(VCFolding::apply, implication, "3 + 2 == 5", "5 == 5", "true"); + assertSimplificationSteps(VCFolding::apply, implication, chain(expect("3 + 2 == 5", "Config.LIMIT + 2 == 5")), + chain(expect("5 == 5", "Config.LIMIT + 2 == 5")), chain(expect("true", "Config.LIMIT + 2 == 5"))); } @Test void preservesOriginFromExistingSimplifiedImplication() { VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == 1", "x + 1 + 2 > 0")); - assertSimplificationSteps(VCFolding::apply, substituted, "2 + 2 > 0", "4 > 0", "true"); + assertSimplificationSteps(VCFolding::apply, substituted, chain(expect("2 + 2 > 0", "∀x:int. x + 1 + 2 > 0")), + chain(expect("4 > 0", "∀x:int. x + 1 + 2 > 0")), chain(expect("true", "∀x:int. x + 1 + 2 > 0"))); } @Test void recordsOriginWhenOnlyGroupIsUnwrapped() { VCImplication implication = vc("(x > 0)"); - VCImplication result = assertSimplificationSteps(VCFolding::apply, implication, "x > 0"); + VCImplication result = assertSimplificationSteps(VCFolding::apply, implication, + chain(expect("x > 0", "x > 0"))); SimplifiedVCImplication simplified = assertInstanceOf(SimplifiedVCImplication.class, result); assertEquals("x > 0", simplified.getRefinement().toString()); @@ -152,12 +165,14 @@ void recordsOriginWhenOnlyGroupIsUnwrapped() { void recordsOriginWhenFoldingLaterImplication() { VCImplication implication = vc("x > 0", "1 + 2 > 0"); - VCImplication result = assertSimplificationSteps(VCFolding::apply, implication, "x > 0 -> 3 > 0"); + VCImplication result = assertSimplificationSteps(VCFolding::apply, implication, + chain(expect("x > 0", "x > 0"), expect("3 > 0", "1 + 2 > 0"))); SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); assertEquals("1 + 2 > 0", simplifiedNext.getOrigin().getRefinement().toString()); - result = assertSimplificationSteps(VCFolding::apply, result, "x > 0 -> true"); + result = assertSimplificationSteps(VCFolding::apply, result, + chain(expect("x > 0", "x > 0"), expect("true", "1 + 2 > 0"))); simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); assertEquals("1 + 2 > 0", simplifiedNext.getOrigin().getRefinement().toString()); diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java index 12f5bbd71..a2ce7c821 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java @@ -1,7 +1,6 @@ package liquidjava.rj_language.opt; -import static liquidjava.utils.VCTestUtils.assertSimplificationSteps; -import static liquidjava.utils.VCTestUtils.vc; +import static liquidjava.utils.VCTestUtils.*; import static org.junit.jupiter.api.Assertions.assertNull; import liquidjava.processor.VCImplication; @@ -23,37 +22,45 @@ void simplifyOnceReturnsNullForNullImplication() { void simplifyOnceAppliesSubstitutionBeforeFolding() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x > 2"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "1 + 2 > 2", "3 > 2", "true"); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + chain(expect("1 + 2 > 2", "∀x:int. x > 2")), chain(expect("3 > 2", "∀x:int. x > 2")), + chain(expect("true", "∀x:int. x > 2"))); } @Test void simplifyOnceDoesNotFoldAfterSubstitutionInSameStep() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x == 3"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "1 + 2 == 3", "3 == 3", "true"); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + chain(expect("1 + 2 == 3", "∀x:int. x == 3")), chain(expect("3 == 3", "∀x:int. x == 3")), + chain(expect("true", "∀x:int. x == 3"))); } @Test void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { VCImplication implication = vc("1 + 2 > 2"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "3 > 2", "true"); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("3 > 2", "1 + 2 > 2")), + chain(expect("true", "1 + 2 > 2"))); } @Test void simplifyKeepsApplyingStepsUntilFixedPoint() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x + 1 > 3"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "1 + 2 + 1 > 3", "3 + 1 > 3", "4 > 3", - "true"); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + chain(expect("1 + 2 + 1 > 3", "∀x:int. x + 1 > 3")), chain(expect("3 + 1 > 3", "∀x:int. x + 1 > 3")), + chain(expect("4 > 3", "∀x:int. x + 1 > 3")), chain(expect("true", "∀x:int. x + 1 > 3"))); } @Test void simplifyAppliesMultipleSubstitutionsBeforeReachingFixedPoint() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "∀y:int. y == 3 + 1 -> y > 3", - "3 + 1 > 3", "4 > 3", "true"); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + chain(expect("y == 3 + 1", "∀x:int. y == x + 1"), expect("y > 3", "∀x:int. y > x")), + chain(expect("3 + 1 > 3", "∀y:int. y > x")), chain(expect("4 > 3", "∀y:int. y > x")), + chain(expect("true", "∀y:int. y > x"))); } @Test @@ -61,29 +68,37 @@ void simplifyAppliesLongSubstitutionChainBeforeReachingFixedPoint() { VCImplication implication = vc("∀x:int. x == 1", "∀y:int. y == x + 1", "∀z:int. z == y + 1", "z == 3"); assertSimplificationSteps(VCSimplification::simplifyOnce, implication, - "∀y:int. y == 1 + 1 -> ∀z:int. z == y + 1 -> z == 3", "∀z:int. z == 1 + 1 + 1 -> z == 3", - "1 + 1 + 1 == 3", "2 + 1 == 3", "3 == 3", "true"); + chain(expect("y == 1 + 1", "∀x:int. y == x + 1"), expect("z == y + 1", "∀z:int. z == y + 1"), + expect("z == 3", "z == 3")), + chain(expect("z == 1 + 1 + 1", "∀y:int. z == y + 1"), expect("z == 3", "z == 3")), + chain(expect("1 + 1 + 1 == 3", "∀z:int. z == 3")), chain(expect("2 + 1 == 3", "∀z:int. z == 3")), + chain(expect("3 == 3", "∀z:int. z == 3")), chain(expect("true", "∀z:int. z == 3"))); } @Test void simplifyCombinesSubstitutionAndNestedFoldingAcrossFixedPoint() { VCImplication implication = vc("∀x:int. x == 1", "∀y:int. y == x + 2", "y - 1 == 2"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "∀y:int. y == 1 + 2 -> y - 1 == 2", - "1 + 2 - 1 == 2", "3 - 1 == 2", "2 == 2", "true"); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + chain(expect("y == 1 + 2", "∀x:int. y == x + 2"), expect("y - 1 == 2", "y - 1 == 2")), + chain(expect("1 + 2 - 1 == 2", "∀y:int. y - 1 == 2")), + chain(expect("3 - 1 == 2", "∀y:int. y - 1 == 2")), chain(expect("2 == 2", "∀y:int. y - 1 == 2")), + chain(expect("true", "∀y:int. y - 1 == 2"))); } @Test void simplifyStopsAfterSubstitutionWhenOnlyNegativeLiteralShapeChanges() { VCImplication implication = vc("∀x:int. x == a + 0", "x >= -3"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "a + 0 >= -3"); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + chain(expect("a + 0 >= -3", "∀x:int. x >= -3"))); } @Test void simplifyLeavesUnchangedVcAsPlainPredicates() { VCImplication implication = vc("x > 0", "y > x"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, "x > 0 -> y > x"); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + chain(expect("x > 0", "x > 0"), expect("y > x", "y > x"))); } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java index 3c77a6dcb..afe8b60fc 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java @@ -17,97 +17,106 @@ void applyReturnsNullForNullImplication() { void substitutesBinderEqualityIntoWholeChain() { VCImplication implication = vc("∀x:int. x == 3", "x > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, "3 > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, chain(expect("3 > 0", "∀x:int. x > 0"))); } @Test void substitutesReverseBinderEquality() { VCImplication implication = vc("∀x:int. 3 == x", "x > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, "3 > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, chain(expect("3 > 0", "∀x:int. x > 0"))); } @Test void substitutesCompoundKnownValue() { VCImplication implication = vc("∀x:int. x == y + 1", "x > y"); - assertSimplificationSteps(VCSubstitution::apply, implication, "y + 1 > y"); + assertSimplificationSteps(VCSubstitution::apply, implication, chain(expect("y + 1 > y", "∀x:int. x > y"))); } @Test void substitutesOnlyWholeVariableReferences() { VCImplication implication = vc("∀x:int. x == 3", "xx > x"); - assertSimplificationSteps(VCSubstitution::apply, implication, "xx > 3"); + assertSimplificationSteps(VCSubstitution::apply, implication, chain(expect("xx > 3", "∀x:int. xx > x"))); } @Test void substitutesEveryOccurrenceInPredicate() { VCImplication implication = vc("∀x:int. x == 2", "x + x > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, "2 + 2 > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, chain(expect("2 + 2 > 0", "∀x:int. x + x > 0"))); } @Test void preservesRemainingBinderAfterSubstitution() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y > x", "y > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, "∀y:int. y > 3 -> y > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, + chain(expect("y > 3", "∀x:int. y > x"), expect("y > 0", "y > 0"))); } @Test void removesSourceNodeWhenItIsLastInChain() { VCImplication implication = vc("x > 0", "∀y:int. y == 1"); - assertSimplificationSteps(VCSubstitution::apply, implication, "x > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, chain(expect("x > 0", "x > 0"))); } @Test void usesFirstSubstitutionFoundInChain() { VCImplication implication = vc("∀x:int. x > 0", "∀y:int. y == 4", "x + y > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, "∀x:int. x > 0 -> x + 4 > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, + chain(expect("x > 0", "∀x:int. x > 0"), expect("x + 4 > 0", "∀y:int. x + y > 0"))); } @Test void substitutesInnerKnownValueAcrossNestedImplications() { VCImplication implication = vc("∀x:int. true", "∀y:int. y == 1", "∀z:int. z > y", "y + z > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, "∀x:int. true -> ∀z:int. z > 1 -> 1 + z > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, chain(expect("true", "∀x:int. true"), + expect("z > 1", "∀y:int. z > y"), expect("1 + z > 0", "∀y:int. y + z > 0"))); } @Test void substitutesOuterKnownValueIntoNestedBinderRefinements() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); - assertSimplificationSteps(VCSubstitution::apply, implication, "∀y:int. y == 3 + 1 -> y > 3", "3 + 1 > 3"); + assertSimplificationSteps(VCSubstitution::apply, implication, + chain(expect("y == 3 + 1", "∀x:int. y == x + 1"), expect("y > 3", "∀x:int. y > x")), + chain(expect("3 + 1 > 3", "∀y:int. y > x"))); } @Test void ignoresRecursiveBinderEquality() { VCImplication implication = vc("∀x:int. x == x + 1", "x > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, "∀x:int. x == x + 1 -> x > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, + chain(expect("x == x + 1", "∀x:int. x == x + 1"), expect("x > 0", "x > 0"))); } @Test void ignoresNonEqualityBinderRefinement() { VCImplication implication = vc("∀x:int. x > 3", "x > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, "∀x:int. x > 3 -> x > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, + chain(expect("x > 3", "∀x:int. x > 3"), expect("x > 0", "x > 0"))); } @Test void ignoresDerivedBinderEquality() { VCImplication implication = vc("∀x:int. x + 1 == 3", "x > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, "∀x:int. x + 1 == 3 -> x > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, + chain(expect("x + 1 == 3", "∀x:int. x + 1 == 3"), expect("x > 0", "x > 0"))); } @Test void ignoresEqualityWithoutBinder() { VCImplication implication = vc("x == 3", "x > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, "x == 3 -> x > 0"); + assertSimplificationSteps(VCSubstitution::apply, implication, + chain(expect("x == 3", "x == 3"), expect("x > 0", "x > 0"))); } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java index c9a0b9d0f..ba82b81eb 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -1,12 +1,10 @@ package liquidjava.utils; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; import java.util.function.UnaryOperator; -import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.parsing.RefinementsParser; @@ -53,13 +51,12 @@ public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplif VCImplication current = implication; for (int i = 0; i < expected.length; i++) { ExpectedSimplifiedVCImplication expectedPredicate = expected[i]; - SimplifiedVCImplication simplified = simplifiedImplication(current, i); - assertEquals(Predicate.class, simplified.getRefinement().getClass(), + assertEquals(Predicate.class, current.getRefinement().getClass(), "Expected simplified refinement at implication " + i + " to be a plain Predicate"); - assertEquals(expectedPredicate.simplified(), simplified.getRefinement().toString(), + assertEquals(expectedPredicate.simplified(), current.getRefinement().toString(), "Unexpected simplified expression at implication " + i); if (expectedPredicate.origin() != null) - assertEquals(expectedPredicate.origin(), formatOrigin(simplified.getOrigin()), + assertEquals(expectedPredicate.origin(), formatOrigin(current.getOrigin()), "Unexpected origin VC at implication " + i); current = current.getNext(); } @@ -67,40 +64,21 @@ public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplif } public static VCImplication assertSimplificationSteps(UnaryOperator simplifier, - VCImplication implication, ExpectedSimplifiedVCImplication... expectedSteps) { + VCImplication implication, ExpectedSimplificationStep... expectedSteps) { VCImplication current = implication; - for (ExpectedSimplifiedVCImplication expectedStep : expectedSteps) { + for (ExpectedSimplificationStep expectedStep : expectedSteps) { current = simplifier.apply(current); - assertSimplifiedVC(current, expectedStep); + assertSimplifiedVC(current, expectedStep.implications()); } return current; } - public static VCImplication assertSimplificationSteps(UnaryOperator simplifier, - VCImplication implication, String... expectedSteps) { - VCImplication current = implication; - for (int i = 0; i < expectedSteps.length; i++) { - current = simplifier.apply(current); - assertExpectedVCChain(current, expectedSteps[i], i); - } - return current; + public static ExpectedSimplificationStep chain(ExpectedSimplifiedVCImplication... implications) { + return new ExpectedSimplificationStep(implications); } - public static SimplifiedVCImplication simplifiedImplication(VCImplication implication, int index) { - return assertInstanceOf(SimplifiedVCImplication.class, implication, - "Expected implication " + index + " to be a SimplifiedVCImplication"); - } - - private static void assertExpectedVCChain(VCImplication implication, String expectedStep, int step) { - VCImplication current = implication; - String[] expected = expectedStep.trim().split("\\s*->\\s*"); - for (int i = 0; i < expected.length; i++) { - assertEquals(expected[i], formatImplication(current), - "Unexpected expression at simplification step " + step + ", implication " + i); - current = current.getNext(); - } - assertNull(current, - "Expected simplification step " + step + " to end after " + expected.length + " implications"); + public static ExpectedSimplifiedVCImplication expect(String simplified, String origin) { + return new ExpectedSimplifiedVCImplication(simplified, origin); } private static String formatOrigin(VCImplication origin) { @@ -109,13 +87,9 @@ private static String formatOrigin(VCImplication origin) { return "∀" + origin.getName() + ":" + origin.getType().getQualifiedName() + ". " + origin.getRefinement(); } - private static String formatImplication(VCImplication implication) { - if (!implication.hasBinder()) - return implication.getRefinement().toString(); - return "∀" + implication.getName() + ":" + implication.getType().getQualifiedName() + ". " - + implication.getRefinement(); + public record ExpectedSimplifiedVCImplication(String simplified, String origin) { } - public record ExpectedSimplifiedVCImplication(String simplified, String origin) { + public record ExpectedSimplificationStep(ExpectedSimplifiedVCImplication... implications) { } } From 9fc831ab6f2e52ae5b99bfe9169f6d6fdb3c820b Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sat, 13 Jun 2026 17:15:14 +0100 Subject: [PATCH 40/41] Add Comments --- .../java/liquidjava/rj_language/opt/VCFolding.java | 13 +++++++++++++ .../liquidjava/rj_language/opt/VCSubstitution.java | 1 + 2 files changed, 14 insertions(+) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java index 9c5cf0cdf..e74b0e467 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java @@ -47,6 +47,7 @@ public static VCImplication apply(VCImplication implication) { * Folds the first foldable expression found */ private static Expression fold(Expression expression) { + // enum constant -> literal if (expression instanceof Enum en && en.getResolvedLiteral() != null) return en.getResolvedLiteral().clone(); if (expression instanceof BinaryExpression binary) @@ -55,6 +56,7 @@ private static Expression fold(Expression expression) { return foldUnary(unary); if (expression instanceof Ite ite) return foldIte(ite); + // (x) -> x if (expression instanceof GroupExpression group && group.getChildren().size() == 1) return group.getExpression().clone(); return expression.clone(); @@ -98,10 +100,13 @@ private static Expression foldUnary(UnaryExpression unary) { String op = unary.getOp(); + // !true -> false + // !false -> true if ("!".equals(op) && operand instanceof LiteralBoolean literal) return new LiteralBoolean(!literal.isBooleanTrue()); if ("-".equals(op)) { + // -(x) -> -x if (operand instanceof LiteralInt literal) return new LiteralInt(-literal.getValue()); if (operand instanceof LiteralReal literal) @@ -130,9 +135,12 @@ private static Expression foldIte(Ite ite) { if (!elseExpression.equals(foldedElse)) return new Ite(condition.clone(), thenExpression.clone(), foldedElse); + // true ? x : y -> x + // false ? x : y -> y if (condition instanceof LiteralBoolean literal) return literal.isBooleanTrue() ? thenExpression : elseExpression; + // y ? x : x -> x if (thenExpression.equals(elseExpression)) return thenExpression; @@ -161,6 +169,8 @@ private static Expression foldLiteralBinary(Expression left, Expression right, S if (left instanceof Enum leftEnum && right instanceof Enum rightEnum && leftEnum.getTypeName().equals(rightEnum.getTypeName())) { boolean equal = leftEnum.getConstName().equals(rightEnum.getConstName()); + // Enum.A == Enum.A -> true + // Enum.A != Enum.B -> true return switch (op) { case "==" -> new LiteralBoolean(equal); case "!=" -> new LiteralBoolean(!equal); @@ -191,10 +201,13 @@ private static Expression foldAdjacentInts(Expression left, Expression right, St int signedRight = "+".equals(op) ? rightLiteral.getValue() : -rightLiteral.getValue(); int constant = signedLeft + signedRight; Expression base = leftBinary.getFirstOperand().clone(); + // x + n - n -> x if (constant == 0) return base; + // x + n + m -> x + k if (constant > 0) return new BinaryExpression(base, "+", new LiteralInt(constant)); + // x + n - m -> x - k return new BinaryExpression(base, "-", new LiteralInt(-constant)); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java index 37dd16bf9..21133bc1b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java @@ -48,6 +48,7 @@ private static VCImplication substitute(VCImplication implication, VCImplication return null; // skip the source node to remove it from the chain and start substitution from the next node + // ∀x. x == v => P(x) -> P(v) if (implication == node) return substitute(implication.getNext(), node, replacement); From 85d9a86eefe6cd8d7ecf7854149ec4d6588acf41 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sat, 13 Jun 2026 22:26:37 +0100 Subject: [PATCH 41/41] Fix Origins --- .../liquidjava/rj_language/opt/VCFolding.java | 3 +- .../rj_language/opt/VCFoldingTest.java | 45 ++++++++++++++----- .../rj_language/opt/VCSimplificationTest.java | 27 ++++++----- .../java/liquidjava/utils/VCTestUtils.java | 10 +++-- 4 files changed, 54 insertions(+), 31 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java index e74b0e467..a8927f2b4 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFolding.java @@ -28,8 +28,7 @@ public static VCImplication apply(VCImplication implication) { Expression expression = implication.getRefinement().getExpression(); Expression folded = fold(expression); if (!expression.equals(folded)) { - VCImplication result = new SimplifiedVCImplication(implication, new Predicate(folded), - implication.getOrigin()); + VCImplication result = new SimplifiedVCImplication(implication, new Predicate(folded), implication); result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); return result; } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java index 53f51404e..b73bd8ec3 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFoldingTest.java @@ -26,7 +26,7 @@ void foldsIntegerArithmeticAndComparisons() { VCImplication implication = vc("1 + 2 == 3"); assertSimplificationSteps(VCFolding::apply, implication, chain(expect("3 == 3", "1 + 2 == 3")), - chain(expect("true", "1 + 2 == 3"))); + chain(expect("true", "3 == 3"))); assertSimplificationSteps(VCFolding::apply, vc("4 > 7"), chain(expect("false", "4 > 7"))); } @@ -36,9 +36,9 @@ void foldsRealAndMixedNumericExpressions() { VCImplication mixedArithmetic = vc("2 + 0.5 > 2"); assertSimplificationSteps(VCFolding::apply, realArithmetic, chain(expect("3.5 == 3.5", "1.5 + 2.0 == 3.5")), - chain(expect("true", "1.5 + 2.0 == 3.5"))); + chain(expect("true", "3.5 == 3.5"))); assertSimplificationSteps(VCFolding::apply, mixedArithmetic, chain(expect("2.5 > 2", "2 + 0.5 > 2")), - chain(expect("true", "2 + 0.5 > 2"))); + chain(expect("true", "2.5 > 2"))); } @Test @@ -55,6 +55,27 @@ void leavesRealDivisionAndModuloByZeroUnchanged() { chain(expect("4.0 % 0.0 == 0.0", "4.0 % 0.0 == 0.0"))); } + @Test + void foldsIntegerDivisionTowardZeroForNegativeResults() { + VCImplication implication = vc("(2 - 7) / 2 == -2"); + + assertSimplificationSteps(VCFolding::apply, implication, + chain(expect("(2 - 7) / 2 == -2", "(2 - 7) / 2 == -2")), + chain(expect("-5 / 2 == -2", "(2 - 7) / 2 == -2")), chain(expect("-2 == -2", "-5 / 2 == -2")), + chain(expect("-2 == -2", "-2 == -2")), chain(expect("true", "-2 == -2"))); + } + + @Test + void foldsIntegerModuloWithJavaSignedRemainder() { + VCImplication negativeDividend = vc("-5 % 2 < 0"); + VCImplication negativeDivisor = vc("5 % -2 > 0"); + + assertSimplificationSteps(VCFolding::apply, negativeDividend, chain(expect("-5 % 2 < 0", "-5 % 2 < 0")), + chain(expect("-1 < 0", "-5 % 2 < 0")), chain(expect("true", "-1 < 0"))); + assertSimplificationSteps(VCFolding::apply, negativeDivisor, chain(expect("5 % -2 > 0", "5 % -2 > 0")), + chain(expect("1 > 0", "5 % -2 > 0")), chain(expect("true", "1 > 0"))); + } + @Test void foldsBooleanBinaryExpressions() { assertSimplificationSteps(VCFolding::apply, vc("true && false"), chain(expect("false", "true && false"))); @@ -100,7 +121,7 @@ void foldsIteBranchesBeforeComparingThem() { VCImplication implication = vc("cond ? 1 + 2 : 3"); assertSimplificationSteps(VCFolding::apply, implication, chain(expect("cond ? 3 : 3", "cond ? 1 + 2 : 3")), - chain(expect("3", "cond ? 1 + 2 : 3"))); + chain(expect("3", "cond ? 3 : 3"))); } @Test @@ -127,7 +148,7 @@ void foldsResolvedEnumLiterals() { new Predicate(new BinaryExpression(limit, "==", new LiteralInt(3)))); assertSimplificationSteps(VCFolding::apply, implication, chain(expect("3 == 3", "Config.LIMIT == 3")), - chain(expect("true", "Config.LIMIT == 3"))); + chain(expect("true", "3 == 3"))); } @Test @@ -139,15 +160,15 @@ void foldsResolvedEnumLiteralsInsideLargerExpression() { new Predicate(new BinaryExpression(arithmetic, "==", new LiteralInt(5)))); assertSimplificationSteps(VCFolding::apply, implication, chain(expect("3 + 2 == 5", "Config.LIMIT + 2 == 5")), - chain(expect("5 == 5", "Config.LIMIT + 2 == 5")), chain(expect("true", "Config.LIMIT + 2 == 5"))); + chain(expect("5 == 5", "3 + 2 == 5")), chain(expect("true", "5 == 5"))); } @Test - void preservesOriginFromExistingSimplifiedImplication() { + void recordsCurrentImplicationAsOriginWhenFoldingExistingSimplifiedImplication() { VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == 1", "x + 1 + 2 > 0")); - assertSimplificationSteps(VCFolding::apply, substituted, chain(expect("2 + 2 > 0", "∀x:int. x + 1 + 2 > 0")), - chain(expect("4 > 0", "∀x:int. x + 1 + 2 > 0")), chain(expect("true", "∀x:int. x + 1 + 2 > 0"))); + assertSimplificationSteps(VCFolding::apply, substituted, chain(expect("2 + 2 > 0", "1 + 1 + 2 > 0")), + chain(expect("4 > 0", "2 + 2 > 0")), chain(expect("true", "4 > 0"))); } @Test @@ -169,13 +190,13 @@ void recordsOriginWhenFoldingLaterImplication() { chain(expect("x > 0", "x > 0"), expect("3 > 0", "1 + 2 > 0"))); SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); - assertEquals("1 + 2 > 0", simplifiedNext.getOrigin().getRefinement().toString()); + assertEquals("1 + 2 > 0", simplifiedNext.getOrigin().getRefinement().getExpression().toDisplayString()); result = assertSimplificationSteps(VCFolding::apply, result, - chain(expect("x > 0", "x > 0"), expect("true", "1 + 2 > 0"))); + chain(expect("x > 0", "x > 0"), expect("true", "3 > 0"))); simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); - assertEquals("1 + 2 > 0", simplifiedNext.getOrigin().getRefinement().toString()); + assertEquals("3 > 0", simplifiedNext.getOrigin().getRefinement().getExpression().toDisplayString()); } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java index a2ce7c821..3a3ee832d 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSimplificationTest.java @@ -23,8 +23,8 @@ void simplifyOnceAppliesSubstitutionBeforeFolding() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x > 2"); assertSimplificationSteps(VCSimplification::simplifyOnce, implication, - chain(expect("1 + 2 > 2", "∀x:int. x > 2")), chain(expect("3 > 2", "∀x:int. x > 2")), - chain(expect("true", "∀x:int. x > 2"))); + chain(expect("1 + 2 > 2", "∀x:int. x > 2")), chain(expect("3 > 2", "1 + 2 > 2")), + chain(expect("true", "3 > 2"))); } @Test @@ -32,8 +32,8 @@ void simplifyOnceDoesNotFoldAfterSubstitutionInSameStep() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x == 3"); assertSimplificationSteps(VCSimplification::simplifyOnce, implication, - chain(expect("1 + 2 == 3", "∀x:int. x == 3")), chain(expect("3 == 3", "∀x:int. x == 3")), - chain(expect("true", "∀x:int. x == 3"))); + chain(expect("1 + 2 == 3", "∀x:int. x == 3")), chain(expect("3 == 3", "1 + 2 == 3")), + chain(expect("true", "3 == 3"))); } @Test @@ -41,7 +41,7 @@ void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { VCImplication implication = vc("1 + 2 > 2"); assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("3 > 2", "1 + 2 > 2")), - chain(expect("true", "1 + 2 > 2"))); + chain(expect("true", "3 > 2"))); } @Test @@ -49,8 +49,8 @@ void simplifyKeepsApplyingStepsUntilFixedPoint() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x + 1 > 3"); assertSimplificationSteps(VCSimplification::simplifyOnce, implication, - chain(expect("1 + 2 + 1 > 3", "∀x:int. x + 1 > 3")), chain(expect("3 + 1 > 3", "∀x:int. x + 1 > 3")), - chain(expect("4 > 3", "∀x:int. x + 1 > 3")), chain(expect("true", "∀x:int. x + 1 > 3"))); + chain(expect("1 + 2 + 1 > 3", "∀x:int. x + 1 > 3")), chain(expect("3 + 1 > 3", "1 + 2 + 1 > 3")), + chain(expect("4 > 3", "3 + 1 > 3")), chain(expect("true", "4 > 3"))); } @Test @@ -59,8 +59,8 @@ void simplifyAppliesMultipleSubstitutionsBeforeReachingFixedPoint() { assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("y == 3 + 1", "∀x:int. y == x + 1"), expect("y > 3", "∀x:int. y > x")), - chain(expect("3 + 1 > 3", "∀y:int. y > x")), chain(expect("4 > 3", "∀y:int. y > x")), - chain(expect("true", "∀y:int. y > x"))); + chain(expect("3 + 1 > 3", "∀y:int. y > x")), chain(expect("4 > 3", "3 + 1 > 3")), + chain(expect("true", "4 > 3"))); } @Test @@ -71,8 +71,8 @@ void simplifyAppliesLongSubstitutionChainBeforeReachingFixedPoint() { chain(expect("y == 1 + 1", "∀x:int. y == x + 1"), expect("z == y + 1", "∀z:int. z == y + 1"), expect("z == 3", "z == 3")), chain(expect("z == 1 + 1 + 1", "∀y:int. z == y + 1"), expect("z == 3", "z == 3")), - chain(expect("1 + 1 + 1 == 3", "∀z:int. z == 3")), chain(expect("2 + 1 == 3", "∀z:int. z == 3")), - chain(expect("3 == 3", "∀z:int. z == 3")), chain(expect("true", "∀z:int. z == 3"))); + chain(expect("1 + 1 + 1 == 3", "∀z:int. z == 3")), chain(expect("2 + 1 == 3", "1 + 1 + 1 == 3")), + chain(expect("3 == 3", "2 + 1 == 3")), chain(expect("true", "3 == 3"))); } @Test @@ -81,9 +81,8 @@ void simplifyCombinesSubstitutionAndNestedFoldingAcrossFixedPoint() { assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("y == 1 + 2", "∀x:int. y == x + 2"), expect("y - 1 == 2", "y - 1 == 2")), - chain(expect("1 + 2 - 1 == 2", "∀y:int. y - 1 == 2")), - chain(expect("3 - 1 == 2", "∀y:int. y - 1 == 2")), chain(expect("2 == 2", "∀y:int. y - 1 == 2")), - chain(expect("true", "∀y:int. y - 1 == 2"))); + chain(expect("1 + 2 - 1 == 2", "∀y:int. y - 1 == 2")), chain(expect("3 - 1 == 2", "1 + 2 - 1 == 2")), + chain(expect("2 == 2", "3 - 1 == 2")), chain(expect("true", "2 == 2"))); } @Test diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java index ba82b81eb..61e46da40 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -53,7 +53,7 @@ public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplif ExpectedSimplifiedVCImplication expectedPredicate = expected[i]; assertEquals(Predicate.class, current.getRefinement().getClass(), "Expected simplified refinement at implication " + i + " to be a plain Predicate"); - assertEquals(expectedPredicate.simplified(), current.getRefinement().toString(), + assertEquals(expectedPredicate.simplified(), formatRefinement(current), "Unexpected simplified expression at implication " + i); if (expectedPredicate.origin() != null) assertEquals(expectedPredicate.origin(), formatOrigin(current.getOrigin()), @@ -83,8 +83,12 @@ public static ExpectedSimplifiedVCImplication expect(String simplified, String o private static String formatOrigin(VCImplication origin) { if (!origin.hasBinder()) - return origin.getRefinement().toString(); - return "∀" + origin.getName() + ":" + origin.getType().getQualifiedName() + ". " + origin.getRefinement(); + return formatRefinement(origin); + return "∀" + origin.getName() + ":" + origin.getType().getQualifiedName() + ". " + formatRefinement(origin); + } + + private static String formatRefinement(VCImplication implication) { + return implication.getRefinement().getExpression().toDisplayString(); } public record ExpectedSimplifiedVCImplication(String simplified, String origin) {