From e38d99d6bbfe6a39c17bb3846ce39f03f572604b Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Mon, 8 Jun 2026 11:19:11 +0100 Subject: [PATCH 01/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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 b813bb78542e2efc3ef568dab0d26cd736508c25 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sat, 13 Jun 2026 16:47:36 +0100 Subject: [PATCH 41/65] Add VC Arithmetic Simplification --- .../opt/VCArithmeticSimplification.java | 284 ++++++++++++++++++ .../rj_language/opt/VCSimplification.java | 10 +- .../opt/VCArithmeticSimplificationTest.java | 107 +++++++ .../opt/VCImplicationGenerator.java | 32 +- .../rj_language/opt/VCSimplificationTest.java | 15 + 5 files changed, 445 insertions(+), 3 deletions(-) create mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java create mode 100644 liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java new file mode 100644 index 000000000..1af38fe7f --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java @@ -0,0 +1,284 @@ +package liquidjava.rj_language.opt; + +import java.util.ArrayList; +import java.util.List; + +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.GroupExpression; +import liquidjava.rj_language.ast.Ite; +import liquidjava.rj_language.ast.LiteralInt; +import liquidjava.rj_language.ast.LiteralReal; +import liquidjava.rj_language.ast.UnaryExpression; + +/** + * Simplifies VCImplication chains by applying arithmetic identities inside refinements. + */ +public class VCArithmeticSimplification { + + /** + * Applies the first arithmetic simplification available in a VC chain. + */ + public static VCImplication apply(VCImplication implication) { + if (implication == null) + return null; + + return apply(implication, List.of()); + } + + private static VCImplication apply(VCImplication implication, List nonZeroTerms) { + if (implication == null) + return null; + + Expression expression = implication.getRefinement().getExpression(); + Expression simplified = simplify(expression, nonZeroTerms); + if (!expression.equals(simplified)) { + VCImplication result = new SimplifiedVCImplication(implication, new Predicate(simplified), + implication.getOrigin()); + result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); + return result; + } + + List nextNonZeroTerms = new ArrayList<>(nonZeroTerms); + addNonZeroTerm(implication.getRefinement().getExpression(), nextNonZeroTerms); + + VCImplication next = apply(implication.getNext(), nextNonZeroTerms); + if (implication.getNext() == null || implication.getNext().equals(next)) + return implication; + + VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); + result.setNext(next); + return result; + } + + /** + * Simplifies the first arithmetic identity found inside an expression. + */ + private static Expression simplify(Expression expression, List nonZeroTerms) { + if (expression instanceof BinaryExpression binary) + return simplifyBinary(binary, nonZeroTerms); + if (expression instanceof UnaryExpression unary) + return simplifyUnary(unary, nonZeroTerms); + if (expression instanceof Ite ite) + return simplifyIte(ite, nonZeroTerms); + if (expression instanceof GroupExpression group) + return simplifyGroup(group, nonZeroTerms); + return expression.clone(); + } + + /** + * Simplifies a binary expression by visiting operands before the current node. + */ + private static Expression simplifyBinary(BinaryExpression binary, List nonZeroTerms) { + Expression left = binary.getFirstOperand(); + Expression simplifiedLeft = simplify(left, nonZeroTerms); + if (!left.equals(simplifiedLeft)) + return new BinaryExpression(simplifiedLeft, binary.getOperator(), binary.getSecondOperand().clone()); + + Expression right = binary.getSecondOperand(); + Expression simplifiedRight = simplify(right, nonZeroTerms); + if (!right.equals(simplifiedRight)) + return new BinaryExpression(left.clone(), binary.getOperator(), simplifiedRight); + + Expression simplifiedBinary = simplifyLocalBinary(left, right, binary.getOperator(), nonZeroTerms); + if (simplifiedBinary != null) + return simplifiedBinary; + + return new BinaryExpression(left.clone(), binary.getOperator(), right.clone()); + } + + /** + * Simplifies a unary expression by visiting its operand before the current node. + */ + private static Expression simplifyUnary(UnaryExpression unary, List nonZeroTerms) { + Expression operand = unary.getExpression(); + Expression simplifiedOperand = simplify(operand, nonZeroTerms); + if (!operand.equals(simplifiedOperand)) + return new UnaryExpression(unary.getOp(), simplifiedOperand); + + if ("-".equals(unary.getOp()) && isNegation(operand)) + return negatedExpression(operand).clone(); + + return new UnaryExpression(unary.getOp(), operand.clone()); + } + + /** + * Simplifies a ternary expression by visiting condition, then branch, and else branch. + */ + private static Expression simplifyIte(Ite ite, List nonZeroTerms) { + Expression condition = ite.getCondition(); + Expression simplifiedCondition = simplify(condition, nonZeroTerms); + if (!condition.equals(simplifiedCondition)) + return new Ite(simplifiedCondition, ite.getThen().clone(), ite.getElse().clone()); + + Expression thenExpression = ite.getThen(); + Expression simplifiedThen = simplify(thenExpression, nonZeroTerms); + if (!thenExpression.equals(simplifiedThen)) + return new Ite(condition.clone(), simplifiedThen, ite.getElse().clone()); + + Expression elseExpression = ite.getElse(); + Expression simplifiedElse = simplify(elseExpression, nonZeroTerms); + if (!elseExpression.equals(simplifiedElse)) + return new Ite(condition.clone(), thenExpression.clone(), simplifiedElse); + + return new Ite(condition.clone(), thenExpression.clone(), elseExpression.clone()); + } + + /** + * Simplifies an expression wrapped in parentheses while preserving the group node. + */ + private static Expression simplifyGroup(GroupExpression group, List nonZeroTerms) { + Expression expression = group.getExpression(); + Expression simplified = simplify(expression, nonZeroTerms); + if (!expression.equals(simplified)) + return new GroupExpression(simplified); + return group.clone(); + } + + /** + * Dispatches a local binary arithmetic identity by operator. + */ + private static Expression simplifyLocalBinary(Expression left, Expression right, String op, + List nonZeroTerms) { + return switch (op) { + case "+" -> simplifyAddition(left, right); + case "-" -> simplifySubtraction(left, right); + case "*" -> simplifyMultiplication(left, right); + case "/" -> simplifyDivision(left, right, nonZeroTerms); + case "%" -> simplifyModulo(left, right, nonZeroTerms); + default -> null; + }; + } + + /** + * Applies addition identities involving zero and unary negation. + */ + private static Expression simplifyAddition(Expression left, Expression right) { + if (isZero(right)) + return left.clone(); + if (isZero(left)) + return right.clone(); + if (isNegation(right) && left.equals(negatedExpression(right))) + return new LiteralInt(0); + if (isNegation(left) && negatedExpression(left).equals(right)) + return new LiteralInt(0); + if (isNegation(right)) + return new BinaryExpression(left.clone(), "-", negatedExpression(right).clone()); + return null; + } + + /** + * Applies subtraction identities involving zero, same operands, and unary negation. + */ + private static Expression simplifySubtraction(Expression left, Expression right) { + if (isZero(right)) + return left.clone(); + if (isZero(left)) + return new UnaryExpression("-", right.clone()); + if (left.equals(right)) + return new LiteralInt(0); + if (isNegation(right)) + return new BinaryExpression(left.clone(), "+", negatedExpression(right).clone()); + return null; + } + + /** + * Applies multiplication identities involving one and zero. + */ + private static Expression simplifyMultiplication(Expression left, Expression right) { + if (isOne(right)) + return left.clone(); + if (isOne(left)) + return right.clone(); + if (isZero(right)) + return right.clone(); + if (isZero(left)) + return left.clone(); + return null; + } + + /** + * Applies division identities, using prior non-zero premises when needed. + */ + private static Expression simplifyDivision(Expression left, Expression right, List nonZeroTerms) { + if (isOne(right)) + return left.clone(); + if (isZero(left) && isKnownNonZero(right, nonZeroTerms)) + return left.clone(); + if (left.equals(right) && isKnownNonZero(right, nonZeroTerms)) + return new LiteralInt(1); + return null; + } + + /** + * Applies modulo identities, using prior non-zero premises when needed. + */ + private static Expression simplifyModulo(Expression left, Expression right, List nonZeroTerms) { + if (isOne(right)) + return new LiteralInt(0); + if (left.equals(right) && isKnownNonZero(right, nonZeroTerms)) + return new LiteralInt(0); + return null; + } + + /** + * Records direct non-zero premises shaped as expr != 0 or 0 != expr. + */ + private static void addNonZeroTerm(Expression expression, List nonZeroTerms) { + if (!(expression instanceof BinaryExpression binary) || !"!=".equals(binary.getOperator())) + return; + + Expression left = binary.getFirstOperand(); + Expression right = binary.getSecondOperand(); + if (isZero(left)) + nonZeroTerms.add(right.clone()); + if (isZero(right)) + nonZeroTerms.add(left.clone()); + } + + /** + * Checks whether a previous premise recorded an expression as non-zero. + */ + private static boolean isKnownNonZero(Expression expression, List nonZeroTerms) { + return nonZeroTerms.stream().anyMatch(term -> term.equals(expression)); + } + + /** + * Checks whether an expression is a numeric zero literal. + */ + private static boolean isZero(Expression expression) { + if (expression instanceof LiteralInt literal) + return literal.getValue() == 0; + if (expression instanceof LiteralReal literal) + return literal.getValue() == 0.0; + return false; + } + + /** + * Checks whether an expression is a numeric one literal. + */ + private static boolean isOne(Expression expression) { + if (expression instanceof LiteralInt literal) + return literal.getValue() == 1; + if (expression instanceof LiteralReal literal) + return literal.getValue() == 1.0; + return false; + } + + /** + * Checks whether an expression is unary negation. + */ + private static boolean isNegation(Expression expression) { + return expression instanceof UnaryExpression unary && "-".equals(unary.getOp()); + } + + /** + * Reads the operand of a unary negation expression. + */ + private static Expression negatedExpression(Expression expression) { + return ((UnaryExpression) expression).getExpression(); + } +} 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 f87b1081f..47be81dc0 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 @@ -31,11 +31,17 @@ public static VCImplication simplifyOnce(VCImplication implication) { if (implication == null) return null; - // first try to apply substitution, then folding + // substitution VCImplication substituted = VCSubstitution.apply(implication); if (!implication.equals(substituted)) return substituted; - return VCFolding.apply(implication); + // folding + VCImplication folded = VCFolding.apply(implication); + if (!implication.equals(folded)) + return folded; + + // arithmetic simplification + return VCArithmeticSimplification.apply(implication); } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java new file mode 100644 index 000000000..f38ac6ab2 --- /dev/null +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java @@ -0,0 +1,107 @@ +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.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; + +import liquidjava.processor.SimplifiedVCImplication; +import liquidjava.processor.VCImplication; +import org.junit.jupiter.api.Test; + +class VCArithmeticSimplificationTest { + + @Test + void applyReturnsNullForNullImplication() { + assertNull(VCArithmeticSimplification.apply(null)); + } + + @Test + void simplifiesAdditiveIdentities() { + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x + 0 > 0"), + chain(expect("x > 0", "x + 0 > 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("0 + x > 0"), + chain(expect("x > 0", "0 + x > 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x - 0 > 0"), + chain(expect("x > 0", "x - 0 > 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("0 - x > 0"), + chain(expect("-x > 0", "0 - x > 0"))); + } + + @Test + void simplifiesNegatedAdditionAndSubtraction() { + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x + -x == 0"), + chain(expect("0 == 0", "x + -x == 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("-x + x == 0"), + chain(expect("0 == 0", "-x + x == 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x - x == 0"), + chain(expect("0 == 0", "x - x == 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("--x == x"), + chain(expect("x == x", "--x == x"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x + -y == 0"), + chain(expect("x - y == 0", "x + -y == 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x - -y == 0"), + chain(expect("x + y == 0", "x - -y == 0"))); + } + + @Test + void simplifiesMultiplicativeIdentities() { + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x * 1 > 0"), + chain(expect("x > 0", "x * 1 > 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("1 * x > 0"), + chain(expect("x > 0", "1 * x > 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x * 0 == 0"), + chain(expect("0 == 0", "x * 0 == 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("0 * x == 0"), + chain(expect("0 == 0", "0 * x == 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x / 1 > 0"), + chain(expect("x > 0", "x / 1 > 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x % 1 == 0"), + chain(expect("0 == 0", "x % 1 == 0"))); + } + + @Test + void simplifiesGuardedDivisionAndModuloIdentities() { + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x != 0", "0 / x == 0"), + chain(expect("x != 0", "x != 0"), expect("0 == 0", "0 / x == 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x != 0", "x / x == 1"), + chain(expect("x != 0", "x != 0"), expect("1 == 1", "x / x == 1"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("0 != x", "x % x == 0"), + chain(expect("0 != x", "0 != x"), expect("0 == 0", "x % x == 0"))); + } + + @Test + void leavesUnguardedDivisionAndModuloIdentitiesUnchanged() { + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("0 / x == 0"), + chain(expect("0 / x == 0", "0 / x == 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x / x == 1"), + chain(expect("x / x == 1", "x / x == 1"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x % x == 0"), + chain(expect("x % x == 0", "x % x == 0"))); + } + + @Test + void simplifiesOnlyFirstArithmeticIdentity() { + assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x + 0 + 1 > 0"), + chain(expect("x + 1 > 0", "x + 0 + 1 > 0"))); + } + + @Test + void recordsOriginWhenSimplifyingLaterImplication() { + VCImplication implication = vc("x > 0", "y + 0 > x"); + + VCImplication result = assertSimplificationSteps(VCArithmeticSimplification::apply, implication, + chain(expect("x > 0", "x > 0"), expect("y > x", "y + 0 > x"))); + + SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); + assertEquals("y + 0 > x", simplifiedNext.getOrigin().getRefinement().toString()); + } + + @Test + void preservesOriginFromExistingSimplifiedImplication() { + VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == y + 0", "x > 0")); + + assertSimplificationSteps(VCArithmeticSimplification::apply, substituted, + chain(expect("y > 0", "∀x:int. x > 0"))); + } +} 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 0ebe0e420..104a619cf 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 @@ -21,7 +21,7 @@ public VCImplicationGenerator() { @Override public VCImplication generate(SourceOfRandomness random, GenerationStatus status) { - return switch (random.nextInt(0, 9)) { + return switch (random.nextInt(0, 11)) { 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")); @@ -31,6 +31,8 @@ public VCImplication generate(SourceOfRandomness random, GenerationStatus status case 6 -> vc(foldableBoolean(random), comparison(random, "x")); case 7 -> vc(foldableIte(random)); case 8 -> vc(adjacentConstants(random) + " " + comparisonOperator(random) + " " + intLiteral(random)); + case 9 -> vc(arithmeticIdentity(random)); + case 10 -> guardedArithmeticIdentity(random); default -> vc(substitution(random, "x"), substitution(random, "y"), foldableComparison(random)); }; } @@ -94,6 +96,34 @@ private static String adjacentConstants(SourceOfRandomness random) { return variable + " " + signed(left) + " " + signed(right); } + private static String arithmeticIdentity(SourceOfRandomness random) { + String var = FREE_VARS[random.nextInt(0, FREE_VARS.length - 1)]; + String other = FREE_VARS[random.nextInt(0, FREE_VARS.length - 1)]; + return switch (random.nextInt(0, 9)) { + case 0 -> var + " + 0 " + comparisonOperator(random) + " " + intLiteral(random); + case 1 -> "0 + " + var + " " + comparisonOperator(random) + " " + intLiteral(random); + case 2 -> var + " - 0 " + comparisonOperator(random) + " " + intLiteral(random); + case 3 -> "0 - " + var + " " + comparisonOperator(random) + " " + intLiteral(random); + case 4 -> var + " - " + var + " == 0"; + case 5 -> var + " * 1 " + comparisonOperator(random) + " " + intLiteral(random); + case 6 -> "1 * " + var + " " + comparisonOperator(random) + " " + intLiteral(random); + case 7 -> var + " * 0 == 0"; + case 8 -> var + " / 1 " + comparisonOperator(random) + " " + intLiteral(random); + default -> var + " + -" + other + " " + comparisonOperator(random) + " " + intLiteral(random); + }; + } + + private static VCImplication guardedArithmeticIdentity(SourceOfRandomness random) { + String var = FREE_VARS[random.nextInt(0, FREE_VARS.length - 1)]; + String guard = random.nextBoolean() ? var + " != 0" : "0 != " + var; + String use = switch (random.nextInt(0, 2)) { + case 0 -> "0 / " + var + " == 0"; + case 1 -> var + " / " + var + " == 1"; + default -> var + " % " + var + " == 0"; + }; + return vc(guard, use); + } + private static String comparisonOperator(SourceOfRandomness random) { return COMPARISON_OPS[random.nextInt(0, COMPARISON_OPS.length - 1)]; } 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..cb7e7d2a0 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 @@ -44,6 +44,21 @@ void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { chain(expect("true", "1 + 2 > 2"))); } + @Test + void simplifyOnceAppliesFoldingBeforeArithmeticSimplification() { + VCImplication implication = vc("1 + 2 + x + 0 > 0"); + + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + chain(expect("3 + x + 0 > 0", "1 + 2 + x + 0 > 0"))); + } + + @Test + void simplifyOnceAppliesArithmeticWhenNoSubstitutionOrFoldingIsAvailable() { + VCImplication implication = vc("x + 0 > 0"); + + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("x > 0", "x + 0 > 0"))); + } + @Test void simplifyKeepsApplyingStepsUntilFixedPoint() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x + 1 > 3"); From 0a04e5b796d3adfc3171f6d046f98300d097ea9d Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sat, 13 Jun 2026 17:17:44 +0100 Subject: [PATCH 42/65] Add Comments --- .../opt/VCArithmeticSimplification.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java index 1af38fe7f..fde41881c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java @@ -99,6 +99,7 @@ private static Expression simplifyUnary(UnaryExpression unary, List if (!operand.equals(simplifiedOperand)) return new UnaryExpression(unary.getOp(), simplifiedOperand); + // -(-x) -> x if ("-".equals(unary.getOp()) && isNegation(operand)) return negatedExpression(operand).clone(); @@ -157,14 +158,19 @@ private static Expression simplifyLocalBinary(Expression left, Expression right, * Applies addition identities involving zero and unary negation. */ private static Expression simplifyAddition(Expression left, Expression right) { + // x + 0 -> x if (isZero(right)) return left.clone(); + // 0 + x -> x if (isZero(left)) return right.clone(); + // x + (-x) -> 0 if (isNegation(right) && left.equals(negatedExpression(right))) return new LiteralInt(0); + // (-x) + x -> 0 if (isNegation(left) && negatedExpression(left).equals(right)) return new LiteralInt(0); + // x + (-y) -> x - y if (isNegation(right)) return new BinaryExpression(left.clone(), "-", negatedExpression(right).clone()); return null; @@ -174,12 +180,16 @@ private static Expression simplifyAddition(Expression left, Expression right) { * Applies subtraction identities involving zero, same operands, and unary negation. */ private static Expression simplifySubtraction(Expression left, Expression right) { + // x - 0 -> x if (isZero(right)) return left.clone(); + // 0 - x -> -x if (isZero(left)) return new UnaryExpression("-", right.clone()); + // x - x -> 0 if (left.equals(right)) return new LiteralInt(0); + // x - (-y) -> x + y if (isNegation(right)) return new BinaryExpression(left.clone(), "+", negatedExpression(right).clone()); return null; @@ -189,12 +199,16 @@ private static Expression simplifySubtraction(Expression left, Expression right) * Applies multiplication identities involving one and zero. */ private static Expression simplifyMultiplication(Expression left, Expression right) { + // x * 1 -> x if (isOne(right)) return left.clone(); + // 1 * x -> x if (isOne(left)) return right.clone(); + // x * 0 -> 0 if (isZero(right)) return right.clone(); + // 0 * x -> 0 if (isZero(left)) return left.clone(); return null; @@ -204,10 +218,13 @@ private static Expression simplifyMultiplication(Expression left, Expression rig * Applies division identities, using prior non-zero premises when needed. */ private static Expression simplifyDivision(Expression left, Expression right, List nonZeroTerms) { + // x / 1 -> x if (isOne(right)) return left.clone(); + // 0 / x -> 0 (x != 0) if (isZero(left) && isKnownNonZero(right, nonZeroTerms)) return left.clone(); + // x / x -> 1 (x != 0) if (left.equals(right) && isKnownNonZero(right, nonZeroTerms)) return new LiteralInt(1); return null; @@ -217,8 +234,10 @@ private static Expression simplifyDivision(Expression left, Expression right, Li * Applies modulo identities, using prior non-zero premises when needed. */ private static Expression simplifyModulo(Expression left, Expression right, List nonZeroTerms) { + // x % 1 -> 0 if (isOne(right)) return new LiteralInt(0); + // x % x -> 0 (x != 0) if (left.equals(right) && isKnownNonZero(right, nonZeroTerms)) return new LiteralInt(0); return null; From e5d91628cbe79ee93326c030e5169bc8496b4216 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sat, 13 Jun 2026 17:52:33 +0100 Subject: [PATCH 43/65] Renaming --- .../opt/VCArithmeticSimplification.java | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java index fde41881c..e8abe6a2a 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java @@ -29,12 +29,12 @@ public static VCImplication apply(VCImplication implication) { return apply(implication, List.of()); } - private static VCImplication apply(VCImplication implication, List nonZeroTerms) { + private static VCImplication apply(VCImplication implication, List nonZeroExpressions) { if (implication == null) return null; Expression expression = implication.getRefinement().getExpression(); - Expression simplified = simplify(expression, nonZeroTerms); + Expression simplified = simplify(expression, nonZeroExpressions); if (!expression.equals(simplified)) { VCImplication result = new SimplifiedVCImplication(implication, new Predicate(simplified), implication.getOrigin()); @@ -42,10 +42,10 @@ private static VCImplication apply(VCImplication implication, List n return result; } - List nextNonZeroTerms = new ArrayList<>(nonZeroTerms); - addNonZeroTerm(implication.getRefinement().getExpression(), nextNonZeroTerms); + List nextNoneZeroExpressions = new ArrayList<>(nonZeroExpressions); + addNonZeroExpression(implication.getRefinement().getExpression(), nextNoneZeroExpressions); - VCImplication next = apply(implication.getNext(), nextNonZeroTerms); + VCImplication next = apply(implication.getNext(), nextNoneZeroExpressions); if (implication.getNext() == null || implication.getNext().equals(next)) return implication; @@ -57,33 +57,33 @@ private static VCImplication apply(VCImplication implication, List n /** * Simplifies the first arithmetic identity found inside an expression. */ - private static Expression simplify(Expression expression, List nonZeroTerms) { + private static Expression simplify(Expression expression, List nonZeroExpressions) { if (expression instanceof BinaryExpression binary) - return simplifyBinary(binary, nonZeroTerms); + return simplifyBinary(binary, nonZeroExpressions); if (expression instanceof UnaryExpression unary) - return simplifyUnary(unary, nonZeroTerms); + return simplifyUnary(unary, nonZeroExpressions); if (expression instanceof Ite ite) - return simplifyIte(ite, nonZeroTerms); + return simplifyIte(ite, nonZeroExpressions); if (expression instanceof GroupExpression group) - return simplifyGroup(group, nonZeroTerms); + return simplifyGroup(group, nonZeroExpressions); return expression.clone(); } /** * Simplifies a binary expression by visiting operands before the current node. */ - private static Expression simplifyBinary(BinaryExpression binary, List nonZeroTerms) { + private static Expression simplifyBinary(BinaryExpression binary, List nonZeroExpressions) { Expression left = binary.getFirstOperand(); - Expression simplifiedLeft = simplify(left, nonZeroTerms); + Expression simplifiedLeft = simplify(left, nonZeroExpressions); if (!left.equals(simplifiedLeft)) return new BinaryExpression(simplifiedLeft, binary.getOperator(), binary.getSecondOperand().clone()); Expression right = binary.getSecondOperand(); - Expression simplifiedRight = simplify(right, nonZeroTerms); + Expression simplifiedRight = simplify(right, nonZeroExpressions); if (!right.equals(simplifiedRight)) return new BinaryExpression(left.clone(), binary.getOperator(), simplifiedRight); - Expression simplifiedBinary = simplifyLocalBinary(left, right, binary.getOperator(), nonZeroTerms); + Expression simplifiedBinary = simplifyLocalBinary(left, right, binary.getOperator(), nonZeroExpressions); if (simplifiedBinary != null) return simplifiedBinary; @@ -93,9 +93,9 @@ private static Expression simplifyBinary(BinaryExpression binary, List nonZeroTerms) { + private static Expression simplifyUnary(UnaryExpression unary, List nonZeroExpressions) { Expression operand = unary.getExpression(); - Expression simplifiedOperand = simplify(operand, nonZeroTerms); + Expression simplifiedOperand = simplify(operand, nonZeroExpressions); if (!operand.equals(simplifiedOperand)) return new UnaryExpression(unary.getOp(), simplifiedOperand); @@ -109,19 +109,19 @@ private static Expression simplifyUnary(UnaryExpression unary, List /** * Simplifies a ternary expression by visiting condition, then branch, and else branch. */ - private static Expression simplifyIte(Ite ite, List nonZeroTerms) { + private static Expression simplifyIte(Ite ite, List nonZeroExpressions) { Expression condition = ite.getCondition(); - Expression simplifiedCondition = simplify(condition, nonZeroTerms); + Expression simplifiedCondition = simplify(condition, nonZeroExpressions); if (!condition.equals(simplifiedCondition)) return new Ite(simplifiedCondition, ite.getThen().clone(), ite.getElse().clone()); Expression thenExpression = ite.getThen(); - Expression simplifiedThen = simplify(thenExpression, nonZeroTerms); + Expression simplifiedThen = simplify(thenExpression, nonZeroExpressions); if (!thenExpression.equals(simplifiedThen)) return new Ite(condition.clone(), simplifiedThen, ite.getElse().clone()); Expression elseExpression = ite.getElse(); - Expression simplifiedElse = simplify(elseExpression, nonZeroTerms); + Expression simplifiedElse = simplify(elseExpression, nonZeroExpressions); if (!elseExpression.equals(simplifiedElse)) return new Ite(condition.clone(), thenExpression.clone(), simplifiedElse); @@ -131,9 +131,9 @@ private static Expression simplifyIte(Ite ite, List nonZeroTerms) { /** * Simplifies an expression wrapped in parentheses while preserving the group node. */ - private static Expression simplifyGroup(GroupExpression group, List nonZeroTerms) { + private static Expression simplifyGroup(GroupExpression group, List nonZeroExpressions) { Expression expression = group.getExpression(); - Expression simplified = simplify(expression, nonZeroTerms); + Expression simplified = simplify(expression, nonZeroExpressions); if (!expression.equals(simplified)) return new GroupExpression(simplified); return group.clone(); @@ -143,13 +143,13 @@ private static Expression simplifyGroup(GroupExpression group, List * Dispatches a local binary arithmetic identity by operator. */ private static Expression simplifyLocalBinary(Expression left, Expression right, String op, - List nonZeroTerms) { + List nonZeroExpressions) { return switch (op) { case "+" -> simplifyAddition(left, right); case "-" -> simplifySubtraction(left, right); case "*" -> simplifyMultiplication(left, right); - case "/" -> simplifyDivision(left, right, nonZeroTerms); - case "%" -> simplifyModulo(left, right, nonZeroTerms); + case "/" -> simplifyDivision(left, right, nonZeroExpressions); + case "%" -> simplifyModulo(left, right, nonZeroExpressions); default -> null; }; } @@ -217,15 +217,15 @@ private static Expression simplifyMultiplication(Expression left, Expression rig /** * Applies division identities, using prior non-zero premises when needed. */ - private static Expression simplifyDivision(Expression left, Expression right, List nonZeroTerms) { + private static Expression simplifyDivision(Expression left, Expression right, List nonZeroExpressions) { // x / 1 -> x if (isOne(right)) return left.clone(); // 0 / x -> 0 (x != 0) - if (isZero(left) && isKnownNonZero(right, nonZeroTerms)) + if (isZero(left) && isNonZero(right, nonZeroExpressions)) return left.clone(); // x / x -> 1 (x != 0) - if (left.equals(right) && isKnownNonZero(right, nonZeroTerms)) + if (left.equals(right) && isNonZero(right, nonZeroExpressions)) return new LiteralInt(1); return null; } @@ -233,12 +233,12 @@ private static Expression simplifyDivision(Expression left, Expression right, Li /** * Applies modulo identities, using prior non-zero premises when needed. */ - private static Expression simplifyModulo(Expression left, Expression right, List nonZeroTerms) { + private static Expression simplifyModulo(Expression left, Expression right, List nonZeroExpressions) { // x % 1 -> 0 if (isOne(right)) return new LiteralInt(0); // x % x -> 0 (x != 0) - if (left.equals(right) && isKnownNonZero(right, nonZeroTerms)) + if (left.equals(right) && isNonZero(right, nonZeroExpressions)) return new LiteralInt(0); return null; } @@ -246,23 +246,23 @@ private static Expression simplifyModulo(Expression left, Expression right, List /** * Records direct non-zero premises shaped as expr != 0 or 0 != expr. */ - private static void addNonZeroTerm(Expression expression, List nonZeroTerms) { + private static void addNonZeroExpression(Expression expression, List nonZeroExpressions) { if (!(expression instanceof BinaryExpression binary) || !"!=".equals(binary.getOperator())) return; Expression left = binary.getFirstOperand(); Expression right = binary.getSecondOperand(); if (isZero(left)) - nonZeroTerms.add(right.clone()); + nonZeroExpressions.add(right.clone()); if (isZero(right)) - nonZeroTerms.add(left.clone()); + nonZeroExpressions.add(left.clone()); } /** * Checks whether a previous premise recorded an expression as non-zero. */ - private static boolean isKnownNonZero(Expression expression, List nonZeroTerms) { - return nonZeroTerms.stream().anyMatch(term -> term.equals(expression)); + private static boolean isNonZero(Expression expression, List nonZeroExpressions) { + return nonZeroExpressions.stream().anyMatch(e -> e.equals(expression)); } /** From f4250de68c9a8d1dc006643e44ef0b9e6267b971 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sat, 13 Jun 2026 18:19:25 +0100 Subject: [PATCH 44/65] Update Comments --- .../opt/VCArithmeticSimplification.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java index e8abe6a2a..665a8520d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java @@ -15,12 +15,12 @@ import liquidjava.rj_language.ast.UnaryExpression; /** - * Simplifies VCImplication chains by applying arithmetic identities inside refinements. + * Simplifies VCImplication chains by applying arithmetic identities inside refinements */ public class VCArithmeticSimplification { /** - * Applies the first arithmetic simplification available in a VC chain. + * Applies the first arithmetic simplification available in a VC chain */ public static VCImplication apply(VCImplication implication) { if (implication == null) @@ -55,7 +55,7 @@ private static VCImplication apply(VCImplication implication, List n } /** - * Simplifies the first arithmetic identity found inside an expression. + * Simplifies the first arithmetic identity found inside an expression */ private static Expression simplify(Expression expression, List nonZeroExpressions) { if (expression instanceof BinaryExpression binary) @@ -70,7 +70,7 @@ private static Expression simplify(Expression expression, List nonZe } /** - * Simplifies a binary expression by visiting operands before the current node. + * Simplifies a binary expression by visiting operands before the current node */ private static Expression simplifyBinary(BinaryExpression binary, List nonZeroExpressions) { Expression left = binary.getFirstOperand(); @@ -91,7 +91,7 @@ private static Expression simplifyBinary(BinaryExpression binary, List nonZeroExpressions) { Expression operand = unary.getExpression(); @@ -107,7 +107,7 @@ private static Expression simplifyUnary(UnaryExpression unary, List } /** - * Simplifies a ternary expression by visiting condition, then branch, and else branch. + * Simplifies a ternary expression by visiting condition, then branch, and else branch */ private static Expression simplifyIte(Ite ite, List nonZeroExpressions) { Expression condition = ite.getCondition(); @@ -129,7 +129,7 @@ private static Expression simplifyIte(Ite ite, List nonZeroExpressio } /** - * Simplifies an expression wrapped in parentheses while preserving the group node. + * Simplifies an expression wrapped in parentheses while preserving the group node */ private static Expression simplifyGroup(GroupExpression group, List nonZeroExpressions) { Expression expression = group.getExpression(); @@ -140,7 +140,7 @@ private static Expression simplifyGroup(GroupExpression group, List } /** - * Dispatches a local binary arithmetic identity by operator. + * Dispatches a local binary arithmetic identity by operator */ private static Expression simplifyLocalBinary(Expression left, Expression right, String op, List nonZeroExpressions) { @@ -155,7 +155,7 @@ private static Expression simplifyLocalBinary(Expression left, Expression right, } /** - * Applies addition identities involving zero and unary negation. + * Applies addition identities involving zero and unary negation */ private static Expression simplifyAddition(Expression left, Expression right) { // x + 0 -> x @@ -177,7 +177,7 @@ private static Expression simplifyAddition(Expression left, Expression right) { } /** - * Applies subtraction identities involving zero, same operands, and unary negation. + * Applies subtraction identities involving zero, same operands, and unary negation */ private static Expression simplifySubtraction(Expression left, Expression right) { // x - 0 -> x @@ -196,7 +196,7 @@ private static Expression simplifySubtraction(Expression left, Expression right) } /** - * Applies multiplication identities involving one and zero. + * Applies multiplication identities involving one and zero */ private static Expression simplifyMultiplication(Expression left, Expression right) { // x * 1 -> x @@ -215,7 +215,7 @@ private static Expression simplifyMultiplication(Expression left, Expression rig } /** - * Applies division identities, using prior non-zero premises when needed. + * Applies division identities, using prior non-zero premises when needed */ private static Expression simplifyDivision(Expression left, Expression right, List nonZeroExpressions) { // x / 1 -> x @@ -231,7 +231,7 @@ private static Expression simplifyDivision(Expression left, Expression right, Li } /** - * Applies modulo identities, using prior non-zero premises when needed. + * Applies modulo identities, using prior non-zero premises when needed */ private static Expression simplifyModulo(Expression left, Expression right, List nonZeroExpressions) { // x % 1 -> 0 @@ -244,7 +244,7 @@ private static Expression simplifyModulo(Expression left, Expression right, List } /** - * Records direct non-zero premises shaped as expr != 0 or 0 != expr. + * Records direct non-zero premises shaped as x != 0 or 0 != x */ private static void addNonZeroExpression(Expression expression, List nonZeroExpressions) { if (!(expression instanceof BinaryExpression binary) || !"!=".equals(binary.getOperator())) @@ -259,14 +259,14 @@ private static void addNonZeroExpression(Expression expression, List } /** - * Checks whether a previous premise recorded an expression as non-zero. + * Checks whether a previous premise recorded an expression as non-zero */ private static boolean isNonZero(Expression expression, List nonZeroExpressions) { return nonZeroExpressions.stream().anyMatch(e -> e.equals(expression)); } /** - * Checks whether an expression is a numeric zero literal. + * Checks whether an expression is a numeric zero literal */ private static boolean isZero(Expression expression) { if (expression instanceof LiteralInt literal) @@ -277,7 +277,7 @@ private static boolean isZero(Expression expression) { } /** - * Checks whether an expression is a numeric one literal. + * Checks whether an expression is a numeric one literal */ private static boolean isOne(Expression expression) { if (expression instanceof LiteralInt literal) @@ -288,14 +288,14 @@ private static boolean isOne(Expression expression) { } /** - * Checks whether an expression is unary negation. + * Checks whether an expression is unary negation */ private static boolean isNegation(Expression expression) { return expression instanceof UnaryExpression unary && "-".equals(unary.getOp()); } /** - * Reads the operand of a unary negation expression. + * Returns the operand of a unary negation expression */ private static Expression negatedExpression(Expression expression) { return ((UnaryExpression) expression).getExpression(); From 334ff7e91a96b5bb929f06fcd7b0f24f964fdc3d Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sat, 13 Jun 2026 18:50:20 +0100 Subject: [PATCH 45/65] Refactor Simplification Passes --- .../rj_language/opt/VCSimplification.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) 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 47be81dc0..6854b547e 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 @@ -1,5 +1,8 @@ package liquidjava.rj_language.opt; +import java.util.List; +import java.util.function.UnaryOperator; + import liquidjava.processor.VCImplication; /** @@ -7,6 +10,9 @@ */ public class VCSimplification { + private static final List> PASSES = List.of(VCSubstitution::apply, VCFolding::apply, + VCArithmeticSimplification::apply); + /** * Applies all available simplification steps to a VC chain until a fixed point is reached */ @@ -31,17 +37,11 @@ public static VCImplication simplifyOnce(VCImplication implication) { if (implication == null) return null; - // substitution - VCImplication substituted = VCSubstitution.apply(implication); - if (!implication.equals(substituted)) - return substituted; - - // folding - VCImplication folded = VCFolding.apply(implication); - if (!implication.equals(folded)) - return folded; - - // arithmetic simplification - return VCArithmeticSimplification.apply(implication); + for (UnaryOperator pass : PASSES) { + VCImplication simplified = pass.apply(implication); + if (!implication.equals(simplified)) + return simplified; + } + return implication; } } From eb9ff24afea554d8a8141777cd1367b2391cbc1b Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sat, 13 Jun 2026 19:11:24 +0100 Subject: [PATCH 46/65] Add VC Logical Simplification --- .../opt/VCLogicalSimplification.java | 252 ++++++++++++++++++ .../rj_language/opt/VCSimplification.java | 2 +- .../opt/VCImplicationGenerator.java | 26 +- .../opt/VCLogicalSimplificationTest.java | 99 +++++++ .../rj_language/opt/VCSimplificationTest.java | 23 ++ 5 files changed, 400 insertions(+), 2 deletions(-) create mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java create mode 100644 liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java new file mode 100644 index 000000000..87b392bcd --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java @@ -0,0 +1,252 @@ +package liquidjava.rj_language.opt; + +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.GroupExpression; +import liquidjava.rj_language.ast.Ite; +import liquidjava.rj_language.ast.LiteralBoolean; +import liquidjava.rj_language.ast.UnaryExpression; + +/** + * Simplifies VCImplication chains by applying logical identities inside refinements + */ +public class VCLogicalSimplification { + + /** + * Applies the first logical simplification available in a VC chain + */ + public static VCImplication apply(VCImplication implication) { + if (implication == null) + return null; + + Expression expression = implication.getRefinement().getExpression(); + Expression simplified = simplify(expression); + if (!expression.equals(simplified)) { + VCImplication result = new SimplifiedVCImplication(implication, new Predicate(simplified), + implication.getOrigin()); + result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); + return result; + } + + VCImplication next = apply(implication.getNext()); + if (implication.getNext() == null || implication.getNext().equals(next)) + return implication; + + VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); + result.setNext(next); + return result; + } + + /** + * Simplifies the first logical identity found inside an expression + */ + private static Expression simplify(Expression expression) { + if (expression instanceof BinaryExpression binary) + return simplifyBinary(binary); + if (expression instanceof UnaryExpression unary) + return simplifyUnary(unary); + if (expression instanceof Ite ite) + return simplifyIte(ite); + if (expression instanceof GroupExpression group) + return simplifyGroup(group); + return expression.clone(); + } + + /** + * Simplifies a binary expression by visiting operands before the current node + */ + private static Expression simplifyBinary(BinaryExpression binary) { + Expression left = binary.getFirstOperand(); + Expression simplifiedLeft = simplify(left); + if (!left.equals(simplifiedLeft)) + return new BinaryExpression(simplifiedLeft, binary.getOperator(), binary.getSecondOperand().clone()); + + Expression right = binary.getSecondOperand(); + Expression simplifiedRight = simplify(right); + if (!right.equals(simplifiedRight)) + return new BinaryExpression(left.clone(), binary.getOperator(), simplifiedRight); + + Expression simplifiedBinary = simplifyLocalBinary(left, right, binary.getOperator()); + if (simplifiedBinary != null) + return simplifiedBinary; + + return new BinaryExpression(left.clone(), binary.getOperator(), right.clone()); + } + + /** + * Simplifies a unary expression by visiting its operand before the current node + */ + private static Expression simplifyUnary(UnaryExpression unary) { + Expression operand = unary.getExpression(); + Expression simplifiedOperand = simplify(operand); + if (!operand.equals(simplifiedOperand)) + return new UnaryExpression(unary.getOp(), simplifiedOperand); + + // !!x -> x + if ("!".equals(unary.getOp()) && isNot(operand)) + return negatedExpression(operand).clone(); + + return new UnaryExpression(unary.getOp(), operand.clone()); + } + + /** + * Simplifies a ternary expression by visiting condition, then branch, and else branch + */ + private static Expression simplifyIte(Ite ite) { + Expression condition = ite.getCondition(); + Expression simplifiedCondition = simplify(condition); + if (!condition.equals(simplifiedCondition)) + return new Ite(simplifiedCondition, ite.getThen().clone(), ite.getElse().clone()); + + Expression thenExpression = ite.getThen(); + Expression simplifiedThen = simplify(thenExpression); + if (!thenExpression.equals(simplifiedThen)) + return new Ite(condition.clone(), simplifiedThen, ite.getElse().clone()); + + Expression elseExpression = ite.getElse(); + Expression simplifiedElse = simplify(elseExpression); + if (!elseExpression.equals(simplifiedElse)) + return new Ite(condition.clone(), thenExpression.clone(), simplifiedElse); + + return new Ite(condition.clone(), thenExpression.clone(), elseExpression.clone()); + } + + /** + * Simplifies an expression wrapped in parentheses while preserving the group node + */ + private static Expression simplifyGroup(GroupExpression group) { + Expression expression = group.getExpression(); + Expression simplified = simplify(expression); + if (!expression.equals(simplified)) + return new GroupExpression(simplified); + return group.clone(); + } + + /** + * Dispatches a local binary logical identity by operator + */ + private static Expression simplifyLocalBinary(Expression left, Expression right, String op) { + return switch (op) { + case "&&" -> simplifyConjunction(left, right); + case "||" -> simplifyDisjunction(left, right); + case "==" -> simplifyEquality(left, right); + case "!=" -> simplifyInequality(left, right); + case "-->" -> simplifyImplication(left, right); + default -> null; + }; + } + + /** + * Applies conjunction identities involving boolean literals and same operands + */ + private static Expression simplifyConjunction(Expression left, Expression right) { + // x && true -> x + if (isTrue(right)) + return left.clone(); + // true && x -> x + if (isTrue(left)) + return right.clone(); + // x && false -> false + if (isFalse(right)) + return right.clone(); + // false && x -> false + if (isFalse(left)) + return left.clone(); + // p && p -> p + if (left.equals(right)) + return left.clone(); + return null; + } + + /** + * Applies disjunction identities involving boolean literals and same operands + */ + private static Expression simplifyDisjunction(Expression left, Expression right) { + // x || true -> true + if (isTrue(right)) + return right.clone(); + // true || x -> true + if (isTrue(left)) + return left.clone(); + // x || false -> x + if (isFalse(right)) + return left.clone(); + // false || x -> x + if (isFalse(left)) + return right.clone(); + // p || p -> p + if (left.equals(right)) + return left.clone(); + return null; + } + + /** + * Applies equality identity for same operands + */ + private static Expression simplifyEquality(Expression left, Expression right) { + // x == x -> true + if (left.equals(right)) + return new LiteralBoolean(true); + return null; + } + + /** + * Applies inequality identity for same operands + */ + private static Expression simplifyInequality(Expression left, Expression right) { + // x != x -> false + if (left.equals(right)) + return new LiteralBoolean(false); + return null; + } + + /** + * Applies implication identities involving boolean literals and same operands + */ + private static Expression simplifyImplication(Expression left, Expression right) { + // x --> true -> true + if (isTrue(right)) + return right.clone(); + // false --> x -> true + if (isFalse(left)) + return new LiteralBoolean(true); + // true --> x -> x + if (isTrue(left)) + return right.clone(); + // x --> x -> true + if (left.equals(right)) + return new LiteralBoolean(true); + return null; + } + + /** + * Checks whether an expression is true + */ + private static boolean isTrue(Expression expression) { + return expression instanceof LiteralBoolean literal && literal.isBooleanTrue(); + } + + /** + * Checks whether an expression is false + */ + private static boolean isFalse(Expression expression) { + return expression instanceof LiteralBoolean literal && !literal.isBooleanTrue(); + } + + /** + * Checks whether an expression is unary logical negation + */ + private static boolean isNot(Expression expression) { + return expression instanceof UnaryExpression unary && "!".equals(unary.getOp()); + } + + /** + * Returns the operand of a unary logical negation expression + */ + private static Expression negatedExpression(Expression expression) { + return ((UnaryExpression) expression).getExpression(); + } +} 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 6854b547e..4c0dbec44 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 @@ -11,7 +11,7 @@ public class VCSimplification { private static final List> PASSES = List.of(VCSubstitution::apply, VCFolding::apply, - VCArithmeticSimplification::apply); + VCArithmeticSimplification::apply, VCLogicalSimplification::apply); /** * Applies all available simplification steps to a VC chain until a fixed point is reached 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 104a619cf..f3e19ff97 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 @@ -21,7 +21,7 @@ public VCImplicationGenerator() { @Override public VCImplication generate(SourceOfRandomness random, GenerationStatus status) { - return switch (random.nextInt(0, 11)) { + return switch (random.nextInt(0, 12)) { 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")); @@ -33,6 +33,7 @@ public VCImplication generate(SourceOfRandomness random, GenerationStatus status case 8 -> vc(adjacentConstants(random) + " " + comparisonOperator(random) + " " + intLiteral(random)); case 9 -> vc(arithmeticIdentity(random)); case 10 -> guardedArithmeticIdentity(random); + case 11 -> vc(logicalIdentity(random)); default -> vc(substitution(random, "x"), substitution(random, "y"), foldableComparison(random)); }; } @@ -124,6 +125,29 @@ private static VCImplication guardedArithmeticIdentity(SourceOfRandomness random return vc(guard, use); } + private static String logicalIdentity(SourceOfRandomness random) { + String predicate = "(" + comparison(random, FREE_VARS[random.nextInt(0, FREE_VARS.length - 1)]) + ")"; + return switch (random.nextInt(0, 16)) { + case 0 -> predicate + " && true"; + case 1 -> "true && " + predicate; + case 2 -> predicate + " && false"; + case 3 -> "false && " + predicate; + case 4 -> predicate + " || true"; + case 5 -> "true || " + predicate; + case 6 -> predicate + " || false"; + case 7 -> "false || " + predicate; + case 8 -> predicate + " && " + predicate; + case 9 -> predicate + " || " + predicate; + case 10 -> predicate + " --> true"; + case 11 -> "false --> " + predicate; + case 12 -> "true --> " + predicate; + case 13 -> predicate + " --> " + predicate; + case 14 -> predicate + " == " + predicate; + case 15 -> predicate + " != " + predicate; + default -> "!!" + predicate; + }; + } + private static String comparisonOperator(SourceOfRandomness random) { return COMPARISON_OPS[random.nextInt(0, COMPARISON_OPS.length - 1)]; } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java new file mode 100644 index 000000000..38086b10e --- /dev/null +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java @@ -0,0 +1,99 @@ +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.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; + +import liquidjava.processor.SimplifiedVCImplication; +import liquidjava.processor.VCImplication; +import org.junit.jupiter.api.Test; + +class VCLogicalSimplificationTest { + + @Test + void applyReturnsNullForNullImplication() { + assertNull(VCLogicalSimplification.apply(null)); + } + + @Test + void simplifiesConjunctionWithBooleanLiterals() { + assertSimplificationSteps(VCLogicalSimplification::apply, vc("x && true"), chain(expect("x", "x && true"))); + assertSimplificationSteps(VCLogicalSimplification::apply, vc("true && x"), chain(expect("x", "true && x"))); + assertSimplificationSteps(VCLogicalSimplification::apply, vc("x && false"), + chain(expect("false", "x && false"))); + assertSimplificationSteps(VCLogicalSimplification::apply, vc("false && x"), + chain(expect("false", "false && x"))); + } + + @Test + void simplifiesDisjunctionWithBooleanLiterals() { + assertSimplificationSteps(VCLogicalSimplification::apply, vc("x || true"), chain(expect("true", "x || true"))); + assertSimplificationSteps(VCLogicalSimplification::apply, vc("true || x"), chain(expect("true", "true || x"))); + assertSimplificationSteps(VCLogicalSimplification::apply, vc("x || false"), chain(expect("x", "x || false"))); + assertSimplificationSteps(VCLogicalSimplification::apply, vc("false || x"), chain(expect("x", "false || x"))); + } + + @Test + void simplifiesDoubleNegation() { + assertSimplificationSteps(VCLogicalSimplification::apply, vc("!!x"), chain(expect("x", "!!x"))); + } + + @Test + void simplifiesDuplicateLogicalOperands() { + assertSimplificationSteps(VCLogicalSimplification::apply, vc("p && p"), chain(expect("p", "p && p"))); + assertSimplificationSteps(VCLogicalSimplification::apply, vc("p || p"), chain(expect("p", "p || p"))); + } + + @Test + void simplifiesSelfEqualityAndInequality() { + assertSimplificationSteps(VCLogicalSimplification::apply, vc("x == x"), chain(expect("true", "x == x"))); + assertSimplificationSteps(VCLogicalSimplification::apply, vc("x != x"), chain(expect("false", "x != x"))); + } + + @Test + void simplifiesImplicationIdentities() { + assertSimplificationSteps(VCLogicalSimplification::apply, vc("x --> true"), + chain(expect("true", "x --> true"))); + assertSimplificationSteps(VCLogicalSimplification::apply, vc("false --> x"), + chain(expect("true", "false --> x"))); + assertSimplificationSteps(VCLogicalSimplification::apply, vc("true --> x"), chain(expect("x", "true --> x"))); + assertSimplificationSteps(VCLogicalSimplification::apply, vc("x --> x"), chain(expect("true", "x --> x"))); + } + + @Test + void simplifiesOnlyFirstLogicalIdentity() { + assertSimplificationSteps(VCLogicalSimplification::apply, vc("x && true && false"), + chain(expect("x && false", "x && true && false"))); + } + + @Test + void simplifiesNestedExpressionsBeforeParent() { + assertSimplificationSteps(VCLogicalSimplification::apply, vc("(x && true) || false"), + chain(expect("x || false", "x && true || false"))); + } + + @Test + void simplifiesIteChildren() { + assertSimplificationSteps(VCLogicalSimplification::apply, vc("cond ? x && true : y || false"), + chain(expect("cond ? x : y || false", "cond ? x && true : y || false"))); + } + + @Test + void recordsOriginWhenSimplifyingLaterImplication() { + VCImplication implication = vc("x > 0", "y || false"); + + VCImplication result = assertSimplificationSteps(VCLogicalSimplification::apply, implication, + chain(expect("x > 0", "x > 0"), expect("y", "y || false"))); + + SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); + assertEquals("y || false", simplifiedNext.getOrigin().getRefinement().toString()); + } + + @Test + void preservesOriginFromExistingSimplifiedImplication() { + VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == y", "x == x")); + + assertSimplificationSteps(VCLogicalSimplification::apply, substituted, chain(expect("true", "∀x:int. x == x"))); + } +} 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 cb7e7d2a0..40a67b585 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 @@ -59,6 +59,29 @@ void simplifyOnceAppliesArithmeticWhenNoSubstitutionOrFoldingIsAvailable() { assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("x > 0", "x + 0 > 0"))); } + @Test + void simplifyOnceAppliesArithmeticBeforeLogicalSimplification() { + VCImplication implication = vc("x + 0 == x"); + + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("x == x", "x + 0 == x")), + chain(expect("true", "x + 0 == x"))); + } + + @Test + void simplifyOnceAppliesLogicalWhenNoEarlierSimplificationIsAvailable() { + VCImplication implication = vc("x && true"); + + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("x", "x && true"))); + } + + @Test + void simplifyAppliesLogicalStepsUntilFixedPoint() { + VCImplication implication = vc("x && true && true"); + + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + chain(expect("x && true", "x && true && true")), chain(expect("x", "x && true && true"))); + } + @Test void simplifyKeepsApplyingStepsUntilFixedPoint() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x + 1 > 3"); From d8f9aee767dd36fb5ec75a3833e347eae7ce7ee8 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sat, 13 Jun 2026 22:26:37 +0100 Subject: [PATCH 47/65] 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 40a67b585..124cf5247 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 @@ -87,8 +87,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 @@ -97,8 +97,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 @@ -109,8 +109,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 @@ -119,9 +119,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) { From f9e46249c3fcc8510e33a91156dbed5257c5ee60 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sat, 13 Jun 2026 22:29:09 +0100 Subject: [PATCH 48/65] Fix Origins --- .../rj_language/opt/VCArithmeticSimplification.java | 3 +-- .../rj_language/opt/VCArithmeticSimplificationTest.java | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java index 665a8520d..d2dbccbef 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java @@ -36,8 +36,7 @@ private static VCImplication apply(VCImplication implication, List n Expression expression = implication.getRefinement().getExpression(); Expression simplified = simplify(expression, nonZeroExpressions); if (!expression.equals(simplified)) { - VCImplication result = new SimplifiedVCImplication(implication, new Predicate(simplified), - implication.getOrigin()); + VCImplication result = new SimplifiedVCImplication(implication, new Predicate(simplified), implication); result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); return result; } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java index f38ac6ab2..88440c492 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java @@ -37,7 +37,7 @@ void simplifiesNegatedAdditionAndSubtraction() { assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x - x == 0"), chain(expect("0 == 0", "x - x == 0"))); assertSimplificationSteps(VCArithmeticSimplification::apply, vc("--x == x"), - chain(expect("x == x", "--x == x"))); + chain(expect("x == x", "-(-x) == x"))); assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x + -y == 0"), chain(expect("x - y == 0", "x + -y == 0"))); assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x - -y == 0"), @@ -94,14 +94,13 @@ void recordsOriginWhenSimplifyingLaterImplication() { chain(expect("x > 0", "x > 0"), expect("y > x", "y + 0 > x"))); SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); - assertEquals("y + 0 > x", simplifiedNext.getOrigin().getRefinement().toString()); + assertEquals("y + 0 > x", simplifiedNext.getOrigin().getRefinement().getExpression().toDisplayString()); } @Test - void preservesOriginFromExistingSimplifiedImplication() { + void recordsCurrentImplicationAsOriginWhenSimplifyingExistingSimplifiedImplication() { VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == y + 0", "x > 0")); - assertSimplificationSteps(VCArithmeticSimplification::apply, substituted, - chain(expect("y > 0", "∀x:int. x > 0"))); + assertSimplificationSteps(VCArithmeticSimplification::apply, substituted, chain(expect("y > 0", "y + 0 > 0"))); } } From 63aeaf96b773608111bdd83539e85c3b03428766 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sat, 13 Jun 2026 22:32:19 +0100 Subject: [PATCH 49/65] Fix Origins --- .../liquidjava/rj_language/opt/VCLogicalSimplification.java | 3 +-- .../rj_language/opt/VCLogicalSimplificationTest.java | 6 +++--- .../liquidjava/rj_language/opt/VCSimplificationTest.java | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java index 87b392bcd..743ad9a45 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java @@ -25,8 +25,7 @@ public static VCImplication apply(VCImplication implication) { Expression expression = implication.getRefinement().getExpression(); Expression simplified = simplify(expression); if (!expression.equals(simplified)) { - VCImplication result = new SimplifiedVCImplication(implication, new Predicate(simplified), - implication.getOrigin()); + VCImplication result = new SimplifiedVCImplication(implication, new Predicate(simplified), implication); result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); return result; } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java index 38086b10e..47b514382 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java @@ -87,13 +87,13 @@ void recordsOriginWhenSimplifyingLaterImplication() { chain(expect("x > 0", "x > 0"), expect("y", "y || false"))); SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); - assertEquals("y || false", simplifiedNext.getOrigin().getRefinement().toString()); + assertEquals("y || false", simplifiedNext.getOrigin().getRefinement().getExpression().toDisplayString()); } @Test - void preservesOriginFromExistingSimplifiedImplication() { + void recordsCurrentImplicationAsOriginWhenSimplifyingExistingSimplifiedImplication() { VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == y", "x == x")); - assertSimplificationSteps(VCLogicalSimplification::apply, substituted, chain(expect("true", "∀x:int. x == x"))); + assertSimplificationSteps(VCLogicalSimplification::apply, substituted, chain(expect("true", "y == y"))); } } 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 124cf5247..5565e3117 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 @@ -64,7 +64,7 @@ void simplifyOnceAppliesArithmeticBeforeLogicalSimplification() { VCImplication implication = vc("x + 0 == x"); assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("x == x", "x + 0 == x")), - chain(expect("true", "x + 0 == x"))); + chain(expect("true", "x == x"))); } @Test @@ -79,7 +79,7 @@ void simplifyAppliesLogicalStepsUntilFixedPoint() { VCImplication implication = vc("x && true && true"); assertSimplificationSteps(VCSimplification::simplifyOnce, implication, - chain(expect("x && true", "x && true && true")), chain(expect("x", "x && true && true"))); + chain(expect("x && true", "x && true && true")), chain(expect("x", "x && true"))); } @Test From 28c5a0f56908985c41eefdc8f7b1a86576931537 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sun, 14 Jun 2026 13:34:12 +0100 Subject: [PATCH 50/65] Refactor VC Simplification --- .../opt/VCArithmeticSimplification.java | 93 ++++++++----------- .../opt/VCExpressionSimplificationPass.java | 48 ++++++++++ .../liquidjava/rj_language/opt/VCFolding.java | 52 +++-------- .../opt/VCLogicalSimplification.java | 60 ++++-------- .../rj_language/opt/VCSimplification.java | 7 +- .../rj_language/opt/VCSimplificationPass.java | 10 ++ .../rj_language/opt/VCSubstitution.java | 21 +++-- .../opt/VCArithmeticSimplificationTest.java | 78 ++++++---------- .../rj_language/opt/VCFoldingTest.java | 84 ++++++++--------- .../opt/VCLogicalSimplificationTest.java | 57 +++++------- .../VCSimplificationPropertyBasedTest.java | 16 +++- .../rj_language/opt/VCSubstitutionTest.java | 32 ++++--- 12 files changed, 263 insertions(+), 295 deletions(-) create mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCExpressionSimplificationPass.java create mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationPass.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java index d2dbccbef..959c799d2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCArithmeticSimplification.java @@ -3,9 +3,7 @@ import java.util.ArrayList; import java.util.List; -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.GroupExpression; @@ -17,46 +15,29 @@ /** * Simplifies VCImplication chains by applying arithmetic identities inside refinements */ -public class VCArithmeticSimplification { +public class VCArithmeticSimplification extends VCExpressionSimplificationPass> { - /** - * Applies the first arithmetic simplification available in a VC chain - */ - public static VCImplication apply(VCImplication implication) { - if (implication == null) - return null; - - return apply(implication, List.of()); + @Override + protected List initialContext() { + return List.of(); } - private static VCImplication apply(VCImplication implication, List nonZeroExpressions) { - if (implication == null) - return null; - - Expression expression = implication.getRefinement().getExpression(); - Expression simplified = simplify(expression, nonZeroExpressions); - if (!expression.equals(simplified)) { - VCImplication result = new SimplifiedVCImplication(implication, new Predicate(simplified), implication); - result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); - return result; - } - - List nextNoneZeroExpressions = new ArrayList<>(nonZeroExpressions); - addNonZeroExpression(implication.getRefinement().getExpression(), nextNoneZeroExpressions); - - VCImplication next = apply(implication.getNext(), nextNoneZeroExpressions); - if (implication.getNext() == null || implication.getNext().equals(next)) - return implication; + @Override + protected List nextContext(List nonZeroExpressions, VCImplication implication) { + List nextNonZeroExpressions = new ArrayList<>(nonZeroExpressions); + addNonZeroExpression(implication.getRefinement().getExpression(), nextNonZeroExpressions); + return nextNonZeroExpressions; + } - VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); - result.setNext(next); - return result; + @Override + protected Expression simplify(Expression expression, List nonZeroExpressions) { + return simplifyExpression(expression, nonZeroExpressions); } /** * Simplifies the first arithmetic identity found inside an expression */ - private static Expression simplify(Expression expression, List nonZeroExpressions) { + private Expression simplifyExpression(Expression expression, List nonZeroExpressions) { if (expression instanceof BinaryExpression binary) return simplifyBinary(binary, nonZeroExpressions); if (expression instanceof UnaryExpression unary) @@ -71,14 +52,14 @@ private static Expression simplify(Expression expression, List nonZe /** * Simplifies a binary expression by visiting operands before the current node */ - private static Expression simplifyBinary(BinaryExpression binary, List nonZeroExpressions) { + private Expression simplifyBinary(BinaryExpression binary, List nonZeroExpressions) { Expression left = binary.getFirstOperand(); - Expression simplifiedLeft = simplify(left, nonZeroExpressions); + Expression simplifiedLeft = simplifyExpression(left, nonZeroExpressions); if (!left.equals(simplifiedLeft)) return new BinaryExpression(simplifiedLeft, binary.getOperator(), binary.getSecondOperand().clone()); Expression right = binary.getSecondOperand(); - Expression simplifiedRight = simplify(right, nonZeroExpressions); + Expression simplifiedRight = simplifyExpression(right, nonZeroExpressions); if (!right.equals(simplifiedRight)) return new BinaryExpression(left.clone(), binary.getOperator(), simplifiedRight); @@ -92,9 +73,9 @@ private static Expression simplifyBinary(BinaryExpression binary, List nonZeroExpressions) { + private Expression simplifyUnary(UnaryExpression unary, List nonZeroExpressions) { Expression operand = unary.getExpression(); - Expression simplifiedOperand = simplify(operand, nonZeroExpressions); + Expression simplifiedOperand = simplifyExpression(operand, nonZeroExpressions); if (!operand.equals(simplifiedOperand)) return new UnaryExpression(unary.getOp(), simplifiedOperand); @@ -108,19 +89,19 @@ private static Expression simplifyUnary(UnaryExpression unary, List /** * Simplifies a ternary expression by visiting condition, then branch, and else branch */ - private static Expression simplifyIte(Ite ite, List nonZeroExpressions) { + private Expression simplifyIte(Ite ite, List nonZeroExpressions) { Expression condition = ite.getCondition(); - Expression simplifiedCondition = simplify(condition, nonZeroExpressions); + Expression simplifiedCondition = simplifyExpression(condition, nonZeroExpressions); if (!condition.equals(simplifiedCondition)) return new Ite(simplifiedCondition, ite.getThen().clone(), ite.getElse().clone()); Expression thenExpression = ite.getThen(); - Expression simplifiedThen = simplify(thenExpression, nonZeroExpressions); + Expression simplifiedThen = simplifyExpression(thenExpression, nonZeroExpressions); if (!thenExpression.equals(simplifiedThen)) return new Ite(condition.clone(), simplifiedThen, ite.getElse().clone()); Expression elseExpression = ite.getElse(); - Expression simplifiedElse = simplify(elseExpression, nonZeroExpressions); + Expression simplifiedElse = simplifyExpression(elseExpression, nonZeroExpressions); if (!elseExpression.equals(simplifiedElse)) return new Ite(condition.clone(), thenExpression.clone(), simplifiedElse); @@ -130,9 +111,9 @@ private static Expression simplifyIte(Ite ite, List nonZeroExpressio /** * Simplifies an expression wrapped in parentheses while preserving the group node */ - private static Expression simplifyGroup(GroupExpression group, List nonZeroExpressions) { + private Expression simplifyGroup(GroupExpression group, List nonZeroExpressions) { Expression expression = group.getExpression(); - Expression simplified = simplify(expression, nonZeroExpressions); + Expression simplified = simplifyExpression(expression, nonZeroExpressions); if (!expression.equals(simplified)) return new GroupExpression(simplified); return group.clone(); @@ -141,7 +122,7 @@ private static Expression simplifyGroup(GroupExpression group, List /** * Dispatches a local binary arithmetic identity by operator */ - private static Expression simplifyLocalBinary(Expression left, Expression right, String op, + private Expression simplifyLocalBinary(Expression left, Expression right, String op, List nonZeroExpressions) { return switch (op) { case "+" -> simplifyAddition(left, right); @@ -156,7 +137,7 @@ private static Expression simplifyLocalBinary(Expression left, Expression right, /** * Applies addition identities involving zero and unary negation */ - private static Expression simplifyAddition(Expression left, Expression right) { + private Expression simplifyAddition(Expression left, Expression right) { // x + 0 -> x if (isZero(right)) return left.clone(); @@ -178,7 +159,7 @@ private static Expression simplifyAddition(Expression left, Expression right) { /** * Applies subtraction identities involving zero, same operands, and unary negation */ - private static Expression simplifySubtraction(Expression left, Expression right) { + private Expression simplifySubtraction(Expression left, Expression right) { // x - 0 -> x if (isZero(right)) return left.clone(); @@ -197,7 +178,7 @@ private static Expression simplifySubtraction(Expression left, Expression right) /** * Applies multiplication identities involving one and zero */ - private static Expression simplifyMultiplication(Expression left, Expression right) { + private Expression simplifyMultiplication(Expression left, Expression right) { // x * 1 -> x if (isOne(right)) return left.clone(); @@ -216,7 +197,7 @@ private static Expression simplifyMultiplication(Expression left, Expression rig /** * Applies division identities, using prior non-zero premises when needed */ - private static Expression simplifyDivision(Expression left, Expression right, List nonZeroExpressions) { + private Expression simplifyDivision(Expression left, Expression right, List nonZeroExpressions) { // x / 1 -> x if (isOne(right)) return left.clone(); @@ -232,7 +213,7 @@ private static Expression simplifyDivision(Expression left, Expression right, Li /** * Applies modulo identities, using prior non-zero premises when needed */ - private static Expression simplifyModulo(Expression left, Expression right, List nonZeroExpressions) { + private Expression simplifyModulo(Expression left, Expression right, List nonZeroExpressions) { // x % 1 -> 0 if (isOne(right)) return new LiteralInt(0); @@ -245,7 +226,7 @@ private static Expression simplifyModulo(Expression left, Expression right, List /** * Records direct non-zero premises shaped as x != 0 or 0 != x */ - private static void addNonZeroExpression(Expression expression, List nonZeroExpressions) { + private void addNonZeroExpression(Expression expression, List nonZeroExpressions) { if (!(expression instanceof BinaryExpression binary) || !"!=".equals(binary.getOperator())) return; @@ -260,14 +241,14 @@ private static void addNonZeroExpression(Expression expression, List /** * Checks whether a previous premise recorded an expression as non-zero */ - private static boolean isNonZero(Expression expression, List nonZeroExpressions) { + private boolean isNonZero(Expression expression, List nonZeroExpressions) { return nonZeroExpressions.stream().anyMatch(e -> e.equals(expression)); } /** * Checks whether an expression is a numeric zero literal */ - private static boolean isZero(Expression expression) { + private boolean isZero(Expression expression) { if (expression instanceof LiteralInt literal) return literal.getValue() == 0; if (expression instanceof LiteralReal literal) @@ -278,7 +259,7 @@ private static boolean isZero(Expression expression) { /** * Checks whether an expression is a numeric one literal */ - private static boolean isOne(Expression expression) { + private boolean isOne(Expression expression) { if (expression instanceof LiteralInt literal) return literal.getValue() == 1; if (expression instanceof LiteralReal literal) @@ -289,14 +270,14 @@ private static boolean isOne(Expression expression) { /** * Checks whether an expression is unary negation */ - private static boolean isNegation(Expression expression) { + private boolean isNegation(Expression expression) { return expression instanceof UnaryExpression unary && "-".equals(unary.getOp()); } /** * Returns the operand of a unary negation expression */ - private static Expression negatedExpression(Expression expression) { + private Expression negatedExpression(Expression expression) { return ((UnaryExpression) expression).getExpression(); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCExpressionSimplificationPass.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCExpressionSimplificationPass.java new file mode 100644 index 000000000..c4baf16a7 --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCExpressionSimplificationPass.java @@ -0,0 +1,48 @@ +package liquidjava.rj_language.opt; + +import liquidjava.processor.SimplifiedVCImplication; +import liquidjava.processor.VCImplication; +import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.ast.Expression; + +/** + * Base implementation for passes that simplify one refinement expression at a time. + */ +abstract class VCExpressionSimplificationPass implements VCSimplificationPass { + + @Override + public final VCImplication apply(VCImplication implication) { + return apply(implication, initialContext()); + } + + protected C initialContext() { + return null; + } + + protected C nextContext(C context, VCImplication implication) { + return context; + } + + protected abstract Expression simplify(Expression expression, C context); + + private VCImplication apply(VCImplication implication, C context) { + if (implication == null) + return null; + + Expression expression = implication.getRefinement().getExpression(); + Expression simplified = simplify(expression, context); + if (!expression.equals(simplified)) { + VCImplication result = new SimplifiedVCImplication(implication, new Predicate(simplified), implication); + result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); + return result; + } + + VCImplication next = apply(implication.getNext(), nextContext(context, implication)); + if (implication.getNext() == null || implication.getNext().equals(next)) + return implication; + + VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); + result.setNext(next); + return result; + } +} 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 a8927f2b4..eaa4760d4 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,8 +1,5 @@ package liquidjava.rj_language.opt; -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.Expression; @@ -16,36 +13,17 @@ /** * Simplifies VCImplication chains by folding constant expressions and other foldable patterns inside refinements */ -public class VCFolding { +public class VCFolding extends VCExpressionSimplificationPass { - /** - * Applies folding to the first foldable predicate in a VC chain - */ - public static VCImplication apply(VCImplication implication) { - if (implication == null) - return null; - - Expression expression = implication.getRefinement().getExpression(); - Expression folded = fold(expression); - if (!expression.equals(folded)) { - VCImplication result = new SimplifiedVCImplication(implication, new Predicate(folded), implication); - result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); - return result; - } - - VCImplication next = apply(implication.getNext()); - if (implication.getNext() == null || implication.getNext().equals(next)) - return implication; - - VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); - result.setNext(next); - return result; + @Override + protected Expression simplify(Expression expression, Void context) { + return fold(expression); } /** * Folds the first foldable expression found */ - private static Expression fold(Expression expression) { + private Expression fold(Expression expression) { // enum constant -> literal if (expression instanceof Enum en && en.getResolvedLiteral() != null) return en.getResolvedLiteral().clone(); @@ -64,7 +42,7 @@ private static Expression fold(Expression expression) { /** * Folds a binary expression and its operands */ - private static Expression foldBinary(BinaryExpression binary) { + private Expression foldBinary(BinaryExpression binary) { Expression left = binary.getFirstOperand(); Expression foldedLeft = fold(left); if (!left.equals(foldedLeft)) @@ -91,7 +69,7 @@ private static Expression foldBinary(BinaryExpression binary) { /** * Folds a unary expression and its operand */ - private static Expression foldUnary(UnaryExpression unary) { + private Expression foldUnary(UnaryExpression unary) { Expression operand = unary.getExpression(); Expression foldedOperand = fold(operand); if (!operand.equals(foldedOperand)) @@ -118,7 +96,7 @@ private static Expression foldUnary(UnaryExpression unary) { /** * Folds a conditional expression and its branches */ - private static Expression foldIte(Ite ite) { + private Expression foldIte(Ite ite) { Expression condition = ite.getCondition(); Expression foldedCondition = fold(condition); if (!condition.equals(foldedCondition)) @@ -149,7 +127,7 @@ private static Expression foldIte(Ite ite) { /** * Folds a binary expression whose operands are both literals */ - private static Expression foldLiteralBinary(Expression left, Expression right, String op) { + private Expression foldLiteralBinary(Expression left, Expression right, String op) { if (left instanceof LiteralInt leftInt && right instanceof LiteralInt rightInt) return foldInts(leftInt.getValue(), rightInt.getValue(), op); @@ -183,7 +161,7 @@ private static Expression foldLiteralBinary(Expression left, Expression right, S /** * Combines adjacent integer constants in additions and subtractions */ - private static Expression foldAdjacentInts(Expression left, Expression right, String op) { + private Expression foldAdjacentInts(Expression left, Expression right, String op) { if (!"+".equals(op) && !"-".equals(op)) return null; if (!(right instanceof LiteralInt rightLiteral)) @@ -213,7 +191,7 @@ private static Expression foldAdjacentInts(Expression left, Expression right, St /** * Folds integer operations */ - private static Expression foldInts(int left, int right, String op) { + private Expression foldInts(int left, int right, String op) { return switch (op) { case "+" -> new LiteralInt(left + right); case "-" -> new LiteralInt(left - right); @@ -233,7 +211,7 @@ private static Expression foldInts(int left, int right, String op) { /** * Folds real number operations */ - private static Expression foldReals(double left, double right, String op) { + private Expression foldReals(double left, double right, String op) { return switch (op) { case "+" -> new LiteralReal(left + right); case "-" -> new LiteralReal(left - right); @@ -253,7 +231,7 @@ private static Expression foldReals(double left, double right, String op) { /** * Folds boolean operations */ - private static Expression foldBooleans(boolean left, boolean right, String op) { + private Expression foldBooleans(boolean left, boolean right, String op) { return switch (op) { case "&&" -> new LiteralBoolean(left && right); case "||" -> new LiteralBoolean(left || right); @@ -267,7 +245,7 @@ private static Expression foldBooleans(boolean left, boolean right, String op) { /** * Checks whether two expressions mix integer and real literals */ - private static boolean isMixedNumeric(Expression left, Expression right) { + private boolean isMixedNumeric(Expression left, Expression right) { return left instanceof LiteralInt && right instanceof LiteralReal || left instanceof LiteralReal && right instanceof LiteralInt; } @@ -275,7 +253,7 @@ private static boolean isMixedNumeric(Expression left, Expression right) { /** * Reads a numeric literal as a double */ - private static double numericValue(Expression expression) { + private double numericValue(Expression expression) { if (expression instanceof LiteralInt literal) return literal.getValue(); return ((LiteralReal) expression).getValue(); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java index 743ad9a45..84c3b3caa 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java @@ -1,8 +1,5 @@ package liquidjava.rj_language.opt; -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.GroupExpression; @@ -13,36 +10,17 @@ /** * Simplifies VCImplication chains by applying logical identities inside refinements */ -public class VCLogicalSimplification { +public class VCLogicalSimplification extends VCExpressionSimplificationPass { - /** - * Applies the first logical simplification available in a VC chain - */ - public static VCImplication apply(VCImplication implication) { - if (implication == null) - return null; - - Expression expression = implication.getRefinement().getExpression(); - Expression simplified = simplify(expression); - if (!expression.equals(simplified)) { - VCImplication result = new SimplifiedVCImplication(implication, new Predicate(simplified), implication); - result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); - return result; - } - - VCImplication next = apply(implication.getNext()); - if (implication.getNext() == null || implication.getNext().equals(next)) - return implication; - - VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); - result.setNext(next); - return result; + @Override + protected Expression simplify(Expression expression, Void context) { + return simplify(expression); } /** * Simplifies the first logical identity found inside an expression */ - private static Expression simplify(Expression expression) { + private Expression simplify(Expression expression) { if (expression instanceof BinaryExpression binary) return simplifyBinary(binary); if (expression instanceof UnaryExpression unary) @@ -57,7 +35,7 @@ private static Expression simplify(Expression expression) { /** * Simplifies a binary expression by visiting operands before the current node */ - private static Expression simplifyBinary(BinaryExpression binary) { + private Expression simplifyBinary(BinaryExpression binary) { Expression left = binary.getFirstOperand(); Expression simplifiedLeft = simplify(left); if (!left.equals(simplifiedLeft)) @@ -78,7 +56,7 @@ private static Expression simplifyBinary(BinaryExpression binary) { /** * Simplifies a unary expression by visiting its operand before the current node */ - private static Expression simplifyUnary(UnaryExpression unary) { + private Expression simplifyUnary(UnaryExpression unary) { Expression operand = unary.getExpression(); Expression simplifiedOperand = simplify(operand); if (!operand.equals(simplifiedOperand)) @@ -94,7 +72,7 @@ private static Expression simplifyUnary(UnaryExpression unary) { /** * Simplifies a ternary expression by visiting condition, then branch, and else branch */ - private static Expression simplifyIte(Ite ite) { + private Expression simplifyIte(Ite ite) { Expression condition = ite.getCondition(); Expression simplifiedCondition = simplify(condition); if (!condition.equals(simplifiedCondition)) @@ -116,7 +94,7 @@ private static Expression simplifyIte(Ite ite) { /** * Simplifies an expression wrapped in parentheses while preserving the group node */ - private static Expression simplifyGroup(GroupExpression group) { + private Expression simplifyGroup(GroupExpression group) { Expression expression = group.getExpression(); Expression simplified = simplify(expression); if (!expression.equals(simplified)) @@ -127,7 +105,7 @@ private static Expression simplifyGroup(GroupExpression group) { /** * Dispatches a local binary logical identity by operator */ - private static Expression simplifyLocalBinary(Expression left, Expression right, String op) { + private Expression simplifyLocalBinary(Expression left, Expression right, String op) { return switch (op) { case "&&" -> simplifyConjunction(left, right); case "||" -> simplifyDisjunction(left, right); @@ -141,7 +119,7 @@ private static Expression simplifyLocalBinary(Expression left, Expression right, /** * Applies conjunction identities involving boolean literals and same operands */ - private static Expression simplifyConjunction(Expression left, Expression right) { + private Expression simplifyConjunction(Expression left, Expression right) { // x && true -> x if (isTrue(right)) return left.clone(); @@ -163,7 +141,7 @@ private static Expression simplifyConjunction(Expression left, Expression right) /** * Applies disjunction identities involving boolean literals and same operands */ - private static Expression simplifyDisjunction(Expression left, Expression right) { + private Expression simplifyDisjunction(Expression left, Expression right) { // x || true -> true if (isTrue(right)) return right.clone(); @@ -185,7 +163,7 @@ private static Expression simplifyDisjunction(Expression left, Expression right) /** * Applies equality identity for same operands */ - private static Expression simplifyEquality(Expression left, Expression right) { + private Expression simplifyEquality(Expression left, Expression right) { // x == x -> true if (left.equals(right)) return new LiteralBoolean(true); @@ -195,7 +173,7 @@ private static Expression simplifyEquality(Expression left, Expression right) { /** * Applies inequality identity for same operands */ - private static Expression simplifyInequality(Expression left, Expression right) { + private Expression simplifyInequality(Expression left, Expression right) { // x != x -> false if (left.equals(right)) return new LiteralBoolean(false); @@ -205,7 +183,7 @@ private static Expression simplifyInequality(Expression left, Expression right) /** * Applies implication identities involving boolean literals and same operands */ - private static Expression simplifyImplication(Expression left, Expression right) { + private Expression simplifyImplication(Expression left, Expression right) { // x --> true -> true if (isTrue(right)) return right.clone(); @@ -224,28 +202,28 @@ private static Expression simplifyImplication(Expression left, Expression right) /** * Checks whether an expression is true */ - private static boolean isTrue(Expression expression) { + private boolean isTrue(Expression expression) { return expression instanceof LiteralBoolean literal && literal.isBooleanTrue(); } /** * Checks whether an expression is false */ - private static boolean isFalse(Expression expression) { + private boolean isFalse(Expression expression) { return expression instanceof LiteralBoolean literal && !literal.isBooleanTrue(); } /** * Checks whether an expression is unary logical negation */ - private static boolean isNot(Expression expression) { + private boolean isNot(Expression expression) { return expression instanceof UnaryExpression unary && "!".equals(unary.getOp()); } /** * Returns the operand of a unary logical negation expression */ - private static Expression negatedExpression(Expression expression) { + private Expression negatedExpression(Expression expression) { return ((UnaryExpression) expression).getExpression(); } } 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 4c0dbec44..38eb7f3bd 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 @@ -1,7 +1,6 @@ package liquidjava.rj_language.opt; import java.util.List; -import java.util.function.UnaryOperator; import liquidjava.processor.VCImplication; @@ -10,8 +9,8 @@ */ public class VCSimplification { - private static final List> PASSES = List.of(VCSubstitution::apply, VCFolding::apply, - VCArithmeticSimplification::apply, VCLogicalSimplification::apply); + private static final List PASSES = List.of(new VCSubstitution(), new VCFolding(), + new VCArithmeticSimplification(), new VCLogicalSimplification()); /** * Applies all available simplification steps to a VC chain until a fixed point is reached @@ -37,7 +36,7 @@ public static VCImplication simplifyOnce(VCImplication implication) { if (implication == null) return null; - for (UnaryOperator pass : PASSES) { + for (VCSimplificationPass pass : PASSES) { VCImplication simplified = pass.apply(implication); if (!implication.equals(simplified)) return simplified; diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationPass.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationPass.java new file mode 100644 index 000000000..0d58cf68d --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationPass.java @@ -0,0 +1,10 @@ +package liquidjava.rj_language.opt; + +import liquidjava.processor.VCImplication; + +/** + * Applies one simplification step to a VC implication chain. + */ +public interface VCSimplificationPass { + VCImplication apply(VCImplication 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 21133bc1b..82d4b08c0 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 @@ -14,7 +14,7 @@ /** * Simplifies VCImplication chains by replacing binder equalities with their known values */ -public class VCSubstitution { +public class VCSubstitution implements VCSimplificationPass { /** * A substitution discovered from an implication node @@ -25,17 +25,18 @@ private record Substitution(VCImplication node, Expression replacement) { /** * Applies one substitution in a VC chain */ - public static VCImplication apply(VCImplication implication) { + @Override + public VCImplication apply(VCImplication implication) { if (implication == null) return null; VCImplication result = implication.clone(); - Optional substitutionOpt = VCSubstitution.findSubstitution(result); + Optional substitutionOpt = 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 = substitute(result, substitution.node(), substitution.replacement()); } return result; } @@ -43,7 +44,7 @@ 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 VCImplication substitute(VCImplication implication, VCImplication node, Expression replacement) { if (implication == null) return null; @@ -60,7 +61,7 @@ private static VCImplication substitute(VCImplication implication, VCImplication /** * Substitutes a source binder inside one VC node while preserving simplification metadata */ - private static VCImplication substituteNode(VCImplication implication, VCImplication node, Expression replacement) { + private 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)); @@ -73,7 +74,7 @@ private static VCImplication substituteNode(VCImplication implication, VCImplica /** * Finds the first substitution candidate in the VC chain */ - private static Optional findSubstitution(VCImplication implication) { + private Optional findSubstitution(VCImplication implication) { if (implication == null) return Optional.empty(); @@ -87,7 +88,7 @@ private static Optional findSubstitution(VCImplication implication /** * Extracts a substitution from one binder equality */ - private static Optional getSubstitution(VCImplication implication) { + private Optional getSubstitution(VCImplication implication) { if (!implication.hasBinder()) return Optional.empty(); @@ -110,14 +111,14 @@ 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 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 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/VCArithmeticSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java index 88440c492..e6f96979b 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java @@ -11,78 +11,61 @@ class VCArithmeticSimplificationTest { + private final VCArithmeticSimplification simplification = new VCArithmeticSimplification(); + @Test void applyReturnsNullForNullImplication() { - assertNull(VCArithmeticSimplification.apply(null)); + assertNull(simplification.apply(null)); } @Test void simplifiesAdditiveIdentities() { - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x + 0 > 0"), - chain(expect("x > 0", "x + 0 > 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("0 + x > 0"), - chain(expect("x > 0", "0 + x > 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x - 0 > 0"), - chain(expect("x > 0", "x - 0 > 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("0 - x > 0"), - chain(expect("-x > 0", "0 - x > 0"))); + assertSimplificationSteps(simplification::apply, vc("x + 0 > 0"), chain(expect("x > 0", "x + 0 > 0"))); + assertSimplificationSteps(simplification::apply, vc("0 + x > 0"), chain(expect("x > 0", "0 + x > 0"))); + assertSimplificationSteps(simplification::apply, vc("x - 0 > 0"), chain(expect("x > 0", "x - 0 > 0"))); + assertSimplificationSteps(simplification::apply, vc("0 - x > 0"), chain(expect("-x > 0", "0 - x > 0"))); } @Test void simplifiesNegatedAdditionAndSubtraction() { - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x + -x == 0"), - chain(expect("0 == 0", "x + -x == 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("-x + x == 0"), - chain(expect("0 == 0", "-x + x == 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x - x == 0"), - chain(expect("0 == 0", "x - x == 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("--x == x"), - chain(expect("x == x", "-(-x) == x"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x + -y == 0"), - chain(expect("x - y == 0", "x + -y == 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x - -y == 0"), - chain(expect("x + y == 0", "x - -y == 0"))); + assertSimplificationSteps(simplification::apply, vc("x + -x == 0"), chain(expect("0 == 0", "x + -x == 0"))); + assertSimplificationSteps(simplification::apply, vc("-x + x == 0"), chain(expect("0 == 0", "-x + x == 0"))); + assertSimplificationSteps(simplification::apply, vc("x - x == 0"), chain(expect("0 == 0", "x - x == 0"))); + assertSimplificationSteps(simplification::apply, vc("--x == x"), chain(expect("x == x", "-(-x) == x"))); + assertSimplificationSteps(simplification::apply, vc("x + -y == 0"), chain(expect("x - y == 0", "x + -y == 0"))); + assertSimplificationSteps(simplification::apply, vc("x - -y == 0"), chain(expect("x + y == 0", "x - -y == 0"))); } @Test void simplifiesMultiplicativeIdentities() { - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x * 1 > 0"), - chain(expect("x > 0", "x * 1 > 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("1 * x > 0"), - chain(expect("x > 0", "1 * x > 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x * 0 == 0"), - chain(expect("0 == 0", "x * 0 == 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("0 * x == 0"), - chain(expect("0 == 0", "0 * x == 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x / 1 > 0"), - chain(expect("x > 0", "x / 1 > 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x % 1 == 0"), - chain(expect("0 == 0", "x % 1 == 0"))); + assertSimplificationSteps(simplification::apply, vc("x * 1 > 0"), chain(expect("x > 0", "x * 1 > 0"))); + assertSimplificationSteps(simplification::apply, vc("1 * x > 0"), chain(expect("x > 0", "1 * x > 0"))); + assertSimplificationSteps(simplification::apply, vc("x * 0 == 0"), chain(expect("0 == 0", "x * 0 == 0"))); + assertSimplificationSteps(simplification::apply, vc("0 * x == 0"), chain(expect("0 == 0", "0 * x == 0"))); + assertSimplificationSteps(simplification::apply, vc("x / 1 > 0"), chain(expect("x > 0", "x / 1 > 0"))); + assertSimplificationSteps(simplification::apply, vc("x % 1 == 0"), chain(expect("0 == 0", "x % 1 == 0"))); } @Test void simplifiesGuardedDivisionAndModuloIdentities() { - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x != 0", "0 / x == 0"), + assertSimplificationSteps(simplification::apply, vc("x != 0", "0 / x == 0"), chain(expect("x != 0", "x != 0"), expect("0 == 0", "0 / x == 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x != 0", "x / x == 1"), + assertSimplificationSteps(simplification::apply, vc("x != 0", "x / x == 1"), chain(expect("x != 0", "x != 0"), expect("1 == 1", "x / x == 1"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("0 != x", "x % x == 0"), + assertSimplificationSteps(simplification::apply, vc("0 != x", "x % x == 0"), chain(expect("0 != x", "0 != x"), expect("0 == 0", "x % x == 0"))); } @Test void leavesUnguardedDivisionAndModuloIdentitiesUnchanged() { - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("0 / x == 0"), - chain(expect("0 / x == 0", "0 / x == 0"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x / x == 1"), - chain(expect("x / x == 1", "x / x == 1"))); - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x % x == 0"), - chain(expect("x % x == 0", "x % x == 0"))); + assertSimplificationSteps(simplification::apply, vc("0 / x == 0"), chain(expect("0 / x == 0", "0 / x == 0"))); + assertSimplificationSteps(simplification::apply, vc("x / x == 1"), chain(expect("x / x == 1", "x / x == 1"))); + assertSimplificationSteps(simplification::apply, vc("x % x == 0"), chain(expect("x % x == 0", "x % x == 0"))); } @Test void simplifiesOnlyFirstArithmeticIdentity() { - assertSimplificationSteps(VCArithmeticSimplification::apply, vc("x + 0 + 1 > 0"), + assertSimplificationSteps(simplification::apply, vc("x + 0 + 1 > 0"), chain(expect("x + 1 > 0", "x + 0 + 1 > 0"))); } @@ -90,17 +73,10 @@ void simplifiesOnlyFirstArithmeticIdentity() { void recordsOriginWhenSimplifyingLaterImplication() { VCImplication implication = vc("x > 0", "y + 0 > x"); - VCImplication result = assertSimplificationSteps(VCArithmeticSimplification::apply, implication, + VCImplication result = assertSimplificationSteps(simplification::apply, implication, chain(expect("x > 0", "x > 0"), expect("y > x", "y + 0 > x"))); SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); assertEquals("y + 0 > x", simplifiedNext.getOrigin().getRefinement().getExpression().toDisplayString()); } - - @Test - void recordsCurrentImplicationAsOriginWhenSimplifyingExistingSimplifiedImplication() { - VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == y + 0", "x > 0")); - - assertSimplificationSteps(VCArithmeticSimplification::apply, substituted, chain(expect("y > 0", "y + 0 > 0"))); - } } 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 b73bd8ec3..ebb9493f5 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,18 +16,20 @@ class VCFoldingTest { + private final VCFolding folding = new VCFolding(); + @Test void applyReturnsNullForNullImplication() { - assertNull(VCFolding.apply(null)); + assertNull(folding.apply(null)); } @Test void foldsIntegerArithmeticAndComparisons() { VCImplication implication = vc("1 + 2 == 3"); - assertSimplificationSteps(VCFolding::apply, implication, chain(expect("3 == 3", "1 + 2 == 3")), + assertSimplificationSteps(folding::apply, implication, chain(expect("3 == 3", "1 + 2 == 3")), chain(expect("true", "3 == 3"))); - assertSimplificationSteps(VCFolding::apply, vc("4 > 7"), chain(expect("false", "4 > 7"))); + assertSimplificationSteps(folding::apply, vc("4 > 7"), chain(expect("false", "4 > 7"))); } @Test @@ -35,23 +37,23 @@ void foldsRealAndMixedNumericExpressions() { VCImplication realArithmetic = vc("1.5 + 2.0 == 3.5"); VCImplication mixedArithmetic = vc("2 + 0.5 > 2"); - assertSimplificationSteps(VCFolding::apply, realArithmetic, chain(expect("3.5 == 3.5", "1.5 + 2.0 == 3.5")), + assertSimplificationSteps(folding::apply, realArithmetic, chain(expect("3.5 == 3.5", "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")), + assertSimplificationSteps(folding::apply, mixedArithmetic, chain(expect("2.5 > 2", "2 + 0.5 > 2")), chain(expect("true", "2.5 > 2"))); } @Test void leavesDivisionAndModuloByZeroUnchanged() { - 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"))); + assertSimplificationSteps(folding::apply, vc("4 / 0 == 0"), chain(expect("4 / 0 == 0", "4 / 0 == 0"))); + assertSimplificationSteps(folding::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"), + assertSimplificationSteps(folding::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"), + assertSimplificationSteps(folding::apply, vc("4.0 % 0.0 == 0.0"), chain(expect("4.0 % 0.0 == 0.0", "4.0 % 0.0 == 0.0"))); } @@ -59,8 +61,7 @@ void leavesRealDivisionAndModuloByZeroUnchanged() { void foldsIntegerDivisionTowardZeroForNegativeResults() { VCImplication implication = vc("(2 - 7) / 2 == -2"); - assertSimplificationSteps(VCFolding::apply, implication, - chain(expect("(2 - 7) / 2 == -2", "(2 - 7) / 2 == -2")), + assertSimplificationSteps(folding::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"))); } @@ -70,73 +71,73 @@ 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")), + assertSimplificationSteps(folding::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")), + assertSimplificationSteps(folding::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"))); - assertSimplificationSteps(VCFolding::apply, vc("false --> true"), chain(expect("true", "false --> true"))); - assertSimplificationSteps(VCFolding::apply, vc("true != false"), chain(expect("true", "true != false"))); + assertSimplificationSteps(folding::apply, vc("true && false"), chain(expect("false", "true && false"))); + assertSimplificationSteps(folding::apply, vc("false --> true"), chain(expect("true", "false --> true"))); + assertSimplificationSteps(folding::apply, vc("true != false"), chain(expect("true", "true != false"))); } @Test void foldsBooleanSubexpressionsInsideLargerExpression() { - assertSimplificationSteps(VCFolding::apply, vc("true && false || ok"), + assertSimplificationSteps(folding::apply, vc("true && false || ok"), chain(expect("false || ok", "true && false || ok"))); } @Test void foldsNestedConstantsInsideLargerExpression() { - 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"))); + assertSimplificationSteps(folding::apply, vc("x > 1 + 2"), chain(expect("x > 3", "x > 1 + 2"))); + assertSimplificationSteps(folding::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"), chain(expect("3 < x + 4", "1 + 2 < x + 4"))); + assertSimplificationSteps(folding::apply, vc("1 + 2 < x + 4"), chain(expect("3 < x + 4", "1 + 2 < x + 4"))); } @Test void foldsUnaryExpressions() { - assertSimplificationSteps(VCFolding::apply, vc("!true"), chain(expect("false", "!true"))); + assertSimplificationSteps(folding::apply, vc("!true"), chain(expect("false", "!true"))); VCImplication implication = vc("-3 < 0"); - assertSimplificationSteps(VCFolding::apply, implication, chain(expect("-3 < 0", "-3 < 0")), + assertSimplificationSteps(folding::apply, implication, chain(expect("-3 < 0", "-3 < 0")), chain(expect("true", "-3 < 0"))); } @Test void foldsIteExpressions() { - 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"))); + assertSimplificationSteps(folding::apply, vc("true ? a : b"), chain(expect("a", "true ? a : b"))); + assertSimplificationSteps(folding::apply, vc("false ? a : b"), chain(expect("b", "false ? a : b"))); + assertSimplificationSteps(folding::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, chain(expect("cond ? 3 : 3", "cond ? 1 + 2 : 3")), + assertSimplificationSteps(folding::apply, implication, chain(expect("cond ? 3 : 3", "cond ? 1 + 2 : 3")), chain(expect("3", "cond ? 3 : 3"))); } @Test void foldsAdjacentIntegerConstants() { - 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"))); + assertSimplificationSteps(folding::apply, vc("x + 1 - 2"), chain(expect("x - 1", "x + 1 - 2"))); + assertSimplificationSteps(folding::apply, vc("x - 1 + 2"), chain(expect("x + 1", "x - 1 + 2"))); + assertSimplificationSteps(folding::apply, vc("x + 1 + 2"), chain(expect("x + 3", "x + 1 + 2"))); + assertSimplificationSteps(folding::apply, vc("x + 1 - 1"), chain(expect("x", "x + 1 - 1"))); } @Test void foldsEnumEqualityAndInequality() { - assertSimplificationSteps(VCFolding::apply, vc("Mode.Photo == Mode.Photo"), + assertSimplificationSteps(folding::apply, vc("Mode.Photo == Mode.Photo"), chain(expect("true", "Mode.Photo == Mode.Photo"))); - assertSimplificationSteps(VCFolding::apply, vc("Mode.Photo != Mode.Video"), + assertSimplificationSteps(folding::apply, vc("Mode.Photo != Mode.Video"), chain(expect("true", "Mode.Photo != Mode.Video"))); } @@ -147,7 +148,7 @@ void foldsResolvedEnumLiterals() { VCImplication implication = new VCImplication( new Predicate(new BinaryExpression(limit, "==", new LiteralInt(3)))); - assertSimplificationSteps(VCFolding::apply, implication, chain(expect("3 == 3", "Config.LIMIT == 3")), + assertSimplificationSteps(folding::apply, implication, chain(expect("3 == 3", "Config.LIMIT == 3")), chain(expect("true", "3 == 3"))); } @@ -159,23 +160,14 @@ void foldsResolvedEnumLiteralsInsideLargerExpression() { VCImplication implication = new VCImplication( new Predicate(new BinaryExpression(arithmetic, "==", new LiteralInt(5)))); - assertSimplificationSteps(VCFolding::apply, implication, chain(expect("3 + 2 == 5", "Config.LIMIT + 2 == 5")), + assertSimplificationSteps(folding::apply, implication, chain(expect("3 + 2 == 5", "Config.LIMIT + 2 == 5")), chain(expect("5 == 5", "3 + 2 == 5")), chain(expect("true", "5 == 5"))); } - @Test - void recordsCurrentImplicationAsOriginWhenFoldingExistingSimplifiedImplication() { - VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == 1", "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 void recordsOriginWhenOnlyGroupIsUnwrapped() { VCImplication implication = vc("(x > 0)"); - VCImplication result = assertSimplificationSteps(VCFolding::apply, implication, - chain(expect("x > 0", "x > 0"))); + VCImplication result = assertSimplificationSteps(folding::apply, implication, chain(expect("x > 0", "x > 0"))); SimplifiedVCImplication simplified = assertInstanceOf(SimplifiedVCImplication.class, result); assertEquals("x > 0", simplified.getRefinement().toString()); @@ -186,13 +178,13 @@ void recordsOriginWhenOnlyGroupIsUnwrapped() { void recordsOriginWhenFoldingLaterImplication() { VCImplication implication = vc("x > 0", "1 + 2 > 0"); - VCImplication result = assertSimplificationSteps(VCFolding::apply, implication, + VCImplication result = assertSimplificationSteps(folding::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().getExpression().toDisplayString()); - result = assertSimplificationSteps(VCFolding::apply, result, + result = assertSimplificationSteps(folding::apply, result, chain(expect("x > 0", "x > 0"), expect("true", "3 > 0"))); simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java index 47b514382..65f333462 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java @@ -11,71 +11,69 @@ class VCLogicalSimplificationTest { + private final VCLogicalSimplification simplification = new VCLogicalSimplification(); + @Test void applyReturnsNullForNullImplication() { - assertNull(VCLogicalSimplification.apply(null)); + assertNull(simplification.apply(null)); } @Test void simplifiesConjunctionWithBooleanLiterals() { - assertSimplificationSteps(VCLogicalSimplification::apply, vc("x && true"), chain(expect("x", "x && true"))); - assertSimplificationSteps(VCLogicalSimplification::apply, vc("true && x"), chain(expect("x", "true && x"))); - assertSimplificationSteps(VCLogicalSimplification::apply, vc("x && false"), - chain(expect("false", "x && false"))); - assertSimplificationSteps(VCLogicalSimplification::apply, vc("false && x"), - chain(expect("false", "false && x"))); + assertSimplificationSteps(simplification::apply, vc("x && true"), chain(expect("x", "x && true"))); + assertSimplificationSteps(simplification::apply, vc("true && x"), chain(expect("x", "true && x"))); + assertSimplificationSteps(simplification::apply, vc("x && false"), chain(expect("false", "x && false"))); + assertSimplificationSteps(simplification::apply, vc("false && x"), chain(expect("false", "false && x"))); } @Test void simplifiesDisjunctionWithBooleanLiterals() { - assertSimplificationSteps(VCLogicalSimplification::apply, vc("x || true"), chain(expect("true", "x || true"))); - assertSimplificationSteps(VCLogicalSimplification::apply, vc("true || x"), chain(expect("true", "true || x"))); - assertSimplificationSteps(VCLogicalSimplification::apply, vc("x || false"), chain(expect("x", "x || false"))); - assertSimplificationSteps(VCLogicalSimplification::apply, vc("false || x"), chain(expect("x", "false || x"))); + assertSimplificationSteps(simplification::apply, vc("x || true"), chain(expect("true", "x || true"))); + assertSimplificationSteps(simplification::apply, vc("true || x"), chain(expect("true", "true || x"))); + assertSimplificationSteps(simplification::apply, vc("x || false"), chain(expect("x", "x || false"))); + assertSimplificationSteps(simplification::apply, vc("false || x"), chain(expect("x", "false || x"))); } @Test void simplifiesDoubleNegation() { - assertSimplificationSteps(VCLogicalSimplification::apply, vc("!!x"), chain(expect("x", "!!x"))); + assertSimplificationSteps(simplification::apply, vc("!!x"), chain(expect("x", "!!x"))); } @Test void simplifiesDuplicateLogicalOperands() { - assertSimplificationSteps(VCLogicalSimplification::apply, vc("p && p"), chain(expect("p", "p && p"))); - assertSimplificationSteps(VCLogicalSimplification::apply, vc("p || p"), chain(expect("p", "p || p"))); + assertSimplificationSteps(simplification::apply, vc("p && p"), chain(expect("p", "p && p"))); + assertSimplificationSteps(simplification::apply, vc("p || p"), chain(expect("p", "p || p"))); } @Test void simplifiesSelfEqualityAndInequality() { - assertSimplificationSteps(VCLogicalSimplification::apply, vc("x == x"), chain(expect("true", "x == x"))); - assertSimplificationSteps(VCLogicalSimplification::apply, vc("x != x"), chain(expect("false", "x != x"))); + assertSimplificationSteps(simplification::apply, vc("x == x"), chain(expect("true", "x == x"))); + assertSimplificationSteps(simplification::apply, vc("x != x"), chain(expect("false", "x != x"))); } @Test void simplifiesImplicationIdentities() { - assertSimplificationSteps(VCLogicalSimplification::apply, vc("x --> true"), - chain(expect("true", "x --> true"))); - assertSimplificationSteps(VCLogicalSimplification::apply, vc("false --> x"), - chain(expect("true", "false --> x"))); - assertSimplificationSteps(VCLogicalSimplification::apply, vc("true --> x"), chain(expect("x", "true --> x"))); - assertSimplificationSteps(VCLogicalSimplification::apply, vc("x --> x"), chain(expect("true", "x --> x"))); + assertSimplificationSteps(simplification::apply, vc("x --> true"), chain(expect("true", "x --> true"))); + assertSimplificationSteps(simplification::apply, vc("false --> x"), chain(expect("true", "false --> x"))); + assertSimplificationSteps(simplification::apply, vc("true --> x"), chain(expect("x", "true --> x"))); + assertSimplificationSteps(simplification::apply, vc("x --> x"), chain(expect("true", "x --> x"))); } @Test void simplifiesOnlyFirstLogicalIdentity() { - assertSimplificationSteps(VCLogicalSimplification::apply, vc("x && true && false"), + assertSimplificationSteps(simplification::apply, vc("x && true && false"), chain(expect("x && false", "x && true && false"))); } @Test void simplifiesNestedExpressionsBeforeParent() { - assertSimplificationSteps(VCLogicalSimplification::apply, vc("(x && true) || false"), + assertSimplificationSteps(simplification::apply, vc("(x && true) || false"), chain(expect("x || false", "x && true || false"))); } @Test void simplifiesIteChildren() { - assertSimplificationSteps(VCLogicalSimplification::apply, vc("cond ? x && true : y || false"), + assertSimplificationSteps(simplification::apply, vc("cond ? x && true : y || false"), chain(expect("cond ? x : y || false", "cond ? x && true : y || false"))); } @@ -83,17 +81,10 @@ void simplifiesIteChildren() { void recordsOriginWhenSimplifyingLaterImplication() { VCImplication implication = vc("x > 0", "y || false"); - VCImplication result = assertSimplificationSteps(VCLogicalSimplification::apply, implication, + VCImplication result = assertSimplificationSteps(simplification::apply, implication, chain(expect("x > 0", "x > 0"), expect("y", "y || false"))); SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); assertEquals("y || false", simplifiedNext.getOrigin().getRefinement().getExpression().toDisplayString()); } - - @Test - void recordsCurrentImplicationAsOriginWhenSimplifyingExistingSimplifiedImplication() { - VCImplication substituted = VCSubstitution.apply(vc("∀x:int. x == y", "x == x")); - - assertSimplificationSteps(VCLogicalSimplification::apply, substituted, chain(expect("true", "y == y"))); - } } 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 c20fc1061..8a638ddc3 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 @@ -1,10 +1,11 @@ package liquidjava.rj_language.opt; -import static liquidjava.rj_language.opt.VCSubstitution.containsVar; -import static liquidjava.rj_language.opt.VCSubstitution.isVar; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.util.ArrayList; +import java.util.List; + import com.pholser.junit.quickcheck.From; import com.pholser.junit.quickcheck.Property; import com.pholser.junit.quickcheck.runner.JUnitQuickcheck; @@ -13,6 +14,7 @@ import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.BinaryExpression; import liquidjava.rj_language.ast.Expression; +import liquidjava.rj_language.ast.Var; import liquidjava.smt.SMTEvaluator; import liquidjava.smt.SMTResult; import liquidjava.utils.TestUtils; @@ -87,6 +89,16 @@ private static boolean isSubstitution(VCImplication implication) { return isVar(left, name) && !containsVar(right, name) || isVar(right, name) && !containsVar(left, name); } + private static boolean isVar(Expression expression, String name) { + return expression instanceof Var var && name.equals(var.getName()); + } + + private static boolean containsVar(Expression expression, String name) { + List names = new ArrayList<>(); + expression.getVariableNames(names); + return names.contains(name); + } + private static void assertImplies(Predicate antecedent, Predicate consequent, VCImplication unsimplified, VCImplication simplified, int step, String direction) { try { 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 afe8b60fc..90ceeee33 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 @@ -8,51 +8,53 @@ class VCSubstitutionTest { + private final VCSubstitution substitution = new VCSubstitution(); + @Test void applyReturnsNullForNullImplication() { - assertNull(VCSubstitution.apply(null)); + assertNull(substitution.apply(null)); } @Test void substitutesBinderEqualityIntoWholeChain() { VCImplication implication = vc("∀x:int. x == 3", "x > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, chain(expect("3 > 0", "∀x:int. x > 0"))); + assertSimplificationSteps(substitution::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, chain(expect("3 > 0", "∀x:int. x > 0"))); + assertSimplificationSteps(substitution::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, chain(expect("y + 1 > y", "∀x:int. x > y"))); + assertSimplificationSteps(substitution::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, chain(expect("xx > 3", "∀x:int. xx > x"))); + assertSimplificationSteps(substitution::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, chain(expect("2 + 2 > 0", "∀x:int. x + x > 0"))); + assertSimplificationSteps(substitution::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, + assertSimplificationSteps(substitution::apply, implication, chain(expect("y > 3", "∀x:int. y > x"), expect("y > 0", "y > 0"))); } @@ -60,14 +62,14 @@ void preservesRemainingBinderAfterSubstitution() { void removesSourceNodeWhenItIsLastInChain() { VCImplication implication = vc("x > 0", "∀y:int. y == 1"); - assertSimplificationSteps(VCSubstitution::apply, implication, chain(expect("x > 0", "x > 0"))); + assertSimplificationSteps(substitution::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, + assertSimplificationSteps(substitution::apply, implication, chain(expect("x > 0", "∀x:int. x > 0"), expect("x + 4 > 0", "∀y:int. x + y > 0"))); } @@ -75,7 +77,7 @@ void usesFirstSubstitutionFoundInChain() { void substitutesInnerKnownValueAcrossNestedImplications() { VCImplication implication = vc("∀x:int. true", "∀y:int. y == 1", "∀z:int. z > y", "y + z > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, chain(expect("true", "∀x:int. true"), + assertSimplificationSteps(substitution::apply, implication, chain(expect("true", "∀x:int. true"), expect("z > 1", "∀y:int. z > y"), expect("1 + z > 0", "∀y:int. y + z > 0"))); } @@ -83,7 +85,7 @@ void substitutesInnerKnownValueAcrossNestedImplications() { void substitutesOuterKnownValueIntoNestedBinderRefinements() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); - assertSimplificationSteps(VCSubstitution::apply, implication, + assertSimplificationSteps(substitution::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"))); } @@ -92,7 +94,7 @@ void substitutesOuterKnownValueIntoNestedBinderRefinements() { void ignoresRecursiveBinderEquality() { VCImplication implication = vc("∀x:int. x == x + 1", "x > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, + assertSimplificationSteps(substitution::apply, implication, chain(expect("x == x + 1", "∀x:int. x == x + 1"), expect("x > 0", "x > 0"))); } @@ -100,7 +102,7 @@ void ignoresRecursiveBinderEquality() { void ignoresNonEqualityBinderRefinement() { VCImplication implication = vc("∀x:int. x > 3", "x > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, + assertSimplificationSteps(substitution::apply, implication, chain(expect("x > 3", "∀x:int. x > 3"), expect("x > 0", "x > 0"))); } @@ -108,7 +110,7 @@ void ignoresNonEqualityBinderRefinement() { void ignoresDerivedBinderEquality() { VCImplication implication = vc("∀x:int. x + 1 == 3", "x > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, + assertSimplificationSteps(substitution::apply, implication, chain(expect("x + 1 == 3", "∀x:int. x + 1 == 3"), expect("x > 0", "x > 0"))); } @@ -116,7 +118,7 @@ void ignoresDerivedBinderEquality() { void ignoresEqualityWithoutBinder() { VCImplication implication = vc("x == 3", "x > 0"); - assertSimplificationSteps(VCSubstitution::apply, implication, + assertSimplificationSteps(substitution::apply, implication, chain(expect("x == 3", "x == 3"), expect("x > 0", "x > 0"))); } } From 8b148b0c955027d3987819e22af4913c7c50115a Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sun, 14 Jun 2026 16:28:38 +0100 Subject: [PATCH 51/65] Minor Changes --- .../java/liquidjava/rj_language/opt/VCSubstitution.java | 3 --- .../rj_language/opt/VCArithmeticSimplificationTest.java | 6 ------ .../test/java/liquidjava/rj_language/opt/VCFoldingTest.java | 6 ------ .../rj_language/opt/VCLogicalSimplificationTest.java | 6 ------ .../java/liquidjava/rj_language/opt/VCSubstitutionTest.java | 6 ------ 5 files changed, 27 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 82d4b08c0..c64e766e8 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 @@ -27,9 +27,6 @@ private record Substitution(VCImplication node, Expression replacement) { */ @Override public VCImplication apply(VCImplication implication) { - if (implication == null) - return null; - VCImplication result = implication.clone(); Optional substitutionOpt = findSubstitution(result); diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java index e6f96979b..1a9c09ac6 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java @@ -3,7 +3,6 @@ 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; import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; @@ -13,11 +12,6 @@ class VCArithmeticSimplificationTest { private final VCArithmeticSimplification simplification = new VCArithmeticSimplification(); - @Test - void applyReturnsNullForNullImplication() { - assertNull(simplification.apply(null)); - } - @Test void simplifiesAdditiveIdentities() { assertSimplificationSteps(simplification::apply, vc("x + 0 > 0"), chain(expect("x > 0", "x + 0 > 0"))); 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 ebb9493f5..9d6f3e0c2 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 @@ -3,7 +3,6 @@ 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; import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; @@ -18,11 +17,6 @@ class VCFoldingTest { private final VCFolding folding = new VCFolding(); - @Test - void applyReturnsNullForNullImplication() { - assertNull(folding.apply(null)); - } - @Test void foldsIntegerArithmeticAndComparisons() { VCImplication implication = vc("1 + 2 == 3"); diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java index 65f333462..7712e630b 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java @@ -3,7 +3,6 @@ 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; import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; @@ -13,11 +12,6 @@ class VCLogicalSimplificationTest { private final VCLogicalSimplification simplification = new VCLogicalSimplification(); - @Test - void applyReturnsNullForNullImplication() { - assertNull(simplification.apply(null)); - } - @Test void simplifiesConjunctionWithBooleanLiterals() { assertSimplificationSteps(simplification::apply, vc("x && true"), chain(expect("x", "x && true"))); 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 90ceeee33..d56693583 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.assertNull; import liquidjava.processor.VCImplication; import org.junit.jupiter.api.Test; @@ -10,11 +9,6 @@ class VCSubstitutionTest { private final VCSubstitution substitution = new VCSubstitution(); - @Test - void applyReturnsNullForNullImplication() { - assertNull(substitution.apply(null)); - } - @Test void substitutesBinderEqualityIntoWholeChain() { VCImplication implication = vc("∀x:int. x == 3", "x > 0"); From 9bc108ffd1c786bb2e5698dd314b1c57ba687dbc Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sun, 14 Jun 2026 16:39:20 +0100 Subject: [PATCH 52/65] Minor Changes --- .../main/java/liquidjava/rj_language/opt/VCSubstitution.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 c64e766e8..c5be01340 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 @@ -28,11 +28,11 @@ private record Substitution(VCImplication node, Expression replacement) { @Override public VCImplication apply(VCImplication implication) { VCImplication result = implication.clone(); - Optional substitutionOpt = findSubstitution(result); + Optional substitutionOpt = findSubstitution(result); // apply only the first available substitution if (substitutionOpt.isPresent()) { - VCSubstitution.Substitution substitution = substitutionOpt.get(); + Substitution substitution = substitutionOpt.get(); result = substitute(result, substitution.node(), substitution.replacement()); } return result; From 8588a9d3974798880883085b98e2dc947860c10d Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sun, 14 Jun 2026 17:31:25 +0100 Subject: [PATCH 53/65] Add VC Binder Simplification --- .../opt/VCBinderSimplification.java | 118 ++++++++++++++++++ .../rj_language/opt/VCSimplification.java | 4 +- .../opt/VCBinderSimplificationTest.java | 69 ++++++++++ .../opt/VCImplicationGenerator.java | 12 +- .../rj_language/opt/VCSimplificationTest.java | 32 +++++ 5 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCBinderSimplification.java create mode 100644 liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCBinderSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCBinderSimplification.java new file mode 100644 index 000000000..7dd614a59 --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCBinderSimplification.java @@ -0,0 +1,118 @@ +package liquidjava.rj_language.opt; + +import java.util.ArrayList; +import java.util.List; + +import liquidjava.processor.SimplifiedVCImplication; +import liquidjava.processor.VCImplication; +import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.ast.Expression; +import liquidjava.rj_language.ast.LiteralBoolean; + +/** + * Simplifies VCImplication chains by removing vacuous binder implications + */ +public class VCBinderSimplification implements VCSimplificationPass { + + /** + * Applies one binder simplification in a VC chain + */ + @Override + public VCImplication apply(VCImplication implication) { + VCImplication cloned = implication.clone(); + VCImplication simplified = simplify(cloned); + return simplified == null ? cloned : simplified; + } + + /** + * Simplifies the first applicable binder in a VC chain + */ + private VCImplication simplify(VCImplication implication) { + if (implication == null) + return null; + + if (isFalseBinder(implication)) + return collapseFalseBinder(implication); + + if (isTrueBinder(implication) && !containsVar(implication.getNext(), implication.getName())) + return removeTrueBinder(implication); + + VCImplication next = simplify(implication.getNext()); + if (next == null) + return null; + + VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); + result.setNext(next); + return result; + } + + /** + * Removes a true binder whose name is not used in the suffix + */ + private VCImplication removeTrueBinder(VCImplication implication) { + VCImplication next = implication.getNext(); + + // ∀x. true => P -> P + if (next != null) { + VCImplication origin = new VCImplication(implication.getName(), implication.getType(), + next.getOriginRefinement()); + VCImplication result = new SimplifiedVCImplication(next, next.getRefinement().clone(), origin); + result.setNext(next.getNext() == null ? null : next.getNext().clone()); + return result; + } + + // ∀x. true -> true + Predicate truePredicate = new Predicate(new LiteralBoolean(true)); + return new SimplifiedVCImplication(new VCImplication(truePredicate), truePredicate, implication); + } + + /** + * Replaces a false binder implication with true + */ + private VCImplication collapseFalseBinder(VCImplication implication) { + // ∀x. false => P -> true + Predicate truePredicate = new Predicate(new LiteralBoolean(true)); + return new SimplifiedVCImplication(new VCImplication(truePredicate), truePredicate, implication); + } + + /** + * Checks whether a VC node is a binder refined with true + */ + private boolean isTrueBinder(VCImplication implication) { + return implication.hasBinder() && isTrue(implication.getRefinement().getExpression()); + } + + /** + * Checks whether a VC node is a binder refined with false + */ + private boolean isFalseBinder(VCImplication implication) { + return implication.hasBinder() && isFalse(implication.getRefinement().getExpression()); + } + + /** + * Checks whether an expression is true + */ + private boolean isTrue(Expression expression) { + return expression instanceof LiteralBoolean literal && literal.isBooleanTrue(); + } + + /** + * Checks whether an expression is false + */ + private boolean isFalse(Expression expression) { + return expression instanceof LiteralBoolean literal && !literal.isBooleanTrue(); + } + + /** + * Checks whether a VC suffix contains a variable name + */ + private boolean containsVar(VCImplication implication, String name) { + for (VCImplication current = implication; current != null; current = current.getNext()) { + List names = new ArrayList<>(); + current.getRefinement().getExpression().getVariableNames(names); + if (names.contains(name)) + return true; + } + return false; + } +} 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 38eb7f3bd..b26af0e66 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 @@ -9,8 +9,8 @@ */ public class VCSimplification { - private static final List PASSES = List.of(new VCSubstitution(), new VCFolding(), - new VCArithmeticSimplification(), new VCLogicalSimplification()); + private static final List PASSES = List.of(new VCSubstitution(), new VCBinderSimplification(), + new VCFolding(), new VCArithmeticSimplification(), new VCLogicalSimplification()); /** * Applies all available simplification steps to a VC chain until a fixed point is reached diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java new file mode 100644 index 000000000..5f49bbb38 --- /dev/null +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java @@ -0,0 +1,69 @@ +package liquidjava.rj_language.opt; + +import static liquidjava.utils.VCTestUtils.*; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import liquidjava.processor.VCImplication; +import org.junit.jupiter.api.Test; + +class VCBinderSimplificationTest { + + private final VCBinderSimplification binderSimplification = new VCBinderSimplification(); + + @Test + void removesTrueBinderWhenVariableIsUnusedDownstream() { + VCImplication implication = vc("∀x:int. true", "y > 0"); + + assertSimplificationSteps(binderSimplification::apply, implication, chain(expect("y > 0", "∀x:int. y > 0"))); + } + + @Test + void keepsTrueBinderWhenVariableIsUsedDownstream() { + VCImplication implication = vc("∀x:int. true", "x > 0"); + + assertSimplificationSteps(binderSimplification::apply, implication, + chain(expect("true", "∀x:int. true"), expect("x > 0", "x > 0"))); + } + + @Test + void collapsesFalseBinderSuffixToPlainTrue() { + VCImplication implication = vc("∀x:int. false", "x > 0", "y > 0"); + VCImplication result = binderSimplification.apply(implication); + + assertFalse(result.hasBinder()); + assertSimplifiedVC(result, expect("true", "∀x:int. false")); + } + + @Test + void simplifiesOnlyFirstApplicableBinder() { + VCImplication implication = vc("∀x:int. true", "∀y:int. true", "z > 0"); + + assertSimplificationSteps(binderSimplification::apply, implication, + chain(expect("true", "∀x:int. true"), expect("z > 0", "z > 0"))); + } + + @Test + void skipsInapplicableTrueBinderAndSimplifiesLaterBinder() { + VCImplication implication = vc("∀x:int. true", "x > 0", "∀y:int. true", "z > 0"); + + assertSimplificationSteps(binderSimplification::apply, implication, + chain(expect("true", "∀x:int. true"), expect("x > 0", "x > 0"), expect("z > 0", "∀y:int. z > 0"))); + } + + @Test + void ignoresNonBinderBooleanLiterals() { + VCImplication implication = vc("true", "false"); + + assertSimplificationSteps(binderSimplification::apply, implication, + chain(expect("true", "true"), expect("false", "false"))); + } + + @Test + void trueBinderWithoutSuffixBecomesPlainTrue() { + VCImplication implication = vc("∀x:int. true"); + VCImplication result = binderSimplification.apply(implication); + + assertFalse(result.hasBinder()); + assertSimplifiedVC(result, expect("true", "∀x:int. true")); + } +} 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 f3e19ff97..b7517c49b 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 @@ -21,7 +21,7 @@ public VCImplicationGenerator() { @Override public VCImplication generate(SourceOfRandomness random, GenerationStatus status) { - return switch (random.nextInt(0, 12)) { + return switch (random.nextInt(0, 14)) { 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")); @@ -34,6 +34,8 @@ public VCImplication generate(SourceOfRandomness random, GenerationStatus status case 9 -> vc(arithmeticIdentity(random)); case 10 -> guardedArithmeticIdentity(random); case 11 -> vc(logicalIdentity(random)); + case 12 -> vc(unusedTrueBinder(random)); + case 13 -> vc(falseBinder(random)); default -> vc(substitution(random, "x"), substitution(random, "y"), foldableComparison(random)); }; } @@ -60,6 +62,14 @@ private static String nonSubstitution(SourceOfRandomness random, String binder) return "∀" + binder + ":int. " + binder + " == " + binder + " " + signed(random.nextInt(1, 5)); } + private static String[] unusedTrueBinder(SourceOfRandomness random) { + return new String[] { "∀x:int. true", comparison(random, "a") }; + } + + private static String[] falseBinder(SourceOfRandomness random) { + return new String[] { "∀x:int. false", comparison(random, "x") }; + } + private static String comparison(SourceOfRandomness random, String preferredVar) { String left = random.nextBoolean() ? preferredVar : arithmetic(random, preferredVar); String right = random.nextBoolean() ? intLiteral(random) 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 5565e3117..7a32db0d6 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 @@ -36,6 +36,31 @@ void simplifyOnceDoesNotFoldAfterSubstitutionInSameStep() { chain(expect("true", "3 == 3"))); } + @Test + void simplifyOnceAppliesSubstitutionBeforeBinderSimplification() { + VCImplication implication = vc("∀x:int. x == 3", "∀y:int. true", "x > 0"); + + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + chain(expect("true", "∀y:int. true"), expect("3 > 0", "∀x:int. x > 0")), + chain(expect("3 > 0", "∀y:int. x > 0")), chain(expect("true", "3 > 0"))); + } + + @Test + void simplifyOnceAppliesBinderSimplificationBeforeFolding() { + VCImplication implication = vc("∀x:int. true", "1 + 2 > 0"); + + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + chain(expect("1 + 2 > 0", "∀x:int. 1 + 2 > 0")), chain(expect("3 > 0", "1 + 2 > 0"))); + } + + @Test + void simplifyOnceAppliesBinderSimplificationBeforeLogicalSimplification() { + VCImplication implication = vc("∀x:int. true", "y && true"); + + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + chain(expect("y && true", "∀x:int. y && true")), chain(expect("y", "y && true"))); + } + @Test void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { VCImplication implication = vc("1 + 2 > 2"); @@ -91,6 +116,13 @@ void simplifyKeepsApplyingStepsUntilFixedPoint() { chain(expect("4 > 3", "3 + 1 > 3")), chain(expect("true", "4 > 3"))); } + @Test + void simplifyToFixedPointRemovesTrueBindersOverMultipleSteps() { + VCImplication implication = vc("∀x:int. true", "∀y:int. true", "z > 0"); + + assertSimplifiedVC(VCSimplification.simplifyToFixedPoint(implication), expect("z > 0", "∀y:int. z > 0")); + } + @Test void simplifyAppliesMultipleSubstitutionsBeforeReachingFixedPoint() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); From 5fbcef74c001555f3e4f0ceae7c3cabdf84ed98f Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Mon, 15 Jun 2026 12:23:18 +0100 Subject: [PATCH 54/65] Return Null Origin for Unsimplified VCs --- .../liquidjava/processor/VCImplication.java | 7 ++++-- .../opt/VCArithmeticSimplificationTest.java | 14 ++++++------ .../opt/VCBinderSimplificationTest.java | 10 ++++----- .../rj_language/opt/VCFoldingTest.java | 15 +++++-------- .../opt/VCLogicalSimplificationTest.java | 2 +- .../rj_language/opt/VCSimplificationTest.java | 14 +++++------- .../rj_language/opt/VCSubstitutionTest.java | 22 ++++++++----------- .../java/liquidjava/utils/VCTestUtils.java | 14 ++++++++++-- 8 files changed, 50 insertions(+), 48 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java b/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java index d3febea64..4a5541f86 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java @@ -33,11 +33,14 @@ public VCImplication(VCImplication implication, Predicate ref) { } public VCImplication getOrigin() { - return new VCImplication(this, refinement.clone()); + return null; } public Predicate getOriginRefinement() { - return getOrigin().getRefinement().clone(); + VCImplication origin = getOrigin(); + if (origin == null) + return refinement.clone(); + return origin.getRefinement().clone(); } public VCImplication copyWithRefinement(Predicate refinement) { diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java index 1a9c09ac6..349d8a310 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java @@ -43,18 +43,18 @@ void simplifiesMultiplicativeIdentities() { @Test void simplifiesGuardedDivisionAndModuloIdentities() { assertSimplificationSteps(simplification::apply, vc("x != 0", "0 / x == 0"), - chain(expect("x != 0", "x != 0"), expect("0 == 0", "0 / x == 0"))); + chain(expect("x != 0"), expect("0 == 0", "0 / x == 0"))); assertSimplificationSteps(simplification::apply, vc("x != 0", "x / x == 1"), - chain(expect("x != 0", "x != 0"), expect("1 == 1", "x / x == 1"))); + chain(expect("x != 0"), expect("1 == 1", "x / x == 1"))); assertSimplificationSteps(simplification::apply, vc("0 != x", "x % x == 0"), - chain(expect("0 != x", "0 != x"), expect("0 == 0", "x % x == 0"))); + chain(expect("0 != x"), expect("0 == 0", "x % x == 0"))); } @Test void leavesUnguardedDivisionAndModuloIdentitiesUnchanged() { - assertSimplificationSteps(simplification::apply, vc("0 / x == 0"), chain(expect("0 / x == 0", "0 / x == 0"))); - assertSimplificationSteps(simplification::apply, vc("x / x == 1"), chain(expect("x / x == 1", "x / x == 1"))); - assertSimplificationSteps(simplification::apply, vc("x % x == 0"), chain(expect("x % x == 0", "x % x == 0"))); + assertSimplificationSteps(simplification::apply, vc("0 / x == 0"), chain(expect("0 / x == 0"))); + assertSimplificationSteps(simplification::apply, vc("x / x == 1"), chain(expect("x / x == 1"))); + assertSimplificationSteps(simplification::apply, vc("x % x == 0"), chain(expect("x % x == 0"))); } @Test @@ -68,7 +68,7 @@ void recordsOriginWhenSimplifyingLaterImplication() { VCImplication implication = vc("x > 0", "y + 0 > x"); VCImplication result = assertSimplificationSteps(simplification::apply, implication, - chain(expect("x > 0", "x > 0"), expect("y > x", "y + 0 > x"))); + chain(expect("x > 0"), expect("y > x", "y + 0 > x"))); SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); assertEquals("y + 0 > x", simplifiedNext.getOrigin().getRefinement().getExpression().toDisplayString()); diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java index 5f49bbb38..6dfa5a295 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java @@ -21,8 +21,7 @@ void removesTrueBinderWhenVariableIsUnusedDownstream() { void keepsTrueBinderWhenVariableIsUsedDownstream() { VCImplication implication = vc("∀x:int. true", "x > 0"); - assertSimplificationSteps(binderSimplification::apply, implication, - chain(expect("true", "∀x:int. true"), expect("x > 0", "x > 0"))); + assertSimplificationSteps(binderSimplification::apply, implication, chain(expect("true"), expect("x > 0"))); } @Test @@ -39,7 +38,7 @@ void simplifiesOnlyFirstApplicableBinder() { VCImplication implication = vc("∀x:int. true", "∀y:int. true", "z > 0"); assertSimplificationSteps(binderSimplification::apply, implication, - chain(expect("true", "∀x:int. true"), expect("z > 0", "z > 0"))); + chain(expect("true", "∀x:int. true"), expect("z > 0"))); } @Test @@ -47,15 +46,14 @@ void skipsInapplicableTrueBinderAndSimplifiesLaterBinder() { VCImplication implication = vc("∀x:int. true", "x > 0", "∀y:int. true", "z > 0"); assertSimplificationSteps(binderSimplification::apply, implication, - chain(expect("true", "∀x:int. true"), expect("x > 0", "x > 0"), expect("z > 0", "∀y:int. z > 0"))); + chain(expect("true"), expect("x > 0"), expect("z > 0", "∀y:int. z > 0"))); } @Test void ignoresNonBinderBooleanLiterals() { VCImplication implication = vc("true", "false"); - assertSimplificationSteps(binderSimplification::apply, implication, - chain(expect("true", "true"), expect("false", "false"))); + assertSimplificationSteps(binderSimplification::apply, implication, chain(expect("true"), expect("false"))); } @Test 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 9d6f3e0c2..64df5d85b 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 @@ -39,16 +39,14 @@ void foldsRealAndMixedNumericExpressions() { @Test void leavesDivisionAndModuloByZeroUnchanged() { - assertSimplificationSteps(folding::apply, vc("4 / 0 == 0"), chain(expect("4 / 0 == 0", "4 / 0 == 0"))); - assertSimplificationSteps(folding::apply, vc("4 % 0 == 0"), chain(expect("4 % 0 == 0", "4 % 0 == 0"))); + assertSimplificationSteps(folding::apply, vc("4 / 0 == 0"), chain(expect("4 / 0 == 0"))); + assertSimplificationSteps(folding::apply, vc("4 % 0 == 0"), chain(expect("4 % 0 == 0"))); } @Test void leavesRealDivisionAndModuloByZeroUnchanged() { - assertSimplificationSteps(folding::apply, vc("4.0 / 0.0 == 0.0"), - chain(expect("4.0 / 0.0 == 0.0", "4.0 / 0.0 == 0.0"))); - assertSimplificationSteps(folding::apply, vc("4.0 % 0.0 == 0.0"), - chain(expect("4.0 % 0.0 == 0.0", "4.0 % 0.0 == 0.0"))); + assertSimplificationSteps(folding::apply, vc("4.0 / 0.0 == 0.0"), chain(expect("4.0 / 0.0 == 0.0"))); + assertSimplificationSteps(folding::apply, vc("4.0 % 0.0 == 0.0"), chain(expect("4.0 % 0.0 == 0.0"))); } @Test @@ -173,13 +171,12 @@ void recordsOriginWhenFoldingLaterImplication() { VCImplication implication = vc("x > 0", "1 + 2 > 0"); VCImplication result = assertSimplificationSteps(folding::apply, implication, - chain(expect("x > 0", "x > 0"), expect("3 > 0", "1 + 2 > 0"))); + chain(expect("x > 0"), expect("3 > 0", "1 + 2 > 0"))); SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); assertEquals("1 + 2 > 0", simplifiedNext.getOrigin().getRefinement().getExpression().toDisplayString()); - result = assertSimplificationSteps(folding::apply, result, - chain(expect("x > 0", "x > 0"), expect("true", "3 > 0"))); + result = assertSimplificationSteps(folding::apply, result, chain(expect("x > 0"), expect("true", "3 > 0"))); simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); assertEquals("3 > 0", simplifiedNext.getOrigin().getRefinement().getExpression().toDisplayString()); diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java index 7712e630b..673025f00 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java @@ -76,7 +76,7 @@ void recordsOriginWhenSimplifyingLaterImplication() { VCImplication implication = vc("x > 0", "y || false"); VCImplication result = assertSimplificationSteps(simplification::apply, implication, - chain(expect("x > 0", "x > 0"), expect("y", "y || false"))); + chain(expect("x > 0"), expect("y", "y || false"))); SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); assertEquals("y || false", 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 7a32db0d6..62514f4e2 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 @@ -41,8 +41,8 @@ void simplifyOnceAppliesSubstitutionBeforeBinderSimplification() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. true", "x > 0"); assertSimplificationSteps(VCSimplification::simplifyOnce, implication, - chain(expect("true", "∀y:int. true"), expect("3 > 0", "∀x:int. x > 0")), - chain(expect("3 > 0", "∀y:int. x > 0")), chain(expect("true", "3 > 0"))); + chain(expect("true"), expect("3 > 0", "∀x:int. x > 0")), chain(expect("3 > 0", "∀y:int. x > 0")), + chain(expect("true", "3 > 0"))); } @Test @@ -138,9 +138,8 @@ 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, - 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("y == 1 + 1", "∀x:int. y == x + 1"), expect("z == y + 1"), expect("z == 3")), + chain(expect("z == 1 + 1 + 1", "∀y:int. z == y + 1"), expect("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"))); } @@ -150,7 +149,7 @@ void simplifyCombinesSubstitutionAndNestedFoldingAcrossFixedPoint() { VCImplication implication = vc("∀x:int. x == 1", "∀y:int. y == x + 2", "y - 1 == 2"); assertSimplificationSteps(VCSimplification::simplifyOnce, implication, - chain(expect("y == 1 + 2", "∀x:int. y == x + 2"), expect("y - 1 == 2", "y - 1 == 2")), + chain(expect("y == 1 + 2", "∀x:int. y == x + 2"), expect("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"))); } @@ -167,7 +166,6 @@ void simplifyStopsAfterSubstitutionWhenOnlyNegativeLiteralShapeChanges() { void simplifyLeavesUnchangedVcAsPlainPredicates() { VCImplication implication = vc("x > 0", "y > x"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, - chain(expect("x > 0", "x > 0"), expect("y > x", "y > x"))); + assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("x > 0"), expect("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 d56693583..629448d61 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 @@ -49,14 +49,14 @@ void preservesRemainingBinderAfterSubstitution() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y > x", "y > 0"); assertSimplificationSteps(substitution::apply, implication, - chain(expect("y > 3", "∀x:int. y > x"), expect("y > 0", "y > 0"))); + chain(expect("y > 3", "∀x:int. y > x"), expect("y > 0"))); } @Test void removesSourceNodeWhenItIsLastInChain() { VCImplication implication = vc("x > 0", "∀y:int. y == 1"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("x > 0", "x > 0"))); + assertSimplificationSteps(substitution::apply, implication, chain(expect("x > 0"))); } @Test @@ -64,15 +64,15 @@ void usesFirstSubstitutionFoundInChain() { VCImplication implication = vc("∀x:int. x > 0", "∀y:int. y == 4", "x + y > 0"); assertSimplificationSteps(substitution::apply, implication, - chain(expect("x > 0", "∀x:int. x > 0"), expect("x + 4 > 0", "∀y:int. x + y > 0"))); + chain(expect("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(substitution::apply, implication, chain(expect("true", "∀x:int. true"), - expect("z > 1", "∀y:int. z > y"), expect("1 + z > 0", "∀y:int. y + z > 0"))); + assertSimplificationSteps(substitution::apply, implication, + chain(expect("true"), expect("z > 1", "∀y:int. z > y"), expect("1 + z > 0", "∀y:int. y + z > 0"))); } @Test @@ -88,31 +88,27 @@ void substitutesOuterKnownValueIntoNestedBinderRefinements() { void ignoresRecursiveBinderEquality() { VCImplication implication = vc("∀x:int. x == x + 1", "x > 0"); - assertSimplificationSteps(substitution::apply, implication, - chain(expect("x == x + 1", "∀x:int. x == x + 1"), expect("x > 0", "x > 0"))); + assertSimplificationSteps(substitution::apply, implication, chain(expect("x == x + 1"), expect("x > 0"))); } @Test void ignoresNonEqualityBinderRefinement() { VCImplication implication = vc("∀x:int. x > 3", "x > 0"); - assertSimplificationSteps(substitution::apply, implication, - chain(expect("x > 3", "∀x:int. x > 3"), expect("x > 0", "x > 0"))); + assertSimplificationSteps(substitution::apply, implication, chain(expect("x > 3"), expect("x > 0"))); } @Test void ignoresDerivedBinderEquality() { VCImplication implication = vc("∀x:int. x + 1 == 3", "x > 0"); - assertSimplificationSteps(substitution::apply, implication, - chain(expect("x + 1 == 3", "∀x:int. x + 1 == 3"), expect("x > 0", "x > 0"))); + assertSimplificationSteps(substitution::apply, implication, chain(expect("x + 1 == 3"), expect("x > 0"))); } @Test void ignoresEqualityWithoutBinder() { VCImplication implication = vc("x == 3", "x > 0"); - assertSimplificationSteps(substitution::apply, implication, - chain(expect("x == 3", "x == 3"), expect("x > 0", "x > 0"))); + assertSimplificationSteps(substitution::apply, implication, chain(expect("x == 3"), expect("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 61e46da40..df051aafd 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -1,6 +1,7 @@ package liquidjava.utils; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import java.util.function.UnaryOperator; @@ -55,9 +56,14 @@ public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplif "Expected simplified refinement at implication " + i + " to be a plain Predicate"); assertEquals(expectedPredicate.simplified(), formatRefinement(current), "Unexpected simplified expression at implication " + i); - if (expectedPredicate.origin() != null) - assertEquals(expectedPredicate.origin(), formatOrigin(current.getOrigin()), + if (expectedPredicate.origin() == null) + assertNull(current.getOrigin(), "Unexpected origin VC at implication " + i); + else { + VCImplication origin = current.getOrigin(); + assertNotNull(origin, "Expected origin VC at implication " + i); + assertEquals(expectedPredicate.origin(), formatOrigin(origin), "Unexpected origin VC at implication " + i); + } current = current.getNext(); } assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); @@ -81,6 +87,10 @@ public static ExpectedSimplifiedVCImplication expect(String simplified, String o return new ExpectedSimplifiedVCImplication(simplified, origin); } + public static ExpectedSimplifiedVCImplication expect(String simplified) { + return new ExpectedSimplifiedVCImplication(simplified, null); + } + private static String formatOrigin(VCImplication origin) { if (!origin.hasBinder()) return formatRefinement(origin); From d068108d029048531f9fefb6a463152a94d7a762 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 17 Jun 2026 16:09:00 +0100 Subject: [PATCH 55/65] Fix VC Substitution --- .../liquidjava/rj_language/opt/VCSubstitution.java | 14 +++++++++++++- .../rj_language/opt/VCSubstitutionTest.java | 11 +++++++++-- 2 files changed, 22 insertions(+), 3 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 c5be01340..f8b3dc170 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 @@ -75,7 +75,8 @@ private Optional findSubstitution(VCImplication implication) { if (implication == null) return Optional.empty(); - Optional current = getSubstitution(implication); + Optional current = containsVar(implication.getNext(), implication.getName()) + ? getSubstitution(implication) : Optional.empty(); if (current.isPresent()) return current; @@ -120,4 +121,15 @@ private boolean containsVar(Expression expression, String name) { expression.getVariableNames(names); return names.contains(name); } + + /** + * Checks whether a VC suffix contains a variable name + */ + private boolean containsVar(VCImplication implication, String name) { + for (VCImplication current = implication; current != null; current = current.getNext()) { + if (containsVar(current.getRefinement().getExpression(), name)) + return true; + } + return false; + } } 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 629448d61..95d6c347d 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 @@ -53,10 +53,17 @@ void preservesRemainingBinderAfterSubstitution() { } @Test - void removesSourceNodeWhenItIsLastInChain() { + void keepsSourceNodeWhenItIsLastInChain() { VCImplication implication = vc("x > 0", "∀y:int. y == 1"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("x > 0"))); + assertSimplificationSteps(substitution::apply, implication, chain(expect("x > 0"), expect("y == 1"))); + } + + @Test + void keepsReturnBinderWhenConclusionIsSeparate() { + VCImplication implication = vc("∀x:int. true", "∀#ret_8:int. #ret_8 == x + 1"); + + assertSimplificationSteps(substitution::apply, implication, chain(expect("true"), expect("#ret⁸ == x + 1"))); } @Test From 935a168a156e727455f62ae8d32acdc98fc574dd Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 17 Jun 2026 16:15:45 +0100 Subject: [PATCH 56/65] VC Simplification Refactoring --- .../opt/VCBinderSimplification.java | 33 ++----------------- .../opt/VCLogicalSimplification.java | 17 ++-------- .../opt/VCSimplificationUtils.java | 33 +++++++++++++++++++ .../rj_language/opt/VCSubstitution.java | 23 ++----------- 4 files changed, 41 insertions(+), 65 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/VCBinderSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCBinderSimplification.java index 7dd614a59..d5dbf55ab 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCBinderSimplification.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCBinderSimplification.java @@ -1,12 +1,12 @@ package liquidjava.rj_language.opt; -import java.util.ArrayList; -import java.util.List; +import static liquidjava.rj_language.opt.VCSimplificationUtils.containsVar; +import static liquidjava.rj_language.opt.VCSimplificationUtils.isFalse; +import static liquidjava.rj_language.opt.VCSimplificationUtils.isTrue; import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.LiteralBoolean; /** @@ -88,31 +88,4 @@ private boolean isTrueBinder(VCImplication implication) { private boolean isFalseBinder(VCImplication implication) { return implication.hasBinder() && isFalse(implication.getRefinement().getExpression()); } - - /** - * Checks whether an expression is true - */ - private boolean isTrue(Expression expression) { - return expression instanceof LiteralBoolean literal && literal.isBooleanTrue(); - } - - /** - * Checks whether an expression is false - */ - private boolean isFalse(Expression expression) { - return expression instanceof LiteralBoolean literal && !literal.isBooleanTrue(); - } - - /** - * Checks whether a VC suffix contains a variable name - */ - private boolean containsVar(VCImplication implication, String name) { - for (VCImplication current = implication; current != null; current = current.getNext()) { - List names = new ArrayList<>(); - current.getRefinement().getExpression().getVariableNames(names); - if (names.contains(name)) - return true; - } - return false; - } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java index 84c3b3caa..2ba262602 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCLogicalSimplification.java @@ -1,5 +1,8 @@ package liquidjava.rj_language.opt; +import static liquidjava.rj_language.opt.VCSimplificationUtils.isFalse; +import static liquidjava.rj_language.opt.VCSimplificationUtils.isTrue; + import liquidjava.rj_language.ast.BinaryExpression; import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.GroupExpression; @@ -199,20 +202,6 @@ private Expression simplifyImplication(Expression left, Expression right) { return null; } - /** - * Checks whether an expression is true - */ - private boolean isTrue(Expression expression) { - return expression instanceof LiteralBoolean literal && literal.isBooleanTrue(); - } - - /** - * Checks whether an expression is false - */ - private boolean isFalse(Expression expression) { - return expression instanceof LiteralBoolean literal && !literal.isBooleanTrue(); - } - /** * Checks whether an expression is unary logical negation */ 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..43b7aed55 --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationUtils.java @@ -0,0 +1,33 @@ +package liquidjava.rj_language.opt; + +import java.util.ArrayList; +import java.util.List; + +import liquidjava.processor.VCImplication; +import liquidjava.rj_language.ast.Expression; +import liquidjava.rj_language.ast.LiteralBoolean; + +public final class VCSimplificationUtils { + + public static boolean containsVar(Expression expression, String name) { + List names = new ArrayList<>(); + expression.getVariableNames(names); + return names.contains(name); + } + + public static boolean containsVar(VCImplication implication, String name) { + for (VCImplication current = implication; current != null; current = current.getNext()) { + if (containsVar(current.getRefinement().getExpression(), name)) + return true; + } + return false; + } + + public static boolean isTrue(Expression expression) { + return expression instanceof LiteralBoolean literal && literal.isBooleanTrue(); + } + + public static boolean isFalse(Expression expression) { + return expression instanceof LiteralBoolean literal && !literal.isBooleanTrue(); + } +} 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 f8b3dc170..556ab9e15 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 @@ -1,7 +1,7 @@ package liquidjava.rj_language.opt; -import java.util.ArrayList; -import java.util.List; +import static liquidjava.rj_language.opt.VCSimplificationUtils.containsVar; + import java.util.Optional; import liquidjava.processor.SimplifiedVCImplication; @@ -113,23 +113,4 @@ private boolean isVar(Expression expression, String name) { return expression instanceof Var var && name.equals(var.getName()); } - /** - * Checks whether an expression contains a variable name - */ - private boolean containsVar(Expression expression, String name) { - List names = new ArrayList<>(); - expression.getVariableNames(names); - return names.contains(name); - } - - /** - * Checks whether a VC suffix contains a variable name - */ - private boolean containsVar(VCImplication implication, String name) { - for (VCImplication current = implication; current != null; current = current.getNext()) { - if (containsVar(current.getRefinement().getExpression(), name)) - return true; - } - return false; - } } From 5c73350d6339f2334d9ce6cb45f5cff20465aa3e Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 17 Jun 2026 16:21:24 +0100 Subject: [PATCH 57/65] Use VC Implication Simplification --- .../diagnostics/errors/RefinementError.java | 28 +++++++++++-------- .../errors/StateRefinementError.java | 20 +++++++------ .../liquidjava/processor/VCImplication.java | 4 +++ .../refinement_checker/VCChecker.java | 10 +++---- .../liquidjava/rj_language/Predicate.java | 17 ++++------- 5 files changed, 41 insertions(+), 38 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java index 3095bb91b..6da726f1f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java @@ -5,9 +5,9 @@ import java.util.stream.Collectors; import liquidjava.diagnostics.TranslationTable; +import liquidjava.processor.VCImplication; import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.formatter.VariableFormatter; -import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; import liquidjava.smt.Counterexample; import spoon.reflect.cu.SourcePosition; @@ -18,14 +18,16 @@ */ public class RefinementError extends LJError { - private final ValDerivationNode expected; - private final ValDerivationNode found; + private final VCImplication expected; + private final VCImplication found; private final Counterexample counterexample; - public RefinementError(SourcePosition position, ValDerivationNode expected, ValDerivationNode found, + public RefinementError(SourcePosition position, VCImplication expected, VCImplication found, TranslationTable translationTable, Counterexample counterexample, String customMessage) { - super("Refinement Error", String.format("%s is not a subtype of %s", found.getValue().toDisplayString(), - expected.getValue().toDisplayString()), position, translationTable, customMessage); + super("Refinement Error", + String.format("%s is not a subtype of %s", found.toPredicate().getExpression().toDisplayString(), + expected.toPredicate().getExpression().toDisplayString()), + position, translationTable, customMessage); this.expected = expected; this.found = found; this.counterexample = counterexample; @@ -44,12 +46,14 @@ public String getCounterExampleString() { return null; List foundVarNames = new ArrayList<>(); - found.getValue().getVariableNames(foundVarNames); + Expression foundExpression = found.toPredicate().getExpression(); + Expression expectedExpression = expected.toPredicate().getExpression(); + foundExpression.getVariableNames(foundVarNames); // also keep resolved static-final constants (e.g. Integer.MAX_VALUE) referenced by either side of the // subtyping check, so the counterexample maps the symbolic name back to its compile-time value - found.getValue().getResolvedConstantNames(foundVarNames); - expected.getValue().getResolvedConstantNames(foundVarNames); - List foundAssignments = found.getValue().getConjuncts().stream().map(Expression::toString).toList(); + foundExpression.getResolvedConstantNames(foundVarNames); + expectedExpression.getResolvedConstantNames(foundVarNames); + List foundAssignments = foundExpression.getConjuncts().stream().map(Expression::toString).toList(); String counterexampleString = counterexample.assignments().stream() // only include variables that appear in the found value and are not already fixed there .filter(a -> foundVarNames.contains(a.first()) @@ -69,11 +73,11 @@ public Counterexample getCounterexample() { return counterexample; } - public ValDerivationNode getExpected() { + public VCImplication getExpected() { return expected; } - public ValDerivationNode getFound() { + public VCImplication getFound() { return found; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java index c2055954a..7476299c1 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java @@ -1,7 +1,7 @@ package liquidjava.diagnostics.errors; import liquidjava.diagnostics.TranslationTable; -import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; +import liquidjava.processor.VCImplication; import spoon.reflect.cu.SourcePosition; /** @@ -11,23 +11,25 @@ */ public class StateRefinementError extends LJError { - private final ValDerivationNode expected; - private final ValDerivationNode found; + private final VCImplication expected; + private final VCImplication found; - public StateRefinementError(SourcePosition position, ValDerivationNode expected, ValDerivationNode found, + public StateRefinementError(SourcePosition position, VCImplication expected, VCImplication found, TranslationTable translationTable, String customMessage) { - super("State Refinement Error", String.format("Expected state %s but found %s", - expected.getValue().toDisplayString(), found.getValue().toDisplayString()), position, translationTable, - customMessage); + super("State Refinement Error", + String.format("Expected state %s but found %s", + expected.toPredicate().getExpression().toDisplayString(), + found.toPredicate().getExpression().toDisplayString()), + position, translationTable, customMessage); this.expected = expected; this.found = found; } - public ValDerivationNode getExpected() { + public VCImplication getExpected() { return expected; } - public ValDerivationNode getFound() { + public VCImplication getFound() { return found; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java b/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java index 4a5541f86..2130ee186 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java @@ -93,6 +93,10 @@ public VCImplication simplify() { return VCSimplification.simplifyToFixedPoint(this); } + public Predicate toPredicate() { + return hasBinder() || hasNext() ? toConjunctions() : refinement; + } + public Predicate toConjunctions() { Predicate c = new Predicate(); if (name == null && type == null && next == null) diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java index 228a318a6..77c5c3b20 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java @@ -53,6 +53,7 @@ public void processSubtyping(Predicate expectedType, List list, CtEl TranslationTable map = new TranslationTable(); String[] s = { Keys.WILDCARD, Keys.THIS }; VCImplication impl = joinPredicates(expectedType, mainVars, lrv, map); + VCImplication implBeforeChange = impl.clone(); Predicate premisesBeforeChange = impl.toConjunctions(); Predicate premises; Predicate expected; @@ -82,7 +83,7 @@ public void processSubtyping(Predicate expectedType, List list, CtEl DebugLog.smtResult(result); if (result.isError()) { throw new RefinementError(element.getPosition(), expectedType.simplify(context), - premisesBeforeChange.simplify(context), map, result.getCounterexample(), customMessage); + implBeforeChange.simplify(), map, result.getCounterexample(), customMessage); } } @@ -405,8 +406,8 @@ private VCImplication buildPremiseChain(TranslationTable map, Predicate... predi protected void throwRefinementError(SourcePosition position, Predicate expected, Predicate found, Counterexample counterexample, String customMessage) throws RefinementError { TranslationTable map = new TranslationTable(); - Predicate premises = buildPremiseChain(map, expected, found).toConjunctions(); - throw new RefinementError(position, expected.simplify(context), premises.simplify(context), map, counterexample, + VCImplication premises = buildPremiseChain(map, expected, found); + throw new RefinementError(position, expected.simplify(context), premises.simplify(), map, counterexample, customMessage); } @@ -414,8 +415,7 @@ protected void throwStateRefinementError(SourcePosition position, Predicate foun String customMessage) throws StateRefinementError { TranslationTable map = new TranslationTable(); VCImplication foundState = buildPremiseChain(map, expected, found); - throw new StateRefinementError(position, expected.simplify(context), - foundState.toConjunctions().simplify(context), map, customMessage); + throw new StateRefinementError(position, expected.simplify(context), foundState.simplify(), map, customMessage); } protected void throwStateConflictError(SourcePosition position, Predicate expected) throws StateConflictError { diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java index e6a4f04fb..4f72f1c70 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java @@ -9,6 +9,7 @@ import liquidjava.diagnostics.DebugLog; import liquidjava.diagnostics.errors.LJError; import liquidjava.diagnostics.errors.NotFoundError; +import liquidjava.processor.VCImplication; import liquidjava.processor.context.AliasWrapper; import liquidjava.processor.context.Context; import liquidjava.processor.context.GhostFunction; @@ -27,10 +28,8 @@ import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.UnaryExpression; import liquidjava.rj_language.ast.Var; -import liquidjava.utils.StaticConstants; -import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; -import liquidjava.rj_language.opt.ExpressionSimplifier; import liquidjava.rj_language.parsing.RefinementsParser; +import liquidjava.utils.StaticConstants; import liquidjava.utils.Utils; import liquidjava.utils.constants.Keys; import liquidjava.utils.constants.Ops; @@ -260,15 +259,9 @@ public Predicate getOrigin() { return this; } - public ValDerivationNode simplify(Context context) { - // collect aliases from context - Map aliases = new HashMap<>(); - for (AliasWrapper aw : context.getAliases()) { - aliases.put(aw.getName(), aw.createAliasDTO()); - } - // simplify expression - ValDerivationNode result = ExpressionSimplifier.simplify(exp.clone(), aliases); - DebugLog.simplification(this.getExpression(), result.getValue()); + public VCImplication simplify(Context context) { + VCImplication result = new VCImplication(clone()).simplify(); + DebugLog.simplification(this.getExpression(), result.getRefinement().getExpression()); return result; } From e789e05384828a4e458aab2a8c60a119124ed803 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 17 Jun 2026 17:45:03 +0100 Subject: [PATCH 58/65] Remove Expression-Based Simplification Classes --- CONTRIBUTING.md | 3 +- .../java/liquidjava/diagnostics/DebugLog.java | 2 +- .../rj_language/opt/AliasExpansion.java | 91 --- .../rj_language/opt/ExpressionFolding.java | 301 -------- .../rj_language/opt/ExpressionSimplifier.java | 237 ------ .../rj_language/opt/VariablePropagation.java | 166 ---- .../rj_language/opt/VariableResolver.java | 225 ------ .../derivation_node/BinaryDerivationNode.java | 26 - .../opt/derivation_node/DerivationNode.java | 15 - .../derivation_node/IteDerivationNode.java | 26 - .../derivation_node/UnaryDerivationNode.java | 20 - .../derivation_node/ValDerivationNode.java | 54 -- .../derivation_node/VarDerivationNode.java | 43 -- .../opt/ExpressionSimplifierTest.java | 710 ------------------ .../rj_language/opt/VariableResolverTest.java | 217 ------ .../test/java/liquidjava/utils/TestUtils.java | 49 +- 16 files changed, 4 insertions(+), 2181 deletions(-) delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/AliasExpansion.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ExpressionFolding.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ExpressionSimplifier.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariablePropagation.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariableResolver.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/BinaryDerivationNode.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/DerivationNode.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/IteDerivationNode.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/UnaryDerivationNode.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/VarDerivationNode.java delete mode 100644 liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/ExpressionSimplifierTest.java delete mode 100644 liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e717e80f..ce98d2738 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,9 +37,8 @@ To run specific tests, run: ```bash mvn -pl liquidjava-verifier -Dtest=ExpressionFormatterTest test -mvn -pl liquidjava-verifier -Dtest=ExpressionSimplifierTest test mvn -pl liquidjava-verifier -Dtest=RefinementsParserTest test -mvn -pl liquidjava-verifier -Dtest=VariableResolverTest test +mvn -pl liquidjava-verifier -Dtest=VCSimplificationTest test ``` ## Release diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/DebugLog.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/DebugLog.java index 0adcccd60..02c407939 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/DebugLog.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/DebugLog.java @@ -56,7 +56,7 @@ public static void smtVerifying(SourcePosition position) { /** * Flat-predicate fallback: prints top-level conjuncts in order with no per-variable grouping. Used by SMT entry - * points that don't carry the structured per-variable {@link VCImplication} chain (e.g. ExpressionSimplifier). + * points that don't carry the structured per-variable {@link VCImplication} chain. */ public static void smtStart(Predicate premises, Predicate conclusion) { if (!enabled()) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/AliasExpansion.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/AliasExpansion.java deleted file mode 100644 index b8ab6b3fa..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/AliasExpansion.java +++ /dev/null @@ -1,91 +0,0 @@ -package liquidjava.rj_language.opt; - -import java.util.Map; - -import liquidjava.processor.facade.AliasDTO; -import liquidjava.rj_language.ast.AliasInvocation; -import liquidjava.rj_language.ast.BinaryExpression; -import liquidjava.rj_language.ast.Expression; -import liquidjava.rj_language.ast.GroupExpression; -import liquidjava.rj_language.ast.UnaryExpression; -import liquidjava.rj_language.ast.Var; -import liquidjava.rj_language.opt.derivation_node.BinaryDerivationNode; -import liquidjava.rj_language.opt.derivation_node.DerivationNode; -import liquidjava.rj_language.opt.derivation_node.UnaryDerivationNode; -import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; - -public class AliasExpansion { - - /** - * Expands alias invocations in a derivation node to their definitions, storing the expanded body as the origin of - * each alias invocation node. - */ - public static ValDerivationNode expand(ValDerivationNode node, Map aliases) { - return expandRecursive(node, aliases); - } - - private static ValDerivationNode expandRecursive(ValDerivationNode node, Map aliases) { - Expression exp = node.getValue(); - - // expand alias invocation - if (exp instanceof AliasInvocation ai) { - return expandAlias(ai, aliases); - } - - // recurse into binary expressions - if (exp instanceof BinaryExpression binary) { - ValDerivationNode leftNode; - ValDerivationNode rightNode; - if (node.getOrigin()instanceof BinaryDerivationNode binOrigin) { - leftNode = expandRecursive(binOrigin.getLeft(), aliases); - rightNode = expandRecursive(binOrigin.getRight(), aliases); - } else { - leftNode = expandRecursive(new ValDerivationNode(binary.getFirstOperand(), null), aliases); - rightNode = expandRecursive(new ValDerivationNode(binary.getSecondOperand(), null), aliases); - } - boolean hasExpansion = leftNode.getOrigin() != null || rightNode.getOrigin() != null; - DerivationNode origin = hasExpansion ? new BinaryDerivationNode(leftNode, rightNode, binary.getOperator()) - : node.getOrigin(); - return new ValDerivationNode(exp, origin); - } - - // recurse into unary expressions - if (exp instanceof UnaryExpression unary) { - ValDerivationNode operandNode; - if (node.getOrigin()instanceof UnaryDerivationNode unaryOrigin) { - operandNode = expandRecursive(unaryOrigin.getOperand(), aliases); - } else { - operandNode = expandRecursive(new ValDerivationNode(unary.getChildren().get(0), null), aliases); - } - DerivationNode origin = operandNode.getOrigin() != null - ? new UnaryDerivationNode(operandNode, unary.getOp()) : node.getOrigin(); - return new ValDerivationNode(exp, origin); - } - - // recurse into group expressions - if (exp instanceof GroupExpression group && group.getChildren().size() == 1) { - return expandRecursive(new ValDerivationNode(group.getChildren().get(0), node.getOrigin()), aliases); - } - - return node; - } - - private static ValDerivationNode expandAlias(AliasInvocation ai, Map aliases) { - AliasDTO dto = aliases.get(ai.getName()); - - // no alias found - if (dto == null || dto.getExpression() == null) { - return new ValDerivationNode(ai, null); - } - - // substitute parameters in the body with the invocation arguments - Expression body = dto.getExpression().clone(); - for (int i = 0; i < ai.getArgs().size() && i < dto.getVarNames().size(); i++) { - body = body.substitute(new Var(dto.getVarNames().get(i)), ai.getArgs().get(i)); - } - - // recursively expand the body - ValDerivationNode expandedBody = expandRecursive(new ValDerivationNode(body, null), aliases); - return new ValDerivationNode(ai, expandedBody); - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ExpressionFolding.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ExpressionFolding.java deleted file mode 100644 index 482a9948d..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ExpressionFolding.java +++ /dev/null @@ -1,301 +0,0 @@ -package liquidjava.rj_language.opt; - -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; -import liquidjava.rj_language.opt.derivation_node.BinaryDerivationNode; -import liquidjava.rj_language.opt.derivation_node.DerivationNode; -import liquidjava.rj_language.opt.derivation_node.IteDerivationNode; -import liquidjava.rj_language.opt.derivation_node.UnaryDerivationNode; -import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; - -public class ExpressionFolding { - - /** - * Performs expression folding on a derivation node by evaluating nodes when possible. Returns a new derivation node - * representing the folding steps taken - */ - public static ValDerivationNode fold(ValDerivationNode node) { - Expression exp = node.getValue(); - if (exp instanceof BinaryExpression) - return foldBinary(node); - - if (exp instanceof UnaryExpression) - return foldUnary(node); - - if (exp instanceof Ite) - return foldIte(node); - - if (exp instanceof GroupExpression group) { - if (group.getChildren().size() == 1) { - return fold(new ValDerivationNode(group.getChildren().get(0), node.getOrigin())); - } - } - return node; - } - - /** - * Folds a binary expression node (e.g. 1 + 2 => 3) - */ - private static ValDerivationNode foldBinary(ValDerivationNode node) { - BinaryExpression binExp = (BinaryExpression) node.getValue(); - DerivationNode parent = node.getOrigin(); - - // fold child nodes - ValDerivationNode leftNode; - ValDerivationNode rightNode; - if (parent instanceof BinaryDerivationNode binaryOrigin) { - // has origin (from constant propagation) - leftNode = fold(binaryOrigin.getLeft()); - rightNode = fold(binaryOrigin.getRight()); - } else { - // no origin - leftNode = fold(new ValDerivationNode(binExp.getFirstOperand(), null)); - rightNode = fold(new ValDerivationNode(binExp.getSecondOperand(), null)); - } - - Expression left = leftNode.getValue(); - Expression right = rightNode.getValue(); - String op = binExp.getOperator(); - - if (left instanceof Enum en && en.getResolvedLiteral() != null) { - left = en.getResolvedLiteral().clone(); - leftNode = new ValDerivationNode(left, leftNode); - } - if (right instanceof Enum en && en.getResolvedLiteral() != null) { - right = en.getResolvedLiteral().clone(); - rightNode = new ValDerivationNode(right, rightNode); - } - - binExp.setChild(0, left); - binExp.setChild(1, right); - - // int and int - if (left instanceof LiteralInt && right instanceof LiteralInt) { - int l = ((LiteralInt) left).getValue(); - int r = ((LiteralInt) right).getValue(); - Expression res = switch (op) { - case "+" -> new LiteralInt(l + r); - case "-" -> new LiteralInt(l - r); - case "*" -> new LiteralInt(l * r); - case "/" -> r != 0 ? new LiteralInt(l / r) : null; - case "%" -> r != 0 ? new LiteralInt(l % r) : null; - case "<" -> new LiteralBoolean(l < r); - case "<=" -> new LiteralBoolean(l <= r); - case ">" -> new LiteralBoolean(l > r); - case ">=" -> new LiteralBoolean(l >= r); - case "==" -> new LiteralBoolean(l == r); - case "!=" -> new LiteralBoolean(l != r); - default -> null; - }; - if (res != null) - return new ValDerivationNode(res, new BinaryDerivationNode(leftNode, rightNode, op)); - } - // real and real - else if (left instanceof LiteralReal && right instanceof LiteralReal) { - double l = ((LiteralReal) left).getValue(); - double r = ((LiteralReal) right).getValue(); - Expression res = switch (op) { - case "+" -> new LiteralReal(l + r); - case "-" -> new LiteralReal(l - r); - case "*" -> new LiteralReal(l * r); - case "/" -> r != 0.0 ? new LiteralReal(l / r) : null; - case "%" -> r != 0.0 ? new LiteralReal(l % r) : null; - case "<" -> new LiteralBoolean(l < r); - case "<=" -> new LiteralBoolean(l <= r); - case ">" -> new LiteralBoolean(l > r); - case ">=" -> new LiteralBoolean(l >= r); - case "==" -> new LiteralBoolean(l == r); - case "!=" -> new LiteralBoolean(l != r); - default -> null; - }; - if (res != null) - return new ValDerivationNode(res, new BinaryDerivationNode(leftNode, rightNode, op)); - } - - // mixed int and real - else if ((left instanceof LiteralInt && right instanceof LiteralReal) - || (left instanceof LiteralReal && right instanceof LiteralInt)) { - double l = left instanceof LiteralInt ? ((LiteralInt) left).getValue() : ((LiteralReal) left).getValue(); - double r = right instanceof LiteralInt ? ((LiteralInt) right).getValue() : ((LiteralReal) right).getValue(); - Expression res = switch (op) { - case "+" -> new LiteralReal(l + r); - case "-" -> new LiteralReal(l - r); - case "*" -> new LiteralReal(l * r); - case "/" -> r != 0.0 ? new LiteralReal(l / r) : null; - case "%" -> r != 0.0 ? new LiteralReal(l % r) : null; - case "<" -> new LiteralBoolean(l < r); - case "<=" -> new LiteralBoolean(l <= r); - case ">" -> new LiteralBoolean(l > r); - case ">=" -> new LiteralBoolean(l >= r); - case "==" -> new LiteralBoolean(l == r); - case "!=" -> new LiteralBoolean(l != r); - default -> null; - }; - if (res != null) - return new ValDerivationNode(res, new BinaryDerivationNode(leftNode, rightNode, op)); - } - // bool and bool - else if (left instanceof LiteralBoolean && right instanceof LiteralBoolean) { - boolean l = left.isBooleanTrue(); - boolean r = right.isBooleanTrue(); - Expression res = switch (op) { - case "&&" -> new LiteralBoolean(l && r); - case "||" -> new LiteralBoolean(l || r); - case "-->" -> new LiteralBoolean(!l || r); - case "==" -> new LiteralBoolean(l == r); - case "!=" -> new LiteralBoolean(l != r); - default -> null; - }; - if (res != null) - return new ValDerivationNode(res, new BinaryDerivationNode(leftNode, rightNode, op)); - } - - else if (left instanceof Enum leftEnum && right instanceof Enum rightEnum - && leftEnum.getTypeName().equals(rightEnum.getTypeName())) { - boolean equal = leftEnum.getConstName().equals(rightEnum.getConstName()); - Expression res = switch (op) { - case "==" -> new LiteralBoolean(equal); - case "!=" -> new LiteralBoolean(!equal); - default -> null; - }; - if (res != null) - return new ValDerivationNode(res, new BinaryDerivationNode(leftNode, rightNode, op)); - } - - ValDerivationNode adjacentConstants = foldAdjacentIntegerConstants(leftNode, rightNode, op); - if (adjacentConstants != null) - return adjacentConstants; - - // no folding - DerivationNode origin = (leftNode.getOrigin() != null || rightNode.getOrigin() != null) - ? new BinaryDerivationNode(leftNode, rightNode, op) : null; - return new ValDerivationNode(binExp, origin); - } - - /** - * Folds a unary expression node (e.g. !true => false) - */ - private static ValDerivationNode foldUnary(ValDerivationNode node) { - UnaryExpression unaryExp = (UnaryExpression) node.getValue(); - DerivationNode parent = node.getOrigin(); - - // fold child node - ValDerivationNode operandNode; - if (parent instanceof UnaryDerivationNode unaryOrigin) { - // has origin (from constant propagation) - operandNode = fold(unaryOrigin.getOperand()); - } else { - // no origin - operandNode = fold(new ValDerivationNode(unaryExp.getChildren().get(0), null)); - } - Expression operand = operandNode.getValue(); - String operator = unaryExp.getOp(); - unaryExp.setChild(0, operand); - - // unary not - if ("!".equals(operator) && operand instanceof LiteralBoolean) { - // !true => false, !false => true - boolean value = operand.isBooleanTrue(); - Expression res = new LiteralBoolean(!value); - DerivationNode origin = operandNode.getOrigin() != null ? new UnaryDerivationNode(operandNode, operator) - : null; - return new ValDerivationNode(res, origin); - } - // unary minus - if ("-".equals(operator)) { - // -(x) => -x - if (operand instanceof LiteralInt) { - Expression res = new LiteralInt(-((LiteralInt) operand).getValue()); - DerivationNode origin = operandNode.getOrigin() != null ? new UnaryDerivationNode(operandNode, operator) - : null; - return new ValDerivationNode(res, origin); - } - if (operand instanceof LiteralReal) { - Expression res = new LiteralReal(-((LiteralReal) operand).getValue()); - DerivationNode origin = operandNode.getOrigin() != null ? new UnaryDerivationNode(operandNode, operator) - : null; - return new ValDerivationNode(res, origin); - } - } - - // no folding - DerivationNode origin = operandNode.getOrigin() != null ? new UnaryDerivationNode(operandNode, operator) : null; - return new ValDerivationNode(unaryExp, origin); - } - - /** - * Folds ternary expressions by checking if condition is a boolean literal or both branches are the same - */ - private static ValDerivationNode foldIte(ValDerivationNode node) { - Ite iteExp = (Ite) node.getValue(); - - ValDerivationNode condNode = fold(new ValDerivationNode(iteExp.getCondition(), null)); - ValDerivationNode thenNode = fold(new ValDerivationNode(iteExp.getThen(), null)); - ValDerivationNode elseNode = fold(new ValDerivationNode(iteExp.getElse(), null)); - - Expression condition = condNode.getValue(); - Expression thenExp = thenNode.getValue(); - Expression elseExp = elseNode.getValue(); - - iteExp.setChild(0, condition); - iteExp.setChild(1, thenExp); - iteExp.setChild(2, elseExp); - - // if condition is a boolean literal, select the corresponding branch: true ? a : b => a, false ? a : b => b - if (condition instanceof LiteralBoolean boolCond) { - Expression selected = boolCond.isBooleanTrue() ? thenExp : elseExp; - DerivationNode origin = new IteDerivationNode(condNode, thenNode, elseNode); - return new ValDerivationNode(selected, origin); - } - - // if both branches are the same, return one of them (e.g. cond ? b : b => b) - if (thenExp.equals(elseExp)) { - DerivationNode origin = new IteDerivationNode(condNode, thenNode, elseNode); - return new ValDerivationNode(thenExp, origin); - } - - // no folding, but keep track of the folding steps in the origin - DerivationNode origin = hasIteChildOrigin(condNode, thenNode, elseNode) - ? new IteDerivationNode(condNode, thenNode, elseNode) : node.getOrigin(); - return new ValDerivationNode(iteExp, origin); - } - - private static boolean hasIteChildOrigin(ValDerivationNode cond, ValDerivationNode then, ValDerivationNode els) { - return cond.getOrigin() != null || then.getOrigin() != null || els.getOrigin() != null; - } - - private static ValDerivationNode foldAdjacentIntegerConstants(ValDerivationNode leftNode, - ValDerivationNode rightNode, String op) { - if (!"+".equals(op) && !"-".equals(op)) - return null; - if (!(rightNode.getValue()instanceof LiteralInt rightLiteral)) - return null; - if (!(leftNode.getValue()instanceof BinaryExpression leftBinary)) - return null; - if (!"+".equals(leftBinary.getOperator()) && !"-".equals(leftBinary.getOperator())) - return null; - if (!(leftBinary.getSecondOperand()instanceof LiteralInt leftLiteral)) - return null; - - 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 new ValDerivationNode(folded, new BinaryDerivationNode(leftNode, rightNode, op)); - } - - 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/main/java/liquidjava/rj_language/opt/ExpressionSimplifier.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ExpressionSimplifier.java deleted file mode 100644 index e94d2574c..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ExpressionSimplifier.java +++ /dev/null @@ -1,237 +0,0 @@ -package liquidjava.rj_language.opt; - -import liquidjava.diagnostics.DebugLog; -import liquidjava.processor.context.Context; -import liquidjava.rj_language.Predicate; -import java.util.Map; - -import liquidjava.processor.facade.AliasDTO; -import liquidjava.rj_language.ast.BinaryExpression; -import liquidjava.rj_language.ast.Expression; -import liquidjava.rj_language.ast.LiteralBoolean; -import liquidjava.rj_language.ast.UnaryExpression; -import liquidjava.rj_language.opt.derivation_node.BinaryDerivationNode; -import liquidjava.rj_language.opt.derivation_node.DerivationNode; -import liquidjava.rj_language.opt.derivation_node.UnaryDerivationNode; -import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; -import liquidjava.smt.SMTEvaluator; -import liquidjava.smt.SMTResult; - -public class ExpressionSimplifier { - - /** - * Simplifies an expression by applying constant propagation, constant folding, removing redundant conjuncts and - * expanding aliases Returns a derivation node representing the tree of simplifications applied - */ - public static ValDerivationNode simplify(Expression exp, Map aliases) { - DebugLog.simplificationStart(exp); - ValDerivationNode fixedPoint = simplifyToFixedPoint(null, exp); - DebugLog.simplificationPass("fixed-point reached", fixedPoint.getValue()); - ValDerivationNode simplified = simplifyValDerivationNode(fixedPoint); - DebugLog.simplificationPass("remove redundant &&", simplified.getValue()); - ValDerivationNode unwrapped = unwrapBooleanLiterals(simplified); - DebugLog.simplificationPass("unwrap boolean literals", unwrapped.getValue()); - ValDerivationNode expanded = AliasExpansion.expand(unwrapped, aliases); - DebugLog.simplificationPass("expand aliases", expanded.getValue()); - return expanded; - } - - public static ValDerivationNode simplify(Expression exp) { - return simplify(exp, Map.of()); - } - - /** - * Recursively applies propagation and folding until the expression stops changing (fixed point) Stops early if the - * expression simplifies to a boolean literal, which means we've simplified too much. - */ - private static ValDerivationNode simplifyToFixedPoint(ValDerivationNode current, Expression prevExp) { - ValDerivationNode simplified = simplifyOnce(current, prevExp); - Expression currExp = simplified.getValue(); - - // fixed point reached — compare on toString() because propagate/fold/reduce mutate the AST in place, so a - // reference-level .equals() can trivially be true on shared (mutated) nodes - if (current != null && currExp.toString().equals(current.getValue().toString())) { - return current; - } - - // prevent oversimplification - if (current != null && currExp instanceof LiteralBoolean && !(current.getValue() instanceof LiteralBoolean)) { - return current; - } - - // continue simplifying - return simplifyToFixedPoint(simplified, simplified.getValue()); - } - - private static ValDerivationNode simplifyOnce(ValDerivationNode current, Expression prevExp) { - ValDerivationNode prop = VariablePropagation.propagate(prevExp, current); - DebugLog.simplificationPass("variable propagation", prop.getValue()); - ValDerivationNode fold = ExpressionFolding.fold(prop); - DebugLog.simplificationPass("expression folding", fold.getValue()); - ValDerivationNode simplified = simplifyValDerivationNode(fold); - DebugLog.simplificationPass("remove redundant && (loop)", simplified.getValue()); - return simplified; - } - - /** - * Recursively simplifies the derivation tree by removing redundant conjuncts - */ - private static ValDerivationNode simplifyValDerivationNode(ValDerivationNode node) { - Expression value = node.getValue(); - DerivationNode origin = node.getOrigin(); - - // binary expression with && - if (value instanceof BinaryExpression binExp && "&&".equals(binExp.getOperator())) { - ValDerivationNode leftSimplified; - ValDerivationNode rightSimplified; - - if (origin instanceof BinaryDerivationNode binOrigin) { - leftSimplified = simplifyValDerivationNode(binOrigin.getLeft()); - rightSimplified = simplifyValDerivationNode(binOrigin.getRight()); - } else { - leftSimplified = simplifyValDerivationNode(new ValDerivationNode(binExp.getFirstOperand(), null)); - rightSimplified = simplifyValDerivationNode(new ValDerivationNode(binExp.getSecondOperand(), null)); - } - - // check if either side is redundant - if (isRedundant(leftSimplified.getValue())) - return rightSimplified; - if (isRedundant(rightSimplified.getValue())) - return leftSimplified; - - // collapse identical sides (x && x => x) — toString() avoids false positives when the two sides share a - // mutated AST node and false negatives are harmless (we just keep the conjunction) - if (leftSimplified.getValue().toString().equals(rightSimplified.getValue().toString())) { - return leftSimplified; - } - - // collapse symmetric equalities (e.g. x == y && y == x => x == y) - if (isSymmetricEquality(leftSimplified.getValue(), rightSimplified.getValue())) { - return leftSimplified; - } - - // remove weaker conjuncts (e.g. x > 0 && x > -1 => x > 0) - if (implies(leftSimplified.getValue(), rightSimplified.getValue())) { - return new ValDerivationNode(leftSimplified.getValue(), - new BinaryDerivationNode(leftSimplified, rightSimplified, "&&")); - } - if (implies(rightSimplified.getValue(), leftSimplified.getValue())) { - return new ValDerivationNode(rightSimplified.getValue(), - new BinaryDerivationNode(leftSimplified, rightSimplified, "&&")); - } - - // return the conjunction with simplified children - Expression newValue = new BinaryExpression(leftSimplified.getValue(), "&&", rightSimplified.getValue()); - // only create origin if at least one child has a meaningful origin - if (leftSimplified.getOrigin() != null || rightSimplified.getOrigin() != null) { - DerivationNode newOrigin = new BinaryDerivationNode(leftSimplified, rightSimplified, "&&"); - return new ValDerivationNode(newValue, newOrigin); - } - return new ValDerivationNode(newValue, null); - } - // no simplification - return node; - } - - private static boolean isSymmetricEquality(Expression left, Expression right) { - if (left instanceof BinaryExpression b1 && "==".equals(b1.getOperator()) && right instanceof BinaryExpression b2 - && "==".equals(b2.getOperator())) { - - Expression l1 = b1.getFirstOperand(); - Expression r1 = b1.getSecondOperand(); - Expression l2 = b2.getFirstOperand(); - Expression r2 = b2.getSecondOperand(); - return l1.equals(r2) && r1.equals(l2); - } - return false; - } - - /** - * Checks if an expression is redundant (e.g. true or x == x) - */ - private static boolean isRedundant(Expression exp) { - // true - if (exp instanceof LiteralBoolean && exp.isBooleanTrue()) { - return true; - } - // x == x - if (exp instanceof BinaryExpression binExp) { - if ("==".equals(binExp.getOperator())) { - Expression left = binExp.getFirstOperand(); - Expression right = binExp.getSecondOperand(); - return left.equals(right); - } - } - return false; - } - - /** - * Recursively traverses the derivation tree and replaces boolean literals with the expressions that produced them, - * but only when at least one operand in the derivation is non-boolean. e.g. "x == true" where true came from "1 > - * 0" becomes "x == 1 > 0" - */ - private static ValDerivationNode unwrapBooleanLiterals(ValDerivationNode node) { - Expression value = node.getValue(); - DerivationNode origin = node.getOrigin(); - - if (origin == null) - return node; - - // unwrap binary expressions - if (value instanceof BinaryExpression binExp && origin instanceof BinaryDerivationNode binOrigin) { - ValDerivationNode left = unwrapBooleanLiterals(binOrigin.getLeft()); - ValDerivationNode right = unwrapBooleanLiterals(binOrigin.getRight()); - if (left != binOrigin.getLeft() || right != binOrigin.getRight()) { - Expression newValue = new BinaryExpression(left.getValue(), binExp.getOperator(), right.getValue()); - return new ValDerivationNode(newValue, new BinaryDerivationNode(left, right, binOrigin.getOp())); - } - return node; - } - - // unwrap unary expressions - if (value instanceof UnaryExpression unaryExp && origin instanceof UnaryDerivationNode unaryOrigin) { - ValDerivationNode operand = unwrapBooleanLiterals(unaryOrigin.getOperand()); - if (operand != unaryOrigin.getOperand()) { - Expression newValue = new UnaryExpression(unaryExp.getOp(), operand.getValue()); - return new ValDerivationNode(newValue, new UnaryDerivationNode(operand, unaryOrigin.getOp())); - } - return node; - } - - // boolean literal with binary origin: unwrap if at least one child is non-boolean - if (value instanceof LiteralBoolean && origin instanceof BinaryDerivationNode binOrigin) { - ValDerivationNode left = unwrapBooleanLiterals(binOrigin.getLeft()); - ValDerivationNode right = unwrapBooleanLiterals(binOrigin.getRight()); - if (!(left.getValue() instanceof LiteralBoolean) || !(right.getValue() instanceof LiteralBoolean)) { - Expression newValue = new BinaryExpression(left.getValue(), binOrigin.getOp(), right.getValue()); - return new ValDerivationNode(newValue, new BinaryDerivationNode(left, right, binOrigin.getOp())); - } - return node; - } - - // boolean literal with unary origin: unwrap if operand is non-boolean - if (value instanceof LiteralBoolean && origin instanceof UnaryDerivationNode unaryOrigin) { - ValDerivationNode operand = unwrapBooleanLiterals(unaryOrigin.getOperand()); - if (!(operand.getValue() instanceof LiteralBoolean)) { - Expression newValue = new UnaryExpression(unaryOrigin.getOp(), operand.getValue()); - return new ValDerivationNode(newValue, new UnaryDerivationNode(operand, unaryOrigin.getOp())); - } - return node; - } - - return node; - } - - /** - * Checks whether one expression implies another by asking Z3, used to remove weaker conjuncts in the simplification - */ - private static boolean implies(Expression stronger, Expression weaker) { - try { - SMTResult result = new SMTEvaluator().verifySubtype(new Predicate(stronger), new Predicate(weaker), - Context.getInstance(), true); - return result.isOk(); - } catch (Exception e) { - return false; - } - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariablePropagation.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariablePropagation.java deleted file mode 100644 index 48b37e030..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariablePropagation.java +++ /dev/null @@ -1,166 +0,0 @@ -package liquidjava.rj_language.opt; - -import liquidjava.rj_language.ast.BinaryExpression; -import liquidjava.rj_language.ast.Enum; -import liquidjava.rj_language.ast.Expression; -import liquidjava.rj_language.ast.FunctionInvocation; -import liquidjava.rj_language.ast.UnaryExpression; -import liquidjava.rj_language.ast.Var; -import liquidjava.rj_language.opt.derivation_node.BinaryDerivationNode; -import liquidjava.rj_language.opt.derivation_node.DerivationNode; -import liquidjava.rj_language.opt.derivation_node.IteDerivationNode; -import liquidjava.rj_language.opt.derivation_node.UnaryDerivationNode; -import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; -import liquidjava.rj_language.opt.derivation_node.VarDerivationNode; - -import java.util.HashMap; -import java.util.Map; - -public class VariablePropagation { - - /** - * Performs constant and variable propagation on an expression, by substituting variables. Uses the VariableResolver - * to extract variable equalities from the expression first. Returns a derivation node representing the propagation - * steps taken. - */ - public static ValDerivationNode propagate(Expression exp, ValDerivationNode previousOrigin) { - Map substitutions = VariableResolver.resolve(exp); - Map directSubstitutions = new HashMap<>(); // var == literal or var == var - Map expressionSubstitutions = new HashMap<>(); // var == expression - for (Map.Entry entry : substitutions.entrySet()) { - Expression value = entry.getValue(); - if (value.isLiteral() || value instanceof Var || value instanceof Enum) { - directSubstitutions.put(entry.getKey(), value); - } else { - expressionSubstitutions.put(entry.getKey(), value); - } - } - - // map of variable origins from the previous derivation tree - Map varOrigins = new HashMap<>(); - if (previousOrigin != null) { - extractVarOrigins(previousOrigin, varOrigins); - } - Map activeSubstitutions = directSubstitutions.isEmpty() ? expressionSubstitutions - : directSubstitutions; - return propagateRecursive(exp, activeSubstitutions, varOrigins); - } - - /** - * Recursively performs propagation on an expression (e.g. x + y && x == 1 && y == 2 => 1 + 2) - */ - private static ValDerivationNode propagateRecursive(Expression exp, Map subs, - Map varOrigins) { - - // substitute variable - if (exp instanceof Var var) { - String name = var.getName(); - Expression value = subs.get(name); - // substitution - if (value != null) { - // check if this variable has an origin from a previous pass - DerivationNode previousOrigin = varOrigins.get(name); - - // preserve origin if value came from previous derivation - DerivationNode origin = previousOrigin != null ? new VarDerivationNode(name, previousOrigin) - : new VarDerivationNode(name); - return new ValDerivationNode(value.clone(), origin); - } - - // no substitution - return new ValDerivationNode(var, null); - } - - if (exp instanceof FunctionInvocation) { - Expression value = subs.get(exp.toString()); - if (value != null) - return new ValDerivationNode(value.clone(), new VarDerivationNode(exp.toString())); - } - - // lift unary origin - if (exp instanceof UnaryExpression unary) { - ValDerivationNode operand = propagateRecursive(unary.getChildren().get(0), subs, varOrigins); - UnaryExpression cloned = (UnaryExpression) unary.clone(); - cloned.setChild(0, operand.getValue()); - - return operand.getOrigin() != null - ? new ValDerivationNode(cloned, new UnaryDerivationNode(operand, cloned.getOp())) - : new ValDerivationNode(cloned, null); - } - - // lift binary origin - if (exp instanceof BinaryExpression binary) { - ValDerivationNode left = propagateRecursive(binary.getFirstOperand(), subs, varOrigins); - ValDerivationNode right = propagateRecursive(binary.getSecondOperand(), subs, varOrigins); - BinaryExpression cloned = (BinaryExpression) binary.clone(); - cloned.setChild(0, left.getValue()); - cloned.setChild(1, right.getValue()); - - return (left.getOrigin() != null || right.getOrigin() != null) - ? new ValDerivationNode(cloned, new BinaryDerivationNode(left, right, cloned.getOperator())) - : new ValDerivationNode(cloned, null); - } - - // recursively propagate children - if (exp.hasChildren()) { - Expression propagated = exp.clone(); - for (int i = 0; i < exp.getChildren().size(); i++) { - ValDerivationNode child = propagateRecursive(exp.getChildren().get(i), subs, varOrigins); - propagated.setChild(i, child.getValue()); - } - return new ValDerivationNode(propagated, null); - } - - // no propagation - return new ValDerivationNode(exp, null); - } - - /** - * Extracts the derivation nodes for variable values from the derivation tree This is so done so when we find "var - * == value" in the tree, we store the derivation of the value So it can be preserved when var is substituted in - * subsequent passes - */ - private static void extractVarOrigins(ValDerivationNode node, Map varOrigins) { - if (node == null) - return; - - Expression value = node.getValue(); - DerivationNode origin = node.getOrigin(); - - // check for equality expressions - if (value instanceof BinaryExpression binExp && "==".equals(binExp.getOperator()) - && origin instanceof BinaryDerivationNode binOrigin) { - Expression left = binExp.getFirstOperand(); - Expression right = binExp.getSecondOperand(); - - // extract variable name and value derivation from either side - String varName = null; - ValDerivationNode valueDerivation = null; - - if (left instanceof Var var && right.isLiteral()) { - varName = var.getName(); - valueDerivation = binOrigin.getRight(); - } else if (right instanceof Var var && left.isLiteral()) { - varName = var.getName(); - valueDerivation = binOrigin.getLeft(); - } - if (varName != null && valueDerivation != null && valueDerivation.getOrigin() != null) { - varOrigins.put(varName, valueDerivation.getOrigin()); - } - } - - // recursively process the origin tree - if (origin instanceof BinaryDerivationNode binOrigin) { - extractVarOrigins(binOrigin.getLeft(), varOrigins); - extractVarOrigins(binOrigin.getRight(), varOrigins); - } else if (origin instanceof UnaryDerivationNode unaryOrigin) { - extractVarOrigins(unaryOrigin.getOperand(), varOrigins); - } else if (origin instanceof IteDerivationNode iteOrigin) { - extractVarOrigins(iteOrigin.getCondition(), varOrigins); - extractVarOrigins(iteOrigin.getThenBranch(), varOrigins); - extractVarOrigins(iteOrigin.getElseBranch(), varOrigins); - } else if (origin instanceof ValDerivationNode valOrigin) { - extractVarOrigins(valOrigin, varOrigins); - } - } -} \ No newline at end of file diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariableResolver.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariableResolver.java deleted file mode 100644 index 32170c796..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariableResolver.java +++ /dev/null @@ -1,225 +0,0 @@ -package liquidjava.rj_language.opt; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import liquidjava.rj_language.ast.BinaryExpression; -import liquidjava.rj_language.ast.Enum; -import liquidjava.rj_language.ast.Expression; -import liquidjava.rj_language.ast.FunctionInvocation; -import liquidjava.rj_language.ast.Var; - -public class VariableResolver { - - /** - * Extracts variables with constant values from an expression - * - * @param exp - * - * @returns map from variable names to their values - */ - public static Map resolve(Expression exp) { - Map map = new HashMap<>(); - - // extract variable equalities recursively - resolveRecursive(exp, map); - - // remove variables that were not used in the expression - map.entrySet().removeIf(entry -> !hasUsage(exp, entry.getKey(), entry.getValue(), true)); - - // transitively resolve variables - return resolveTransitive(map); - } - - /** - * Recursively extracts variable equalities from an expression (e.g. ... && x == 1 && y == 2 => map: x -> 1, y -> 2) - * - * @param exp - * @param map - */ - private static void resolveRecursive(Expression exp, Map map) { - if (!(exp instanceof BinaryExpression be)) - return; - - String op = be.getOperator(); - if ("&&".equals(op)) { - resolveRecursive(be.getFirstOperand(), map); - resolveRecursive(be.getSecondOperand(), map); - return; - } - if (!"==".equals(op)) - return; - - Expression left = be.getFirstOperand(); - Expression right = be.getSecondOperand(); - String leftKey = substitutionKey(left); - String rightKey = substitutionKey(right); - - if (leftKey != null && isConstant(right)) { - map.put(leftKey, right.clone()); - } else if (rightKey != null && isConstant(left)) { - map.put(rightKey, left.clone()); - } else if (left instanceof Var leftVar && right instanceof Var rightVar) { - // to substitute internal variable with user-facing variable - if (isInternal(leftVar) && !isInternal(rightVar) && !isReturnVar(leftVar)) { - map.put(leftVar.getName(), right.clone()); - } else if (isInternal(rightVar) && !isInternal(leftVar) && !isReturnVar(rightVar)) { - map.put(rightVar.getName(), left.clone()); - } else if (isInternal(leftVar) && isInternal(rightVar)) { - // to substitute the lower-counter variable with the higher-counter one - boolean isLeftCounterLower = getCounter(leftVar) <= getCounter(rightVar); - Var lowerVar = isLeftCounterLower ? leftVar : rightVar; - Var higherVar = isLeftCounterLower ? rightVar : leftVar; - if (!isReturnVar(lowerVar) && !isFreshVar(higherVar)) - map.putIfAbsent(lowerVar.getName(), higherVar.clone()); - } - } else if (left instanceof Var var && canSubstitute(var, right)) { - map.put(var.getName(), right.clone()); - } else if (left instanceof FunctionInvocation && !containsExpression(right, left)) { - map.put(leftKey, right.clone()); - } - } - - private static String substitutionKey(Expression exp) { - if (exp instanceof Var var) - return var.getName(); - if (exp instanceof FunctionInvocation) - return exp.toString(); - return null; - } - - /** - * Handles transitive variable equalities in the map (e.g. map: x -> y, y -> 1 => map: x -> 1, y -> 1) - * - * @param map - * - * @return new map with resolved values - */ - private static Map resolveTransitive(Map map) { - Map result = new HashMap<>(); - for (Map.Entry entry : map.entrySet()) { - result.put(entry.getKey(), lookup(entry.getValue(), map, new HashSet<>())); - } - return result; - } - - /** - * Returns the value of a variable by looking up in the map recursively Uses the seen set to avoid circular - * references (e.g. x -> y, y -> x) which would cause infinite recursion - * - * @param exp - * @param map - * @param seen - * - * @return resolved expression - */ - private static Expression lookup(Expression exp, Map map, Set seen) { - String name = substitutionKey(exp); - if (name == null) - return exp; - - if (seen.contains(name)) - return exp; // circular reference - - Expression value = map.get(name); - if (value == null) - return exp; - - seen.add(name); - return lookup(value, map, seen); - } - - /** - * Checks if a variable is used in the expression (excluding its own definitions) - * - * @param exp - * @param name - * @param value - * @param topLevel - * - * @return true if used, false otherwise - */ - private static boolean hasUsage(Expression exp, String name, Expression value, boolean topLevel) { - if (topLevel && exp instanceof BinaryExpression binary && "&&".equals(binary.getOperator())) { - return hasUsage(binary.getFirstOperand(), name, value, true) - || hasUsage(binary.getSecondOperand(), name, value, true); - } - - if (topLevel && exp instanceof BinaryExpression binary && "==".equals(binary.getOperator())) { - Expression left = binary.getFirstOperand(); - Expression right = binary.getSecondOperand(); - boolean leftDefinition = name.equals(substitutionKey(left)) && right.equals(value) - && (isConstant(right) - || (left instanceof Var v && !(right instanceof Var) && canSubstitute(v, right)) - || (left instanceof FunctionInvocation && !(right instanceof Var) - && !containsExpression(right, left))); - boolean rightDefinition = name.equals(substitutionKey(right)) && left.equals(value) && isConstant(left); - if (leftDefinition || rightDefinition) - return false; - } - - if (name.equals(substitutionKey(exp))) - return true; - for (Expression child : exp.getChildren()) - if (hasUsage(child, name, value, false)) - return true; - return false; - } - - private static int getCounter(Var var) { - if (!isInternal(var)) - throw new IllegalStateException("Cannot get counter of non-internal variable"); - int lastUnderscore = var.getName().lastIndexOf('_'); - return Integer.parseInt(var.getName().substring(lastUnderscore + 1)); - } - - private static boolean isInternal(Var var) { - return var.getName().startsWith("#"); - } - - private static boolean isReturnVar(Var var) { - return var.getName().startsWith("#ret_"); - } - - private static boolean isFreshVar(Var var) { - return var.getName().startsWith("#fresh_"); - } - - private static boolean canSubstitute(Var var, Expression value) { - return !isReturnVar(var) && !isFreshVar(var) && !containsVariable(value, var.getName()); - } - - private static boolean isConstant(Expression exp) { - return exp.isLiteral() || exp instanceof Enum; - } - - private static boolean containsVariable(Expression exp, String name) { - if (exp instanceof Var var) - return var.getName().equals(name); - - if (!exp.hasChildren()) - return false; - - for (Expression child : exp.getChildren()) { - if (containsVariable(child, name)) - return true; - } - return false; - } - - private static boolean containsExpression(Expression exp, Expression target) { - if (exp.equals(target)) - return true; - - if (!exp.hasChildren()) - return false; - - for (Expression child : exp.getChildren()) { - if (containsExpression(child, target)) - return true; - } - return false; - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/BinaryDerivationNode.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/BinaryDerivationNode.java deleted file mode 100644 index 8c30a802d..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/BinaryDerivationNode.java +++ /dev/null @@ -1,26 +0,0 @@ -package liquidjava.rj_language.opt.derivation_node; - -public class BinaryDerivationNode extends DerivationNode { - - private final String op; - private final ValDerivationNode left; - private final ValDerivationNode right; - - public BinaryDerivationNode(ValDerivationNode left, ValDerivationNode right, String op) { - this.left = left; - this.right = right; - this.op = op; - } - - public ValDerivationNode getLeft() { - return left; - } - - public ValDerivationNode getRight() { - return right; - } - - public String getOp() { - return op; - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/DerivationNode.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/DerivationNode.java deleted file mode 100644 index a6b08e542..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/DerivationNode.java +++ /dev/null @@ -1,15 +0,0 @@ -package liquidjava.rj_language.opt.derivation_node; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -public abstract class DerivationNode { - - // disable html escaping to avoid escaping characters like &, >, <, =, etc. - private static final Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); - - @Override - public String toString() { - return gson.toJson(this); - } -} \ No newline at end of file diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/IteDerivationNode.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/IteDerivationNode.java deleted file mode 100644 index 8d0c7fc08..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/IteDerivationNode.java +++ /dev/null @@ -1,26 +0,0 @@ -package liquidjava.rj_language.opt.derivation_node; - -public class IteDerivationNode extends DerivationNode { - - private final ValDerivationNode condition; - private final ValDerivationNode thenBranch; - private final ValDerivationNode elseBranch; - - public IteDerivationNode(ValDerivationNode condition, ValDerivationNode thenBranch, ValDerivationNode elseBranch) { - this.condition = condition; - this.thenBranch = thenBranch; - this.elseBranch = elseBranch; - } - - public ValDerivationNode getCondition() { - return condition; - } - - public ValDerivationNode getThenBranch() { - return thenBranch; - } - - public ValDerivationNode getElseBranch() { - return elseBranch; - } -} \ No newline at end of file diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/UnaryDerivationNode.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/UnaryDerivationNode.java deleted file mode 100644 index f0693dc3d..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/UnaryDerivationNode.java +++ /dev/null @@ -1,20 +0,0 @@ -package liquidjava.rj_language.opt.derivation_node; - -public class UnaryDerivationNode extends DerivationNode { - - private final String op; - private final ValDerivationNode operand; - - public UnaryDerivationNode(ValDerivationNode operand, String op) { - this.operand = operand; - this.op = op; - } - - public ValDerivationNode getOperand() { - return operand; - } - - public String getOp() { - return op; - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java deleted file mode 100644 index 02367ee7f..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java +++ /dev/null @@ -1,54 +0,0 @@ -package liquidjava.rj_language.opt.derivation_node; - -import java.lang.reflect.Type; - -import com.google.gson.JsonElement; -import com.google.gson.JsonNull; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; -import com.google.gson.annotations.JsonAdapter; - -import liquidjava.rj_language.ast.Expression; -import liquidjava.rj_language.ast.LiteralBoolean; -import liquidjava.rj_language.ast.LiteralInt; -import liquidjava.rj_language.ast.LiteralLong; -import liquidjava.rj_language.ast.LiteralReal; - -public class ValDerivationNode extends DerivationNode { - - @JsonAdapter(ExpressionSerializer.class) - private final Expression value; - private final DerivationNode origin; - - public ValDerivationNode(Expression exp, DerivationNode origin) { - this.value = exp; - this.origin = origin; - } - - public Expression getValue() { - return value; - } - - public DerivationNode getOrigin() { - return origin; - } - - // Custom serializer to handle Expression subclasses properly - private static class ExpressionSerializer implements JsonSerializer { - @Override - public JsonElement serialize(Expression exp, Type typeOfSrc, JsonSerializationContext context) { - if (exp == null) - return JsonNull.INSTANCE; - if (exp instanceof LiteralInt v) - return new JsonPrimitive(v.getValue()); - if (exp instanceof LiteralLong v) - return new JsonPrimitive(v.getValue()); - if (exp instanceof LiteralReal v) - return new JsonPrimitive(v.getValue()); - if (exp instanceof LiteralBoolean v) - return new JsonPrimitive(v.isBooleanTrue()); - return new JsonPrimitive(exp.toDisplayString()); - } - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/VarDerivationNode.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/VarDerivationNode.java deleted file mode 100644 index a5aa6cc75..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/VarDerivationNode.java +++ /dev/null @@ -1,43 +0,0 @@ -package liquidjava.rj_language.opt.derivation_node; - -import java.lang.reflect.Type; - -import com.google.gson.JsonElement; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; -import com.google.gson.annotations.JsonAdapter; - -import liquidjava.rj_language.ast.formatter.VariableFormatter; - -public class VarDerivationNode extends DerivationNode { - - @JsonAdapter(VariableSerializer.class) - private final String var; - private final DerivationNode origin; - - public VarDerivationNode(String var) { - this.var = var; - this.origin = null; - } - - public VarDerivationNode(String var, DerivationNode origin) { - this.var = var; - this.origin = origin; - } - - public String getVar() { - return var; - } - - public DerivationNode getOrigin() { - return origin; - } - - private static class VariableSerializer implements JsonSerializer { - @Override - public JsonElement serialize(String var, Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive(VariableFormatter.format(var)); - } - } -} diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/ExpressionSimplifierTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/ExpressionSimplifierTest.java deleted file mode 100644 index 310515460..000000000 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/ExpressionSimplifierTest.java +++ /dev/null @@ -1,710 +0,0 @@ -package liquidjava.rj_language.opt; - -import static org.junit.jupiter.api.Assertions.*; -import static liquidjava.utils.TestUtils.*; - -import java.util.List; -import java.util.Map; - -import liquidjava.processor.facade.AliasDTO; -import liquidjava.rj_language.ast.BinaryExpression; -import liquidjava.rj_language.ast.Expression; -import liquidjava.rj_language.ast.LiteralBoolean; -import liquidjava.rj_language.ast.LiteralInt; -import liquidjava.rj_language.ast.Var; -import liquidjava.rj_language.opt.derivation_node.BinaryDerivationNode; -import liquidjava.rj_language.opt.derivation_node.IteDerivationNode; -import liquidjava.rj_language.opt.derivation_node.UnaryDerivationNode; -import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; -import liquidjava.rj_language.opt.derivation_node.VarDerivationNode; -import liquidjava.rj_language.parsing.RefinementsParser; -import org.junit.jupiter.api.Test; - -/** - * Test suite for expression simplification - */ -class ExpressionSimplifierTest { - - private static Expression parse(String sut) { - return RefinementsParser.createAST(sut, ""); - } - - @Test - void testNegation() { - Expression expression = parse("-a && a == 7"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("-7", result.getValue().toString(), "Expected result to be -7"); - - // 7 from variable a - ValDerivationNode val7 = new ValDerivationNode(new LiteralInt(7), new VarDerivationNode("a")); - - // -7 - UnaryDerivationNode negation = new UnaryDerivationNode(val7, "-"); - ValDerivationNode expected = new ValDerivationNode(new LiteralInt(-7), negation); - - // Compare the derivation trees - assertDerivationEquals(expected, result, ""); - } - - @Test - void testSimpleAddition() { - Expression expression = parse("a + b && a == 3 && b == 5"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("8", result.getValue().toString(), "Expected result to be 8"); - - // 3 from variable a - ValDerivationNode val3 = new ValDerivationNode(new LiteralInt(3), new VarDerivationNode("a")); - - // 5 from variable b - ValDerivationNode val5 = new ValDerivationNode(new LiteralInt(5), new VarDerivationNode("b")); - - // 3 + 5 - BinaryDerivationNode add3Plus5 = new BinaryDerivationNode(val3, val5, "+"); - ValDerivationNode expected = new ValDerivationNode(new LiteralInt(8), add3Plus5); - - // Compare the derivation trees - assertDerivationEquals(expected, result, ""); - } - - @Test - void testSimpleComparison() { - Expression expression = parse("((y || true) && !true) && y == false"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertInstanceOf(LiteralBoolean.class, result.getValue(), "Result should be a boolean"); - assertFalse((result.getValue()).isBooleanTrue(), "Expected result to be false"); - } - - @Test - void testArithmeticWithConstants() { - Expression expression = parse("((a / b + (-5)) + x) && (a == 6 && b == 2)"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertNotNull(result.getValue(), "Result value should not be null"); - - String resultStr = result.getValue().toString(); - assertEquals("-2 + x", resultStr, "Expected result to be -2 + x"); - - // 6 from variable a - ValDerivationNode val6 = new ValDerivationNode(new LiteralInt(6), new VarDerivationNode("a")); - - // 2 from variable b - ValDerivationNode val2 = new ValDerivationNode(new LiteralInt(2), new VarDerivationNode("b")); - - // 6 / 2 = 3 - BinaryDerivationNode div6By2 = new BinaryDerivationNode(val6, val2, "/"); - ValDerivationNode val3 = new ValDerivationNode(new LiteralInt(3), div6By2); - - // -5 is a literal with no origin - ValDerivationNode valNeg5 = new ValDerivationNode(new LiteralInt(-5), null); - - // 3 + (-5) = -2 - BinaryDerivationNode add3AndNeg5 = new BinaryDerivationNode(val3, valNeg5, "+"); - ValDerivationNode valNeg2 = new ValDerivationNode(new LiteralInt(-2), add3AndNeg5); - - // x (variable with null origin) - ValDerivationNode valX = new ValDerivationNode(new Var("x"), null); - - // -2 + x - BinaryDerivationNode addNeg2AndX = new BinaryDerivationNode(valNeg2, valX, "+"); - Expression expectedResultExpr = new BinaryExpression(new LiteralInt(-2), "+", new Var("x")); - ValDerivationNode expected = new ValDerivationNode(expectedResultExpr, addNeg2AndX); - - // Compare the derivation trees - assertDerivationEquals(expected, result, ""); - } - - @Test - void testComplexArithmeticWithMultipleOperations() { - Expression expression = parse("a * 2 + b - 3 == c && a == 5 && b == 7 && c == 14"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - // boolean literals are unwrapped to show the verified conditions - assertNotNull(result, "Result should not be null"); - assertNotNull(result.getValue(), "Result value should not be null"); - assertEquals("14 == 14 && 5 == 5 && 7 == 7 && 14 == 14", result.getValue().toString(), - "All verified conditions should be visible instead of collapsed to true"); - } - - @Test - void testFixedPointSimplification() { - Expression expression = parse("x == -y && y == a / b && a == 6 && b == 3"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("x == -2", result.getValue().toString(), "Expected result to be x == -2"); - - // Compare derivation tree structure - - // Build the derivation chain for the right side: - // 6 came from a, 3 came from b - ValDerivationNode val6FromA = new ValDerivationNode(new LiteralInt(6), new VarDerivationNode("a")); - ValDerivationNode val3FromB = new ValDerivationNode(new LiteralInt(3), new VarDerivationNode("b")); - - // 6 / 3 -> 2 - BinaryDerivationNode divOrigin = new BinaryDerivationNode(val6FromA, val3FromB, "/"); - - // 2 came from y, and y's value came from 6 / 2 - VarDerivationNode yChainedOrigin = new VarDerivationNode("y", divOrigin); - ValDerivationNode val2FromY = new ValDerivationNode(new LiteralInt(2), yChainedOrigin); - - // -2 - UnaryDerivationNode negOrigin = new UnaryDerivationNode(val2FromY, "-"); - ValDerivationNode rightNode = new ValDerivationNode(new LiteralInt(-2), negOrigin); - - // Left node x has no origin - ValDerivationNode leftNode = new ValDerivationNode(new Var("x"), null); - - // Root equality - BinaryDerivationNode rootOrigin = new BinaryDerivationNode(leftNode, rightNode, "=="); - ValDerivationNode expected = new ValDerivationNode(result.getValue(), rootOrigin); - - assertDerivationEquals(expected, result, "Derivation tree structure"); - } - - @Test - void testSingleEqualityShouldNotSimplify() { - Expression xEquals1 = parse("x == 1"); - ValDerivationNode result = ExpressionSimplifier.simplify(xEquals1); - - assertNotNull(result, "Result should not be null"); - assertEquals("x == 1", result.getValue().toString(), - "Single equality should not be simplified to a boolean literal"); - - // The result should be the original expression unchanged - assertInstanceOf(BinaryExpression.class, result.getValue(), "Result should still be a binary expression"); - BinaryExpression resultExpr = (BinaryExpression) result.getValue(); - assertEquals("==", resultExpr.getOperator(), "Operator should still be =="); - assertEquals("x", resultExpr.getFirstOperand().toString(), "Left operand should be x"); - assertEquals("1", resultExpr.getSecondOperand().toString(), "Right operand should be 1"); - } - - @Test - void testTwoEqualitiesShouldNotSimplify() { - Expression expression = parse("x == 1 && y == 2"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("x == 1 && y == 2", result.getValue().toString(), - "Two equalities should not be simplified to a boolean literal"); - - // The result should be the original expression unchanged - assertInstanceOf(BinaryExpression.class, result.getValue(), "Result should still be a binary expression"); - BinaryExpression resultExpr = (BinaryExpression) result.getValue(); - assertEquals("&&", resultExpr.getOperator(), "Operator should still be &&"); - assertEquals("x == 1", resultExpr.getFirstOperand().toString(), "Left operand should be x == 1"); - assertEquals("y == 2", resultExpr.getSecondOperand().toString(), "Right operand should be y == 2"); - } - - @Test - void testSameVarTwiceShouldSimplifyToSingle() { - Expression expression = parse("x && x"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("x", result.getValue().toString(), - "Same variable twice should be simplified to a single variable"); - } - - @Test - void testSameEqualityTwiceShouldSimplifyToSingle() { - Expression expression = parse("x == 1 && x == 1"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("x == 1", result.getValue().toString(), - "Same equality twice should be simplified to a single equality"); - } - - @Test - void testSameExpressionTwiceShouldSimplifyToSingle() { - Expression expression = parse("a + b == 1 && a + b == 1"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("a + b == 1", result.getValue().toString(), - "Same expression twice should be simplified to a single equality"); - } - - @Test - void testSymmetricEqualityShouldSimplify() { - Expression expression = parse("x == y && y == x"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("x == y", result.getValue().toString(), - "Symmetric equality should be simplified to a single equality"); - } - - @Test - void testRealExpression() { - String input = "#a_5 == -#fresh_4 && #fresh_4 == #x_2 / #y_3 && #x_2 == #x_0 && #x_0 == 6 && #y_3 == #y_1 && #y_1 == 3"; - Expression expression = parse(input); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("#a_5 == -2", result.getValue().toString(), "Expected result to be #a_5 == -2"); - - } - - @Test - void testTransitive() { - Expression expression = parse("a == b && b == 1"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("a == 1", result.getValue().toString(), "Expected result to be a == 1"); - } - - @Test - void testShouldNotOversimplifyToTrue() { - Expression expression = parse("x > 5 && x == y && y == 10"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertFalse(result.getValue() instanceof LiteralBoolean, - "Should not oversimplify to a boolean literal, but got: " + result.getValue()); - assertEquals("x > 5 && x == 10", result.getValue().toString(), - "Should stop simplification before collapsing to true"); - } - - @Test - void testShouldUnwrapBooleanInEquality() { - Expression expression = parse("x == (1 > 0)"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("x == (1 > 0)", result.getValue().toDisplayString(), - "Boolean in equality should be unwrapped to show the original comparison"); - } - - @Test - void testShouldUnwrapBooleanInEqualityWithPropagation() { - Expression expression = parse("x == (a > b) && a == 3 && b == 1"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("x == (3 > 1)", result.getValue().toDisplayString(), - "Boolean in equality should be unwrapped after propagation"); - } - - @Test - void testShouldNotUnwrapBooleanWithBooleanChildren() { - Expression expression = parse("(y || true) && !true && y == false"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - // false stays as false since both sides in the derivation are booleans - assertNotNull(result, "Result should not be null"); - assertInstanceOf(LiteralBoolean.class, result.getValue(), "Result should remain a boolean"); - assertFalse(result.getValue().isBooleanTrue(), "Expected result to be false"); - } - - @Test - void testShouldUnwrapNestedBooleanInEquality() { - Expression expression = parse("x == (a + b > 10) && a == 3 && b == 5"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("x == (8 > 10)", result.getValue().toDisplayString(), - "Boolean in equality should be unwrapped to show the computed comparison"); - } - - @Test - void testVarToVarPropagationWithInternalVariable() { - Expression expression = parse("#x_0 == a && #x_0 > 5"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("a > 5", result.getValue().toString(), - "Internal variable #x_0 should be substituted with user-facing variable a"); - } - - @Test - void testVarToVarInternalToInternal() { - Expression expression = parse("#a_1 == #b_2 && #b_2 == 5 && x == #a_1 + 1"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("x == 6", result.getValue().toString(), - "#a should resolve through #b to 5 across passes, then x == 5 + 1 = x == 6"); - } - - @Test - void testVarToVarDoesNotAffectUserFacingVariables() { - Expression expression = parse("x == y && x > 5"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("x == y && x > 5", result.getValue().toString(), - "User-facing variable equalities should not trigger var-to-var propagation"); - } - - @Test - void testVarToVarRemovesRedundantEquality() { - Expression expression = parse("#ret_1 == #b_0 - 100 && #b_0 == b && b >= -128 && b <= 127"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("#ret_1 == b - 100 && b >= -128 && b <= 127", result.getValue().toString(), - "Internal variable #b_0 should be replaced with b and redundant equality removed"); - assertNotNull(result.getOrigin(), "Origin should be present showing the var-to-var derivation"); - } - - @Test - void testInternalToInternalReducesRedundantVariable() { - Expression expression = parse("#a_3 == #b_7 && #a_3 > 5"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result); - assertEquals("#b_7 > 5", result.getValue().toString(), - "#a_3 (lower counter) should be substituted with #b_7 (higher counter)"); - } - - @Test - void testInternalToInternalChainWithUserFacingVariableUserFacingFirst() { - Expression expression = parse("#b_7 == x && #a_3 == #b_7 && x > 0"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result); - assertEquals("x > 0", result.getValue().toString(), - "Both internal variables should be eliminated via chain resolution"); - } - - @Test - void testInternalToInternalChainWithUserFacingVariableInternalFirst() { - Expression expression = parse("#a_3 == #b_7 && #b_7 == x && x > 0"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result); - assertEquals("x > 0", result.getValue().toString(), - "Both internal variables should be eliminated via fixed-point iteration"); - } - - @Test - void testInternalToInternalBothResolvingToLiteral() { - Expression expression = parse("#a_3 == #b_7 && #b_7 == 5"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result); - assertEquals("5 == 5 && 5 == 5", result.getValue().toString(), - "#a_3 -> #b_7 -> 5 and #b_7 -> 5; both equalities collapse to 5 == 5"); - } - - @Test - void testInternalToInternalNoFurtherResolution() { - Expression expression = parse("#a_3 == #b_7 && #b_7 + 1 > 0"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result); - assertEquals("#b_7 + 1 > 0", result.getValue().toString(), - "#a_3 (lower counter) replaced by #b_7 (higher counter); equality collapses to trivial"); - } - - @Test - void testIteTrueConditionSimplifiesToThenBranch() { - Expression expr = parse("true ? a : b"); - ValDerivationNode result = ExpressionSimplifier.simplify(expr); - - assertNotNull(result, "Result should not be null"); - assertEquals("a", result.getValue().toString(), "Expected result to be a"); - - ValDerivationNode conditionNode = new ValDerivationNode(new LiteralBoolean(true), null); - ValDerivationNode thenNode = new ValDerivationNode(new Var("a"), null); - ValDerivationNode elseNode = new ValDerivationNode(new Var("b"), null); - IteDerivationNode iteOrigin = new IteDerivationNode(conditionNode, thenNode, elseNode); - ValDerivationNode expected = new ValDerivationNode(new Var("a"), iteOrigin); - - assertDerivationEquals(expected, result, ""); - } - - @Test - void testIteFalseConditionSimplifiesToElseBranch() { - Expression expr = parse("false ? a : b"); - ValDerivationNode result = ExpressionSimplifier.simplify(expr); - - assertNotNull(result, "Result should not be null"); - assertEquals("b", result.getValue().toString(), "Expected result to be b"); - - ValDerivationNode conditionNode = new ValDerivationNode(new LiteralBoolean(false), null); - ValDerivationNode thenNode = new ValDerivationNode(new Var("a"), null); - ValDerivationNode elseNode = new ValDerivationNode(new Var("b"), null); - IteDerivationNode iteOrigin = new IteDerivationNode(conditionNode, thenNode, elseNode); - ValDerivationNode expected = new ValDerivationNode(new Var("b"), iteOrigin); - - assertDerivationEquals(expected, result, ""); - } - - @Test - void testIteEqualBranchesSimplifiesToBranch() { - Expression expr = parse("cond ? b : b"); - ValDerivationNode result = ExpressionSimplifier.simplify(expr); - - assertNotNull(result, "Result should not be null"); - assertEquals("b", result.getValue().toString(), "Expected result to be b"); - - ValDerivationNode conditionNode = new ValDerivationNode(new Var("cond"), null); - ValDerivationNode thenNode = new ValDerivationNode(new Var("b"), null); - ValDerivationNode elseNode = new ValDerivationNode(new Var("b"), null); - IteDerivationNode iteOrigin = new IteDerivationNode(conditionNode, thenNode, elseNode); - ValDerivationNode expected = new ValDerivationNode(new Var("b"), iteOrigin); - - assertDerivationEquals(expected, result, ""); - } - - @Test - void testIteConditionUsesEqualityFromConjunction() { - Expression expr = parse("mode == 1 && (mode == 2 ? explicit(param) : start(param))"); - ValDerivationNode result = ExpressionSimplifier.simplify(expr); - - assertNotNull(result, "Result should not be null"); - assertEquals("start(param)", result.getValue().toString(), - "mode == 1 should make the mode == 2 ternary condition false"); - } - - @Test - void testByteAliasExpansion() { - String sut = "Byte(b)"; - AliasDTO byteAlias = new AliasDTO("Byte", List.of("int"), List.of("b"), "b >= -128 && b <= 127"); - byteAlias.parse(""); - Map aliases = Map.of("Byte", byteAlias); - Expression exp = parse(sut); - ValDerivationNode result = ExpressionSimplifier.simplify(exp, aliases); - - assertEquals("Byte(b)", result.getValue().toString()); - assertNotNull(result.getOrigin(), "Origin should contain the expanded body"); - ValDerivationNode origin = (ValDerivationNode) result.getOrigin(); - assertEquals("b >= -128 && b <= 127", origin.getValue().toString()); - } - - @Test - void testPositiveAliasExpansion() { - String sut = "Positive(x)"; - AliasDTO positiveAlias = new AliasDTO("Positive", List.of("int"), List.of("v"), "v > 0"); - positiveAlias.parse(""); - Map aliases = Map.of("Positive", positiveAlias); - Expression exp = parse(sut); - ValDerivationNode result = ExpressionSimplifier.simplify(exp, aliases); - - assertEquals("Positive(x)", result.getValue().toString()); - assertNotNull(result.getOrigin(), "Origin should contain the expanded body"); - ValDerivationNode origin = (ValDerivationNode) result.getOrigin(); - assertEquals("x > 0", origin.getValue().toString()); - } - - @Test - void testTwoArgAliasWithNormalExpression() { - String sut = "Bounded(v, 100) && v > 50"; - AliasDTO boundedAlias = new AliasDTO("Bounded", List.of("int", "int"), List.of("x", "n"), "x > 0 && x < n"); - boundedAlias.parse(""); - Map aliases = Map.of("Bounded", boundedAlias); - Expression expression = parse(sut); - ValDerivationNode result = ExpressionSimplifier.simplify(expression, aliases); - - assertEquals("Bounded(v, 100) && v > 50", result.getValue().toString()); - assertInstanceOf(BinaryDerivationNode.class, result.getOrigin()); - BinaryDerivationNode binOrigin = (BinaryDerivationNode) result.getOrigin(); - assertEquals("&&", binOrigin.getOp()); - ValDerivationNode leftNode = binOrigin.getLeft(); - assertEquals("Bounded(v, 100)", leftNode.getValue().toString()); - assertNotNull(leftNode.getOrigin(), "Alias invocation should have expanded body as origin"); - ValDerivationNode expandedBody = (ValDerivationNode) leftNode.getOrigin(); - assertEquals("v > 0 && v < 100", expandedBody.getValue().toString()); - ValDerivationNode rightNode = binOrigin.getRight(); - assertEquals("v > 50", rightNode.getValue().toString()); - assertNull(rightNode.getOrigin()); - } - - @Test - void testEntailedConjunctIsRemovedButOriginIsPreserved() { - String sut = "b >= 100 && b > 0"; - addIntVariableToContext("b"); - Expression expression = parse(sut); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result); - assertEquals("b >= 100", result.getValue().toString(), - "The weaker conjunct should be removed when implied by the stronger one"); - - BinaryExpression parsed = (BinaryExpression) expression; - Expression bGe100 = parsed.getFirstOperand(); - Expression bGt0 = parsed.getSecondOperand(); - ValDerivationNode expectedLeft = new ValDerivationNode(bGe100, null); - ValDerivationNode expectedRight = new ValDerivationNode(bGt0, null); - ValDerivationNode expected = new ValDerivationNode(bGe100, - new BinaryDerivationNode(expectedLeft, expectedRight, "&&")); - - assertDerivationEquals(expected, result, "Entailment simplification should preserve conjunction origin"); - } - - @Test - void testStrictComparisonImpliesNonStrictComparison() { - String sut = "x > y && x >= y"; - addIntVariableToContext("x"); - addIntVariableToContext("y"); - Expression expression = parse(sut); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result); - assertEquals("x > y", result.getValue().toString(), - "The stricter comparison should be kept when it implies the weaker one"); - - BinaryExpression parsed = (BinaryExpression) expression; - Expression xGtY = parsed.getFirstOperand(); - Expression xGeY = parsed.getSecondOperand(); - ValDerivationNode expectedLeft = new ValDerivationNode(xGtY, null); - ValDerivationNode expectedRight = new ValDerivationNode(xGeY, null); - ValDerivationNode expected = new ValDerivationNode(xGtY, - new BinaryDerivationNode(expectedLeft, expectedRight, "&&")); - - assertDerivationEquals(expected, result, "Strict comparison simplification should preserve conjunction origin"); - } - - @Test - void testEquivalentBoundsKeepOneSide() { - String sut = "0 <= i && i >= 0"; - addIntVariableToContext("i"); - Expression expression = parse(sut); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result); - assertEquals("0 <= i", result.getValue().toString(), "Equivalent bounds should collapse to a single conjunct"); - - BinaryExpression parsed = (BinaryExpression) expression; - Expression zeroLeI = parsed.getFirstOperand(); - Expression iGeZero = parsed.getSecondOperand(); - ValDerivationNode expectedLeft = new ValDerivationNode(zeroLeI, null); - ValDerivationNode expectedRight = new ValDerivationNode(iGeZero, null); - ValDerivationNode expected = new ValDerivationNode(zeroLeI, - new BinaryDerivationNode(expectedLeft, expectedRight, "&&")); - - assertDerivationEquals(expected, result, "Equivalent bounds simplification should preserve conjunction origin"); - } - - @Test - void testSubstitutesVariableDefinedByArithmeticExpression() { - Expression expression = parse("z == y - 2 && y == x + 1"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertNotNull(result, "Result should not be null"); - assertEquals("z == x - 1", result.getValue().toString(), "Expected variable definition to be substituted"); - } - - @Test - void testFoldsAdjacentIntegerConstantsInLeftAssociatedArithmetic() { - assertEquals("x - 1", ExpressionSimplifier.simplify(parse("x + 1 - 2")).getValue().toString()); - assertEquals("x + 1", ExpressionSimplifier.simplify(parse("x - 1 + 2")).getValue().toString()); - assertEquals("x + 3", ExpressionSimplifier.simplify(parse("x + 1 + 2")).getValue().toString()); - assertEquals("x", ExpressionSimplifier.simplify(parse("x + 1 - 1")).getValue().toString()); - } - - @Test - void testFunctionInvocationEqualitiesPropagateTransitively() { - Expression expression = parse("size(x3) == size(x2) - 1 && size(x2) == size(x1) + 1 && size(x1) == 0"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertEquals("size(x3) == 0", result.getValue().toString()); - } - - @Test - void testFunctionInvocationOnLeftBehavesLikeVariable() { - Expression expression = parse("func(a) == func(b) && func(b) == 1"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertEquals("func(a) == 1", result.getValue().toString()); - } - - @Test - void testFunctionInvocationEqualitiesMixWithVariables() { - Expression expression = parse("func(a) + x && func(a) == y && y == 1 && x == 2"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertEquals("3", result.getValue().toString()); - } - - @Test - void testEnumConstantsPropagateIntoVariableEquality() { - Expression expression = parse("current == mode && mode == Mode.Photo"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertEquals("current == Mode.Photo", result.getValue().toString()); - } - - @Test - void testEnumConstantsPropagateTransitively() { - Expression expression = parse("target == current && current == mode && mode == Mode.Photo"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertEquals("target == Mode.Photo", result.getValue().toString()); - } - - @Test - void testEnumConstantsPropagateThroughFunctionInvocations() { - Expression expression = parse("modeOf(x) == Mode.Photo && current == modeOf(x)"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertEquals("current == Mode.Photo", result.getValue().toString()); - } - - @Test - void testEnumConstantsPropagateIntoTernaryCondition() { - Expression expression = parse("mode == Mode.Photo && (mode == Mode.Video ? explicit(param) : start(param))"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertEquals("start(param)", result.getValue().toString()); - } - - @Test - void testRepeatedEqualDefinitionPropagatesIntoTernaryCondition() { - Expression expression = parse("mode == 2 && (mode == 2 ? explicit(param) : start(param))"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertEquals("explicit(param)", result.getValue().toString()); - } - - @Test - void testRepeatedEqualDefinitionsPropagateIntoCompoundTernaryCondition() { - Expression expression = parse( - "mode == 2 && other == 5 && ((mode == 2 && other == 5) ? explicit(param) : start(param))"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertEquals("explicit(param)", result.getValue().toString()); - } - - @Test - void testRepeatedEqualDefinitionPropagatesWhenUsageComesFirst() { - Expression expression = parse("(mode == 2 ? explicit(param) : start(param)) && mode == 2"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertEquals("explicit(param)", result.getValue().toString()); - } - - @Test - void testRepeatedFunctionInvocationDefinitionPropagatesIntoTernaryCondition() { - Expression expression = parse("modeOf(param) == 2 && (modeOf(param) == 2 ? explicit(param) : start(param))"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertEquals("explicit(param)", result.getValue().toString()); - } - - @Test - void testRepeatedEqualDefinitionsPropagateIntoNestedTernaryConditions() { - Expression expression = parse("mode == 2 && other == 3 && (mode == 2 ? (other == 3 ? a(p) : b(p)) : c(p))"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertEquals("a(p)", result.getValue().toString()); - } - - @Test - void testRepeatedEqualDefinitionsPropagateIntoNestedTernaryElseBranch() { - Expression expression = parse("mode == 2 && other == 4 && (mode == 2 ? (other == 3 ? a(p) : b(p)) : c(p))"); - ValDerivationNode result = ExpressionSimplifier.simplify(expression); - - assertEquals("b(p)", result.getValue().toString()); - } -} diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java deleted file mode 100644 index 136af9e27..000000000 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java +++ /dev/null @@ -1,217 +0,0 @@ -package liquidjava.rj_language.opt; - -import static org.junit.jupiter.api.Assertions.*; - -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import liquidjava.rj_language.ast.Expression; -import liquidjava.rj_language.parsing.RefinementsParser; - -class VariableResolverTest { - - private static Expression parse(String refinement) { - return RefinementsParser.createAST(refinement, ""); - } - - @Test - void testSingleEqualityNotExtracted() { - Expression expression = parse("x == 1"); - Map result = VariableResolver.resolve(expression); - - assertTrue(result.isEmpty(), "Single equality should not extract variable mapping"); - } - - @Test - void testConjunctionExtractsVariables() { - Expression expression = parse("x + y && x == 1 && y == 2"); - Map result = VariableResolver.resolve(expression); - - assertEquals(2, result.size(), "Should extract both variables"); - assertEquals("1", result.get("x").toString()); - assertEquals("2", result.get("y").toString()); - } - - @Test - void testSingleComparisonNotExtracted() { - Expression expression = parse("x > 0"); - Map result = VariableResolver.resolve(expression); - - assertTrue(result.isEmpty(), "Single comparison should not extract variable mapping"); - } - - @Test - void testSingleArithmeticExpression() { - Expression expression = parse("x + 1"); - Map result = VariableResolver.resolve(expression); - - assertTrue(result.isEmpty(), "Single arithmetic expression should not extract variable mapping"); - } - - @Test - void testDisjunctionWithEqualities() { - Expression expression = parse("x == 1 || y == 2"); - Map result = VariableResolver.resolve(expression); - - assertTrue(result.isEmpty(), "Disjunction should not extract variable mappings"); - } - - @Test - void testNegatedEquality() { - Expression expression = parse("!(x == 1)"); - Map result = VariableResolver.resolve(expression); - - assertTrue(result.isEmpty(), "Negated equality should not extract variable mapping"); - } - - @Test - void testGroupedEquality() { - Expression expression = parse("(x == 1)"); - Map result = VariableResolver.resolve(expression); - - assertTrue(result.isEmpty(), "Grouped single equality should not extract variable mapping"); - } - - @Test - void testCircularDependency() { - Expression expression = parse("x == y && y == x"); - Map result = VariableResolver.resolve(expression); - - assertTrue(result.isEmpty(), "Circular dependency should not extract variable mappings"); - } - - @Test - void testUnusedEqualitiesShouldBeIgnored() { - Expression expression = parse("z > 0 && x == 1 && y == 2 && z == 3"); - Map result = VariableResolver.resolve(expression); - - assertEquals(1, result.size(), "Should only extract used variable z"); - assertEquals("3", result.get("z").toString()); - } - - @Test - void testDifferentEqualityInIteConditionCountsAsUsage() { - Expression expression = parse("mode == 1 && (mode == 2 ? explicit(param) : start(param))"); - Map result = VariableResolver.resolve(expression); - - assertEquals(1, result.size(), "Ternary condition should count as a use of mode"); - assertEquals("1", result.get("mode").toString()); - } - - @Test - void testRepeatedEqualDefinitionCountsAsUsage() { - Expression expression = parse("mode == 2 && (mode == 2 ? explicit(param) : start(param))"); - Map result = VariableResolver.resolve(expression); - - assertEquals(1, result.size(), "Repeated equalities should keep one definition and treat the other as usage"); - assertEquals("2", result.get("mode").toString()); - } - - @Test - void testRepeatedEqualDefinitionsInCompoundIteConditionCountAsUsage() { - Expression expression = parse( - "mode == 2 && other == 5 && ((mode == 2 && other == 5) ? explicit(param) : start(param))"); - Map result = VariableResolver.resolve(expression); - - assertEquals(2, result.size(), "Compound ternary condition should count as a use of both variables"); - assertEquals("2", result.get("mode").toString()); - assertEquals("5", result.get("other").toString()); - } - - @Test - void testRepeatedEqualDefinitionCountsAsUsageBeforeDefinitionConjunct() { - Expression expression = parse("(mode == 2 ? explicit(param) : start(param)) && mode == 2"); - Map result = VariableResolver.resolve(expression); - - assertEquals(1, result.size(), "Conjunct order should not affect repeated equality usage detection"); - assertEquals("2", result.get("mode").toString()); - } - - @Test - void testRepeatedFunctionInvocationDefinitionCountsAsUsage() { - Expression expression = parse("modeOf(param) == 2 && (modeOf(param) == 2 ? explicit(param) : start(param))"); - Map result = VariableResolver.resolve(expression); - - assertEquals(1, result.size(), "Function invocation definition should count repeated nested equality as usage"); - assertEquals("2", result.get("modeOf(param)").toString()); - } - - @Test - void testRepeatedExpressionDefinitionCountsAsUsage() { - Expression expression = parse("limit == max - 1 && (limit == max - 1 ? a(p) : b(p))"); - Map result = VariableResolver.resolve(expression); - - assertEquals(1, result.size(), "Expression definition should count repeated nested equality as usage"); - assertEquals("max - 1", result.get("limit").toString()); - } - - @Test - void testRepeatedTopLevelDefinitionsOnlyDoNotCountAsUsage() { - Expression expression = parse("mode == 2 && mode == 2"); - Map result = VariableResolver.resolve(expression); - - assertTrue(result.isEmpty(), "Repeated top-level definitions alone should not count as usage"); - } - - @Test - void testReturnVariableIsNotSubstituted() { - Expression expression = parse("x > 0 && #ret_1 == x"); - Map result = VariableResolver.resolve(expression); - - assertTrue(result.isEmpty(), "Return variables should not be substituted with another variable"); - } - - @Test - void testFreshVariableIsNotUsedAsSubstitutionTarget() { - Expression expression = parse("#tmp_1 > 0 && #tmp_1 == #fresh_2"); - Map result = VariableResolver.resolve(expression); - - assertTrue(result.isEmpty(), "Fresh variables should not replace another variable"); - } - - @Test - void testFunctionInvocationEqualityExtractsFunctionKey() { - Expression expression = parse("size(stack) > 0 && size(stack) == 1"); - Map result = VariableResolver.resolve(expression); - - assertEquals(1, result.size(), "Should extract the function invocation as a substitution key"); - assertEquals("1", result.get("size(stack)").toString()); - } - - @Test - void testLiteralOnLeftExtractsFunctionInvocationKey() { - Expression expression = parse("size(stack) > 0 && 1 == size(stack)"); - Map result = VariableResolver.resolve(expression); - - assertEquals(1, result.size(), "Should extract function invocation equalities from either side"); - assertEquals("1", result.get("size(stack)").toString()); - } - - @Test - void testFunctionInvocationEqualitiesResolveTransitively() { - Expression expression = parse("func(a) > 0 && func(a) == func(b) && func(b) == 1"); - Map result = VariableResolver.resolve(expression); - - assertEquals(2, result.size(), "Should keep the function invocation chain that was used"); - assertEquals("1", result.get("func(a)").toString()); - assertEquals("1", result.get("func(b)").toString()); - } - - @Test - void testFunctionInvocationNamesAreMatchedStructurally() { - Expression expression = parse("f(a) > 0 && f(a) == ff(a) + b"); - Map result = VariableResolver.resolve(expression); - - assertEquals(1, result.size(), "Should not treat ff(a) as a use of f(a)"); - assertEquals("ff(a) + b", result.get("f(a)").toString()); - } - - @Test - void testUnusedFunctionInvocationEqualityIsIgnored() { - Expression expression = parse("x > 0 && size(stack) == 1"); - Map result = VariableResolver.resolve(expression); - - assertTrue(result.isEmpty(), "Function invocation definitions with no usage should be ignored"); - } -} diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/TestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/TestUtils.java index 35e1ead6d..f81f013fc 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/TestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/TestUtils.java @@ -1,9 +1,6 @@ package liquidjava.utils; import java.io.BufferedReader; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -14,12 +11,6 @@ import liquidjava.processor.context.Context; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.opt.derivation_node.BinaryDerivationNode; -import liquidjava.rj_language.opt.derivation_node.DerivationNode; -import liquidjava.rj_language.opt.derivation_node.IteDerivationNode; -import liquidjava.rj_language.opt.derivation_node.UnaryDerivationNode; -import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; -import liquidjava.rj_language.opt.derivation_node.VarDerivationNode; import spoon.Launcher; import spoon.reflect.factory.Factory; @@ -87,7 +78,7 @@ public static List> getExpectedErrorsFromDirectory(Path di try { List files = Files.list(dirPath).filter(Files::isRegularFile).toList(); for (Path file : files) { - getExpectedErrorsFromFile(file).forEach(expectedErrors::add); + expectedErrors.addAll(getExpectedErrorsFromFile(file)); } } catch (IOException e) { return List.of(); @@ -96,43 +87,7 @@ public static List> getExpectedErrorsFromDirectory(Path di } /** - * Helper method to compare two derivation nodes recursively - */ - public static void assertDerivationEquals(DerivationNode expected, DerivationNode actual, String message) { - if (expected == null && actual == null) - return; - - assertNotNull(expected); - assertEquals(expected.getClass(), actual.getClass(), message + ": node types should match"); - if (expected instanceof ValDerivationNode expectedVal) { - ValDerivationNode actualVal = (ValDerivationNode) actual; - assertEquals(expectedVal.getValue().toString(), actualVal.getValue().toString(), - message + ": values should match"); - assertDerivationEquals(expectedVal.getOrigin(), actualVal.getOrigin(), message + " > origin"); - } else if (expected instanceof BinaryDerivationNode expectedBin) { - BinaryDerivationNode actualBin = (BinaryDerivationNode) actual; - assertEquals(expectedBin.getOp(), actualBin.getOp(), message + ": operators should match"); - assertDerivationEquals(expectedBin.getLeft(), actualBin.getLeft(), message + " > left"); - assertDerivationEquals(expectedBin.getRight(), actualBin.getRight(), message + " > right"); - } else if (expected instanceof VarDerivationNode expectedVar) { - VarDerivationNode actualVar = (VarDerivationNode) actual; - assertEquals(expectedVar.getVar(), actualVar.getVar(), message + ": variables should match"); - } else if (expected instanceof UnaryDerivationNode expectedUnary) { - UnaryDerivationNode actualUnary = (UnaryDerivationNode) actual; - assertEquals(expectedUnary.getOp(), actualUnary.getOp(), message + ": operators should match"); - assertDerivationEquals(expectedUnary.getOperand(), actualUnary.getOperand(), message + " > operand"); - } else if (expected instanceof IteDerivationNode expectedIte) { - IteDerivationNode actualIte = (IteDerivationNode) actual; - assertDerivationEquals(expectedIte.getCondition(), actualIte.getCondition(), message + " > condition"); - assertDerivationEquals(expectedIte.getThenBranch(), actualIte.getThenBranch(), message + " > then"); - assertDerivationEquals(expectedIte.getElseBranch(), actualIte.getElseBranch(), message + " > else"); - } - } - - /** - * Helper method to add an integer variable to the context Needed for tests that rely on the SMT-based implication - * checks The simplifier asks Z3 whether one conjunct implies another, so every variable in those expressions must - * be in the context + * Helper method to add an integer variable to the context */ public static void addIntVariableToContext(String name) { context.addVarToContext(name, factory.Type().INTEGER_PRIMITIVE, new Predicate(), From 8932b4979e038f8c291f13346766d5bcf6595ba5 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 17 Jun 2026 23:06:54 +0100 Subject: [PATCH 59/65] Don't Simplify Expected Type --- .../diagnostics/errors/RefinementError.java | 14 +++++++------- .../diagnostics/errors/StateRefinementError.java | 10 +++++----- .../processor/refinement_checker/VCChecker.java | 9 ++++----- .../java/liquidjava/rj_language/Predicate.java | 2 +- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java index 6da726f1f..e56ce3c82 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java @@ -6,6 +6,7 @@ import liquidjava.diagnostics.TranslationTable; import liquidjava.processor.VCImplication; +import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.formatter.VariableFormatter; import liquidjava.smt.Counterexample; @@ -18,15 +19,14 @@ */ public class RefinementError extends LJError { - private final VCImplication expected; + private final Predicate expected; private final VCImplication found; private final Counterexample counterexample; - public RefinementError(SourcePosition position, VCImplication expected, VCImplication found, + public RefinementError(SourcePosition position, Predicate expected, VCImplication found, TranslationTable translationTable, Counterexample counterexample, String customMessage) { - super("Refinement Error", - String.format("%s is not a subtype of %s", found.toPredicate().getExpression().toDisplayString(), - expected.toPredicate().getExpression().toDisplayString()), + super("Refinement Error", String.format("%s is not a subtype of %s", + found.toPredicate().getExpression().toDisplayString(), expected.getExpression().toDisplayString()), position, translationTable, customMessage); this.expected = expected; this.found = found; @@ -47,7 +47,7 @@ public String getCounterExampleString() { List foundVarNames = new ArrayList<>(); Expression foundExpression = found.toPredicate().getExpression(); - Expression expectedExpression = expected.toPredicate().getExpression(); + Expression expectedExpression = expected.getExpression(); foundExpression.getVariableNames(foundVarNames); // also keep resolved static-final constants (e.g. Integer.MAX_VALUE) referenced by either side of the // subtyping check, so the counterexample maps the symbolic name back to its compile-time value @@ -73,7 +73,7 @@ public Counterexample getCounterexample() { return counterexample; } - public VCImplication getExpected() { + public Predicate getExpected() { return expected; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java index 7476299c1..874ccd921 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java @@ -2,6 +2,7 @@ import liquidjava.diagnostics.TranslationTable; import liquidjava.processor.VCImplication; +import liquidjava.rj_language.Predicate; import spoon.reflect.cu.SourcePosition; /** @@ -11,21 +12,20 @@ */ public class StateRefinementError extends LJError { - private final VCImplication expected; + private final Predicate expected; private final VCImplication found; - public StateRefinementError(SourcePosition position, VCImplication expected, VCImplication found, + public StateRefinementError(SourcePosition position, Predicate expected, VCImplication found, TranslationTable translationTable, String customMessage) { super("State Refinement Error", - String.format("Expected state %s but found %s", - expected.toPredicate().getExpression().toDisplayString(), + String.format("Expected state %s but found %s", expected.getExpression().toDisplayString(), found.toPredicate().getExpression().toDisplayString()), position, translationTable, customMessage); this.expected = expected; this.found = found; } - public VCImplication getExpected() { + public Predicate getExpected() { return expected; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java index 77c5c3b20..d2ba3cc17 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java @@ -82,8 +82,8 @@ public void processSubtyping(Predicate expectedType, List list, CtEl } DebugLog.smtResult(result); if (result.isError()) { - throw new RefinementError(element.getPosition(), expectedType.simplify(context), - implBeforeChange.simplify(), map, result.getCounterexample(), customMessage); + throw new RefinementError(element.getPosition(), expectedType, implBeforeChange.simplify(), map, + result.getCounterexample(), customMessage); } } @@ -407,15 +407,14 @@ protected void throwRefinementError(SourcePosition position, Predicate expected, Counterexample counterexample, String customMessage) throws RefinementError { TranslationTable map = new TranslationTable(); VCImplication premises = buildPremiseChain(map, expected, found); - throw new RefinementError(position, expected.simplify(context), premises.simplify(), map, counterexample, - customMessage); + throw new RefinementError(position, expected, premises.simplify(), map, counterexample, customMessage); } protected void throwStateRefinementError(SourcePosition position, Predicate found, Predicate expected, String customMessage) throws StateRefinementError { TranslationTable map = new TranslationTable(); VCImplication foundState = buildPremiseChain(map, expected, found); - throw new StateRefinementError(position, expected.simplify(context), foundState.simplify(), map, customMessage); + throw new StateRefinementError(position, expected, foundState.simplify(), map, customMessage); } protected void throwStateConflictError(SourcePosition position, Predicate expected) throws StateConflictError { diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java index 4f72f1c70..303551cfa 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java @@ -259,7 +259,7 @@ public Predicate getOrigin() { return this; } - public VCImplication simplify(Context context) { + public VCImplication simplify() { VCImplication result = new VCImplication(clone()).simplify(); DebugLog.simplification(this.getExpression(), result.getRefinement().getExpression()); return result; From ea7ad09cb0d68e5c64fc61e3c8f6f1d1ba7b13d5 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 18 Jun 2026 15:13:44 +0100 Subject: [PATCH 60/65] Track Simplification Origins by Full VC Implication --- .../diagnostics/errors/RefinementError.java | 15 ++-- .../errors/StateRefinementError.java | 11 ++- .../processor/SimplifiedVCImplication.java | 53 -------------- .../liquidjava/processor/VCImplication.java | 18 +---- .../liquidjava/rj_language/Predicate.java | 7 +- .../opt/VCBinderSimplification.java | 17 ++--- .../opt/VCExpressionSimplificationPass.java | 7 +- .../rj_language/opt/VCSimplification.java | 24 ++++--- .../opt/VCSimplificationResult.java | 44 ++++++++++++ .../opt/VCSimplificationUtils.java | 5 ++ .../rj_language/opt/VCSubstitution.java | 9 ++- .../opt/VCArithmeticSimplificationTest.java | 9 +-- .../rj_language/opt/VCFoldingTest.java | 15 +--- .../opt/VCLogicalSimplificationTest.java | 9 +-- .../VCSimplificationPropertyBasedTest.java | 4 +- .../rj_language/opt/VCSimplificationTest.java | 71 ++++++++++++++----- .../rj_language/opt/VCSubstitutionTest.java | 3 +- .../java/liquidjava/utils/VCTestUtils.java | 43 ++++++----- 18 files changed, 182 insertions(+), 182 deletions(-) delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java create mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationResult.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java index e56ce3c82..78b5e0aad 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java @@ -9,6 +9,7 @@ import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.formatter.VariableFormatter; +import liquidjava.rj_language.opt.VCSimplificationResult; import liquidjava.smt.Counterexample; import spoon.reflect.cu.SourcePosition; @@ -20,13 +21,15 @@ public class RefinementError extends LJError { private final Predicate expected; - private final VCImplication found; + private final VCSimplificationResult found; private final Counterexample counterexample; - public RefinementError(SourcePosition position, Predicate expected, VCImplication found, + public RefinementError(SourcePosition position, Predicate expected, VCSimplificationResult found, TranslationTable translationTable, Counterexample counterexample, String customMessage) { - super("Refinement Error", String.format("%s is not a subtype of %s", - found.toPredicate().getExpression().toDisplayString(), expected.getExpression().toDisplayString()), + super("Refinement Error", + String.format("%s is not a subtype of %s", + found.getImplication().toPredicate().getExpression().toDisplayString(), + expected.getExpression().toDisplayString()), position, translationTable, customMessage); this.expected = expected; this.found = found; @@ -46,7 +49,7 @@ public String getCounterExampleString() { return null; List foundVarNames = new ArrayList<>(); - Expression foundExpression = found.toPredicate().getExpression(); + Expression foundExpression = getFound().getImplication().toPredicate().getExpression(); Expression expectedExpression = expected.getExpression(); foundExpression.getVariableNames(foundVarNames); // also keep resolved static-final constants (e.g. Integer.MAX_VALUE) referenced by either side of the @@ -77,7 +80,7 @@ public Predicate getExpected() { return expected; } - public VCImplication getFound() { + public VCSimplificationResult getFound() { return found; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java index 874ccd921..763c8579f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java @@ -3,6 +3,7 @@ import liquidjava.diagnostics.TranslationTable; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.opt.VCSimplificationResult; import spoon.reflect.cu.SourcePosition; /** @@ -13,13 +14,13 @@ public class StateRefinementError extends LJError { private final Predicate expected; - private final VCImplication found; + private final VCSimplificationResult found; - public StateRefinementError(SourcePosition position, Predicate expected, VCImplication found, + public StateRefinementError(SourcePosition position, Predicate expected, VCSimplificationResult found, TranslationTable translationTable, String customMessage) { super("State Refinement Error", String.format("Expected state %s but found %s", expected.getExpression().toDisplayString(), - found.toPredicate().getExpression().toDisplayString()), + found.getImplication().toPredicate().getExpression().toDisplayString()), position, translationTable, customMessage); this.expected = expected; this.found = found; @@ -30,6 +31,10 @@ public Predicate getExpected() { } public VCImplication getFound() { + return found.getImplication(); + } + + public VCSimplificationResult getFoundSimplification() { return found; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java b/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java deleted file mode 100644 index 7c741cdea..000000000 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/SimplifiedVCImplication.java +++ /dev/null @@ -1,53 +0,0 @@ -package liquidjava.processor; - -import java.util.Objects; - -import liquidjava.rj_language.Predicate; - -/** - * Represents a VC implication node whose refinement was simplified from another quantified VC shape. - */ -public class SimplifiedVCImplication extends VCImplication { - - private final VCImplication origin; - - public SimplifiedVCImplication(VCImplication implication, Predicate refinement, VCImplication origin) { - super(implication, refinement); - this.origin = origin.clone(); - } - - @Override - public VCImplication getOrigin() { - return origin; - } - - @Override - public VCImplication copyWithRefinement(Predicate refinement) { - return new SimplifiedVCImplication(this, refinement, origin); - } - - @Override - public SimplifiedVCImplication clone() { - SimplifiedVCImplication vc = new SimplifiedVCImplication(this, this.refinement.clone(), origin); - if (this.next != null) - vc.next = this.next.clone(); - return vc; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), origin); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!(obj instanceof SimplifiedVCImplication)) - return false; - if (!super.equals(obj)) - return false; - SimplifiedVCImplication other = (SimplifiedVCImplication) obj; - return origin.equals(other.origin); - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java b/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java index 2130ee186..37ea117b4 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java @@ -4,6 +4,7 @@ import liquidjava.rj_language.Predicate; import liquidjava.rj_language.opt.VCSimplification; +import liquidjava.rj_language.opt.VCSimplificationResult; import liquidjava.utils.Utils; import spoon.reflect.reference.CtTypeReference; @@ -32,21 +33,6 @@ public VCImplication(VCImplication implication, Predicate ref) { this.refinement = ref; } - public VCImplication getOrigin() { - return null; - } - - public Predicate getOriginRefinement() { - VCImplication origin = getOrigin(); - if (origin == null) - return refinement.clone(); - return origin.getRefinement().clone(); - } - - public VCImplication copyWithRefinement(Predicate refinement) { - return new VCImplication(this, refinement); - } - public void setNext(VCImplication c) { next = c; } @@ -89,7 +75,7 @@ public String toString() { return String.format("%-20s %s", "", refinement.toString()); } - public VCImplication simplify() { + public VCSimplificationResult simplify() { return VCSimplification.simplifyToFixedPoint(this); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java index 303551cfa..0516b0985 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java @@ -10,6 +10,7 @@ import liquidjava.diagnostics.errors.LJError; import liquidjava.diagnostics.errors.NotFoundError; import liquidjava.processor.VCImplication; +import liquidjava.rj_language.opt.VCSimplificationResult; import liquidjava.processor.context.AliasWrapper; import liquidjava.processor.context.Context; import liquidjava.processor.context.GhostFunction; @@ -259,12 +260,6 @@ public Predicate getOrigin() { return this; } - public VCImplication simplify() { - VCImplication result = new VCImplication(clone()).simplify(); - DebugLog.simplification(this.getExpression(), result.getRefinement().getExpression()); - return result; - } - private static boolean isBooleanLiteral(Expression expr, boolean value) { return expr instanceof LiteralBoolean && expr.isBooleanTrue() == value; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCBinderSimplification.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCBinderSimplification.java index d5dbf55ab..3b5f28663 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCBinderSimplification.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCBinderSimplification.java @@ -1,10 +1,10 @@ package liquidjava.rj_language.opt; import static liquidjava.rj_language.opt.VCSimplificationUtils.containsVar; +import static liquidjava.rj_language.opt.VCSimplificationUtils.copyWithRefinement; import static liquidjava.rj_language.opt.VCSimplificationUtils.isFalse; import static liquidjava.rj_language.opt.VCSimplificationUtils.isTrue; -import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.LiteralBoolean; @@ -41,7 +41,7 @@ private VCImplication simplify(VCImplication implication) { if (next == null) return null; - VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); + VCImplication result = copyWithRefinement(implication, implication.getRefinement().clone()); result.setNext(next); return result; } @@ -53,17 +53,12 @@ private VCImplication removeTrueBinder(VCImplication implication) { VCImplication next = implication.getNext(); // ∀x. true => P -> P - if (next != null) { - VCImplication origin = new VCImplication(implication.getName(), implication.getType(), - next.getOriginRefinement()); - VCImplication result = new SimplifiedVCImplication(next, next.getRefinement().clone(), origin); - result.setNext(next.getNext() == null ? null : next.getNext().clone()); - return result; - } + if (next != null) + return next.clone(); // ∀x. true -> true Predicate truePredicate = new Predicate(new LiteralBoolean(true)); - return new SimplifiedVCImplication(new VCImplication(truePredicate), truePredicate, implication); + return new VCImplication(truePredicate); } /** @@ -72,7 +67,7 @@ private VCImplication removeTrueBinder(VCImplication implication) { private VCImplication collapseFalseBinder(VCImplication implication) { // ∀x. false => P -> true Predicate truePredicate = new Predicate(new LiteralBoolean(true)); - return new SimplifiedVCImplication(new VCImplication(truePredicate), truePredicate, implication); + return new VCImplication(truePredicate); } /** diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCExpressionSimplificationPass.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCExpressionSimplificationPass.java index c4baf16a7..5f40533c4 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCExpressionSimplificationPass.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCExpressionSimplificationPass.java @@ -1,6 +1,7 @@ package liquidjava.rj_language.opt; -import liquidjava.processor.SimplifiedVCImplication; +import static liquidjava.rj_language.opt.VCSimplificationUtils.copyWithRefinement; + import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.Expression; @@ -32,7 +33,7 @@ private VCImplication apply(VCImplication implication, C context) { Expression expression = implication.getRefinement().getExpression(); Expression simplified = simplify(expression, context); if (!expression.equals(simplified)) { - VCImplication result = new SimplifiedVCImplication(implication, new Predicate(simplified), implication); + VCImplication result = copyWithRefinement(implication, new Predicate(simplified)); result.setNext(implication.getNext() == null ? null : implication.getNext().clone()); return result; } @@ -41,7 +42,7 @@ private VCImplication apply(VCImplication implication, C context) { if (implication.getNext() == null || implication.getNext().equals(next)) return implication; - VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); + VCImplication result = copyWithRefinement(implication, implication.getRefinement().clone()); result.setNext(next); return result; } 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 b26af0e66..4de3ff94b 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 @@ -15,16 +15,15 @@ public class VCSimplification { /** * Applies all available simplification steps to a VC chain until a fixed point is reached */ - public static VCImplication simplifyToFixedPoint(VCImplication implication) { + public static VCSimplificationResult simplifyToFixedPoint(VCImplication implication) { if (implication == null) return null; - // keep applying simplification steps until a fixed point is reached - VCImplication current = implication.clone(); + VCSimplificationResult current = new VCSimplificationResult(implication); while (true) { - VCImplication simplified = simplifyOnce(current); - if (current.equals(simplified)) - return simplified; // fixed point reached + VCSimplificationResult simplified = simplifyOnce(current.getImplication(), current); + if (simplified.getOrigin() == null) + return current; // fixed point reached current = simplified; } } @@ -32,15 +31,22 @@ public static VCImplication simplifyToFixedPoint(VCImplication implication) { /** * Applies one simplification step to a VC chain */ - public static VCImplication simplifyOnce(VCImplication implication) { + public static VCSimplificationResult simplifyOnce(VCImplication implication) { if (implication == null) return null; + return simplifyOnce(implication, new VCSimplificationResult(implication)); + } + + /** + * Applies one simplification step to a VC chain, keeping track of the origin of the simplification + */ + private static VCSimplificationResult simplifyOnce(VCImplication implication, VCSimplificationResult origin) { for (VCSimplificationPass pass : PASSES) { VCImplication simplified = pass.apply(implication); if (!implication.equals(simplified)) - return simplified; + return new VCSimplificationResult(simplified, origin); } - return implication; + return new VCSimplificationResult(implication); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationResult.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationResult.java new file mode 100644 index 000000000..30ef879df --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSimplificationResult.java @@ -0,0 +1,44 @@ +package liquidjava.rj_language.opt; + +import java.util.Objects; + +import liquidjava.processor.VCImplication; + +/** + * Result of simplifying VC implication chain + */ +public final class VCSimplificationResult { + + private final VCImplication implication; + private final VCSimplificationResult origin; + + public VCSimplificationResult(VCImplication implication) { + this(implication, null); + } + + public VCSimplificationResult(VCImplication implication, VCSimplificationResult origin) { + this.implication = Objects.requireNonNull(implication).clone(); + this.origin = origin; + } + + /** + * Returns the simplified VC chain represented by this result + */ + public VCImplication getImplication() { + return implication; + } + + /** + * Returns the origin of this simplification result or null if this result is the original VC chain + */ + public VCSimplificationResult getOrigin() { + return origin; + } + + @Override + public String toString() { + if (origin == null) + return "\n" + implication; + return "\n" + implication + "\n" + origin.toString().indent(2).stripTrailing(); + } +} 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 43b7aed55..042aa4131 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 @@ -4,11 +4,16 @@ import java.util.List; import liquidjava.processor.VCImplication; +import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.LiteralBoolean; public final class VCSimplificationUtils { + public static VCImplication copyWithRefinement(VCImplication implication, Predicate refinement) { + return new VCImplication(implication, refinement); + } + public static boolean containsVar(Expression expression, String name) { List names = new ArrayList<>(); expression.getVariableNames(names); 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 556ab9e15..e08a058cf 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 @@ -1,10 +1,10 @@ package liquidjava.rj_language.opt; import static liquidjava.rj_language.opt.VCSimplificationUtils.containsVar; +import static liquidjava.rj_language.opt.VCSimplificationUtils.copyWithRefinement; import java.util.Optional; -import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.BinaryExpression; @@ -56,16 +56,15 @@ private VCImplication substitute(VCImplication implication, VCImplication node, } /** - * Substitutes a source binder inside one VC node while preserving simplification metadata + * Substitutes a source binder inside one VC node */ private 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)); + return copyWithRefinement(implication, 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); + return copyWithRefinement(implication, new Predicate(substituted)); } /** diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java index 349d8a310..bac89371e 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java @@ -1,10 +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.assertInstanceOf; - -import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import org.junit.jupiter.api.Test; @@ -67,10 +63,7 @@ void simplifiesOnlyFirstArithmeticIdentity() { void recordsOriginWhenSimplifyingLaterImplication() { VCImplication implication = vc("x > 0", "y + 0 > x"); - VCImplication result = assertSimplificationSteps(simplification::apply, implication, + assertSimplificationSteps(simplification::apply, implication, chain(expect("x > 0"), expect("y > x", "y + 0 > x"))); - - SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); - assertEquals("y + 0 > x", simplifiedNext.getOrigin().getRefinement().getExpression().toDisplayString()); } } 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 64df5d85b..140946c3a 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,10 @@ import static liquidjava.utils.VCTestUtils.*; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; - -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; @@ -161,9 +157,7 @@ void recordsOriginWhenOnlyGroupIsUnwrapped() { VCImplication implication = vc("(x > 0)"); VCImplication result = assertSimplificationSteps(folding::apply, implication, chain(expect("x > 0", "x > 0"))); - SimplifiedVCImplication simplified = assertInstanceOf(SimplifiedVCImplication.class, result); - assertEquals("x > 0", simplified.getRefinement().toString()); - assertInstanceOf(GroupExpression.class, simplified.getOrigin().getRefinement().getExpression()); + assertEquals("x > 0", result.getRefinement().toString()); } @Test @@ -173,13 +167,8 @@ void recordsOriginWhenFoldingLaterImplication() { VCImplication result = assertSimplificationSteps(folding::apply, implication, chain(expect("x > 0"), expect("3 > 0", "1 + 2 > 0"))); - SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); - assertEquals("1 + 2 > 0", simplifiedNext.getOrigin().getRefinement().getExpression().toDisplayString()); - result = assertSimplificationSteps(folding::apply, result, chain(expect("x > 0"), expect("true", "3 > 0"))); - - simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); - assertEquals("3 > 0", simplifiedNext.getOrigin().getRefinement().getExpression().toDisplayString()); + assertEquals("true", result.getNext().getRefinement().getExpression().toDisplayString()); } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java index 673025f00..5d3e3ccdb 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java @@ -1,10 +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.assertInstanceOf; - -import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import org.junit.jupiter.api.Test; @@ -75,10 +71,7 @@ void simplifiesIteChildren() { void recordsOriginWhenSimplifyingLaterImplication() { VCImplication implication = vc("x > 0", "y || false"); - VCImplication result = assertSimplificationSteps(simplification::apply, implication, + assertSimplificationSteps(simplification::apply, implication, chain(expect("x > 0"), expect("y", "y || false"))); - - SimplifiedVCImplication simplifiedNext = assertInstanceOf(SimplifiedVCImplication.class, result.getNext()); - assertEquals("y || false", simplifiedNext.getOrigin().getRefinement().getExpression().toDisplayString()); } } 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 8a638ddc3..4ed71a949 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 @@ -32,9 +32,11 @@ public void eachSimplificationStepPreservesVcSemantics(@From(VCImplicationGenera VCImplication current = vc; for (int step = 0; step < MAX_STEPS; step++) { - VCImplication simplified = VCSimplification.simplifyOnce(current); + VCSimplificationResult result = VCSimplification.simplifyOnce(current); + VCImplication simplified = result.getImplication(); if (current.equals(simplified)) return; + assertTrue(current.equals(result.getOrigin().getImplication())); assertEquivalent(current, simplified, step); current = simplified; } 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 62514f4e2..79cbde0f1 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,8 @@ 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.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import liquidjava.processor.VCImplication; @@ -22,7 +24,7 @@ void simplifyOnceReturnsNullForNullImplication() { void simplifyOnceAppliesSubstitutionBeforeFolding() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x > 2"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("1 + 2 > 2", "∀x:int. x > 2")), chain(expect("3 > 2", "1 + 2 > 2")), chain(expect("true", "3 > 2"))); } @@ -31,7 +33,7 @@ void simplifyOnceAppliesSubstitutionBeforeFolding() { void simplifyOnceDoesNotFoldAfterSubstitutionInSameStep() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x == 3"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("1 + 2 == 3", "∀x:int. x == 3")), chain(expect("3 == 3", "1 + 2 == 3")), chain(expect("true", "3 == 3"))); } @@ -40,7 +42,7 @@ void simplifyOnceDoesNotFoldAfterSubstitutionInSameStep() { void simplifyOnceAppliesSubstitutionBeforeBinderSimplification() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. true", "x > 0"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("true"), expect("3 > 0", "∀x:int. x > 0")), chain(expect("3 > 0", "∀y:int. x > 0")), chain(expect("true", "3 > 0"))); } @@ -49,7 +51,7 @@ void simplifyOnceAppliesSubstitutionBeforeBinderSimplification() { void simplifyOnceAppliesBinderSimplificationBeforeFolding() { VCImplication implication = vc("∀x:int. true", "1 + 2 > 0"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("1 + 2 > 0", "∀x:int. 1 + 2 > 0")), chain(expect("3 > 0", "1 + 2 > 0"))); } @@ -57,7 +59,7 @@ void simplifyOnceAppliesBinderSimplificationBeforeFolding() { void simplifyOnceAppliesBinderSimplificationBeforeLogicalSimplification() { VCImplication implication = vc("∀x:int. true", "y && true"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("y && true", "∀x:int. y && true")), chain(expect("y", "y && true"))); } @@ -65,7 +67,7 @@ void simplifyOnceAppliesBinderSimplificationBeforeLogicalSimplification() { void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { VCImplication implication = vc("1 + 2 > 2"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("3 > 2", "1 + 2 > 2")), + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("3 > 2", "1 + 2 > 2")), chain(expect("true", "3 > 2"))); } @@ -73,7 +75,7 @@ void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { void simplifyOnceAppliesFoldingBeforeArithmeticSimplification() { VCImplication implication = vc("1 + 2 + x + 0 > 0"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("3 + x + 0 > 0", "1 + 2 + x + 0 > 0"))); } @@ -81,14 +83,14 @@ void simplifyOnceAppliesFoldingBeforeArithmeticSimplification() { void simplifyOnceAppliesArithmeticWhenNoSubstitutionOrFoldingIsAvailable() { VCImplication implication = vc("x + 0 > 0"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("x > 0", "x + 0 > 0"))); + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("x > 0", "x + 0 > 0"))); } @Test void simplifyOnceAppliesArithmeticBeforeLogicalSimplification() { VCImplication implication = vc("x + 0 == x"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("x == x", "x + 0 == x")), + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("x == x", "x + 0 == x")), chain(expect("true", "x == x"))); } @@ -96,14 +98,14 @@ void simplifyOnceAppliesArithmeticBeforeLogicalSimplification() { void simplifyOnceAppliesLogicalWhenNoEarlierSimplificationIsAvailable() { VCImplication implication = vc("x && true"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("x", "x && true"))); + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("x", "x && true"))); } @Test void simplifyAppliesLogicalStepsUntilFixedPoint() { VCImplication implication = vc("x && true && true"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("x && true", "x && true && true")), chain(expect("x", "x && true"))); } @@ -111,7 +113,7 @@ void simplifyAppliesLogicalStepsUntilFixedPoint() { void simplifyKeepsApplyingStepsUntilFixedPoint() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x + 1 > 3"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + assertSimplificationResults(VCSimplification::simplifyOnce, implication, 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"))); } @@ -120,16 +122,20 @@ void simplifyKeepsApplyingStepsUntilFixedPoint() { void simplifyToFixedPointRemovesTrueBindersOverMultipleSteps() { VCImplication implication = vc("∀x:int. true", "∀y:int. true", "z > 0"); - assertSimplifiedVC(VCSimplification.simplifyToFixedPoint(implication), expect("z > 0", "∀y:int. z > 0")); + VCSimplificationResult result = VCSimplification.simplifyToFixedPoint(implication); + + assertSimplifiedVC(result.getImplication(), expect("z > 0", "∀y:int. z > 0")); + assertNotNull(result.getOrigin()); + assertNotNull(result.getOrigin().getOrigin()); } @Test void simplifyAppliesMultipleSubstitutionsBeforeReachingFixedPoint() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + assertSimplificationResults(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", "3 + 1 > 3")), + chain(expect("3 + 1 > 3", "∀y:int. y > 3")), chain(expect("4 > 3", "3 + 1 > 3")), chain(expect("true", "4 > 3"))); } @@ -137,7 +143,7 @@ void simplifyAppliesMultipleSubstitutionsBeforeReachingFixedPoint() { 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, + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("y == 1 + 1", "∀x:int. y == x + 1"), expect("z == y + 1"), expect("z == 3")), chain(expect("z == 1 + 1 + 1", "∀y:int. z == y + 1"), expect("z == 3")), chain(expect("1 + 1 + 1 == 3", "∀z:int. z == 3")), chain(expect("2 + 1 == 3", "1 + 1 + 1 == 3")), @@ -148,7 +154,7 @@ void simplifyAppliesLongSubstitutionChainBeforeReachingFixedPoint() { void simplifyCombinesSubstitutionAndNestedFoldingAcrossFixedPoint() { VCImplication implication = vc("∀x:int. x == 1", "∀y:int. y == x + 2", "y - 1 == 2"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("y == 1 + 2", "∀x:int. y == x + 2"), expect("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"))); @@ -158,7 +164,7 @@ void simplifyCombinesSubstitutionAndNestedFoldingAcrossFixedPoint() { void simplifyStopsAfterSubstitutionWhenOnlyNegativeLiteralShapeChanges() { VCImplication implication = vc("∀x:int. x == a + 0", "x >= -3"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, + assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("a + 0 >= -3", "∀x:int. x >= -3"))); } @@ -166,6 +172,33 @@ void simplifyStopsAfterSubstitutionWhenOnlyNegativeLiteralShapeChanges() { void simplifyLeavesUnchangedVcAsPlainPredicates() { VCImplication implication = vc("x > 0", "y > x"); - assertSimplificationSteps(VCSimplification::simplifyOnce, implication, chain(expect("x > 0"), expect("y > x"))); + VCSimplificationResult result = VCSimplification.simplifyOnce(implication); + + assertSimplifiedVC(result.getImplication(), expect("x > 0"), expect("y > x")); + assertNull(result.getOrigin()); + } + + @Test + void simplifyOnceStoresACompleteClonedOriginChain() { + VCImplication implication = vc("x > 0", "y + 0 > x"); + + VCSimplificationResult result = VCSimplification.simplifyOnce(implication); + implication.getNext().setRefinement(vc("changed").getRefinement()); + + assertSimplifiedVC(result.getImplication(), expect("x > 0"), expect("y > x")); + assertSimplifiedVC(result.getOrigin().getImplication(), expect("x > 0"), expect("y + 0 > x")); + } + + @Test + void fixedPointHistoryLinksEverySuccessfulStep() { + VCImplication implication = vc("∀x:int. x == 1 + 2", "x + 0 > 2"); + + VCSimplificationResult result = VCSimplification.simplifyToFixedPoint(implication); + int historyLength = 0; + for (VCSimplificationResult current = result.getOrigin(); current != null; current = current.getOrigin()) + historyLength++; + + assertEquals(4, historyLength); + assertSimplifiedVC(result.getImplication(), expect("true")); } } 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 95d6c347d..a9b130546 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 liquidjava.processor.VCImplication; import org.junit.jupiter.api.Test; @@ -88,7 +87,7 @@ void substitutesOuterKnownValueIntoNestedBinderRefinements() { assertSimplificationSteps(substitution::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"))); + chain(expect("3 + 1 > 3", "∀y:int. y > 3"))); } @Test diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java index df051aafd..3556cf5eb 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -1,13 +1,14 @@ package liquidjava.utils; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import java.util.function.Function; import java.util.function.UnaryOperator; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.opt.VCSimplificationResult; import liquidjava.rj_language.parsing.RefinementsParser; import spoon.Launcher; import spoon.reflect.reference.CtTypeReference; @@ -55,15 +56,7 @@ public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplif assertEquals(Predicate.class, current.getRefinement().getClass(), "Expected simplified refinement at implication " + i + " to be a plain Predicate"); assertEquals(expectedPredicate.simplified(), formatRefinement(current), - "Unexpected simplified expression at implication " + i); - if (expectedPredicate.origin() == null) - assertNull(current.getOrigin(), "Unexpected origin VC at implication " + i); - else { - VCImplication origin = current.getOrigin(); - assertNotNull(origin, "Expected origin VC at implication " + i); - assertEquals(expectedPredicate.origin(), formatOrigin(origin), - "Unexpected origin VC at implication " + i); - } + "Unexpected simplified expression at implication " + i + sourceContext(expectedPredicate)); current = current.getNext(); } assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); @@ -79,29 +72,41 @@ public static VCImplication assertSimplificationSteps(UnaryOperator simplifier, VCImplication implication, + ExpectedSimplificationStep... expectedSteps) { + VCSimplificationResult current = new VCSimplificationResult(implication); + for (ExpectedSimplificationStep expectedStep : expectedSteps) { + VCSimplificationResult result = simplifier.apply(current.getImplication()); + assertEquals(current.getImplication(), result.getOrigin().getImplication(), + "Unexpected whole-chain simplification origin"); + assertSimplifiedVC(result.getImplication(), expectedStep.implications()); + current = result; + } + return current; + } + public static ExpectedSimplificationStep chain(ExpectedSimplifiedVCImplication... implications) { return new ExpectedSimplificationStep(implications); } - public static ExpectedSimplifiedVCImplication expect(String simplified, String origin) { - return new ExpectedSimplifiedVCImplication(simplified, origin); + public static ExpectedSimplifiedVCImplication expect(String simplified, String source) { + return new ExpectedSimplifiedVCImplication(simplified, source); } public static ExpectedSimplifiedVCImplication expect(String simplified) { return new ExpectedSimplifiedVCImplication(simplified, null); } - private static String formatOrigin(VCImplication origin) { - if (!origin.hasBinder()) - 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) { + private static String sourceContext(ExpectedSimplifiedVCImplication expected) { + return expected.source() == null ? "" : " (rewrite source: " + expected.source() + ")"; + } + + public record ExpectedSimplifiedVCImplication(String simplified, String source) { } public record ExpectedSimplificationStep(ExpectedSimplifiedVCImplication... implications) { From 08ec7307e97a516b2f945d8ed3c7385b3fafdf3f Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 18 Jun 2026 15:49:00 +0100 Subject: [PATCH 61/65] Refactoring --- .../rj_language/opt/VCSimplification.java | 35 ++++---- .../opt/VCArithmeticSimplificationTest.java | 53 +++++------ .../opt/VCBinderSimplificationTest.java | 16 ++-- .../rj_language/opt/VCFoldingTest.java | 90 ++++++++----------- .../opt/VCLogicalSimplificationTest.java | 46 +++++----- .../VCSimplificationPropertyBasedTest.java | 13 +-- .../rj_language/opt/VCSimplificationTest.java | 75 ++++++---------- .../rj_language/opt/VCSubstitutionTest.java | 35 ++++---- .../java/liquidjava/utils/VCTestUtils.java | 64 ++++++------- 9 files changed, 187 insertions(+), 240 deletions(-) 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 4de3ff94b..b96788742 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 @@ -21,32 +21,35 @@ public static VCSimplificationResult simplifyToFixedPoint(VCImplication implicat VCSimplificationResult current = new VCSimplificationResult(implication); while (true) { - VCSimplificationResult simplified = simplifyOnce(current.getImplication(), current); - if (simplified.getOrigin() == null) + VCSimplificationResult simplified = simplifyOnce(current); + if (simplified == current) return current; // fixed point reached current = simplified; } } /** - * Applies one simplification step to a VC chain + * Applies one simplification step to a VC chain from all available simplification passes */ - public static VCSimplificationResult simplifyOnce(VCImplication implication) { - if (implication == null) - return null; - - return simplifyOnce(implication, new VCSimplificationResult(implication)); + public static VCSimplificationResult simplifyOnce(VCSimplificationResult implication) { + for (VCSimplificationPass pass : PASSES) { + VCSimplificationResult simplified = simplifyOnce(implication, pass); + if (simplified != implication) + return simplified; + } + return implication; } /** - * Applies one simplification step to a VC chain, keeping track of the origin of the simplification + * Applies one selected simplification pass to a VC chain */ - private static VCSimplificationResult simplifyOnce(VCImplication implication, VCSimplificationResult origin) { - for (VCSimplificationPass pass : PASSES) { - VCImplication simplified = pass.apply(implication); - if (!implication.equals(simplified)) - return new VCSimplificationResult(simplified, origin); - } - return new VCSimplificationResult(implication); + public static VCSimplificationResult simplifyOnce(VCSimplificationResult implication, VCSimplificationPass pass) { + if (implication == null) + return null; + + VCImplication simplified = pass.apply(implication.getImplication()); + if (implication.getImplication().equals(simplified)) + return implication; + return new VCSimplificationResult(simplified, implication); } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java index bac89371e..e0496dc3a 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java @@ -10,60 +10,55 @@ class VCArithmeticSimplificationTest { @Test void simplifiesAdditiveIdentities() { - assertSimplificationSteps(simplification::apply, vc("x + 0 > 0"), chain(expect("x > 0", "x + 0 > 0"))); - assertSimplificationSteps(simplification::apply, vc("0 + x > 0"), chain(expect("x > 0", "0 + x > 0"))); - assertSimplificationSteps(simplification::apply, vc("x - 0 > 0"), chain(expect("x > 0", "x - 0 > 0"))); - assertSimplificationSteps(simplification::apply, vc("0 - x > 0"), chain(expect("-x > 0", "0 - x > 0"))); + assertSimplificationSteps(simplification, vc("x + 0 > 0"), step("x > 0")); + assertSimplificationSteps(simplification, vc("0 + x > 0"), step("x > 0")); + assertSimplificationSteps(simplification, vc("x - 0 > 0"), step("x > 0")); + assertSimplificationSteps(simplification, vc("0 - x > 0"), step("-x > 0")); } @Test void simplifiesNegatedAdditionAndSubtraction() { - assertSimplificationSteps(simplification::apply, vc("x + -x == 0"), chain(expect("0 == 0", "x + -x == 0"))); - assertSimplificationSteps(simplification::apply, vc("-x + x == 0"), chain(expect("0 == 0", "-x + x == 0"))); - assertSimplificationSteps(simplification::apply, vc("x - x == 0"), chain(expect("0 == 0", "x - x == 0"))); - assertSimplificationSteps(simplification::apply, vc("--x == x"), chain(expect("x == x", "-(-x) == x"))); - assertSimplificationSteps(simplification::apply, vc("x + -y == 0"), chain(expect("x - y == 0", "x + -y == 0"))); - assertSimplificationSteps(simplification::apply, vc("x - -y == 0"), chain(expect("x + y == 0", "x - -y == 0"))); + assertSimplificationSteps(simplification, vc("x + -x == 0"), step("0 == 0")); + assertSimplificationSteps(simplification, vc("-x + x == 0"), step("0 == 0")); + assertSimplificationSteps(simplification, vc("x - x == 0"), step("0 == 0")); + assertSimplificationSteps(simplification, vc("--x == x"), step("x == x")); + assertSimplificationSteps(simplification, vc("x + -y == 0"), step("x - y == 0")); + assertSimplificationSteps(simplification, vc("x - -y == 0"), step("x + y == 0")); } @Test void simplifiesMultiplicativeIdentities() { - assertSimplificationSteps(simplification::apply, vc("x * 1 > 0"), chain(expect("x > 0", "x * 1 > 0"))); - assertSimplificationSteps(simplification::apply, vc("1 * x > 0"), chain(expect("x > 0", "1 * x > 0"))); - assertSimplificationSteps(simplification::apply, vc("x * 0 == 0"), chain(expect("0 == 0", "x * 0 == 0"))); - assertSimplificationSteps(simplification::apply, vc("0 * x == 0"), chain(expect("0 == 0", "0 * x == 0"))); - assertSimplificationSteps(simplification::apply, vc("x / 1 > 0"), chain(expect("x > 0", "x / 1 > 0"))); - assertSimplificationSteps(simplification::apply, vc("x % 1 == 0"), chain(expect("0 == 0", "x % 1 == 0"))); + assertSimplificationSteps(simplification, vc("x * 1 > 0"), step("x > 0")); + assertSimplificationSteps(simplification, vc("1 * x > 0"), step("x > 0")); + assertSimplificationSteps(simplification, vc("x * 0 == 0"), step("0 == 0")); + assertSimplificationSteps(simplification, vc("0 * x == 0"), step("0 == 0")); + assertSimplificationSteps(simplification, vc("x / 1 > 0"), step("x > 0")); + assertSimplificationSteps(simplification, vc("x % 1 == 0"), step("0 == 0")); } @Test void simplifiesGuardedDivisionAndModuloIdentities() { - assertSimplificationSteps(simplification::apply, vc("x != 0", "0 / x == 0"), - chain(expect("x != 0"), expect("0 == 0", "0 / x == 0"))); - assertSimplificationSteps(simplification::apply, vc("x != 0", "x / x == 1"), - chain(expect("x != 0"), expect("1 == 1", "x / x == 1"))); - assertSimplificationSteps(simplification::apply, vc("0 != x", "x % x == 0"), - chain(expect("0 != x"), expect("0 == 0", "x % x == 0"))); + assertSimplificationSteps(simplification, vc("x != 0", "0 / x == 0"), step("x != 0", "0 == 0")); + assertSimplificationSteps(simplification, vc("x != 0", "x / x == 1"), step("x != 0", "1 == 1")); + assertSimplificationSteps(simplification, vc("0 != x", "x % x == 0"), step("0 != x", "0 == 0")); } @Test void leavesUnguardedDivisionAndModuloIdentitiesUnchanged() { - assertSimplificationSteps(simplification::apply, vc("0 / x == 0"), chain(expect("0 / x == 0"))); - assertSimplificationSteps(simplification::apply, vc("x / x == 1"), chain(expect("x / x == 1"))); - assertSimplificationSteps(simplification::apply, vc("x % x == 0"), chain(expect("x % x == 0"))); + assertSimplificationSteps(simplification, vc("0 / x == 0"), step("0 / x == 0")); + assertSimplificationSteps(simplification, vc("x / x == 1"), step("x / x == 1")); + assertSimplificationSteps(simplification, vc("x % x == 0"), step("x % x == 0")); } @Test void simplifiesOnlyFirstArithmeticIdentity() { - assertSimplificationSteps(simplification::apply, vc("x + 0 + 1 > 0"), - chain(expect("x + 1 > 0", "x + 0 + 1 > 0"))); + assertSimplificationSteps(simplification, vc("x + 0 + 1 > 0"), step("x + 1 > 0")); } @Test void recordsOriginWhenSimplifyingLaterImplication() { VCImplication implication = vc("x > 0", "y + 0 > x"); - assertSimplificationSteps(simplification::apply, implication, - chain(expect("x > 0"), expect("y > x", "y + 0 > x"))); + assertSimplificationSteps(simplification, implication, step("x > 0", "y > x")); } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java index 6dfa5a295..1028b7b37 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java @@ -14,14 +14,14 @@ class VCBinderSimplificationTest { void removesTrueBinderWhenVariableIsUnusedDownstream() { VCImplication implication = vc("∀x:int. true", "y > 0"); - assertSimplificationSteps(binderSimplification::apply, implication, chain(expect("y > 0", "∀x:int. y > 0"))); + assertSimplificationSteps(binderSimplification, implication, step("y > 0")); } @Test void keepsTrueBinderWhenVariableIsUsedDownstream() { VCImplication implication = vc("∀x:int. true", "x > 0"); - assertSimplificationSteps(binderSimplification::apply, implication, chain(expect("true"), expect("x > 0"))); + assertSimplificationSteps(binderSimplification, implication, step("true", "x > 0")); } @Test @@ -30,30 +30,28 @@ void collapsesFalseBinderSuffixToPlainTrue() { VCImplication result = binderSimplification.apply(implication); assertFalse(result.hasBinder()); - assertSimplifiedVC(result, expect("true", "∀x:int. false")); + assertSimplifiedVC(result, "true"); } @Test void simplifiesOnlyFirstApplicableBinder() { VCImplication implication = vc("∀x:int. true", "∀y:int. true", "z > 0"); - assertSimplificationSteps(binderSimplification::apply, implication, - chain(expect("true", "∀x:int. true"), expect("z > 0"))); + assertSimplificationSteps(binderSimplification, implication, step("true", "z > 0")); } @Test void skipsInapplicableTrueBinderAndSimplifiesLaterBinder() { VCImplication implication = vc("∀x:int. true", "x > 0", "∀y:int. true", "z > 0"); - assertSimplificationSteps(binderSimplification::apply, implication, - chain(expect("true"), expect("x > 0"), expect("z > 0", "∀y:int. z > 0"))); + assertSimplificationSteps(binderSimplification, implication, step("true", "x > 0", "z > 0")); } @Test void ignoresNonBinderBooleanLiterals() { VCImplication implication = vc("true", "false"); - assertSimplificationSteps(binderSimplification::apply, implication, chain(expect("true"), expect("false"))); + assertSimplificationSteps(binderSimplification, implication, step("true", "false")); } @Test @@ -62,6 +60,6 @@ void trueBinderWithoutSuffixBecomesPlainTrue() { VCImplication result = binderSimplification.apply(implication); assertFalse(result.hasBinder()); - assertSimplifiedVC(result, expect("true", "∀x:int. true")); + assertSimplifiedVC(result, "true"); } } 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 140946c3a..c6255752a 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 @@ -17,9 +17,8 @@ class VCFoldingTest { void foldsIntegerArithmeticAndComparisons() { VCImplication implication = vc("1 + 2 == 3"); - assertSimplificationSteps(folding::apply, implication, chain(expect("3 == 3", "1 + 2 == 3")), - chain(expect("true", "3 == 3"))); - assertSimplificationSteps(folding::apply, vc("4 > 7"), chain(expect("false", "4 > 7"))); + assertSimplificationSteps(folding, implication, step("3 == 3"), step("true")); + assertSimplificationSteps(folding, vc("4 > 7"), step("false")); } @Test @@ -27,31 +26,28 @@ void foldsRealAndMixedNumericExpressions() { VCImplication realArithmetic = vc("1.5 + 2.0 == 3.5"); VCImplication mixedArithmetic = vc("2 + 0.5 > 2"); - assertSimplificationSteps(folding::apply, realArithmetic, chain(expect("3.5 == 3.5", "1.5 + 2.0 == 3.5")), - chain(expect("true", "3.5 == 3.5"))); - assertSimplificationSteps(folding::apply, mixedArithmetic, chain(expect("2.5 > 2", "2 + 0.5 > 2")), - chain(expect("true", "2.5 > 2"))); + assertSimplificationSteps(folding, realArithmetic, step("3.5 == 3.5"), step("true")); + assertSimplificationSteps(folding, mixedArithmetic, step("2.5 > 2"), step("true")); } @Test void leavesDivisionAndModuloByZeroUnchanged() { - assertSimplificationSteps(folding::apply, vc("4 / 0 == 0"), chain(expect("4 / 0 == 0"))); - assertSimplificationSteps(folding::apply, vc("4 % 0 == 0"), chain(expect("4 % 0 == 0"))); + assertSimplificationSteps(folding, vc("4 / 0 == 0"), step("4 / 0 == 0")); + assertSimplificationSteps(folding, vc("4 % 0 == 0"), step("4 % 0 == 0")); } @Test void leavesRealDivisionAndModuloByZeroUnchanged() { - assertSimplificationSteps(folding::apply, vc("4.0 / 0.0 == 0.0"), chain(expect("4.0 / 0.0 == 0.0"))); - assertSimplificationSteps(folding::apply, vc("4.0 % 0.0 == 0.0"), chain(expect("4.0 % 0.0 == 0.0"))); + assertSimplificationSteps(folding, vc("4.0 / 0.0 == 0.0"), step("4.0 / 0.0 == 0.0")); + assertSimplificationSteps(folding, vc("4.0 % 0.0 == 0.0"), step("4.0 % 0.0 == 0.0")); } @Test void foldsIntegerDivisionTowardZeroForNegativeResults() { VCImplication implication = vc("(2 - 7) / 2 == -2"); - assertSimplificationSteps(folding::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"))); + assertSimplificationSteps(folding, implication, step("(2 - 7) / 2 == -2"), step("-5 / 2 == -2"), + step("-2 == -2"), step("-2 == -2"), step("true")); } @Test @@ -59,74 +55,67 @@ void foldsIntegerModuloWithJavaSignedRemainder() { VCImplication negativeDividend = vc("-5 % 2 < 0"); VCImplication negativeDivisor = vc("5 % -2 > 0"); - assertSimplificationSteps(folding::apply, negativeDividend, chain(expect("-5 % 2 < 0", "-5 % 2 < 0")), - chain(expect("-1 < 0", "-5 % 2 < 0")), chain(expect("true", "-1 < 0"))); - assertSimplificationSteps(folding::apply, negativeDivisor, chain(expect("5 % -2 > 0", "5 % -2 > 0")), - chain(expect("1 > 0", "5 % -2 > 0")), chain(expect("true", "1 > 0"))); + assertSimplificationSteps(folding, negativeDividend, step("-5 % 2 < 0"), step("-1 < 0"), step("true")); + assertSimplificationSteps(folding, negativeDivisor, step("5 % -2 > 0"), step("1 > 0"), step("true")); } @Test void foldsBooleanBinaryExpressions() { - assertSimplificationSteps(folding::apply, vc("true && false"), chain(expect("false", "true && false"))); - assertSimplificationSteps(folding::apply, vc("false --> true"), chain(expect("true", "false --> true"))); - assertSimplificationSteps(folding::apply, vc("true != false"), chain(expect("true", "true != false"))); + assertSimplificationSteps(folding, vc("true && false"), step("false")); + assertSimplificationSteps(folding, vc("false --> true"), step("true")); + assertSimplificationSteps(folding, vc("true != false"), step("true")); } @Test void foldsBooleanSubexpressionsInsideLargerExpression() { - assertSimplificationSteps(folding::apply, vc("true && false || ok"), - chain(expect("false || ok", "true && false || ok"))); + assertSimplificationSteps(folding, vc("true && false || ok"), step("false || ok")); } @Test void foldsNestedConstantsInsideLargerExpression() { - assertSimplificationSteps(folding::apply, vc("x > 1 + 2"), chain(expect("x > 3", "x > 1 + 2"))); - assertSimplificationSteps(folding::apply, vc("x + 1 + 2 > 4"), chain(expect("x + 3 > 4", "x + 1 + 2 > 4"))); + assertSimplificationSteps(folding, vc("x > 1 + 2"), step("x > 3")); + assertSimplificationSteps(folding, vc("x + 1 + 2 > 4"), step("x + 3 > 4")); } @Test void foldsPartialComparisonsWithoutDroppingSymbolicTerms() { - assertSimplificationSteps(folding::apply, vc("1 + 2 < x + 4"), chain(expect("3 < x + 4", "1 + 2 < x + 4"))); + assertSimplificationSteps(folding, vc("1 + 2 < x + 4"), step("3 < x + 4")); } @Test void foldsUnaryExpressions() { - assertSimplificationSteps(folding::apply, vc("!true"), chain(expect("false", "!true"))); + assertSimplificationSteps(folding, vc("!true"), step("false")); VCImplication implication = vc("-3 < 0"); - assertSimplificationSteps(folding::apply, implication, chain(expect("-3 < 0", "-3 < 0")), - chain(expect("true", "-3 < 0"))); + assertSimplificationSteps(folding, implication, step("-3 < 0"), step("true")); } @Test void foldsIteExpressions() { - assertSimplificationSteps(folding::apply, vc("true ? a : b"), chain(expect("a", "true ? a : b"))); - assertSimplificationSteps(folding::apply, vc("false ? a : b"), chain(expect("b", "false ? a : b"))); - assertSimplificationSteps(folding::apply, vc("cond ? b : b"), chain(expect("b", "cond ? b : b"))); + assertSimplificationSteps(folding, vc("true ? a : b"), step("a")); + assertSimplificationSteps(folding, vc("false ? a : b"), step("b")); + assertSimplificationSteps(folding, vc("cond ? b : b"), step("b")); } @Test void foldsIteBranchesBeforeComparingThem() { VCImplication implication = vc("cond ? 1 + 2 : 3"); - assertSimplificationSteps(folding::apply, implication, chain(expect("cond ? 3 : 3", "cond ? 1 + 2 : 3")), - chain(expect("3", "cond ? 3 : 3"))); + assertSimplificationSteps(folding, implication, step("cond ? 3 : 3"), step("3")); } @Test void foldsAdjacentIntegerConstants() { - assertSimplificationSteps(folding::apply, vc("x + 1 - 2"), chain(expect("x - 1", "x + 1 - 2"))); - assertSimplificationSteps(folding::apply, vc("x - 1 + 2"), chain(expect("x + 1", "x - 1 + 2"))); - assertSimplificationSteps(folding::apply, vc("x + 1 + 2"), chain(expect("x + 3", "x + 1 + 2"))); - assertSimplificationSteps(folding::apply, vc("x + 1 - 1"), chain(expect("x", "x + 1 - 1"))); + assertSimplificationSteps(folding, vc("x + 1 - 2"), step("x - 1")); + assertSimplificationSteps(folding, vc("x - 1 + 2"), step("x + 1")); + assertSimplificationSteps(folding, vc("x + 1 + 2"), step("x + 3")); + assertSimplificationSteps(folding, vc("x + 1 - 1"), step("x")); } @Test void foldsEnumEqualityAndInequality() { - assertSimplificationSteps(folding::apply, vc("Mode.Photo == Mode.Photo"), - chain(expect("true", "Mode.Photo == Mode.Photo"))); - assertSimplificationSteps(folding::apply, vc("Mode.Photo != Mode.Video"), - chain(expect("true", "Mode.Photo != Mode.Video"))); + assertSimplificationSteps(folding, vc("Mode.Photo == Mode.Photo"), step("true")); + assertSimplificationSteps(folding, vc("Mode.Photo != Mode.Video"), step("true")); } @Test @@ -136,8 +125,7 @@ void foldsResolvedEnumLiterals() { VCImplication implication = new VCImplication( new Predicate(new BinaryExpression(limit, "==", new LiteralInt(3)))); - assertSimplificationSteps(folding::apply, implication, chain(expect("3 == 3", "Config.LIMIT == 3")), - chain(expect("true", "3 == 3"))); + assertSimplificationSteps(folding, implication, step("3 == 3"), step("true")); } @Test @@ -148,27 +136,25 @@ void foldsResolvedEnumLiteralsInsideLargerExpression() { VCImplication implication = new VCImplication( new Predicate(new BinaryExpression(arithmetic, "==", new LiteralInt(5)))); - assertSimplificationSteps(folding::apply, implication, chain(expect("3 + 2 == 5", "Config.LIMIT + 2 == 5")), - chain(expect("5 == 5", "3 + 2 == 5")), chain(expect("true", "5 == 5"))); + assertSimplificationSteps(folding, implication, step("3 + 2 == 5"), step("5 == 5"), step("true")); } @Test void recordsOriginWhenOnlyGroupIsUnwrapped() { VCImplication implication = vc("(x > 0)"); - VCImplication result = assertSimplificationSteps(folding::apply, implication, chain(expect("x > 0", "x > 0"))); + VCSimplificationResult result = assertSimplificationSteps(folding, implication, step("x > 0")); - assertEquals("x > 0", result.getRefinement().toString()); + assertEquals("x > 0", result.getImplication().getRefinement().toString()); } @Test void recordsOriginWhenFoldingLaterImplication() { VCImplication implication = vc("x > 0", "1 + 2 > 0"); - VCImplication result = assertSimplificationSteps(folding::apply, implication, - chain(expect("x > 0"), expect("3 > 0", "1 + 2 > 0"))); + VCSimplificationResult result = assertSimplificationSteps(folding, implication, step("x > 0", "3 > 0")); - result = assertSimplificationSteps(folding::apply, result, chain(expect("x > 0"), expect("true", "3 > 0"))); - assertEquals("true", result.getNext().getRefinement().getExpression().toDisplayString()); + result = assertSimplificationSteps(folding, result.getImplication(), step("x > 0", "true")); + assertEquals("true", result.getImplication().getNext().getRefinement().getExpression().toDisplayString()); } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java index 5d3e3ccdb..99ad7cc10 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java @@ -10,68 +10,64 @@ class VCLogicalSimplificationTest { @Test void simplifiesConjunctionWithBooleanLiterals() { - assertSimplificationSteps(simplification::apply, vc("x && true"), chain(expect("x", "x && true"))); - assertSimplificationSteps(simplification::apply, vc("true && x"), chain(expect("x", "true && x"))); - assertSimplificationSteps(simplification::apply, vc("x && false"), chain(expect("false", "x && false"))); - assertSimplificationSteps(simplification::apply, vc("false && x"), chain(expect("false", "false && x"))); + assertSimplificationSteps(simplification, vc("x && true"), step("x")); + assertSimplificationSteps(simplification, vc("true && x"), step("x")); + assertSimplificationSteps(simplification, vc("x && false"), step("false")); + assertSimplificationSteps(simplification, vc("false && x"), step("false")); } @Test void simplifiesDisjunctionWithBooleanLiterals() { - assertSimplificationSteps(simplification::apply, vc("x || true"), chain(expect("true", "x || true"))); - assertSimplificationSteps(simplification::apply, vc("true || x"), chain(expect("true", "true || x"))); - assertSimplificationSteps(simplification::apply, vc("x || false"), chain(expect("x", "x || false"))); - assertSimplificationSteps(simplification::apply, vc("false || x"), chain(expect("x", "false || x"))); + assertSimplificationSteps(simplification, vc("x || true"), step("true")); + assertSimplificationSteps(simplification, vc("true || x"), step("true")); + assertSimplificationSteps(simplification, vc("x || false"), step("x")); + assertSimplificationSteps(simplification, vc("false || x"), step("x")); } @Test void simplifiesDoubleNegation() { - assertSimplificationSteps(simplification::apply, vc("!!x"), chain(expect("x", "!!x"))); + assertSimplificationSteps(simplification, vc("!!x"), step("x")); } @Test void simplifiesDuplicateLogicalOperands() { - assertSimplificationSteps(simplification::apply, vc("p && p"), chain(expect("p", "p && p"))); - assertSimplificationSteps(simplification::apply, vc("p || p"), chain(expect("p", "p || p"))); + assertSimplificationSteps(simplification, vc("p && p"), step("p")); + assertSimplificationSteps(simplification, vc("p || p"), step("p")); } @Test void simplifiesSelfEqualityAndInequality() { - assertSimplificationSteps(simplification::apply, vc("x == x"), chain(expect("true", "x == x"))); - assertSimplificationSteps(simplification::apply, vc("x != x"), chain(expect("false", "x != x"))); + assertSimplificationSteps(simplification, vc("x == x"), step("true")); + assertSimplificationSteps(simplification, vc("x != x"), step("false")); } @Test void simplifiesImplicationIdentities() { - assertSimplificationSteps(simplification::apply, vc("x --> true"), chain(expect("true", "x --> true"))); - assertSimplificationSteps(simplification::apply, vc("false --> x"), chain(expect("true", "false --> x"))); - assertSimplificationSteps(simplification::apply, vc("true --> x"), chain(expect("x", "true --> x"))); - assertSimplificationSteps(simplification::apply, vc("x --> x"), chain(expect("true", "x --> x"))); + assertSimplificationSteps(simplification, vc("x --> true"), step("true")); + assertSimplificationSteps(simplification, vc("false --> x"), step("true")); + assertSimplificationSteps(simplification, vc("true --> x"), step("x")); + assertSimplificationSteps(simplification, vc("x --> x"), step("true")); } @Test void simplifiesOnlyFirstLogicalIdentity() { - assertSimplificationSteps(simplification::apply, vc("x && true && false"), - chain(expect("x && false", "x && true && false"))); + assertSimplificationSteps(simplification, vc("x && true && false"), step("x && false")); } @Test void simplifiesNestedExpressionsBeforeParent() { - assertSimplificationSteps(simplification::apply, vc("(x && true) || false"), - chain(expect("x || false", "x && true || false"))); + assertSimplificationSteps(simplification, vc("(x && true) || false"), step("x || false")); } @Test void simplifiesIteChildren() { - assertSimplificationSteps(simplification::apply, vc("cond ? x && true : y || false"), - chain(expect("cond ? x : y || false", "cond ? x && true : y || false"))); + assertSimplificationSteps(simplification, vc("cond ? x && true : y || false"), step("cond ? x : y || false")); } @Test void recordsOriginWhenSimplifyingLaterImplication() { VCImplication implication = vc("x > 0", "y || false"); - assertSimplificationSteps(simplification::apply, implication, - chain(expect("x > 0"), expect("y", "y || false"))); + assertSimplificationSteps(simplification, implication, step("x > 0", "y")); } } 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 4ed71a949..49186622f 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 @@ -29,18 +29,19 @@ public class VCSimplificationPropertyBasedTest { @Property(trials = TRIALS) public void eachSimplificationStepPreservesVcSemantics(@From(VCImplicationGenerator.class) VCImplication vc) { setUpContext(); - VCImplication current = vc; + VCSimplificationResult current = new VCSimplificationResult(vc); for (int step = 0; step < MAX_STEPS; step++) { VCSimplificationResult result = VCSimplification.simplifyOnce(current); VCImplication simplified = result.getImplication(); - if (current.equals(simplified)) + if (result == current) return; - assertTrue(current.equals(result.getOrigin().getImplication())); - assertEquivalent(current, simplified, step); - current = simplified; + assertTrue(current.getImplication().equals(result.getOrigin().getImplication())); + assertEquivalent(current.getImplication(), simplified, step); + current = result; } - fail("VC simplification did not reach a fixed point within " + MAX_STEPS + " steps: " + current); + fail("VC simplification did not reach a fixed point within " + MAX_STEPS + " steps: " + + current.getImplication()); } private static void setUpContext() { 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 79cbde0f1..8e3115bdc 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 @@ -24,98 +24,84 @@ void simplifyOnceReturnsNullForNullImplication() { void simplifyOnceAppliesSubstitutionBeforeFolding() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x > 2"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, - chain(expect("1 + 2 > 2", "∀x:int. x > 2")), chain(expect("3 > 2", "1 + 2 > 2")), - chain(expect("true", "3 > 2"))); + assertSimplificationSteps(implication, step("1 + 2 > 2"), step("3 > 2"), step("true")); } @Test void simplifyOnceDoesNotFoldAfterSubstitutionInSameStep() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x == 3"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, - chain(expect("1 + 2 == 3", "∀x:int. x == 3")), chain(expect("3 == 3", "1 + 2 == 3")), - chain(expect("true", "3 == 3"))); + assertSimplificationSteps(implication, step("1 + 2 == 3"), step("3 == 3"), step("true")); } @Test void simplifyOnceAppliesSubstitutionBeforeBinderSimplification() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. true", "x > 0"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, - chain(expect("true"), expect("3 > 0", "∀x:int. x > 0")), chain(expect("3 > 0", "∀y:int. x > 0")), - chain(expect("true", "3 > 0"))); + assertSimplificationSteps(implication, step("true", "3 > 0"), step("3 > 0"), step("true")); } @Test void simplifyOnceAppliesBinderSimplificationBeforeFolding() { VCImplication implication = vc("∀x:int. true", "1 + 2 > 0"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, - chain(expect("1 + 2 > 0", "∀x:int. 1 + 2 > 0")), chain(expect("3 > 0", "1 + 2 > 0"))); + assertSimplificationSteps(implication, step("1 + 2 > 0"), step("3 > 0")); } @Test void simplifyOnceAppliesBinderSimplificationBeforeLogicalSimplification() { VCImplication implication = vc("∀x:int. true", "y && true"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, - chain(expect("y && true", "∀x:int. y && true")), chain(expect("y", "y && true"))); + assertSimplificationSteps(implication, step("y && true"), step("y")); } @Test void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { VCImplication implication = vc("1 + 2 > 2"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("3 > 2", "1 + 2 > 2")), - chain(expect("true", "3 > 2"))); + assertSimplificationSteps(implication, step("3 > 2"), step("true")); } @Test void simplifyOnceAppliesFoldingBeforeArithmeticSimplification() { VCImplication implication = vc("1 + 2 + x + 0 > 0"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, - chain(expect("3 + x + 0 > 0", "1 + 2 + x + 0 > 0"))); + assertSimplificationSteps(implication, step("3 + x + 0 > 0")); } @Test void simplifyOnceAppliesArithmeticWhenNoSubstitutionOrFoldingIsAvailable() { VCImplication implication = vc("x + 0 > 0"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("x > 0", "x + 0 > 0"))); + assertSimplificationSteps(implication, step("x > 0")); } @Test void simplifyOnceAppliesArithmeticBeforeLogicalSimplification() { VCImplication implication = vc("x + 0 == x"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("x == x", "x + 0 == x")), - chain(expect("true", "x == x"))); + assertSimplificationSteps(implication, step("x == x"), step("true")); } @Test void simplifyOnceAppliesLogicalWhenNoEarlierSimplificationIsAvailable() { VCImplication implication = vc("x && true"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, chain(expect("x", "x && true"))); + assertSimplificationSteps(implication, step("x")); } @Test void simplifyAppliesLogicalStepsUntilFixedPoint() { VCImplication implication = vc("x && true && true"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, - chain(expect("x && true", "x && true && true")), chain(expect("x", "x && true"))); + assertSimplificationSteps(implication, step("x && true"), step("x")); } @Test void simplifyKeepsApplyingStepsUntilFixedPoint() { VCImplication implication = vc("∀x:int. x == 1 + 2", "x + 1 > 3"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, - 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"))); + assertSimplificationSteps(implication, step("1 + 2 + 1 > 3"), step("3 + 1 > 3"), step("4 > 3"), step("true")); } @Test @@ -124,7 +110,7 @@ void simplifyToFixedPointRemovesTrueBindersOverMultipleSteps() { VCSimplificationResult result = VCSimplification.simplifyToFixedPoint(implication); - assertSimplifiedVC(result.getImplication(), expect("z > 0", "∀y:int. z > 0")); + assertSimplifiedVC(result.getImplication(), "z > 0"); assertNotNull(result.getOrigin()); assertNotNull(result.getOrigin().getOrigin()); } @@ -133,48 +119,41 @@ void simplifyToFixedPointRemovesTrueBindersOverMultipleSteps() { void simplifyAppliesMultipleSubstitutionsBeforeReachingFixedPoint() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); - assertSimplificationResults(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 > 3")), chain(expect("4 > 3", "3 + 1 > 3")), - chain(expect("true", "4 > 3"))); + assertSimplificationSteps(implication, step("y == 3 + 1", "y > 3"), step("3 + 1 > 3"), step("4 > 3"), + step("true")); } @Test void simplifyAppliesLongSubstitutionChainBeforeReachingFixedPoint() { VCImplication implication = vc("∀x:int. x == 1", "∀y:int. y == x + 1", "∀z:int. z == y + 1", "z == 3"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, - chain(expect("y == 1 + 1", "∀x:int. y == x + 1"), expect("z == y + 1"), expect("z == 3")), - chain(expect("z == 1 + 1 + 1", "∀y:int. z == y + 1"), expect("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"))); + assertSimplificationSteps(implication, step("y == 1 + 1", "z == y + 1", "z == 3"), + step("z == 1 + 1 + 1", "z == 3"), step("1 + 1 + 1 == 3"), step("2 + 1 == 3"), step("3 == 3"), + step("true")); } @Test void simplifyCombinesSubstitutionAndNestedFoldingAcrossFixedPoint() { VCImplication implication = vc("∀x:int. x == 1", "∀y:int. y == x + 2", "y - 1 == 2"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, - chain(expect("y == 1 + 2", "∀x:int. y == x + 2"), expect("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"))); + assertSimplificationSteps(implication, step("y == 1 + 2", "y - 1 == 2"), step("1 + 2 - 1 == 2"), + step("3 - 1 == 2"), step("2 == 2"), step("true")); } @Test void simplifyStopsAfterSubstitutionWhenOnlyNegativeLiteralShapeChanges() { VCImplication implication = vc("∀x:int. x == a + 0", "x >= -3"); - assertSimplificationResults(VCSimplification::simplifyOnce, implication, - chain(expect("a + 0 >= -3", "∀x:int. x >= -3"))); + assertSimplificationSteps(implication, step("a + 0 >= -3")); } @Test void simplifyLeavesUnchangedVcAsPlainPredicates() { VCImplication implication = vc("x > 0", "y > x"); - VCSimplificationResult result = VCSimplification.simplifyOnce(implication); + VCSimplificationResult result = VCSimplification.simplifyOnce(new VCSimplificationResult(implication)); - assertSimplifiedVC(result.getImplication(), expect("x > 0"), expect("y > x")); + assertSimplifiedVC(result.getImplication(), "x > 0", "y > x"); assertNull(result.getOrigin()); } @@ -182,11 +161,11 @@ void simplifyLeavesUnchangedVcAsPlainPredicates() { void simplifyOnceStoresACompleteClonedOriginChain() { VCImplication implication = vc("x > 0", "y + 0 > x"); - VCSimplificationResult result = VCSimplification.simplifyOnce(implication); + VCSimplificationResult result = VCSimplification.simplifyOnce(new VCSimplificationResult(implication)); implication.getNext().setRefinement(vc("changed").getRefinement()); - assertSimplifiedVC(result.getImplication(), expect("x > 0"), expect("y > x")); - assertSimplifiedVC(result.getOrigin().getImplication(), expect("x > 0"), expect("y + 0 > x")); + assertSimplifiedVC(result.getImplication(), "x > 0", "y > x"); + assertSimplifiedVC(result.getOrigin().getImplication(), "x > 0", "y + 0 > x"); } @Test @@ -199,6 +178,6 @@ void fixedPointHistoryLinksEverySuccessfulStep() { historyLength++; assertEquals(4, historyLength); - assertSimplifiedVC(result.getImplication(), expect("true")); + assertSimplifiedVC(result.getImplication(), "true"); } } 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 a9b130546..2af1733a6 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,109 +12,104 @@ class VCSubstitutionTest { void substitutesBinderEqualityIntoWholeChain() { VCImplication implication = vc("∀x:int. x == 3", "x > 0"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("3 > 0", "∀x:int. x > 0"))); + assertSimplificationSteps(substitution, implication, step("3 > 0")); } @Test void substitutesReverseBinderEquality() { VCImplication implication = vc("∀x:int. 3 == x", "x > 0"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("3 > 0", "∀x:int. x > 0"))); + assertSimplificationSteps(substitution, implication, step("3 > 0")); } @Test void substitutesCompoundKnownValue() { VCImplication implication = vc("∀x:int. x == y + 1", "x > y"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("y + 1 > y", "∀x:int. x > y"))); + assertSimplificationSteps(substitution, implication, step("y + 1 > y")); } @Test void substitutesOnlyWholeVariableReferences() { VCImplication implication = vc("∀x:int. x == 3", "xx > x"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("xx > 3", "∀x:int. xx > x"))); + assertSimplificationSteps(substitution, implication, step("xx > 3")); } @Test void substitutesEveryOccurrenceInPredicate() { VCImplication implication = vc("∀x:int. x == 2", "x + x > 0"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("2 + 2 > 0", "∀x:int. x + x > 0"))); + assertSimplificationSteps(substitution, implication, step("2 + 2 > 0")); } @Test void preservesRemainingBinderAfterSubstitution() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y > x", "y > 0"); - assertSimplificationSteps(substitution::apply, implication, - chain(expect("y > 3", "∀x:int. y > x"), expect("y > 0"))); + assertSimplificationSteps(substitution, implication, step("y > 3", "y > 0")); } @Test void keepsSourceNodeWhenItIsLastInChain() { VCImplication implication = vc("x > 0", "∀y:int. y == 1"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("x > 0"), expect("y == 1"))); + assertSimplificationSteps(substitution, implication, step("x > 0", "y == 1")); } @Test void keepsReturnBinderWhenConclusionIsSeparate() { VCImplication implication = vc("∀x:int. true", "∀#ret_8:int. #ret_8 == x + 1"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("true"), expect("#ret⁸ == x + 1"))); + assertSimplificationSteps(substitution, implication, step("true", "#ret⁸ == x + 1")); } @Test void usesFirstSubstitutionFoundInChain() { VCImplication implication = vc("∀x:int. x > 0", "∀y:int. y == 4", "x + y > 0"); - assertSimplificationSteps(substitution::apply, implication, - chain(expect("x > 0"), expect("x + 4 > 0", "∀y:int. x + y > 0"))); + assertSimplificationSteps(substitution, implication, step("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"); - assertSimplificationSteps(substitution::apply, implication, - chain(expect("true"), expect("z > 1", "∀y:int. z > y"), expect("1 + z > 0", "∀y:int. y + z > 0"))); + assertSimplificationSteps(substitution, implication, step("true", "z > 1", "1 + z > 0")); } @Test void substitutesOuterKnownValueIntoNestedBinderRefinements() { VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); - assertSimplificationSteps(substitution::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 > 3"))); + assertSimplificationSteps(substitution, implication, step("y == 3 + 1", "y > 3"), step("3 + 1 > 3")); } @Test void ignoresRecursiveBinderEquality() { VCImplication implication = vc("∀x:int. x == x + 1", "x > 0"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("x == x + 1"), expect("x > 0"))); + assertSimplificationSteps(substitution, implication, step("x == x + 1", "x > 0")); } @Test void ignoresNonEqualityBinderRefinement() { VCImplication implication = vc("∀x:int. x > 3", "x > 0"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("x > 3"), expect("x > 0"))); + assertSimplificationSteps(substitution, implication, step("x > 3", "x > 0")); } @Test void ignoresDerivedBinderEquality() { VCImplication implication = vc("∀x:int. x + 1 == 3", "x > 0"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("x + 1 == 3"), expect("x > 0"))); + assertSimplificationSteps(substitution, implication, step("x + 1 == 3", "x > 0")); } @Test void ignoresEqualityWithoutBinder() { VCImplication implication = vc("x == 3", "x > 0"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("x == 3"), expect("x > 0"))); + assertSimplificationSteps(substitution, implication, step("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 3556cf5eb..96b23754b 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -1,13 +1,13 @@ package liquidjava.utils; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; -import java.util.function.Function; -import java.util.function.UnaryOperator; - import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.opt.VCSimplification; +import liquidjava.rj_language.opt.VCSimplificationPass; import liquidjava.rj_language.opt.VCSimplificationResult; import liquidjava.rj_language.parsing.RefinementsParser; import spoon.Launcher; @@ -49,66 +49,60 @@ private static CtTypeReference type(String name) { throw new IllegalArgumentException("Unsupported test type: " + name); } - public static void assertSimplifiedVC(VCImplication implication, ExpectedSimplifiedVCImplication... expected) { + public static void assertSimplifiedVC(VCImplication implication, String... expected) { VCImplication current = implication; for (int i = 0; i < expected.length; i++) { - ExpectedSimplifiedVCImplication expectedPredicate = expected[i]; assertEquals(Predicate.class, current.getRefinement().getClass(), "Expected simplified refinement at implication " + i + " to be a plain Predicate"); - assertEquals(expectedPredicate.simplified(), formatRefinement(current), - "Unexpected simplified expression at implication " + i + sourceContext(expectedPredicate)); + assertEquals(expected[i], formatRefinement(current), + "Unexpected simplified expression at implication " + i); current = current.getNext(); } assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); } - public static VCImplication assertSimplificationSteps(UnaryOperator simplifier, - VCImplication implication, ExpectedSimplificationStep... expectedSteps) { - VCImplication current = implication; + public static VCSimplificationResult assertSimplificationSteps(VCImplication implication, + ExpectedSimplificationStep... expectedSteps) { + VCSimplificationResult current = new VCSimplificationResult(implication); for (ExpectedSimplificationStep expectedStep : expectedSteps) { - current = simplifier.apply(current); - assertSimplifiedVC(current, expectedStep.implications()); + VCSimplificationResult result = VCSimplification.simplifyOnce(current); + assertSimplificationResult(current, result, expectedStep); + current = result; } return current; } - public static VCSimplificationResult assertSimplificationResults( - Function simplifier, VCImplication implication, - ExpectedSimplificationStep... expectedSteps) { + public static VCSimplificationResult assertSimplificationSteps(VCSimplificationPass simplifier, + VCImplication implication, ExpectedSimplificationStep... expectedSteps) { VCSimplificationResult current = new VCSimplificationResult(implication); for (ExpectedSimplificationStep expectedStep : expectedSteps) { - VCSimplificationResult result = simplifier.apply(current.getImplication()); - assertEquals(current.getImplication(), result.getOrigin().getImplication(), - "Unexpected whole-chain simplification origin"); - assertSimplifiedVC(result.getImplication(), expectedStep.implications()); + VCSimplificationResult result = VCSimplification.simplifyOnce(current, simplifier); + assertSimplificationResult(current, result, expectedStep); current = result; } return current; } - public static ExpectedSimplificationStep chain(ExpectedSimplifiedVCImplication... implications) { - return new ExpectedSimplificationStep(implications); - } - - public static ExpectedSimplifiedVCImplication expect(String simplified, String source) { - return new ExpectedSimplifiedVCImplication(simplified, source); + private static void assertSimplificationResult(VCSimplificationResult previous, VCSimplificationResult result, + ExpectedSimplificationStep expectedStep) { + if (previous.getImplication().equals(result.getImplication())) { + assertNull(result.getOrigin(), "Unchanged simplification result should not have an origin"); + } else { + assertNotNull(result.getOrigin(), "Changed simplification result should have an origin"); + assertEquals(previous.getImplication(), result.getOrigin().getImplication(), + "Simplification origin should be the complete previous VC"); + } + assertSimplifiedVC(result.getImplication(), expectedStep.implications()); } - public static ExpectedSimplifiedVCImplication expect(String simplified) { - return new ExpectedSimplifiedVCImplication(simplified, null); + public static ExpectedSimplificationStep step(String... implications) { + return new ExpectedSimplificationStep(implications); } private static String formatRefinement(VCImplication implication) { return implication.getRefinement().getExpression().toDisplayString(); } - private static String sourceContext(ExpectedSimplifiedVCImplication expected) { - return expected.source() == null ? "" : " (rewrite source: " + expected.source() + ")"; - } - - public record ExpectedSimplifiedVCImplication(String simplified, String source) { - } - - public record ExpectedSimplificationStep(ExpectedSimplifiedVCImplication... implications) { + public record ExpectedSimplificationStep(String... implications) { } } From 96f984263b5242dad350b37db8aa663e0cebde00 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 18 Jun 2026 16:11:27 +0100 Subject: [PATCH 62/65] Refactor Tests --- .../opt/VCArithmeticSimplificationTest.java | 5 +- .../opt/VCBinderSimplificationTest.java | 37 ++---- .../rj_language/opt/VCFoldingTest.java | 60 +++------ .../opt/VCLogicalSimplificationTest.java | 5 +- .../rj_language/opt/VCSimplificationTest.java | 119 ++++-------------- .../rj_language/opt/VCSubstitutionTest.java | 65 +++------- .../java/liquidjava/utils/VCTestUtils.java | 64 +++++----- 7 files changed, 106 insertions(+), 249 deletions(-) diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java index e0496dc3a..6551459b5 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCArithmeticSimplificationTest.java @@ -1,7 +1,6 @@ package liquidjava.rj_language.opt; import static liquidjava.utils.VCTestUtils.*; -import liquidjava.processor.VCImplication; import org.junit.jupiter.api.Test; class VCArithmeticSimplificationTest { @@ -57,8 +56,6 @@ void simplifiesOnlyFirstArithmeticIdentity() { @Test void recordsOriginWhenSimplifyingLaterImplication() { - VCImplication implication = vc("x > 0", "y + 0 > x"); - - assertSimplificationSteps(simplification, implication, step("x > 0", "y > x")); + assertSimplificationSteps(simplification, vc("x > 0", "y + 0 > x"), step("x > 0", "y > x")); } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java index 1028b7b37..581688dd6 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCBinderSimplificationTest.java @@ -1,9 +1,6 @@ package liquidjava.rj_language.opt; import static liquidjava.utils.VCTestUtils.*; -import static org.junit.jupiter.api.Assertions.assertFalse; - -import liquidjava.processor.VCImplication; import org.junit.jupiter.api.Test; class VCBinderSimplificationTest { @@ -12,54 +9,38 @@ class VCBinderSimplificationTest { @Test void removesTrueBinderWhenVariableIsUnusedDownstream() { - VCImplication implication = vc("∀x:int. true", "y > 0"); - - assertSimplificationSteps(binderSimplification, implication, step("y > 0")); + assertSimplificationSteps(binderSimplification, vc("∀x:int. true", "y > 0"), step("y > 0")); } @Test void keepsTrueBinderWhenVariableIsUsedDownstream() { - VCImplication implication = vc("∀x:int. true", "x > 0"); - - assertSimplificationSteps(binderSimplification, implication, step("true", "x > 0")); + assertSimplificationSteps(binderSimplification, vc("∀x:int. true", "x > 0"), step("true", "x > 0")); } @Test void collapsesFalseBinderSuffixToPlainTrue() { - VCImplication implication = vc("∀x:int. false", "x > 0", "y > 0"); - VCImplication result = binderSimplification.apply(implication); - - assertFalse(result.hasBinder()); - assertSimplifiedVC(result, "true"); + assertSimplificationSteps(binderSimplification, vc("∀x:int. false", "y > 0"), step("true")); } @Test void simplifiesOnlyFirstApplicableBinder() { - VCImplication implication = vc("∀x:int. true", "∀y:int. true", "z > 0"); - - assertSimplificationSteps(binderSimplification, implication, step("true", "z > 0")); + assertSimplificationSteps(binderSimplification, vc("∀x:int. true", "∀y:int. true", "z > 0"), + step("true", "z > 0")); } @Test void skipsInapplicableTrueBinderAndSimplifiesLaterBinder() { - VCImplication implication = vc("∀x:int. true", "x > 0", "∀y:int. true", "z > 0"); - - assertSimplificationSteps(binderSimplification, implication, step("true", "x > 0", "z > 0")); + assertSimplificationSteps(binderSimplification, vc("∀x:int. true", "x > 0", "∀y:int. true", "z > 0"), + step("true", "x > 0", "z > 0")); } @Test void ignoresNonBinderBooleanLiterals() { - VCImplication implication = vc("true", "false"); - - assertSimplificationSteps(binderSimplification, implication, step("true", "false")); + assertSimplificationSteps(binderSimplification, vc("true", "false"), step("true", "false")); } @Test void trueBinderWithoutSuffixBecomesPlainTrue() { - VCImplication implication = vc("∀x:int. true"); - VCImplication result = binderSimplification.apply(implication); - - assertFalse(result.hasBinder()); - assertSimplifiedVC(result, "true"); + assertSimplificationSteps(binderSimplification, vc("∀x:int. true"), step("true")); } } 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 c6255752a..15cc99bc3 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.*; -import static org.junit.jupiter.api.Assertions.assertEquals; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.BinaryExpression; @@ -15,19 +14,14 @@ class VCFoldingTest { @Test void foldsIntegerArithmeticAndComparisons() { - VCImplication implication = vc("1 + 2 == 3"); - - assertSimplificationSteps(folding, implication, step("3 == 3"), step("true")); + assertSimplificationSteps(folding, vc("1 + 2 == 3"), step("3 == 3"), step("true")); assertSimplificationSteps(folding, vc("4 > 7"), step("false")); } @Test void foldsRealAndMixedNumericExpressions() { - VCImplication realArithmetic = vc("1.5 + 2.0 == 3.5"); - VCImplication mixedArithmetic = vc("2 + 0.5 > 2"); - - assertSimplificationSteps(folding, realArithmetic, step("3.5 == 3.5"), step("true")); - assertSimplificationSteps(folding, mixedArithmetic, step("2.5 > 2"), step("true")); + assertSimplificationSteps(folding, vc("1.5 + 2.0 == 3.5"), step("3.5 == 3.5"), step("true")); + assertSimplificationSteps(folding, vc("2 + 0.5 > 2"), step("2.5 > 2"), step("true")); } @Test @@ -44,19 +38,14 @@ void leavesRealDivisionAndModuloByZeroUnchanged() { @Test void foldsIntegerDivisionTowardZeroForNegativeResults() { - VCImplication implication = vc("(2 - 7) / 2 == -2"); - - assertSimplificationSteps(folding, implication, step("(2 - 7) / 2 == -2"), step("-5 / 2 == -2"), + assertSimplificationSteps(folding, vc("(2 - 7) / 2 == -2"), step("(2 - 7) / 2 == -2"), step("-5 / 2 == -2"), step("-2 == -2"), step("-2 == -2"), step("true")); } @Test void foldsIntegerModuloWithJavaSignedRemainder() { - VCImplication negativeDividend = vc("-5 % 2 < 0"); - VCImplication negativeDivisor = vc("5 % -2 > 0"); - - assertSimplificationSteps(folding, negativeDividend, step("-5 % 2 < 0"), step("-1 < 0"), step("true")); - assertSimplificationSteps(folding, negativeDivisor, step("5 % -2 > 0"), step("1 > 0"), step("true")); + assertSimplificationSteps(folding, vc("-5 % 2 < 0"), step("-5 % 2 < 0"), step("-1 < 0"), step("true")); + assertSimplificationSteps(folding, vc("5 % -2 > 0"), step("5 % -2 > 0"), step("1 > 0"), step("true")); } @Test @@ -85,9 +74,7 @@ void foldsPartialComparisonsWithoutDroppingSymbolicTerms() { @Test void foldsUnaryExpressions() { assertSimplificationSteps(folding, vc("!true"), step("false")); - VCImplication implication = vc("-3 < 0"); - - assertSimplificationSteps(folding, implication, step("-3 < 0"), step("true")); + assertSimplificationSteps(folding, vc("-3 < 0"), step("-3 < 0"), step("true")); } @Test @@ -99,9 +86,7 @@ void foldsIteExpressions() { @Test void foldsIteBranchesBeforeComparingThem() { - VCImplication implication = vc("cond ? 1 + 2 : 3"); - - assertSimplificationSteps(folding, implication, step("cond ? 3 : 3"), step("3")); + assertSimplificationSteps(folding, vc("cond ? 1 + 2 : 3"), step("cond ? 3 : 3"), step("3")); } @Test @@ -118,6 +103,16 @@ void foldsEnumEqualityAndInequality() { assertSimplificationSteps(folding, vc("Mode.Photo != Mode.Video"), step("true")); } + @Test + void recordsOriginWhenOnlyGroupIsUnwrapped() { + assertSimplificationSteps(folding, vc("(x > 0)"), step("x > 0")); + } + + @Test + void recordsOriginWhenFoldingLaterImplication() { + assertSimplificationSteps(folding, vc("x > 0", "1 + 2 > 0"), step("x > 0", "3 > 0"), step("x > 0", "true")); + } + @Test void foldsResolvedEnumLiterals() { Enum limit = new Enum("Config", "LIMIT"); @@ -138,23 +133,4 @@ void foldsResolvedEnumLiteralsInsideLargerExpression() { assertSimplificationSteps(folding, implication, step("3 + 2 == 5"), step("5 == 5"), step("true")); } - - @Test - void recordsOriginWhenOnlyGroupIsUnwrapped() { - VCImplication implication = vc("(x > 0)"); - VCSimplificationResult result = assertSimplificationSteps(folding, implication, step("x > 0")); - - assertEquals("x > 0", result.getImplication().getRefinement().toString()); - } - - @Test - void recordsOriginWhenFoldingLaterImplication() { - VCImplication implication = vc("x > 0", "1 + 2 > 0"); - - VCSimplificationResult result = assertSimplificationSteps(folding, implication, step("x > 0", "3 > 0")); - - result = assertSimplificationSteps(folding, result.getImplication(), step("x > 0", "true")); - assertEquals("true", result.getImplication().getNext().getRefinement().getExpression().toDisplayString()); - } - } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java index 99ad7cc10..141744d9f 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCLogicalSimplificationTest.java @@ -1,7 +1,6 @@ package liquidjava.rj_language.opt; import static liquidjava.utils.VCTestUtils.*; -import liquidjava.processor.VCImplication; import org.junit.jupiter.api.Test; class VCLogicalSimplificationTest { @@ -66,8 +65,6 @@ void simplifiesIteChildren() { @Test void recordsOriginWhenSimplifyingLaterImplication() { - VCImplication implication = vc("x > 0", "y || false"); - - assertSimplificationSteps(simplification, implication, step("x > 0", "y")); + assertSimplificationSteps(simplification, vc("x > 0", "y || false"), step("x > 0", "y")); } } 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 8e3115bdc..b2c18116d 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,11 +1,8 @@ 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.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; -import liquidjava.processor.VCImplication; import org.junit.jupiter.api.Test; class VCSimplificationTest { @@ -22,162 +19,98 @@ void simplifyOnceReturnsNullForNullImplication() { @Test void simplifyOnceAppliesSubstitutionBeforeFolding() { - VCImplication implication = vc("∀x:int. x == 1 + 2", "x > 2"); - - assertSimplificationSteps(implication, step("1 + 2 > 2"), step("3 > 2"), step("true")); + assertSimplificationSteps(vc("∀x:int. x == 1 + 2", "x > 2"), step("1 + 2 > 2"), step("3 > 2"), step("true")); } @Test void simplifyOnceDoesNotFoldAfterSubstitutionInSameStep() { - VCImplication implication = vc("∀x:int. x == 1 + 2", "x == 3"); - - assertSimplificationSteps(implication, step("1 + 2 == 3"), step("3 == 3"), step("true")); + assertSimplificationSteps(vc("∀x:int. x == 1 + 2", "x == 3"), step("1 + 2 == 3"), step("3 == 3"), step("true")); } @Test void simplifyOnceAppliesSubstitutionBeforeBinderSimplification() { - VCImplication implication = vc("∀x:int. x == 3", "∀y:int. true", "x > 0"); - - assertSimplificationSteps(implication, step("true", "3 > 0"), step("3 > 0"), step("true")); + assertSimplificationSteps(vc("∀x:int. x == 3", "∀y:int. true", "x > 0"), step("true", "3 > 0"), step("3 > 0"), + step("true")); } @Test void simplifyOnceAppliesBinderSimplificationBeforeFolding() { - VCImplication implication = vc("∀x:int. true", "1 + 2 > 0"); - - assertSimplificationSteps(implication, step("1 + 2 > 0"), step("3 > 0")); + assertSimplificationSteps(vc("∀x:int. true", "1 + 2 > 0"), step("1 + 2 > 0"), step("3 > 0")); } @Test void simplifyOnceAppliesBinderSimplificationBeforeLogicalSimplification() { - VCImplication implication = vc("∀x:int. true", "y && true"); - - assertSimplificationSteps(implication, step("y && true"), step("y")); + assertSimplificationSteps(vc("∀x:int. true", "y && true"), step("y && true"), step("y")); } @Test void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { - VCImplication implication = vc("1 + 2 > 2"); - - assertSimplificationSteps(implication, step("3 > 2"), step("true")); + assertSimplificationSteps(vc("1 + 2 > 2"), step("3 > 2"), step("true")); } @Test void simplifyOnceAppliesFoldingBeforeArithmeticSimplification() { - VCImplication implication = vc("1 + 2 + x + 0 > 0"); - - assertSimplificationSteps(implication, step("3 + x + 0 > 0")); + assertSimplificationSteps(vc("1 + 2 + x + 0 > 0"), step("3 + x + 0 > 0")); } @Test void simplifyOnceAppliesArithmeticWhenNoSubstitutionOrFoldingIsAvailable() { - VCImplication implication = vc("x + 0 > 0"); - - assertSimplificationSteps(implication, step("x > 0")); + assertSimplificationSteps(vc("x + 0 > 0"), step("x > 0")); } @Test void simplifyOnceAppliesArithmeticBeforeLogicalSimplification() { - VCImplication implication = vc("x + 0 == x"); - - assertSimplificationSteps(implication, step("x == x"), step("true")); + assertSimplificationSteps(vc("x + 0 == x"), step("x == x"), step("true")); } @Test void simplifyOnceAppliesLogicalWhenNoEarlierSimplificationIsAvailable() { - VCImplication implication = vc("x && true"); - - assertSimplificationSteps(implication, step("x")); + assertSimplificationSteps(vc("x && true"), step("x")); } @Test void simplifyAppliesLogicalStepsUntilFixedPoint() { - VCImplication implication = vc("x && true && true"); - - assertSimplificationSteps(implication, step("x && true"), step("x")); + assertSimplificationSteps(vc("x && true && true"), step("x && true"), step("x")); } @Test void simplifyKeepsApplyingStepsUntilFixedPoint() { - VCImplication implication = vc("∀x:int. x == 1 + 2", "x + 1 > 3"); - - assertSimplificationSteps(implication, step("1 + 2 + 1 > 3"), step("3 + 1 > 3"), step("4 > 3"), step("true")); + assertSimplificationSteps(vc("∀x:int. x == 1 + 2", "x + 1 > 3"), step("1 + 2 + 1 > 3"), step("3 + 1 > 3"), + step("4 > 3"), step("true")); } @Test - void simplifyToFixedPointRemovesTrueBindersOverMultipleSteps() { - VCImplication implication = vc("∀x:int. true", "∀y:int. true", "z > 0"); - - VCSimplificationResult result = VCSimplification.simplifyToFixedPoint(implication); - - assertSimplifiedVC(result.getImplication(), "z > 0"); - assertNotNull(result.getOrigin()); - assertNotNull(result.getOrigin().getOrigin()); + void simplifyRemovesTrueBindersOverMultipleSteps() { + assertSimplificationSteps(vc("∀x:int. true", "∀y:int. true", "z > 0"), step("true", "z > 0"), step("z > 0")); } @Test void simplifyAppliesMultipleSubstitutionsBeforeReachingFixedPoint() { - VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); - - assertSimplificationSteps(implication, step("y == 3 + 1", "y > 3"), step("3 + 1 > 3"), step("4 > 3"), - step("true")); + assertSimplificationSteps(vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"), step("y == 3 + 1", "y > 3"), + step("3 + 1 > 3"), step("4 > 3"), step("true")); } @Test void simplifyAppliesLongSubstitutionChainBeforeReachingFixedPoint() { - VCImplication implication = vc("∀x:int. x == 1", "∀y:int. y == x + 1", "∀z:int. z == y + 1", "z == 3"); - - assertSimplificationSteps(implication, step("y == 1 + 1", "z == y + 1", "z == 3"), - step("z == 1 + 1 + 1", "z == 3"), step("1 + 1 + 1 == 3"), step("2 + 1 == 3"), step("3 == 3"), - step("true")); + assertSimplificationSteps(vc("∀x:int. x == 1", "∀y:int. y == x + 1", "∀z:int. z == y + 1", "z == 3"), + step("y == 1 + 1", "z == y + 1", "z == 3"), step("z == 1 + 1 + 1", "z == 3"), step("1 + 1 + 1 == 3"), + step("2 + 1 == 3"), step("3 == 3"), step("true")); } @Test void simplifyCombinesSubstitutionAndNestedFoldingAcrossFixedPoint() { - VCImplication implication = vc("∀x:int. x == 1", "∀y:int. y == x + 2", "y - 1 == 2"); - - assertSimplificationSteps(implication, step("y == 1 + 2", "y - 1 == 2"), step("1 + 2 - 1 == 2"), - step("3 - 1 == 2"), step("2 == 2"), step("true")); + assertSimplificationSteps(vc("∀x:int. x == 1", "∀y:int. y == x + 2", "y - 1 == 2"), + step("y == 1 + 2", "y - 1 == 2"), step("1 + 2 - 1 == 2"), step("3 - 1 == 2"), step("2 == 2"), + step("true")); } @Test void simplifyStopsAfterSubstitutionWhenOnlyNegativeLiteralShapeChanges() { - VCImplication implication = vc("∀x:int. x == a + 0", "x >= -3"); - - assertSimplificationSteps(implication, step("a + 0 >= -3")); + assertSimplificationSteps(vc("∀x:int. x == a + 0", "x >= -3"), step("a + 0 >= -3")); } @Test void simplifyLeavesUnchangedVcAsPlainPredicates() { - VCImplication implication = vc("x > 0", "y > x"); - - VCSimplificationResult result = VCSimplification.simplifyOnce(new VCSimplificationResult(implication)); - - assertSimplifiedVC(result.getImplication(), "x > 0", "y > x"); - assertNull(result.getOrigin()); - } - - @Test - void simplifyOnceStoresACompleteClonedOriginChain() { - VCImplication implication = vc("x > 0", "y + 0 > x"); - - VCSimplificationResult result = VCSimplification.simplifyOnce(new VCSimplificationResult(implication)); - implication.getNext().setRefinement(vc("changed").getRefinement()); - - assertSimplifiedVC(result.getImplication(), "x > 0", "y > x"); - assertSimplifiedVC(result.getOrigin().getImplication(), "x > 0", "y + 0 > x"); - } - - @Test - void fixedPointHistoryLinksEverySuccessfulStep() { - VCImplication implication = vc("∀x:int. x == 1 + 2", "x + 0 > 2"); - - VCSimplificationResult result = VCSimplification.simplifyToFixedPoint(implication); - int historyLength = 0; - for (VCSimplificationResult current = result.getOrigin(); current != null; current = current.getOrigin()) - historyLength++; - - assertEquals(4, historyLength); - assertSimplifiedVC(result.getImplication(), "true"); + assertSimplificationSteps(vc("x > 0", "y > x"), step("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 2af1733a6..299e7b023 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 liquidjava.processor.VCImplication; import org.junit.jupiter.api.Test; class VCSubstitutionTest { @@ -10,106 +9,80 @@ class VCSubstitutionTest { @Test void substitutesBinderEqualityIntoWholeChain() { - VCImplication implication = vc("∀x:int. x == 3", "x > 0"); - - assertSimplificationSteps(substitution, implication, step("3 > 0")); + assertSimplificationSteps(substitution, vc("∀x:int. x == 3", "x > 0"), step("3 > 0")); } @Test void substitutesReverseBinderEquality() { - VCImplication implication = vc("∀x:int. 3 == x", "x > 0"); - - assertSimplificationSteps(substitution, implication, step("3 > 0")); + assertSimplificationSteps(substitution, vc("∀x:int. 3 == x", "x > 0"), step("3 > 0")); } @Test void substitutesCompoundKnownValue() { - VCImplication implication = vc("∀x:int. x == y + 1", "x > y"); - - assertSimplificationSteps(substitution, implication, step("y + 1 > y")); + assertSimplificationSteps(substitution, vc("∀x:int. x == y + 1", "x > y"), step("y + 1 > y")); } @Test void substitutesOnlyWholeVariableReferences() { - VCImplication implication = vc("∀x:int. x == 3", "xx > x"); - - assertSimplificationSteps(substitution, implication, step("xx > 3")); + assertSimplificationSteps(substitution, vc("∀x:int. x == 3", "xx > x"), step("xx > 3")); } @Test void substitutesEveryOccurrenceInPredicate() { - VCImplication implication = vc("∀x:int. x == 2", "x + x > 0"); - - assertSimplificationSteps(substitution, implication, step("2 + 2 > 0")); + assertSimplificationSteps(substitution, vc("∀x:int. x == 2", "x + x > 0"), step("2 + 2 > 0")); } @Test void preservesRemainingBinderAfterSubstitution() { - VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y > x", "y > 0"); - - assertSimplificationSteps(substitution, implication, step("y > 3", "y > 0")); + assertSimplificationSteps(substitution, vc("∀x:int. x == 3", "∀y:int. y > x", "y > 0"), step("y > 3", "y > 0")); } @Test void keepsSourceNodeWhenItIsLastInChain() { - VCImplication implication = vc("x > 0", "∀y:int. y == 1"); - - assertSimplificationSteps(substitution, implication, step("x > 0", "y == 1")); + assertSimplificationSteps(substitution, vc("x > 0", "∀y:int. y == 1"), step("x > 0", "y == 1")); } @Test void keepsReturnBinderWhenConclusionIsSeparate() { - VCImplication implication = vc("∀x:int. true", "∀#ret_8:int. #ret_8 == x + 1"); - - assertSimplificationSteps(substitution, implication, step("true", "#ret⁸ == x + 1")); + assertSimplificationSteps(substitution, vc("∀x:int. true", "∀#ret_8:int. #ret_8 == x + 1"), + step("true", "#ret⁸ == x + 1")); } @Test void usesFirstSubstitutionFoundInChain() { - VCImplication implication = vc("∀x:int. x > 0", "∀y:int. y == 4", "x + y > 0"); - - assertSimplificationSteps(substitution, implication, step("x > 0", "x + 4 > 0")); + assertSimplificationSteps(substitution, vc("∀x:int. x > 0", "∀y:int. y == 4", "x + y > 0"), + step("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"); - - assertSimplificationSteps(substitution, implication, step("true", "z > 1", "1 + z > 0")); + assertSimplificationSteps(substitution, vc("∀x:int. true", "∀y:int. y == 1", "∀z:int. z > y", "y + z > 0"), + step("true", "z > 1", "1 + z > 0")); } @Test void substitutesOuterKnownValueIntoNestedBinderRefinements() { - VCImplication implication = vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"); - - assertSimplificationSteps(substitution, implication, step("y == 3 + 1", "y > 3"), step("3 + 1 > 3")); + assertSimplificationSteps(substitution, vc("∀x:int. x == 3", "∀y:int. y == x + 1", "y > x"), + step("y == 3 + 1", "y > 3"), step("3 + 1 > 3")); } @Test void ignoresRecursiveBinderEquality() { - VCImplication implication = vc("∀x:int. x == x + 1", "x > 0"); - - assertSimplificationSteps(substitution, implication, step("x == x + 1", "x > 0")); + assertSimplificationSteps(substitution, vc("∀x:int. x == x + 1", "x > 0"), step("x == x + 1", "x > 0")); } @Test void ignoresNonEqualityBinderRefinement() { - VCImplication implication = vc("∀x:int. x > 3", "x > 0"); - - assertSimplificationSteps(substitution, implication, step("x > 3", "x > 0")); + assertSimplificationSteps(substitution, vc("∀x:int. x > 3", "x > 0"), step("x > 3", "x > 0")); } @Test void ignoresDerivedBinderEquality() { - VCImplication implication = vc("∀x:int. x + 1 == 3", "x > 0"); - - assertSimplificationSteps(substitution, implication, step("x + 1 == 3", "x > 0")); + assertSimplificationSteps(substitution, vc("∀x:int. x + 1 == 3", "x > 0"), step("x + 1 == 3", "x > 0")); } @Test void ignoresEqualityWithoutBinder() { - VCImplication implication = vc("x == 3", "x > 0"); - - assertSimplificationSteps(substitution, implication, step("x == 3", "x > 0")); + assertSimplificationSteps(substitution, vc("x == 3", "x > 0"), step("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 96b23754b..89203ea46 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -31,36 +31,6 @@ public static VCImplication vc(String... implications) { return first; } - private static VCImplication parseImplication(String implication) { - if (!implication.startsWith("∀")) - 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(RefinementsParser.createAST(refinement, ""))); - } - - private static CtTypeReference type(String name) { - if ("int".equals(name)) - return INT; - throw new IllegalArgumentException("Unsupported test type: " + name); - } - - public static void assertSimplifiedVC(VCImplication implication, String... expected) { - VCImplication current = implication; - for (int i = 0; i < expected.length; i++) { - assertEquals(Predicate.class, current.getRefinement().getClass(), - "Expected simplified refinement at implication " + i + " to be a plain Predicate"); - assertEquals(expected[i], formatRefinement(current), - "Unexpected simplified expression at implication " + i); - current = current.getNext(); - } - assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); - } - public static VCSimplificationResult assertSimplificationSteps(VCImplication implication, ExpectedSimplificationStep... expectedSteps) { VCSimplificationResult current = new VCSimplificationResult(implication); @@ -95,14 +65,44 @@ private static void assertSimplificationResult(VCSimplificationResult previous, assertSimplifiedVC(result.getImplication(), expectedStep.implications()); } - public static ExpectedSimplificationStep step(String... implications) { - return new ExpectedSimplificationStep(implications); + private static void assertSimplifiedVC(VCImplication implication, String... expected) { + VCImplication current = implication; + for (int i = 0; i < expected.length; i++) { + assertEquals(Predicate.class, current.getRefinement().getClass(), + "Expected simplified refinement at implication " + i + " to be a plain Predicate"); + assertEquals(expected[i], formatRefinement(current), + "Unexpected simplified expression at implication " + i); + current = current.getNext(); + } + assertNull(current, "Expected VC chain to end after " + expected.length + " implications"); + } + + private static VCImplication parseImplication(String implication) { + if (!implication.startsWith("∀")) + 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(RefinementsParser.createAST(refinement, ""))); + } + + private static CtTypeReference type(String name) { + if ("int".equals(name)) + return INT; + throw new IllegalArgumentException("Unsupported test type: " + name); } private static String formatRefinement(VCImplication implication) { return implication.getRefinement().getExpression().toDisplayString(); } + public static ExpectedSimplificationStep step(String... implications) { + return new ExpectedSimplificationStep(implications); + } + public record ExpectedSimplificationStep(String... implications) { } } From 9e76dcf67c0fb2696caab95a1d3fbf148e567df7 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 18 Jun 2026 16:14:38 +0100 Subject: [PATCH 63/65] Add More Tests --- .../rj_language/opt/VCSimplificationTest.java | 53 ++++++++++++++++++- .../java/liquidjava/utils/VCTestUtils.java | 1 + 2 files changed, 53 insertions(+), 1 deletion(-) 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 b2c18116d..908ea7164 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 @@ -33,6 +33,12 @@ void simplifyOnceAppliesSubstitutionBeforeBinderSimplification() { step("true")); } + @Test + void simplifyCombinesSubstitutionWithArithmeticAndLogicalSimplification() { + assertSimplificationSteps(vc("∀x:int. x == y + 0", "x > 0 && true"), step("y + 0 > 0 && true"), + step("y > 0 && true"), step("y > 0")); + } + @Test void simplifyOnceAppliesBinderSimplificationBeforeFolding() { assertSimplificationSteps(vc("∀x:int. true", "1 + 2 > 0"), step("1 + 2 > 0"), step("3 > 0")); @@ -43,6 +49,11 @@ void simplifyOnceAppliesBinderSimplificationBeforeLogicalSimplification() { assertSimplificationSteps(vc("∀x:int. true", "y && true"), step("y && true"), step("y")); } + @Test + void simplifyOnceAppliesBinderSimplificationBeforeArithmeticSimplification() { + assertSimplificationSteps(vc("∀x:int. true", "y + 0 > 0"), step("y + 0 > 0"), step("y > 0")); + } + @Test void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { assertSimplificationSteps(vc("1 + 2 > 2"), step("3 > 2"), step("true")); @@ -50,7 +61,13 @@ void simplifyOnceAppliesFoldingWhenNoSubstitutionIsAvailable() { @Test void simplifyOnceAppliesFoldingBeforeArithmeticSimplification() { - assertSimplificationSteps(vc("1 + 2 + x + 0 > 0"), step("3 + x + 0 > 0")); + assertSimplificationSteps(vc("1 + 2 + x + 0 > 0"), step("3 + x + 0 > 0"), step("3 + x > 0")); + } + + @Test + void simplifyCombinesFoldingArithmeticAndLogicalSimplification() { + assertSimplificationSteps(vc("1 + 2 + x + 0 == 3 + x"), step("3 + x + 0 == 3 + x"), step("3 + x == 3 + x"), + step("true")); } @Test @@ -63,11 +80,45 @@ void simplifyOnceAppliesArithmeticBeforeLogicalSimplification() { assertSimplificationSteps(vc("x + 0 == x"), step("x == x"), step("true")); } + @Test + void simplifyOnceAppliesArithmeticBeforeFoldingOnNextStep() { + assertSimplificationSteps(vc("x - x == 0"), step("0 == 0"), step("true")); + } + @Test void simplifyOnceAppliesLogicalWhenNoEarlierSimplificationIsAvailable() { assertSimplificationSteps(vc("x && true"), step("x")); } + @Test + void simplifyUsesFoldingToEnableSubstitutionOnNextStep() { + assertSimplificationSteps(vc("∀x:int. (x) == 3", "x > 0"), step("x == 3", "x > 0"), step("3 > 0"), + step("true")); + } + + @Test + void simplifyUsesArithmeticToEnableSubstitutionOnNextStep() { + assertSimplificationSteps(vc("∀x:int. x + 0 == 3", "x > 0"), step("x == 3", "x > 0"), step("3 > 0"), + step("true")); + } + + @Test + void simplifyUsesLogicalSimplificationToEnableSubstitutionOnNextStep() { + assertSimplificationSteps(vc("∀x:int. true && x == 3", "x > 0"), step("x == 3", "x > 0"), step("3 > 0"), + step("true")); + } + + @Test + void simplifyUsesFoldingToEnableBinderSimplificationOnNextStep() { + assertSimplificationSteps(vc("∀x:int. 1 > 2", "y > 0"), step("false", "y > 0"), step("true")); + } + + @Test + void simplifyUsesArithmeticAndLogicalSimplificationToEnableBinderRemoval() { + assertSimplificationSteps(vc("∀x:int. x + 0 == x", "y > 0"), step("x == x", "y > 0"), step("true", "y > 0"), + step("y > 0")); + } + @Test void simplifyAppliesLogicalStepsUntilFixedPoint() { assertSimplificationSteps(vc("x && true && true"), step("x && true"), step("x")); diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java index 89203ea46..ab99e559e 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -68,6 +68,7 @@ private static void assertSimplificationResult(VCSimplificationResult previous, private static void assertSimplifiedVC(VCImplication implication, String... expected) { VCImplication current = implication; for (int i = 0; i < expected.length; i++) { + assertNotNull(current, "Expected implication " + i + " with refinement " + expected[i]); assertEquals(Predicate.class, current.getRefinement().getClass(), "Expected simplified refinement at implication " + i + " to be a plain Predicate"); assertEquals(expected[i], formatRefinement(current), From f5cccb137f417facb9a653750c1114bb3d0cb80f Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 18 Jun 2026 16:15:46 +0100 Subject: [PATCH 64/65] Assert Origin Not Equal to Simplified --- .../src/test/java/liquidjava/utils/VCTestUtils.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java index ab99e559e..a3e1aa3c0 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/VCTestUtils.java @@ -2,6 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import liquidjava.processor.VCImplication; @@ -59,6 +61,9 @@ private static void assertSimplificationResult(VCSimplificationResult previous, assertNull(result.getOrigin(), "Unchanged simplification result should not have an origin"); } else { assertNotNull(result.getOrigin(), "Changed simplification result should have an origin"); + assertNotSame(result, result.getOrigin(), "Simplification result should not be its own origin"); + assertNotEquals(result.getImplication(), result.getOrigin().getImplication(), + "Simplification origin should differ from the simplified VC"); assertEquals(previous.getImplication(), result.getOrigin().getImplication(), "Simplification origin should be the complete previous VC"); } From a3988dceb8db6046e275cdd448ec6e996b53099e Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sun, 21 Jun 2026 22:59:05 +0100 Subject: [PATCH 65/65] Fix `VCFunctionSubstitution` --- .../opt/VCFunctionSubstitution.java | 25 ++++++++++--------- .../opt/VCFunctionSubstitutionTest.java | 25 +++++++------------ .../VCSimplificationPropertyBasedTest.java | 7 ++++++ 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFunctionSubstitution.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFunctionSubstitution.java index b36be0f17..9c07296ca 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFunctionSubstitution.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCFunctionSubstitution.java @@ -1,8 +1,9 @@ package liquidjava.rj_language.opt; +import static liquidjava.rj_language.opt.VCSimplificationUtils.copyWithRefinement; + import java.util.Optional; -import liquidjava.processor.SimplifiedVCImplication; import liquidjava.processor.VCImplication; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.BinaryExpression; @@ -46,13 +47,13 @@ private VCImplication substitute(VCImplication implication, VCImplication node, // skip the source node to remove it from the chain and start substitution from the next node if (implication == node) { - VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); - result.setNext(substituteSuffix(implication.getNext(), node, invocation, replacement)); + VCImplication result = copyWithRefinement(implication, implication.getRefinement().clone()); + result.setNext(substituteSuffix(implication.getNext(), invocation, replacement)); return result; } // preserve the current node and continue rewriting the suffix - VCImplication result = implication.copyWithRefinement(implication.getRefinement().clone()); + VCImplication result = copyWithRefinement(implication, implication.getRefinement().clone()); result.setNext(substitute(implication.getNext(), node, invocation, replacement)); return result; } @@ -60,27 +61,27 @@ private VCImplication substitute(VCImplication implication, VCImplication node, /** * Rewrites every node after the source equality with one function substitution */ - private VCImplication substituteSuffix(VCImplication implication, VCImplication source, - FunctionInvocation invocation, Expression replacement) { + private VCImplication substituteSuffix(VCImplication implication, FunctionInvocation invocation, + Expression replacement) { if (implication == null) return null; - VCImplication result = substituteNode(implication, source, invocation, replacement); - result.setNext(substituteSuffix(implication.getNext(), source, invocation, replacement)); + VCImplication result = substituteNode(implication, invocation, replacement); + result.setNext(substituteSuffix(implication.getNext(), invocation, replacement)); return result; } /** - * Substitutes one exact function invocation inside one VC node while preserving simplification metadata + * Substitutes one exact function invocation inside one VC node */ - private VCImplication substituteNode(VCImplication implication, VCImplication source, FunctionInvocation invocation, + private VCImplication substituteNode(VCImplication implication, FunctionInvocation invocation, Expression replacement) { Expression expression = implication.getRefinement().getExpression().clone(); if (!containsExpression(expression, invocation)) - return implication.copyWithRefinement(new Predicate(expression)); + return copyWithRefinement(implication, new Predicate(expression)); Expression substituted = expression.substitute(invocation, replacement.clone()); - return new SimplifiedVCImplication(implication, new Predicate(substituted), source); + return copyWithRefinement(implication, new Predicate(substituted)); } /** diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFunctionSubstitutionTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFunctionSubstitutionTest.java index 5b71075bd..91ea94682 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFunctionSubstitutionTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCFunctionSubstitutionTest.java @@ -13,63 +13,56 @@ class VCFunctionSubstitutionTest { void substitutesExactFunctionInvocationIntoSuffix() { VCImplication implication = vc("f(x) == 0", "f(y) == f(x) + 1"); - assertSimplificationSteps(substitution::apply, implication, - chain(expect("f(x) == 0"), expect("f(y) == 0 + 1", "f(x) == 0"))); + assertSimplificationSteps(substitution::apply, implication, step("f(x) == 0", "f(y) == 0 + 1")); } @Test void substitutesReverseFunctionEquality() { VCImplication implication = vc("0 == f(x)", "f(y) == f(x) + 1"); - assertSimplificationSteps(substitution::apply, implication, - chain(expect("0 == f(x)"), expect("f(y) == 0 + 1", "0 == f(x)"))); + assertSimplificationSteps(substitution::apply, implication, step("0 == f(x)", "f(y) == 0 + 1")); } @Test void preservesSourceNode() { VCImplication implication = vc("f(x) == 0", "f(x) > -1"); - assertSimplificationSteps(substitution::apply, implication, - chain(expect("f(x) == 0"), expect("0 > -1", "f(x) == 0"))); + assertSimplificationSteps(substitution::apply, implication, step("f(x) == 0", "0 > -1")); } @Test void doesNotRewriteEarlierNodesFromLaterEquality() { VCImplication implication = vc("f(x) > 0", "f(x) == 1"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("f(x) > 0"), expect("f(x) == 1"))); + assertSimplificationSteps(substitution::apply, implication, step("f(x) > 0", "f(x) == 1")); } @Test void skipsUsedUpEqualityAndUsesNextAvailableEquality() { VCImplication implication = vc("f(x) == 0", "f(y) == f(x) + 1", "f(y) == 1"); - assertSimplificationSteps(substitution::apply, implication, - chain(expect("f(x) == 0"), expect("f(y) == 0 + 1", "f(x) == 0"), expect("f(y) == 1")), - chain(expect("f(x) == 0"), expect("f(y) == 0 + 1", "f(x) == 0"), - expect("0 + 1 == 1", "f(y) == 0 + 1"))); + assertSimplificationSteps(substitution::apply, implication, step("f(x) == 0", "f(y) == 0 + 1", "f(y) == 1"), + step("f(x) == 0", "f(y) == 0 + 1", "0 + 1 == 1")); } @Test void doesNotGeneralizeAcrossDifferentArguments() { VCImplication implication = vc("f(x) == 0", "f(y) == 0"); - assertSimplificationSteps(substitution::apply, implication, chain(expect("f(x) == 0"), expect("f(y) == 0"))); + assertSimplificationSteps(substitution::apply, implication, step("f(x) == 0", "f(y) == 0")); } @Test void ignoresRecursiveFunctionEquality() { VCImplication implication = vc("f(x) == f(x) + 1", "f(x) > 0"); - assertSimplificationSteps(substitution::apply, implication, - chain(expect("f(x) == f(x) + 1"), expect("f(x) > 0"))); + assertSimplificationSteps(substitution::apply, implication, step("f(x) == f(x) + 1", "f(x) > 0")); } @Test void extractsEqualityFromTopLevelConjunction() { VCImplication implication = vc("ok && f(x) == 0", "f(y) == f(x) + 1"); - assertSimplificationSteps(substitution::apply, implication, - chain(expect("ok && f(x) == 0"), expect("f(y) == 0 + 1", "ok && f(x) == 0"))); + assertSimplificationSteps(substitution::apply, implication, step("ok && f(x) == 0", "f(y) == 0 + 1")); } } 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 49186622f..9d97a1c5e 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 @@ -11,6 +11,7 @@ import com.pholser.junit.quickcheck.runner.JUnitQuickcheck; import liquidjava.processor.VCImplication; import liquidjava.processor.context.Context; +import liquidjava.processor.context.GhostFunction; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.BinaryExpression; import liquidjava.rj_language.ast.Expression; @@ -19,12 +20,15 @@ import liquidjava.smt.SMTResult; import liquidjava.utils.TestUtils; import org.junit.runner.RunWith; +import spoon.Launcher; +import spoon.reflect.factory.Factory; @RunWith(JUnitQuickcheck.class) public class VCSimplificationPropertyBasedTest { 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 + private static final Factory FACTORY = new Launcher().getFactory(); @Property(trials = TRIALS) public void eachSimplificationStepPreservesVcSemantics(@From(VCImplicationGenerator.class) VCImplication vc) { @@ -50,6 +54,9 @@ private static void setUpContext() { TestUtils.addIntVariableToContext(variable); for (String variable : VCImplicationGenerator.FREE_VARS) TestUtils.addIntVariableToContext(variable); + for (String function : VCImplicationGenerator.FUNCTIONS) + Context.getInstance().addGhostFunction( + new GhostFunction(function, List.of("int"), FACTORY.Type().INTEGER_PRIMITIVE, FACTORY, "")); } private static void assertEquivalent(VCImplication unsimplified, VCImplication simplified, int step) {