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 index 48b37e03..36745853 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariablePropagation.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariablePropagation.java @@ -149,6 +149,18 @@ private static void extractVarOrigins(ValDerivationNode node, Map resolve(Expression exp) { * @param map */ private static void resolveRecursive(Expression exp, Map map) { + // Internal-var conjuncts assert truth: `#x` ⇒ `#x → true`, `!#x` ⇒ `#x → false`. Restricted to + // internal vars so user-facing predicates like `x && x` still display as `x` instead of `true`. + if (exp instanceof Var var && isInternal(var)) { + map.putIfAbsent(var.getName(), new LiteralBoolean(true)); + return; + } + if (exp instanceof UnaryExpression unary && "!".equals(unary.getOp()) + && unary.getExpression()instanceof Var inner && isInternal(inner)) { + map.putIfAbsent(inner.getName(), new LiteralBoolean(false)); + return; + } + if (!(exp instanceof BinaryExpression be)) return; 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 index 136af9e2..1ae2c36c 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java @@ -214,4 +214,33 @@ void testUnusedFunctionInvocationEqualityIsIgnored() { assertTrue(result.isEmpty(), "Function invocation definitions with no usage should be ignored"); } + + @Test + void testBareNegatedInternalVarSubstitutesFalse() { + Expression expression = parse("!#fresh_1 && #fresh_1 == #hasNext_2"); + Map result = VariableResolver.resolve(expression); + + assertEquals(1, result.size(), "Should extract only the bare-conjunct substitution"); + assertEquals("false", result.get("#fresh_1").toString(), + "!#fresh_1 as top-level conjunct should bind #fresh_1 to false"); + } + + @Test + void testBareInternalVarSubstitutesTrue() { + Expression expression = parse("#fresh_1 && #fresh_1 == #hasNext_2"); + Map result = VariableResolver.resolve(expression); + + assertEquals(1, result.size(), "Should extract only the bare-conjunct substitution"); + assertEquals("true", result.get("#fresh_1").toString(), + "Bare #fresh_1 as top-level conjunct should bind #fresh_1 to true"); + } + + @Test + void testBareUserVarNotSubstituted() { + Expression expression = parse("x && y == x"); + Map result = VariableResolver.resolve(expression); + + assertTrue(result.isEmpty(), + "Bare user-facing variables should not be substituted, to preserve their names in error messages"); + } }