Skip to content

Commit 3117701

Browse files
l46kokcopybara-github
authored andcommitted
Introduce common helpers for handling AST navigation
PiperOrigin-RevId: 960507413
1 parent 2da98c9 commit 3117701

9 files changed

Lines changed: 471 additions & 51 deletions

File tree

common/navigation/BUILD.bazel

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,13 @@ java_library(
2525
name = "mutable_navigation",
2626
exports = ["//common/src/main/java/dev/cel/common/navigation:mutable_navigation"],
2727
)
28+
29+
java_library(
30+
name = "expr_util",
31+
exports = ["//common/src/main/java/dev/cel/common/navigation:expr_util"],
32+
)
33+
34+
cel_android_library(
35+
name = "expr_util_android",
36+
exports = ["//common/src/main/java/dev/cel/common/navigation:expr_util_android"],
37+
)

common/src/main/java/dev/cel/common/navigation/BUILD.bazel

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,36 @@ cel_android_library(
4848
],
4949
)
5050

51+
java_library(
52+
name = "expr_util",
53+
srcs = [
54+
"CelNavigableExprUtil.java",
55+
],
56+
tags = [
57+
],
58+
deps = [
59+
":common",
60+
"//common/ast",
61+
"@maven//:com_google_errorprone_error_prone_annotations",
62+
"@maven//:com_google_guava_guava",
63+
],
64+
)
65+
66+
cel_android_library(
67+
name = "expr_util_android",
68+
srcs = [
69+
"CelNavigableExprUtil.java",
70+
],
71+
tags = [
72+
],
73+
deps = [
74+
":common_android",
75+
"//common/ast:ast_android",
76+
"@maven//:com_google_errorprone_error_prone_annotations",
77+
"@maven_android//:com_google_guava_guava",
78+
],
79+
)
80+
5181
java_library(
5282
name = "navigation",
5383
srcs = [

common/src/main/java/dev/cel/common/navigation/BaseNavigableExpr.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import com.google.errorprone.annotations.CanIgnoreReturnValue;
1818
import com.google.errorprone.annotations.CheckReturnValue;
19+
import com.google.errorprone.annotations.DoNotMock;
1920
import dev.cel.common.ast.CelExpr;
2021
import dev.cel.common.ast.CelExpr.ExprKind;
2122
import dev.cel.common.ast.Expression;
@@ -25,9 +26,15 @@
2526
/**
2627
* BaseNavigableExpr represents the base navigable expression value with methods to inspect the
2728
* parent and child expressions.
29+
*
30+
* <p>This class is intentionally non-extensible outside of the {@code dev.cel.common.navigation}
31+
* package.
2832
*/
33+
@DoNotMock("Use CelNavigableExpr or CelNavigableMutableExpr")
2934
@SuppressWarnings("unchecked") // Generic types are properly bound to Expression
30-
abstract class BaseNavigableExpr<E extends Expression> {
35+
public abstract class BaseNavigableExpr<E extends Expression> {
36+
37+
BaseNavigableExpr() {}
3138

3239
public abstract E expr();
3340

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.common.navigation;
16+
17+
import static com.google.common.base.Preconditions.checkNotNull;
18+
19+
import com.google.errorprone.annotations.CheckReturnValue;
20+
import dev.cel.common.ast.CelExpr.ExprKind.Kind;
21+
import dev.cel.common.ast.Expression;
22+
import java.util.Collection;
23+
import java.util.Collections;
24+
import java.util.Optional;
25+
26+
/** Utility class for common AST navigation and scoping inspections on {@link BaseNavigableExpr}. */
27+
@CheckReturnValue
28+
public final class CelNavigableExprUtil {
29+
30+
/**
31+
* Returns true if {@code variableName} is in scope and shadowed by an enclosing comprehension
32+
* above {@code expr}.
33+
*/
34+
public static boolean isVariableShadowed(BaseNavigableExpr<?> expr, String variableName) {
35+
return areVariablesShadowed(expr, Collections.singleton(variableName));
36+
}
37+
38+
/**
39+
* Returns true if any of {@code variableNames} is in scope and shadowed by an enclosing
40+
* comprehension above {@code expr}.
41+
*/
42+
public static boolean areVariablesShadowed(
43+
BaseNavigableExpr<?> expr, Collection<String> variableNames) {
44+
checkNotNull(expr);
45+
checkNotNull(variableNames);
46+
if (variableNames.isEmpty()) {
47+
return false;
48+
}
49+
BaseNavigableExpr<?> curr = expr;
50+
Optional<? extends BaseNavigableExpr<?>> maybeParent = curr.parent();
51+
while (maybeParent.isPresent()) {
52+
BaseNavigableExpr<?> parent = maybeParent.get();
53+
if (parent.getKind() == Kind.COMPREHENSION) {
54+
Expression.Comprehension<?> comp = parent.expr().comprehension();
55+
long currId = curr.id();
56+
long iterRangeId = comp.iterRange().id();
57+
long accuInitId = comp.accuInit().id();
58+
long resultId = comp.result().id();
59+
60+
if (currId != iterRangeId && currId != accuInitId) {
61+
if (currId == resultId) {
62+
if (variableNames.contains(comp.accuVar())) {
63+
return true;
64+
}
65+
} else {
66+
if (variableNames.contains(comp.iterVar())
67+
|| variableNames.contains(comp.iterVar2())
68+
|| variableNames.contains(comp.accuVar())) {
69+
return true;
70+
}
71+
}
72+
}
73+
}
74+
curr = parent;
75+
maybeParent = parent.parent();
76+
}
77+
return false;
78+
}
79+
80+
/**
81+
* Returns true if {@code expr} is an {@code IDENT} node that references a variable declared by an
82+
* enclosing comprehension.
83+
*/
84+
public static boolean isComprehensionVariable(BaseNavigableExpr<?> expr) {
85+
checkNotNull(expr);
86+
return expr.getKind() == Kind.IDENT
87+
&& areVariablesShadowed(expr, Collections.singleton(expr.expr().ident().name()));
88+
}
89+
90+
/**
91+
* Returns true if {@code expr} or any identifier within {@code expr} references a variable
92+
* declared by an enclosing comprehension.
93+
*/
94+
public static boolean hasComprehensionVariable(BaseNavigableExpr<?> expr) {
95+
checkNotNull(expr);
96+
return expr.allNodes()
97+
.filter(node -> node.getKind() == Kind.IDENT)
98+
.anyMatch(CelNavigableExprUtil::isComprehensionVariable);
99+
}
100+
101+
private CelNavigableExprUtil() {}
102+
}

common/src/test/java/dev/cel/common/navigation/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ java_library(
2121
"//common/ast:mutable_expr",
2222
"//common/navigation",
2323
"//common/navigation:common",
24+
"//common/navigation:expr_util",
2425
"//common/navigation:mutable_navigation",
2526
"//common/types",
2627
"//compiler",

0 commit comments

Comments
 (0)