Skip to content

Commit 4c9a5e9

Browse files
l46kokcopybara-github
authored andcommitted
Namespace resolution fix for planner
PiperOrigin-RevId: 857005456
1 parent e91cb90 commit 4c9a5e9

7 files changed

Lines changed: 262 additions & 22 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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.runtime.planner;
16+
17+
import dev.cel.runtime.GlobalResolver;
18+
19+
/** Identifies a resolver that can be unwrapped to bypass local variable state. */
20+
public interface ActivationWrapper extends GlobalResolver {
21+
GlobalResolver unwrap();
22+
}

runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,22 @@ final class AttributeFactory {
2929
private final CelValueConverter celValueConverter;
3030

3131
NamespacedAttribute newAbsoluteAttribute(String... names) {
32-
return new NamespacedAttribute(typeProvider, celValueConverter, ImmutableSet.copyOf(names));
32+
return NamespacedAttribute.create(typeProvider, celValueConverter, ImmutableSet.copyOf(names));
3333
}
3434

3535
RelativeAttribute newRelativeAttribute(PlannedInterpretable operand) {
3636
return new RelativeAttribute(operand, celValueConverter);
3737
}
3838

3939
MaybeAttribute newMaybeAttribute(String name) {
40+
// When there's a single name with a dot prefix, it indicates that the 'maybe' attribute is a
41+
// globally namespaced identifier.
42+
// Otherwise, the candidate names resolved from the container should be inferred.
43+
ImmutableSet<String> names =
44+
name.startsWith(".") ? ImmutableSet.of(name) : container.resolveCandidateNames(name);
45+
4046
return new MaybeAttribute(
41-
this,
42-
ImmutableList.of(
43-
new NamespacedAttribute(
44-
typeProvider, celValueConverter, container.resolveCandidateNames(name))));
47+
this, ImmutableList.of(NamespacedAttribute.create(typeProvider, celValueConverter, names)));
4548
}
4649

