Skip to content

Commit 8d22c9d

Browse files
l46kokcopybara-github
authored andcommitted
Add EvalIndex to planner
PiperOrigin-RevId: 967304194
1 parent 426fa24 commit 8d22c9d

6 files changed

Lines changed: 237 additions & 1 deletion

File tree

‎runtime/src/main/java/dev/cel/runtime/CelAttribute.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ public static Qualifier fromGeneric(Object value) {
104104
return ofUint((UnsignedLong) value);
105105
} else if (value instanceof Long) {
106106
return ofInt((Long) value);
107+
} else if (value instanceof Integer) {
108+
return ofInt(((Integer) value).longValue());
107109
} else if (value instanceof Boolean) {
108110
return ofBool((boolean) value);
109111
} else if (value instanceof String) {

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ java_library(
2929
":eval_exhaustive_conditional",
3030
":eval_exhaustive_or",
3131
":eval_fold",
32+
":eval_index",
3233
":eval_late_bound_call",
3334
":eval_optional_or",
3435
":eval_optional_or_value",
@@ -230,6 +231,21 @@ java_library(
230231
],
231232
)
232233

234+
java_library(
235+
name = "eval_index",
236+
srcs = ["EvalIndex.java"],
237+
deps = [
238+
":eval_helpers",
239+
":planned_interpretable",
240+
"//common/ast",
241+
"//common/values",
242+
"//runtime:accumulated_unknowns",
243+
"//runtime:evaluation_exception",
244+
"//runtime:interpretable",
245+
"//runtime:resolved_overload",
246+
],
247+
)
248+
233249
java_library(
234250
name = "eval_block",
235251
srcs = ["EvalBlock.java"],
@@ -544,6 +560,7 @@ cel_android_library(
544560
":eval_exhaustive_conditional_android",
545561
":eval_exhaustive_or_android",
546562
":eval_fold_android",
563+
":eval_index_android",
547564
":eval_late_bound_call_android",
548565
":eval_optional_or_android",
549566
":eval_optional_or_value_android",
@@ -735,10 +752,25 @@ cel_android_library(
735752
":planned_interpretable_android",
736753
"//common/ast:ast_android",
737754
"//common/values:values_android",
755+
"//runtime:accumulated_unknowns_android",
756+
"//runtime:evaluation_exception",
757+
"//runtime:interpretable_android",
758+
"//runtime:resolved_overload_android",
759+
],
760+
)
761+
762+
cel_android_library(
763+
name = "eval_index_android",
764+
srcs = ["EvalIndex.java"],
765+
deps = [
766+
":eval_helpers_android",
767+
":planned_interpretable_android",
768+
"//common/ast:ast_android",
769+
"//common/values:values_android",
770+
"//runtime:accumulated_unknowns_android",
738771
"//runtime:evaluation_exception",
739772
"//runtime:interpretable_android",
740773
"//runtime:resolved_overload_android",
741-
"//runtime/src/main/java/dev/cel/runtime:accumulated_unknowns_android",
742774
],
743775
)
744776

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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 static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly;
18+
import static dev.cel.runtime.planner.EvalHelpers.evalStrictly;
19+
20+
import dev.cel.common.ast.CelExpr;
21+
import dev.cel.common.values.CelValueConverter;
22+
import dev.cel.runtime.AccumulatedUnknowns;
23+
import dev.cel.runtime.CelEvaluationException;
24+
import dev.cel.runtime.CelResolvedOverload;
25+
import dev.cel.runtime.GlobalResolver;
26+
27+
final class EvalIndex extends PlannedInterpretable {
28+
29+
private final String functionName;
30+
private final CelResolvedOverload resolvedOverload;
31+
private final PlannedInterpretable target;
32+
private final PlannedInterpretable index;
33+
private final CelValueConverter celValueConverter;
34+
35+
@Override
36+
Object evalInternal(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException {
37+
boolean isStrict = resolvedOverload.isStrict();
38+
Object targetVal =
39+
isStrict ? evalStrictly(target, resolver, frame) : evalNonstrictly(target, resolver, frame);
40+
Object indexVal =
41+
isStrict ? evalStrictly(index, resolver, frame) : evalNonstrictly(index, resolver, frame);
42+
43+
if (isStrict) {
44+
AccumulatedUnknowns unknowns = AccumulatedUnknowns.maybeMerge(null, targetVal);
45+
unknowns = AccumulatedUnknowns.maybeMerge(unknowns, indexVal);
46+
if (unknowns != null) {
47+
return unknowns;
48+
}
49+
}
50+
51+
return EvalHelpers.dispatch(
52+
functionName, resolvedOverload, celValueConverter, targetVal, indexVal);
53+
}
54+
55+
static EvalIndex create(
56+
CelExpr expr,
57+
String functionName,
58+
CelResolvedOverload resolvedOverload,
59+
PlannedInterpretable target,
60+
PlannedInterpretable index,
61+
CelValueConverter celValueConverter) {
62+
return new EvalIndex(expr, functionName, resolvedOverload, target, index, celValueConverter);
63+
}
64+
65+
private EvalIndex(
66+
CelExpr expr,
67+
String functionName,
68+
CelResolvedOverload resolvedOverload,
69+
PlannedInterpretable target,
70+
PlannedInterpretable index,
71+
CelValueConverter celValueConverter) {
72+
super(expr);
73+
this.functionName = functionName;
74+
this.resolvedOverload = resolvedOverload;
75+
this.target = target;
76+
this.index = index;
77+
this.celValueConverter = celValueConverter;
78+
}
79+
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,15 @@ private PlannedInterpretable planCall(CelExpr expr, PlannerContext ctx) {
327327
return EvalUnary.create(
328328
expr, functionName, resolvedOverload, evaluatedArgs[0], celValueConverter);
329329
case 2:
330+
if (Operator.INDEX.getFunction().equals(functionName)) {
331+
return EvalIndex.create(
332+
expr,
333+
functionName,
334+
resolvedOverload,
335+
evaluatedArgs[0],
336+
evaluatedArgs[1],
337+
celValueConverter);
338+
}
330339
return EvalBinary.create(
331340
expr,
332341
functionName,

‎runtime/src/test/java/dev/cel/runtime/CelAttributeTest.java‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,33 @@ public void fromQualifiedIdentifier_parseIdents() {
143143

144144
@Test
145145
public void fromGeneric_supportedTypes() {
146+
assertThat(Qualifier.fromGeneric(1)).isEqualTo(Qualifier.ofInt(1));
146147
assertThat(Qualifier.fromGeneric(Long.valueOf(1))).isEqualTo(Qualifier.ofInt(1));
147148
assertThat(Qualifier.fromGeneric(UnsignedLong.valueOf(1))).isEqualTo(Qualifier.ofUint(1));
148149
assertThat(Qualifier.fromGeneric("abcd")).isEqualTo(Qualifier.ofString("abcd"));
149150
assertThat(Qualifier.fromGeneric(Boolean.valueOf(false))).isEqualTo(Qualifier.ofBool(false));
150151
}
151152

153+
@Test
154+
public void fromGeneric_integerBoundaryValues() {
155+
assertThat(Qualifier.fromGeneric(Integer.MAX_VALUE))
156+
.isEqualTo(Qualifier.ofInt(Integer.MAX_VALUE));
157+
assertThat(Qualifier.fromGeneric(Integer.MIN_VALUE))
158+
.isEqualTo(Qualifier.ofInt(Integer.MIN_VALUE));
159+
assertThat(Qualifier.fromGeneric(0)).isEqualTo(Qualifier.ofInt(0));
160+
}
161+
162+
@Test
163+
public void fromGeneric_nullThrows() {
164+
assertThrows(IllegalArgumentException.class, () -> Qualifier.fromGeneric(null));
165+
}
166+
152167
@Test
153168
public void fromGeneric_unsupportedTypeThrows() {
154169
assertThrows(
155170
IllegalArgumentException.class, () -> Qualifier.fromGeneric(new ArrayList<String>()));
171+
assertThrows(IllegalArgumentException.class, () -> Qualifier.fromGeneric(1.0));
172+
assertThrows(IllegalArgumentException.class, () -> Qualifier.fromGeneric(new byte[] {1, 2}));
156173
}
157174

158175
@Test

‎runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java‎

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,103 @@ public void plan_call_mapIndex() throws Exception {
573573
assertThat(result).isEqualTo(2L);
574574
}
575575

576+
@Test
577+
public void plan_call_listIndex() throws Exception {
578+
CelAbstractSyntaxTree ast = compile("[10, 20, 30][1]");
579+
Program program = PLANNER.plan(ast);
580+
581+
Long result = (Long) program.eval();
582+
583+
assertThat(result).isEqualTo(20L);
584+
}
585+
586+
@Test
587+
public void plan_call_listIndex_outOfBounds_throws() throws Exception {
588+
CelAbstractSyntaxTree ast = compile("[10, 20, 30][5]");
589+
Program program = PLANNER.plan(ast);
590+
591+
assertThrows(CelEvaluationException.class, program::eval);
592+
}
593+
594+
@Test
595+
public void plan_call_listIndex_negative_throws() throws Exception {
596+
CelAbstractSyntaxTree ast = compile("[10, 20, 30][-1]");
597+
Program program = PLANNER.plan(ast);
598+
599+
assertThrows(CelEvaluationException.class, program::eval);
600+
}
601+
602+
@Test
603+
public void plan_call_mapIndex_missingKey_throws() throws Exception {
604+
CelAbstractSyntaxTree ast = compile("map_var['missing']");
605+
Program program = PLANNER.plan(ast);
606+
607+
assertThrows(
608+
CelEvaluationException.class,
609+
() -> program.eval(ImmutableMap.of("map_var", ImmutableMap.of("key", 1L))));
610+
}
611+
612+
@Test
613+
public void plan_call_index_withUnknownTarget() throws Exception {
614+
CelCompiler compiler =
615+
CelCompilerFactory.standardCelCompilerBuilder()
616+
.addVar("unk_list", ListType.create(SimpleType.INT))
617+
.build();
618+
CelAbstractSyntaxTree ast = compile(compiler, "unk_list[0]");
619+
Program program = PLANNER.plan(ast);
620+
621+
CelUnknownSet result =
622+
(CelUnknownSet) program.eval(PartialVars.of(CelAttributePattern.create("unk_list")));
623+
624+
assertThat(result)
625+
.isEqualTo(
626+
CelUnknownSet.create(
627+
ImmutableSet.of(CelAttribute.create("unk_list")), ImmutableSet.of(1L)));
628+
}
629+
630+
@Test
631+
public void plan_call_index_withUnknownIndex() throws Exception {
632+
CelCompiler compiler =
633+
CelCompilerFactory.standardCelCompilerBuilder()
634+
.addVar("unk_index", SimpleType.INT)
635+
.build();
636+
CelAbstractSyntaxTree ast = compile(compiler, "[10, 20, 30][unk_index]");
637+
Program program = PLANNER.plan(ast);
638+
639+
CelUnknownSet result =
640+
(CelUnknownSet) program.eval(PartialVars.of(CelAttributePattern.create("unk_index")));
641+
642+
assertThat(result)
643+
.isEqualTo(
644+
CelUnknownSet.create(
645+
ImmutableSet.of(CelAttribute.create("unk_index")), ImmutableSet.of(5L)));
646+
}
647+
648+
@Test
649+
public void plan_call_index_withMultipleUnknowns_mergesUnknowns() throws Exception {
650+
CelCompiler compiler =
651+
CelCompilerFactory.standardCelCompilerBuilder()
652+
.addVar("unk_map", MapType.create(SimpleType.STRING, SimpleType.INT))
653+
.addVar("unk_key", SimpleType.STRING)
654+
.build();
655+
CelAbstractSyntaxTree ast = compile(compiler, "unk_map[unk_key]");
656+
Program program = PLANNER.plan(ast);
657+
658+
CelUnknownSet result =
659+
(CelUnknownSet)
660+
program.eval(
661+
PartialVars.of(
662+
CelAttributePattern.create("unk_map"),
663+
CelAttributePattern.create("unk_key")));
664+
665+
assertThat(result)
666+
.isEqualTo(
667+
CelUnknownSet.create(
668+
ImmutableSet.of(
669+
CelAttribute.create("unk_map"), CelAttribute.create("unk_key")),
670+
ImmutableSet.of(1L, 2L)));
671+
}
672+
576673
@Test
577674
public void plan_call_noMatchingOverload_throws() throws Exception {
578675
CelAbstractSyntaxTree ast = compile("concat(b'abc', dyn_var)");

0 commit comments

Comments
 (0)