diff --git a/common/navigation/BUILD.bazel b/common/navigation/BUILD.bazel index 0c03596f9..8da2514b8 100644 --- a/common/navigation/BUILD.bazel +++ b/common/navigation/BUILD.bazel @@ -25,3 +25,13 @@ java_library( name = "mutable_navigation", exports = ["//common/src/main/java/dev/cel/common/navigation:mutable_navigation"], ) + +java_library( + name = "expr_util", + exports = ["//common/src/main/java/dev/cel/common/navigation:expr_util"], +) + +cel_android_library( + name = "expr_util_android", + exports = ["//common/src/main/java/dev/cel/common/navigation:expr_util_android"], +) diff --git a/common/src/main/java/dev/cel/common/navigation/BUILD.bazel b/common/src/main/java/dev/cel/common/navigation/BUILD.bazel index 3c2eaad62..4ae2908bc 100644 --- a/common/src/main/java/dev/cel/common/navigation/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/navigation/BUILD.bazel @@ -48,6 +48,36 @@ cel_android_library( ], ) +java_library( + name = "expr_util", + srcs = [ + "CelNavigableExprUtil.java", + ], + tags = [ + ], + deps = [ + ":common", + "//common/ast", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "expr_util_android", + srcs = [ + "CelNavigableExprUtil.java", + ], + tags = [ + ], + deps = [ + ":common_android", + "//common/ast:ast_android", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "navigation", srcs = [ diff --git a/common/src/main/java/dev/cel/common/navigation/BaseNavigableExpr.java b/common/src/main/java/dev/cel/common/navigation/BaseNavigableExpr.java index 1699b4a96..dabcac3a2 100644 --- a/common/src/main/java/dev/cel/common/navigation/BaseNavigableExpr.java +++ b/common/src/main/java/dev/cel/common/navigation/BaseNavigableExpr.java @@ -16,6 +16,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; +import com.google.errorprone.annotations.DoNotMock; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.ExprKind; import dev.cel.common.ast.Expression; @@ -25,9 +26,15 @@ /** * BaseNavigableExpr represents the base navigable expression value with methods to inspect the * parent and child expressions. + * + *
This class is intentionally non-extensible outside of the {@code dev.cel.common.navigation}
+ * package.
*/
+@DoNotMock("Use CelNavigableExpr or CelNavigableMutableExpr")
@SuppressWarnings("unchecked") // Generic types are properly bound to Expression
-abstract class BaseNavigableExpr A variable is shadowed at {@code expr} if an ancestor comprehension declares it as an
+ * iteration variable ({@code iterVar}, {@code iterVar2}) or accumulator variable ({@code
+ * accuVar}) and {@code expr} resides within a branch where that variable is active:
+ *
+ * For example, in the expression:
+ *
+ * For example, in the nested comprehension expression:
+ *
+ * For example, in the expression:
+ *
+ * For example, in the expression:
+ *
+ *
+ *
+ *
+ * {@code
+ * [1, 2].all(x, x > 0)
+ * }
+ *
+ *
+ *
+ */
+ public static boolean isVariableShadowed(BaseNavigableExpr> expr, String variableName) {
+ return areVariablesShadowed(expr, Collections.singleton(variableName));
+ }
+
+ /**
+ * Returns true if any of {@code variableNames} is in scope and shadowed by an enclosing
+ * comprehension above {@code expr}.
+ *
+ * {@code
+ * [1, 2].all(x, [3, 4].all(y, x > 0 && y > 0))
+ * }
+ *
+ * At {@code y > 0}, {@code areVariablesShadowed(node, ImmutableSet.of("x", "z"))} is {@code true}
+ * because {@code x} is in scope from the outer comprehension.
+ */
+ @SuppressWarnings("ReferenceEquality") // Required to disambiguate child branches
+ public static boolean areVariablesShadowed(
+ BaseNavigableExpr> expr, Collection{@code
+ * [a].all(x, x > a)
+ * }
+ *
+ *
+ *
+ */
+ public static boolean isComprehensionVariable(BaseNavigableExpr> expr) {
+ checkNotNull(expr);
+ return expr.getKind() == Kind.IDENT
+ && areVariablesShadowed(expr, Collections.singleton(expr.expr().ident().name()));
+ }
+
+ /**
+ * Returns true if {@code expr} or any identifier within {@code expr} references a variable
+ * declared by an enclosing comprehension.
+ *
+ * {@code
+ * [a].all(x, x > a)
+ * }
+ *
+ *
+ *
+ */
+ public static boolean hasComprehensionVariable(BaseNavigableExpr> expr) {
+ checkNotNull(expr);
+ return expr.allNodes()
+ .filter(node -> node.getKind() == Kind.IDENT)
+ .anyMatch(CelNavigableExprUtil::isComprehensionVariable);
+ }
+
+ private CelNavigableExprUtil() {}
+}
diff --git a/common/src/test/java/dev/cel/common/navigation/BUILD.bazel b/common/src/test/java/dev/cel/common/navigation/BUILD.bazel
index f8b2b988b..0a29dfe8a 100644
--- a/common/src/test/java/dev/cel/common/navigation/BUILD.bazel
+++ b/common/src/test/java/dev/cel/common/navigation/BUILD.bazel
@@ -21,10 +21,12 @@ java_library(
"//common/ast:mutable_expr",
"//common/navigation",
"//common/navigation:common",
+ "//common/navigation:expr_util",
"//common/navigation:mutable_navigation",
"//common/types",
"//compiler",
"//compiler:compiler_builder",
+ "//extensions",
"//parser:macro",
"@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
"@maven//:com_google_guava_guava",
diff --git a/common/src/test/java/dev/cel/common/navigation/CelNavigableExprUtilTest.java b/common/src/test/java/dev/cel/common/navigation/CelNavigableExprUtilTest.java
new file mode 100644
index 000000000..56e06d187
--- /dev/null
+++ b/common/src/test/java/dev/cel/common/navigation/CelNavigableExprUtilTest.java
@@ -0,0 +1,348 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.common.navigation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import dev.cel.common.CelAbstractSyntaxTree;
+import dev.cel.common.CelMutableAst;
+import dev.cel.common.ast.CelConstant;
+import dev.cel.common.ast.CelExpr.ExprKind.Kind;
+import dev.cel.common.ast.CelMutableExpr;
+import dev.cel.common.ast.CelMutableExpr.CelMutableCall;
+import dev.cel.common.ast.CelMutableExpr.CelMutableComprehension;
+import dev.cel.common.ast.CelMutableExpr.CelMutableList;
+import dev.cel.common.types.SimpleType;
+import dev.cel.compiler.CelCompiler;
+import dev.cel.compiler.CelCompilerFactory;
+import dev.cel.extensions.CelExtensions;
+import dev.cel.parser.CelStandardMacro;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class CelNavigableExprUtilTest {
+
+ private static final CelCompiler COMPILER =
+ CelCompilerFactory.standardCelCompilerBuilder()
+ .setStandardMacros(CelStandardMacro.STANDARD_MACROS)
+ .addLibraries(CelExtensions.comprehensions())
+ .addVar("a", SimpleType.INT)
+ .addVar("b", SimpleType.INT)
+ .build();
+
+ @Test
+ public void isVariableShadowed_singleVarComprehension_loopStep() throws Exception {
+ CelAbstractSyntaxTree ast = COMPILER.compile("[1, 2].all(x, x > 0)").getAst();
+ CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast);
+
+ CelNavigableExpr identX =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.expr().identOrDefault().name().equals("x"))
+ .findFirst()
+ .get();
+
+ assertThat(CelNavigableExprUtil.isVariableShadowed(identX, "x")).isTrue();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(identX, "y")).isFalse();
+ }
+
+ @Test
+ public void isVariableShadowed_twoVarComprehension_loopStep() throws Exception {
+ CelAbstractSyntaxTree ast =
+ COMPILER.compile("{'k1': 1, 'k2': 2}.all(k, v, k != '' && v > 0)").getAst();
+ CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast);
+
+ CelNavigableExpr identK =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.expr().identOrDefault().name().equals("k"))
+ .findFirst()
+ .get();
+ CelNavigableExpr identV =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.expr().identOrDefault().name().equals("v"))
+ .findFirst()
+ .get();
+ CelNavigableExpr iterRangeMap =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.getKind() == Kind.MAP)
+ .findFirst()
+ .get();
+
+ assertThat(CelNavigableExprUtil.isVariableShadowed(identK, "k")).isTrue();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(identK, "v")).isTrue();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(identV, "k")).isTrue();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(identV, "v")).isTrue();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(identK, "other")).isFalse();
+
+ assertThat(CelNavigableExprUtil.isVariableShadowed(iterRangeMap, "k")).isFalse();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(iterRangeMap, "v")).isFalse();
+ }
+
+ @Test
+ public void isVariableShadowed_iterRange_notShadowed() throws Exception {
+ CelAbstractSyntaxTree ast = COMPILER.compile("[a].all(x, x > 0)").getAst();
+ CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast);
+
+ CelNavigableExpr identA =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.expr().identOrDefault().name().equals("a"))
+ .findFirst()
+ .get();
+
+ assertThat(CelNavigableExprUtil.isVariableShadowed(identA, "x")).isFalse();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(identA, "a")).isFalse();
+ }
+
+ @Test
+ public void isVariableShadowed_nestedComprehension_scopedCorrectly() throws Exception {
+ CelAbstractSyntaxTree ast =
+ COMPILER.compile("[1, 2].all(x, [3, 4].all(y, x > 0 && y > 0))").getAst();
+ CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast);
+
+ CelNavigableExpr innerIdentX =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.expr().identOrDefault().name().equals("x"))
+ .findFirst()
+ .get();
+ CelNavigableExpr innerIdentY =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.expr().identOrDefault().name().equals("y"))
+ .findFirst()
+ .get();
+
+ assertThat(CelNavigableExprUtil.isVariableShadowed(innerIdentX, "x")).isTrue();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(innerIdentX, "y")).isTrue();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(innerIdentY, "x")).isTrue();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(innerIdentY, "y")).isTrue();
+ }
+
+ @Test
+ public void isVariableShadowed_nestedComprehension_innerIterRangeShadowsOuterOnly()
+ throws Exception {
+ CelAbstractSyntaxTree ast = COMPILER.compile("[1, 2].all(x, [x].all(y, y > 0))").getAst();
+ CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast);
+
+ CelNavigableExpr innerIterRangeIdentX =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(
+ node ->
+ node.expr().identOrDefault().name().equals("x")
+ && node.parent().isPresent()
+ && node.parent().get().getKind() == Kind.LIST)
+ .findFirst()
+ .get();
+
+ // In the inner comprehension's iterRange, outer 'x' IS in scope, but inner 'y' is NOT in scope
+ assertThat(CelNavigableExprUtil.isVariableShadowed(innerIterRangeIdentX, "x")).isTrue();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(innerIterRangeIdentX, "y")).isFalse();
+ }
+
+ @Test
+ public void isVariableShadowed_comprehensionResultBranch() throws Exception {
+ CelAbstractSyntaxTree ast = COMPILER.compile("[1, 2].all(x, x > 0)").getAst();
+ CelNavigableMutableAst navigableAst =
+ CelNavigableMutableAst.fromAst(CelMutableAst.fromCelAst(ast));
+
+ CelNavigableMutableExpr comprehensionNode =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.getKind() == Kind.COMPREHENSION)
+ .findFirst()
+ .get();
+
+ CelMutableComprehension comprehension = comprehensionNode.expr().comprehension();
+ long resultId = comprehension.result().id();
+
+ CelNavigableMutableExpr resultNode =
+ comprehensionNode.allNodes().filter(node -> node.id() == resultId).findFirst().get();
+
+ // In result branch, accuVar is in scope, but iterVar is not
+ assertThat(CelNavigableExprUtil.isVariableShadowed(resultNode, comprehension.accuVar()))
+ .isTrue();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(resultNode, comprehension.iterVar()))
+ .isFalse();
+ }
+
+ @Test
+ public void areVariablesShadowed_multipleVariables() throws Exception {
+ CelAbstractSyntaxTree ast = COMPILER.compile("[1, 2].all(x, x > 0)").getAst();
+ CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast);
+
+ CelNavigableExpr identX =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.expr().identOrDefault().name().equals("x"))
+ .findFirst()
+ .get();
+
+ assertThat(CelNavigableExprUtil.areVariablesShadowed(identX, ImmutableSet.of("y", "z", "x")))
+ .isTrue();
+ assertThat(CelNavigableExprUtil.areVariablesShadowed(identX, ImmutableSet.of("y", "z")))
+ .isFalse();
+ assertThat(CelNavigableExprUtil.areVariablesShadowed(identX, ImmutableList.of())).isFalse();
+ }
+
+ @Test
+ public void isComprehensionVariable_identNode() throws Exception {
+ CelAbstractSyntaxTree ast = COMPILER.compile("[a].all(x, x > a)").getAst();
+ CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast);
+
+ CelNavigableExpr identX =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.expr().identOrDefault().name().equals("x"))
+ .findFirst()
+ .get();
+ CelNavigableExpr identAInLoop =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(
+ node ->
+ node.expr().identOrDefault().name().equals("a")
+ && node.parent().isPresent()
+ && node.parent().get().getKind() == Kind.CALL)
+ .findFirst()
+ .get();
+ CelNavigableExpr constNode =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.getKind() == Kind.CONSTANT)
+ .findFirst()
+ .get();
+
+ assertThat(CelNavigableExprUtil.isComprehensionVariable(identX)).isTrue();
+ assertThat(CelNavigableExprUtil.isComprehensionVariable(identAInLoop)).isFalse();
+ assertThat(CelNavigableExprUtil.isComprehensionVariable(constNode)).isFalse();
+ }
+
+ @Test
+ public void hasComprehensionVariable_subtreeCheck() throws Exception {
+ CelAbstractSyntaxTree ast = COMPILER.compile("[a].all(x, x > a)").getAst();
+ CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast);
+
+ CelNavigableExpr root = navigableAst.getRoot();
+ CelNavigableExpr iterRange =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.getKind() == Kind.LIST)
+ .findFirst()
+ .get();
+ CelNavigableExpr loopStepCall =
+ navigableAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.expr().callOrDefault().function().equals("@not_strictly_false"))
+ .findFirst()
+ .get();
+
+ assertThat(CelNavigableExprUtil.hasComprehensionVariable(root)).isTrue();
+ assertThat(CelNavigableExprUtil.hasComprehensionVariable(loopStepCall)).isTrue();
+ assertThat(CelNavigableExprUtil.hasComprehensionVariable(iterRange)).isFalse();
+ }
+
+ @Test
+ public void mutableAst_parityWithImmutableAst() throws Exception {
+ CelAbstractSyntaxTree ast = COMPILER.compile("[1, 2].all(x, x > 0)").getAst();
+ CelNavigableAst immutableNavAst = CelNavigableAst.fromAst(ast);
+ CelNavigableMutableAst mutableNavAst =
+ CelNavigableMutableAst.fromAst(CelMutableAst.fromCelAst(ast));
+
+ CelNavigableExpr immutableIdentX =
+ immutableNavAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.expr().identOrDefault().name().equals("x"))
+ .findFirst()
+ .get();
+ CelNavigableMutableExpr mutableIdentX =
+ mutableNavAst
+ .getRoot()
+ .allNodes()
+ .filter(node -> node.getKind() == Kind.IDENT && node.expr().ident().name().equals("x"))
+ .findFirst()
+ .get();
+
+ assertThat(CelNavigableExprUtil.isVariableShadowed(mutableIdentX, "x"))
+ .isEqualTo(CelNavigableExprUtil.isVariableShadowed(immutableIdentX, "x"));
+ assertThat(CelNavigableExprUtil.isComprehensionVariable(mutableIdentX))
+ .isEqualTo(CelNavigableExprUtil.isComprehensionVariable(immutableIdentX));
+ assertThat(CelNavigableExprUtil.hasComprehensionVariable(mutableNavAst.getRoot()))
+ .isEqualTo(CelNavigableExprUtil.hasComprehensionVariable(immutableNavAst.getRoot()));
+ }
+
+ @Test
+ public void isVariableShadowed_zeroedOutIds_scopedCorrectly() {
+ // Construct a mutable comprehension where ALL expression IDs are 0 (e.g. freshly minted AST)
+ CelMutableExpr iterRange = CelMutableExpr.ofList(0, CelMutableList.create());
+ CelMutableExpr accuInit = CelMutableExpr.ofConstant(0, CelConstant.ofValue(true));
+ CelMutableExpr loopCond = CelMutableExpr.ofConstant(0, CelConstant.ofValue(true));
+ CelMutableExpr identX = CelMutableExpr.ofIdent(0, "x");
+ CelMutableExpr loopStep = CelMutableExpr.ofCall(0, CelMutableCall.create("!_", identX));
+ CelMutableExpr result = CelMutableExpr.ofIdent(0, "accu");
+
+ CelMutableExpr comp =
+ CelMutableExpr.ofComprehension(
+ 0,
+ CelMutableComprehension.create(
+ "x", iterRange, "accu", accuInit, loopCond, loopStep, result));
+
+ CelNavigableMutableExpr root = CelNavigableMutableExpr.fromExpr(comp);
+
+ CelNavigableMutableExpr navIdentX =
+ root.allNodes()
+ .filter(node -> node.getKind() == Kind.IDENT && node.expr().ident().name().equals("x"))
+ .findFirst()
+ .get();
+ CelNavigableMutableExpr navIterRange =
+ root.allNodes().filter(node -> node.getKind() == Kind.LIST).findFirst().get();
+ CelNavigableMutableExpr navResult =
+ root.allNodes()
+ .filter(
+ node -> node.getKind() == Kind.IDENT && node.expr().ident().name().equals("accu"))
+ .findFirst()
+ .get();
+
+ assertThat(CelNavigableExprUtil.isVariableShadowed(navIdentX, "x")).isTrue();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(navIdentX, "accu")).isTrue();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(navIterRange, "x")).isFalse();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(navResult, "x")).isFalse();
+ assertThat(CelNavigableExprUtil.isVariableShadowed(navResult, "accu")).isTrue();
+ }
+}
diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel b/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel
index da722d521..0c4b78826 100644
--- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel
+++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel
@@ -29,6 +29,7 @@ java_library(
"//common/ast:mutable_expr",
"//common/internal:date_time_helpers",
"//common/navigation:common",
+ "//common/navigation:expr_util",
"//common/navigation:mutable_navigation",
"//common/types",
"//common/types:type_providers",
@@ -95,6 +96,7 @@ java_library(
"//common:operator",
"//common/ast",
"//common/ast:mutable_expr",
+ "//common/navigation:expr_util",
"//common/navigation:mutable_navigation",
"//common/types",
"//common/types:type_providers",
diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java
index 1cf52bcbe..266059426 100644
--- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java
+++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java
@@ -36,12 +36,12 @@
import dev.cel.common.ast.CelExpr.ExprKind.Kind;
import dev.cel.common.ast.CelMutableExpr;
import dev.cel.common.ast.CelMutableExpr.CelMutableCall;
-import dev.cel.common.ast.CelMutableExpr.CelMutableComprehension;
import dev.cel.common.ast.CelMutableExpr.CelMutableList;
import dev.cel.common.ast.CelMutableExpr.CelMutableMap;
import dev.cel.common.ast.CelMutableExpr.CelMutableStruct;
import dev.cel.common.ast.CelMutableExprConverter;
import dev.cel.common.internal.DateTimeHelpers;
+import dev.cel.common.navigation.CelNavigableExprUtil;
import dev.cel.common.navigation.CelNavigableMutableAst;
import dev.cel.common.navigation.CelNavigableMutableExpr;
import dev.cel.common.navigation.TraversalOrder;
@@ -247,7 +247,7 @@ private boolean canFold(CelNavigableMutableExpr navigableExpr) {
if (functionName.equals(Operator.EQUALS.getFunction())
|| functionName.equals(Operator.NOT_EQUALS.getFunction())) {
- if (hasComprehensionVar(navigableExpr)) {
+ if (CelNavigableExprUtil.hasComprehensionVariable(navigableExpr)) {
return false;
}
if (mutableCall.args().stream()
@@ -259,7 +259,7 @@ private boolean canFold(CelNavigableMutableExpr navigableExpr) {
}
if (functionName.equals(Operator.IN.getFunction())) {
- return !hasComprehensionVar(navigableExpr);
+ return !CelNavigableExprUtil.hasComprehensionVariable(navigableExpr);
}
// Default case: all call arguments must be constants. If the argument is a container (ex:
@@ -288,33 +288,6 @@ private static boolean isCallTimestampOrDuration(CelMutableCall call) {
|| call.function().equals(DURATION.functionName());
}
- private static boolean hasComprehensionVar(CelNavigableMutableExpr expr) {
- return expr.allNodes()
- .filter(node -> node.getKind().equals(Kind.IDENT))
- .anyMatch(
- identNode -> {
- String identName = identNode.expr().ident().name();
- CelNavigableMutableExpr curr = identNode;
- Optional