Skip to content

Commit 96f7b4c

Browse files
committed
Substitute Internal with User-Facing Variables
1 parent 0d5b026 commit 96f7b4c

File tree

2 files changed

+101
-0
lines changed

2 files changed

+101
-0
lines changed

liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariableResolver.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ private static void resolveRecursive(Expression exp, Map<String, Expression> map
5252
map.put(var.getName(), right.clone());
5353
} else if (right instanceof Var var && left.isLiteral()) {
5454
map.put(var.getName(), left.clone());
55+
} else if (left instanceof Var leftVar && right instanceof Var rightVar) {
56+
// to later substitute internal variable with user-facing variable
57+
if (leftVar.isInternal() && !rightVar.isInternal()) {
58+
map.put(leftVar.getName(), right.clone());
59+
} else if (rightVar.isInternal() && !leftVar.isInternal()) {
60+
map.put(rightVar.getName(), left.clone());
61+
}
5562
}
5663
}
5764
}

liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/ExpressionSimplifierTest.java

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,100 @@ void testNoSimplificationHasNoOrigin() {
599599
assertNull(result.getOrigin(), "No origin should be present when nothing was simplified");
600600
}
601601

602+
@Test
603+
void testVarToVarPropagationWithInternalVariable() {
604+
// Given: #x_0 == a && #x_0 > 5
605+
// Expected: a > 5 (internal #x_0 substituted with user-facing a)
606+
607+
Expression varX0 = new Var("#x_0");
608+
Expression varA = new Var("a");
609+
Expression x0EqualsA = new BinaryExpression(varX0, "==", varA);
610+
Expression x0Greater5 = new BinaryExpression(varX0, ">", new LiteralInt(5));
611+
Expression fullExpression = new BinaryExpression(x0EqualsA, "&&", x0Greater5);
612+
613+
// When
614+
ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression);
615+
616+
// Then
617+
assertNotNull(result, "Result should not be null");
618+
assertEquals("a > 5", result.getValue().toString(),
619+
"Internal variable #x_0 should be substituted with user-facing variable a");
620+
}
621+
622+
@Test
623+
void testVarToVarInternalToInternal() {
624+
// Given: #a == #b && #b == 5 && x == #a + 1
625+
// Internal-to-internal var-to-var is resolved across fixed-point passes:
626+
// Pass 1: #b -> 5 => #a == 5 && x == #a + 1
627+
// Pass 2: #a -> 5 => x == 6
628+
629+
Expression varA = new Var("#a");
630+
Expression varB = new Var("#b");
631+
Expression varX = new Var("x");
632+
Expression five = new LiteralInt(5);
633+
Expression aEqualsB = new BinaryExpression(varA, "==", varB);
634+
Expression bEquals5 = new BinaryExpression(varB, "==", five);
635+
Expression aPlus1 = new BinaryExpression(varA, "+", new LiteralInt(1));
636+
Expression xEqualsAPlus1 = new BinaryExpression(varX, "==", aPlus1);
637+
Expression firstAnd = new BinaryExpression(aEqualsB, "&&", bEquals5);
638+
Expression fullExpression = new BinaryExpression(firstAnd, "&&", xEqualsAPlus1);
639+
640+
// When
641+
ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression);
642+
643+
// Then
644+
assertNotNull(result, "Result should not be null");
645+
assertEquals("x == 6", result.getValue().toString(),
646+
"#a should resolve through #b to 5 across passes, then x == 5 + 1 = x == 6");
647+
}
648+
649+
@Test
650+
void testVarToVarDoesNotAffectUserFacingVariables() {
651+
// Given: x == y && x > 5
652+
// Expected: x == y && x > 5 (user-facing var-to-var should not be propagated)
653+
654+
Expression varX = new Var("x");
655+
Expression varY = new Var("y");
656+
Expression xEqualsY = new BinaryExpression(varX, "==", varY);
657+
Expression xGreater5 = new BinaryExpression(varX, ">", new LiteralInt(5));
658+
Expression fullExpression = new BinaryExpression(xEqualsY, "&&", xGreater5);
659+
660+
// When
661+
ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression);
662+
663+
// Then
664+
assertNotNull(result, "Result should not be null");
665+
assertEquals("x == y && x > 5", result.getValue().toString(),
666+
"User-facing variable equalities should not trigger var-to-var propagation");
667+
}
668+
669+
@Test
670+
void testVarToVarRemovesRedundantEquality() {
671+
// Given: #ret_1 == #b_0 - 100 && #b_0 == b && b >= -128 && b <= 127
672+
// Expected: #ret_1 == b - 100 && b >= -128 && b <= 127 (#b_0 replaced with b, #b_0 == b removed)
673+
674+
Expression ret1 = new Var("#ret_1");
675+
Expression b0 = new Var("#b_0");
676+
Expression b = new Var("b");
677+
Expression ret1EqB0Minus100 = new BinaryExpression(ret1, "==",
678+
new BinaryExpression(b0, "-", new LiteralInt(100)));
679+
Expression b0EqB = new BinaryExpression(b0, "==", b);
680+
Expression bGeMinus128 = new BinaryExpression(b, ">=", new UnaryExpression("-", new LiteralInt(128)));
681+
Expression bLe127 = new BinaryExpression(b, "<=", new LiteralInt(127));
682+
Expression and1 = new BinaryExpression(ret1EqB0Minus100, "&&", b0EqB);
683+
Expression and2 = new BinaryExpression(bGeMinus128, "&&", bLe127);
684+
Expression fullExpression = new BinaryExpression(and1, "&&", and2);
685+
686+
// When
687+
ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression);
688+
689+
// Then
690+
assertNotNull(result, "Result should not be null");
691+
assertEquals("#ret_1 == b - 100 && b >= -128 && b <= 127", result.getValue().toString(),
692+
"Internal variable #b_0 should be replaced with b and redundant equality removed");
693+
assertNotNull(result.getOrigin(), "Origin should be present showing the var-to-var derivation");
694+
}
695+
602696
/**
603697
* Helper method to compare two derivation nodes recursively
604698
*/

0 commit comments

Comments
 (0)