Skip to content

Commit e50e7f8

Browse files
l46kokcopybara-github
authored andcommitted
Namespace resolution fix for planner
PiperOrigin-RevId: 862278837
1 parent b8eafbf commit e50e7f8

File tree

7 files changed

+258
-22
lines changed

7 files changed

+258
-22
lines changed
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: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,29 @@
2828

2929
@Immutable
3030
final class NamespacedAttribute implements Attribute {
31+
private final boolean 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) {
44+
inputVars = unwrapToNonLocal(ctx);
45+
}
46+
3847
for (String name : namespacedNames) {
39-
Object value = ctx.resolve(name);
48+
GlobalResolver resolver = ctx;
49+
if (disambiguateNames) {
50+
resolver = inputVars;
51+
}
52+
53+
Object value = resolver.resolve(name);
4054
if (value != null) {
4155
if (!qualifiers.isEmpty()) {
4256
return applyQualifiers(value, celValueConverter, qualifiers);
@@ -82,12 +96,20 @@ ImmutableSet<String> candidateVariableNames() {
8296
return namespacedNames;
8397
}
8498

99+
private GlobalResolver unwrapToNonLocal(GlobalResolver resolver) {
100+
while (resolver instanceof ActivationWrapper) {
101+
resolver = ((ActivationWrapper) resolver).unwrap();
102+
}
103+
return resolver;
104+
}
105+
85106
@Override
86107
public NamespacedAttribute addQualifier(Qualifier qualifier) {
87108
return new NamespacedAttribute(
88109
typeProvider,
89110
celValueConverter,
90111
namespacedNames,
112+
disambiguateNames,
91113
ImmutableList.<Qualifier>builder().addAll(qualifiers).add(qualifier).build());
92114
}
93115

@@ -106,21 +128,38 @@ private static Object applyQualifiers(
106128
return obj;
107129
}
108130

109-
NamespacedAttribute(
131+
static NamespacedAttribute create(
110132
CelTypeProvider typeProvider,
111133
CelValueConverter celValueConverter,
112134
ImmutableSet<String> namespacedNames) {
113-
this(typeProvider, celValueConverter, namespacedNames, ImmutableList.of());
135+
ImmutableSet.Builder<String> namesBuilder = ImmutableSet.builder();
136+
boolean disambiguateNames = false;
137+
for (String name : namespacedNames) {
138+
if (name.startsWith(".")) {
139+
disambiguateNames = true;
140+
namesBuilder.add(name.substring(1));
141+
} else {
142+
namesBuilder.add(name);
143+
}
144+
}
145+
return new NamespacedAttribute(
146+
typeProvider,
147+
celValueConverter,
148+
namesBuilder.build(),
149+
disambiguateNames,
150+
ImmutableList.of());
114151
}
115152

116-
private NamespacedAttribute(
153+
NamespacedAttribute(
117154
CelTypeProvider typeProvider,
118155
CelValueConverter celValueConverter,
119156
ImmutableSet<String> namespacedNames,
157+
boolean disambiguateNames,
120158
ImmutableList<Qualifier> qualifiers) {
121159
this.typeProvider = typeProvider;
122160
this.celValueConverter = celValueConverter;
123161
this.namespacedNames = namespacedNames;
162+
this.disambiguateNames = disambiguateNames;
124163
this.qualifiers = qualifiers;
125164
}
126165
}

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)