4750
static AttributeFactory newAttributeFactory(

runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ java_library(
114114
"RelativeAttribute.java",
115115
],
116116
deps = [
117+
":activation_wrapper",
117118
":eval_helpers",
118119
":execution_frame",
119120
":planned_interpretable",
@@ -130,6 +131,14 @@ java_library(
130131
],
131132
)
132133

134+
java_library(
135+
name = "activation_wrapper",
136+
srcs = ["ActivationWrapper.java"],
137+
deps = [
138+
"//runtime:interpretable",
139+
],
140+
)
141+
133142
java_library(
134143
name = "qualifier",
135144
srcs = ["Qualifier.java"],
@@ -333,6 +342,7 @@ java_library(
333342
name = "eval_fold",
334343
srcs = ["EvalFold.java"],
335344
deps = [
345+
":activation_wrapper",
336346
":execution_frame",
337347
":planned_interpretable",
338348
"//runtime:concatenated_list_view",

runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ private Object evalMap(Map<?, ?> iterRange, Folder folder, ExecutionFrame frame)
9797
if (!iterVar2.isEmpty()) {
9898
folder.iterVar2Val = entry.getValue();
9999
}
100-
100+
101101
boolean cond = (boolean) condition.eval(folder, frame);
102102
if (!cond) {
103103
return result.eval(folder, frame);
@@ -149,7 +149,7 @@ private static Object maybeUnwrapAccumulator(Object val) {
149149
return val;
150150
}
151151

152-
private static class Folder implements GlobalResolver {
152+
private static class Folder implements ActivationWrapper {
153153
private final GlobalResolver resolver;
154154
private final String accuVar;
155155
private final String iterVar;
@@ -166,6 +166,11 @@ private Folder(GlobalResolver resolver, String accuVar, String iterVar, String i
166166
this.iterVar2 = iterVar2;
167167
}
168168

169+
@Override
170+
public GlobalResolver unwrap() {
171+
return resolver;
172+
}
173+
169174
@Override
170175
public @Nullable Object resolve(String name) {
171176
if (name.equals(accuVar)) {

runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,30 @@
2828

2929
@Immutable
3030
final class NamespacedAttribute implements Attribute {
31+
private final ImmutableSet<Integer> disambiguateNames;
3132
private final ImmutableSet<String> namespacedNames;
3233
private final ImmutableList<Qualifier> qualifiers;
3334
private final CelValueConverter celValueConverter;
3435
private final CelTypeProvider typeProvider;
3536

3637
@Override
3738
public Object resolve(GlobalResolver ctx, ExecutionFrame frame) {
39+
GlobalResolver inputVars = ctx;
40+
// Unwrap any local activations to ensure that we reach the variables provided as input
41+
// to the expression in the event that we need to disambiguate between global and local
42+
// variables.
43+
if (!disambiguateNames.isEmpty()) {
44+
inputVars = unwrapToRoot(ctx);
45+
}
46+
47+
int i = 0;
3848
for (String name : namespacedNames) {
39-
Object value = ctx.resolve(name);
49+
GlobalResolver resolver = ctx;
50+
if (disambiguateNames.contains(i)) {
51+
resolver = inputVars;
52+
}
53+
54+
Object value = resolver.resolve(name);
4055
if (value != null) {
4156
if (!qualifiers.isEmpty()) {
4257
return applyQualifiers(value, celValueConverter, qualifiers);
@@ -69,6 +84,7 @@ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) {
6984
throw new IllegalStateException(
7085
"Unexpected type resolution when there were remaining qualifiers: " + type.name());
7186
}
87+
i++;
7288
}
7389

7490
return MissingAttribute.newMissingAttribute(namespacedNames);
@@ -82,12 +98,20 @@ ImmutableSet<String> candidateVariableNames() {
8298
return namespacedNames;
8399
}
84100

101+
private GlobalResolver unwrapToRoot(GlobalResolver resolver) {
102+
while (resolver instanceof ActivationWrapper) {
103+
resolver = ((ActivationWrapper) resolver).unwrap();
104+
}
105+
return resolver;
106+
}
107+
85108
@Override
86109
public NamespacedAttribute addQualifier(Qualifier qualifier) {
87110
return new NamespacedAttribute(
88111
typeProvider,
89112
celValueConverter,
90113
namespacedNames,
114+
disambiguateNames,
91115
ImmutableList.<Qualifier>builder().addAll(qualifiers).add(qualifier).build());
92116
}
93117

@@ -106,21 +130,40 @@ private static Object applyQualifiers(
106130
return obj;
107131
}
108132

109-
NamespacedAttribute(
133+
static NamespacedAttribute create(
110134
CelTypeProvider typeProvider,
111135
CelValueConverter celValueConverter,
112136
ImmutableSet<String> namespacedNames) {
113-
this(typeProvider, celValueConverter, namespacedNames, ImmutableList.of());
137+
ImmutableSet.Builder<String> namesBuilder = ImmutableSet.builder();
138+
ImmutableSet.Builder<Integer> indicesBuilder = ImmutableSet.builder();
139+
int i = 0;
140+
for (String name : namespacedNames) {
141+
if (name.startsWith(".")) {
142+
indicesBuilder.add(i);
143+
namesBuilder.add(name.substring(1));
144+
} else {
145+
namesBuilder.add(name);
146+
}
147+
i++;
148+
}
149+
return new NamespacedAttribute(
150+
typeProvider,
151+
celValueConverter,
152+
namesBuilder.build(),
153+
indicesBuilder.build(),
154+
ImmutableList.of());
114155
}
115156

116-
private NamespacedAttribute(
157+
NamespacedAttribute(
117158
CelTypeProvider typeProvider,
118159
CelValueConverter celValueConverter,
119160
ImmutableSet<String> namespacedNames,
161+
ImmutableSet<Integer> disambiguateNames,
120162
ImmutableList<Qualifier> qualifiers) {
121163
this.typeProvider = typeProvider;
122164
this.celValueConverter = celValueConverter;
123165
this.namespacedNames = namespacedNames;
166+
this.disambiguateNames = disambiguateNames;
124167
this.qualifiers = qualifiers;
125168
}
126169
}

runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package dev.cel.runtime.planner;
1616

1717
import com.google.auto.value.AutoValue;
18+
import com.google.common.base.Strings;
1819
import com.google.common.collect.ImmutableList;
1920
import com.google.common.collect.ImmutableMap;
2021
import com.google.common.collect.ImmutableSet;
@@ -48,6 +49,7 @@
4849
import dev.cel.runtime.CelResolvedOverload;
4950
import dev.cel.runtime.DefaultDispatcher;
5051
import dev.cel.runtime.Program;
52+
import java.util.HashMap;
5153
import java.util.NoSuchElementException;
5254
import java.util.Optional;
5355

@@ -161,8 +163,12 @@ private PlannedInterpretable planIdent(CelExpr celExpr, PlannerContext ctx) {
161163
return planCheckedIdent(celExpr.id(), ref, ctx.typeMap());
162164
}
163165

164-
return EvalAttribute.create(
165-
celExpr.id(), attributeFactory.newMaybeAttribute(celExpr.ident().name()));
166+
String identName = celExpr.ident().name();
167+
if (ctx.isLocalVar(identName)) {
168+
return EvalAttribute.create(celExpr.id(), attributeFactory.newAbsoluteAttribute(identName));
169+
}
170+
171+
return EvalAttribute.create(celExpr.id(), attributeFactory.newMaybeAttribute(identName));
166172
}
167173

168174
private PlannedInterpretable planCheckedIdent(
@@ -314,10 +320,18 @@ private PlannedInterpretable planComprehension(CelExpr expr, PlannerContext ctx)
314320

315321
PlannedInterpretable accuInit = plan(comprehension.accuInit(), ctx);
316322
PlannedInterpretable iterRange = plan(comprehension.iterRange(), ctx);
323+
324+
ctx.pushLocalVars(comprehension.accuVar(), comprehension.iterVar(), comprehension.iterVar2());
325+
317326
PlannedInterpretable loopCondition = plan(comprehension.loopCondition(), ctx);
318327
PlannedInterpretable loopStep = plan(comprehension.loopStep(), ctx);
328+
329+
ctx.popLocalVars(comprehension.iterVar(), comprehension.iterVar2());
330+
319331
PlannedInterpretable result = plan(comprehension.result(), ctx);
320332

333+
ctx.popLocalVars(comprehension.accuVar());
334+
321335
return EvalFold.create(
322336
expr.id(),
323337
comprehension.accuVar(),
@@ -460,15 +474,57 @@ private static Builder newBuilder() {
460474
}
461475
}
462476

463-
@AutoValue
464-
abstract static class PlannerContext {
477+
static final class PlannerContext {
478+
private final ImmutableMap<Long, CelReference> referenceMap;
479+
private final ImmutableMap<Long, CelType> typeMap;
480+
private final HashMap<String, Integer> localVars = new HashMap<>();
481+
482+
ImmutableMap<Long, CelReference> referenceMap() {
483+
return referenceMap;
484+
}
465485

466-
abstract ImmutableMap<Long, CelReference> referenceMap();
486+
ImmutableMap<Long, CelType> typeMap() {
487+
return typeMap;
488+
}
467489

468-
abstract ImmutableMap<Long, CelType> typeMap();
490+
private void pushLocalVars(String... names) {
491+
for (String name : names) {
492+
if (Strings.isNullOrEmpty(name)) {
493+
continue;
494+
}
495+
localVars.merge(name, 1, Integer::sum);
496+
}
497+
}
498+
499+
private void popLocalVars(String... names) {
500+
for (String name : names) {
501+
if (Strings.isNullOrEmpty(name)) {
502+
continue;
503+
}
504+
Integer count = localVars.get(name);
505+
if (count != null) {
506+
if (count == 1) {
507+
localVars.remove(name);
508+
} else {
509+
localVars.put(name, count - 1);
510+
}
511+
}
512+
}
513+
}
514+
515+
/** Checks if the given name is a local variable in the current scope. */
516+
private boolean isLocalVar(String name) {
517+
return localVars.containsKey(name);
518+
}
519+
520+
private PlannerContext(
521+
ImmutableMap<Long, CelReference> referenceMap, ImmutableMap<Long, CelType> typeMap) {
522+
this.referenceMap = referenceMap;
523+
this.typeMap = typeMap;
524+
}
469525

470-
private static PlannerContext create(CelAbstractSyntaxTree ast) {
471-
return new AutoValue_ProgramPlanner_PlannerContext(ast.getReferenceMap(), ast.getTypeMap());
526+
static PlannerContext create(CelAbstractSyntaxTree ast) {
527+
return new PlannerContext(ast.getReferenceMap(), ast.getTypeMap());
472528
}
473529
}
474530

0 commit comments

Comments
 (0)