From 14103e05ff85d75971ffbf0bc28bd0e3cc821faa Mon Sep 17 00:00:00 2001 From: Sean Huh Date: Wed, 19 Aug 2026 13:11:36 -0700 Subject: [PATCH] Add EvalOptionalIndex PiperOrigin-RevId: 967370343 --- .../extensions/CelOptionalLibraryTest.java | 84 +++++++++++++++-- .../java/dev/cel/runtime/CelAttribute.java | 2 + .../java/dev/cel/runtime/planner/BUILD.bazel | 72 +++++++++++++- .../dev/cel/runtime/planner/EvalBinary.java | 2 + .../dev/cel/runtime/planner/EvalIndex.java | 81 ++++++++++++++++ .../runtime/planner/EvalOptionalIndex.java | 82 ++++++++++++++++ .../cel/runtime/planner/ProgramPlanner.java | 20 +++- .../dev/cel/runtime/CelAttributeTest.java | 17 ++++ .../runtime/planner/ProgramPlannerTest.java | 93 +++++++++++++++++++ 9 files changed, 441 insertions(+), 12 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalIndex.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalIndex.java diff --git a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java index 650c01526..ac93fba2c 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java @@ -280,7 +280,7 @@ public void optionalOfNonZeroValue_withZeroValue_returnsEmptyOptionalValue( Object result = cel.createProgram(ast).eval(); assertThat(result).isInstanceOf(Optional.class); - assertThat(result).isEqualTo(Optional.empty()); + assertThat((Optional) result).isEmpty(); } @Test @@ -306,7 +306,7 @@ public void optionalOfNonZeroValue_withNullValue_returnsEmptyOptionalValue() thr Object result = cel.createProgram(ast).eval(); assertThat(result).isInstanceOf(Optional.class); - assertThat(result).isEqualTo(Optional.empty()); + assertThat((Optional) result).isEmpty(); } @Test @@ -317,7 +317,7 @@ public void optionalOfNonZeroValue_withEmptyMessage_returnsEmptyOptionalValue() Object result = cel.createProgram(ast).eval(); assertThat(result).isInstanceOf(Optional.class); - assertThat(result).isEqualTo(Optional.empty()); + assertThat((Optional) result).isEmpty(); } @Test @@ -328,7 +328,7 @@ public void optionalNone_success() throws Exception { Object result = cel.createProgram(ast).eval(); assertThat(result).isInstanceOf(Optional.class); - assertThat(result).isEqualTo(Optional.empty()); + assertThat((Optional) result).isEmpty(); } @Test @@ -592,7 +592,7 @@ public void optionalFieldSelection_onMap_returnsOptionalEmpty() throws Exception Object result = cel.createProgram(ast).eval(); - assertThat(result).isEqualTo(Optional.empty()); + assertThat((Optional) result).isEmpty(); } @Test @@ -882,7 +882,7 @@ public void optionalIndex_onOptionalMap_returnsOptionalEmpty() throws Exception cel.createProgram(ast) .eval(ImmutableMap.of("optm", Optional.of(ImmutableMap.of("c", ImmutableMap.of())))); - assertThat(result).isEqualTo(Optional.empty()); + assertThat((Optional) result).isEmpty(); } @Test @@ -900,7 +900,7 @@ public void optionalIndex_onMap_returnsOptionalEmpty() throws Exception { Object result = cel.createProgram(ast).eval(ImmutableMap.of("m", ImmutableMap.of("c", ImmutableMap.of()))); - assertThat(result).isEqualTo(Optional.empty()); + assertThat((Optional) result).isEmpty(); } @Test @@ -982,7 +982,71 @@ public void optionalIndex_onList_returnsOptionalValue() throws Exception { Object result = cel.createProgram(ast).eval(ImmutableMap.of("l", ImmutableList.of("hello"))); - assertThat(result).isEqualTo(Optional.of("hello")); + assertThat((Optional) result).hasValue("hello"); + } + + @Test + public void optionalIndex_onList_negativeIndex_returnsOptionalEmpty() throws Exception { + Cel cel = + newCelBuilder() + .addVar("l", ListType.create(SimpleType.STRING)) + .setResultType(OptionalType.create(SimpleType.STRING)) + .build(); + CelAbstractSyntaxTree ast = compile(cel, "l[?-1]"); + + Object result = cel.createProgram(ast).eval(ImmutableMap.of("l", ImmutableList.of("hello"))); + + assertThat((Optional) result).isEmpty(); + } + + @Test + public void optionalIndex_onList_outOfBoundsIndex_returnsOptionalEmpty() throws Exception { + Cel cel = + newCelBuilder() + .addVar("l", ListType.create(SimpleType.STRING)) + .setResultType(OptionalType.create(SimpleType.STRING)) + .build(); + CelAbstractSyntaxTree ast = compile(cel, "l[?5]"); + + Object result = cel.createProgram(ast).eval(ImmutableMap.of("l", ImmutableList.of("hello"))); + + assertThat((Optional) result).isEmpty(); + } + + @Test + public void optionalIndex_targetIsUnknown_returnsUnknown() throws Exception { + Cel cel = + newCelBuilder() + .addVar("l", ListType.create(SimpleType.STRING)) + .setResultType(OptionalType.create(SimpleType.STRING)) + .build(); + CelAbstractSyntaxTree ast = compile(cel, "l[?0]"); + + Object result = + cel.createProgram(ast) + .eval(PartialVars.of(CelAttributePattern.fromQualifiedIdentifier("l"))); + + assertThat(result).isInstanceOf(CelUnknownSet.class); + } + + @Test + public void optionalIndex_indexIsUnknown_returnsUnknown() throws Exception { + Cel cel = + newCelBuilder() + .addVar("l", ListType.create(SimpleType.STRING)) + .addVar("i", SimpleType.INT) + .setResultType(OptionalType.create(SimpleType.STRING)) + .build(); + CelAbstractSyntaxTree ast = compile(cel, "l[?i]"); + + Object result = + cel.createProgram(ast) + .eval( + PartialVars.of( + ImmutableMap.of("l", ImmutableList.of("hello")), + CelAttributePattern.fromQualifiedIdentifier("i"))); + + assertThat(result).isInstanceOf(CelUnknownSet.class); } @Test @@ -1013,7 +1077,7 @@ public void optionalIndex_onOptionalList_returnsOptionalValue() throws Exception cel.createProgram(ast) .eval(ImmutableMap.of("optl", Optional.of(ImmutableList.of("hello")))); - assertThat(result).isEqualTo(Optional.of("hello")); + assertThat((Optional) result).hasValue("hello"); } @Test @@ -1043,7 +1107,7 @@ public void traditionalIndex_onOptionalList_returnsOptionalEmpty() throws Except Object result = cel.createProgram(ast).eval(ImmutableMap.of("optl", Optional.empty())); - assertThat(result).isEqualTo(Optional.empty()); + assertThat((Optional) result).isEmpty(); } @Test diff --git a/runtime/src/main/java/dev/cel/runtime/CelAttribute.java b/runtime/src/main/java/dev/cel/runtime/CelAttribute.java index f04418e0c..8db377abc 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/CelAttribute.java @@ -104,6 +104,8 @@ public static Qualifier fromGeneric(Object value) { return ofUint((UnsignedLong) value); } else if (value instanceof Long) { return ofInt((Long) value); + } else if (value instanceof Integer) { + return ofInt(((Integer) value).longValue()); } else if (value instanceof Boolean) { return ofBool((boolean) value); } else if (value instanceof String) { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index e05fca9b4..f967cc6ec 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -29,7 +29,9 @@ java_library( ":eval_exhaustive_conditional", ":eval_exhaustive_or", ":eval_fold", + ":eval_index", ":eval_late_bound_call", + ":eval_optional_index", ":eval_optional_or", ":eval_optional_or_value", ":eval_optional_select_field", @@ -227,6 +229,39 @@ java_library( "//runtime:evaluation_exception", "//runtime:interpretable", "//runtime:resolved_overload", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "eval_index", + srcs = ["EvalIndex.java"], + deps = [ + ":eval_helpers", + ":planned_interpretable", + "//common/ast", + "//common/values", + "//runtime:accumulated_unknowns", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "//runtime:resolved_overload", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "eval_optional_index", + srcs = ["EvalOptionalIndex.java"], + deps = [ + ":eval_helpers", + ":planned_interpretable", + "//common/ast", + "//common/values", + "//runtime:accumulated_unknowns", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "//runtime:resolved_overload", + "@maven//:com_google_errorprone_error_prone_annotations", ], ) @@ -544,7 +579,9 @@ cel_android_library( ":eval_exhaustive_conditional_android", ":eval_exhaustive_or_android", ":eval_fold_android", + ":eval_index_android", ":eval_late_bound_call_android", + ":eval_optional_index_android", ":eval_optional_or_android", ":eval_optional_or_value_android", ":eval_optional_select_field_android", @@ -735,10 +772,43 @@ cel_android_library( ":planned_interpretable_android", "//common/ast:ast_android", "//common/values:values_android", + "//runtime:accumulated_unknowns_android", + "//runtime:evaluation_exception", + "//runtime:interpretable_android", + "//runtime:resolved_overload_android", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +cel_android_library( + name = "eval_index_android", + srcs = ["EvalIndex.java"], + deps = [ + ":eval_helpers_android", + ":planned_interpretable_android", + "//common/ast:ast_android", + "//common/values:values_android", + "//runtime:accumulated_unknowns_android", "//runtime:evaluation_exception", "//runtime:interpretable_android", "//runtime:resolved_overload_android", - "//runtime/src/main/java/dev/cel/runtime:accumulated_unknowns_android", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +cel_android_library( + name = "eval_optional_index_android", + srcs = ["EvalOptionalIndex.java"], + deps = [ + ":eval_helpers_android", + ":planned_interpretable_android", + "//common/ast:ast_android", + "//common/values:values_android", + "//runtime:accumulated_unknowns_android", + "//runtime:evaluation_exception", + "//runtime:interpretable_android", + "//runtime:resolved_overload_android", + "@maven//:com_google_errorprone_error_prone_annotations", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalBinary.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalBinary.java index 1713195ab..dbc805ca8 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalBinary.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalBinary.java @@ -17,6 +17,7 @@ import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; import static dev.cel.runtime.planner.EvalHelpers.evalStrictly; +import com.google.errorprone.annotations.Immutable; import dev.cel.common.ast.CelExpr; import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.AccumulatedUnknowns; @@ -24,6 +25,7 @@ import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.GlobalResolver; +@Immutable final class EvalBinary extends PlannedInterpretable { private final String functionName; diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalIndex.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalIndex.java new file mode 100644 index 000000000..027fd8339 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalIndex.java @@ -0,0 +1,81 @@ +// 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.runtime.planner; + +import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; +import static dev.cel.runtime.planner.EvalHelpers.evalStrictly; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.ast.CelExpr; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.AccumulatedUnknowns; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelResolvedOverload; +import dev.cel.runtime.GlobalResolver; + +@Immutable +final class EvalIndex extends PlannedInterpretable { + + private final String functionName; + private final CelResolvedOverload resolvedOverload; + private final PlannedInterpretable target; + private final PlannedInterpretable index; + private final CelValueConverter celValueConverter; + + @Override + Object evalInternal(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { + boolean isStrict = resolvedOverload.isStrict(); + Object targetVal = + isStrict ? evalStrictly(target, resolver, frame) : evalNonstrictly(target, resolver, frame); + Object indexVal = + isStrict ? evalStrictly(index, resolver, frame) : evalNonstrictly(index, resolver, frame); + + if (isStrict) { + AccumulatedUnknowns unknowns = AccumulatedUnknowns.maybeMerge(null, targetVal); + unknowns = AccumulatedUnknowns.maybeMerge(unknowns, indexVal); + if (unknowns != null) { + return unknowns; + } + } + + return EvalHelpers.dispatch( + functionName, resolvedOverload, celValueConverter, targetVal, indexVal); + } + + static EvalIndex create( + CelExpr expr, + String functionName, + CelResolvedOverload resolvedOverload, + PlannedInterpretable target, + PlannedInterpretable index, + CelValueConverter celValueConverter) { + return new EvalIndex(expr, functionName, resolvedOverload, target, index, celValueConverter); + } + + private EvalIndex( + CelExpr expr, + String functionName, + CelResolvedOverload resolvedOverload, + PlannedInterpretable target, + PlannedInterpretable index, + CelValueConverter celValueConverter) { + super(expr); + this.functionName = functionName; + this.resolvedOverload = resolvedOverload; + this.target = target; + this.index = index; + this.celValueConverter = celValueConverter; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalIndex.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalIndex.java new file mode 100644 index 000000000..11eb438ae --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalIndex.java @@ -0,0 +1,82 @@ +// 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.runtime.planner; + +import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; +import static dev.cel.runtime.planner.EvalHelpers.evalStrictly; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.ast.CelExpr; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.AccumulatedUnknowns; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelResolvedOverload; +import dev.cel.runtime.GlobalResolver; + +@Immutable +final class EvalOptionalIndex extends PlannedInterpretable { + + private final String functionName; + private final CelResolvedOverload resolvedOverload; + private final PlannedInterpretable target; + private final PlannedInterpretable index; + private final CelValueConverter celValueConverter; + + @Override + Object evalInternal(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { + boolean isStrict = resolvedOverload.isStrict(); + Object targetVal = + isStrict ? evalStrictly(target, resolver, frame) : evalNonstrictly(target, resolver, frame); + Object indexVal = + isStrict ? evalStrictly(index, resolver, frame) : evalNonstrictly(index, resolver, frame); + + if (isStrict) { + AccumulatedUnknowns unknowns = AccumulatedUnknowns.maybeMerge(null, targetVal); + unknowns = AccumulatedUnknowns.maybeMerge(unknowns, indexVal); + if (unknowns != null) { + return unknowns; + } + } + + return EvalHelpers.dispatch( + functionName, resolvedOverload, celValueConverter, targetVal, indexVal); + } + + static EvalOptionalIndex create( + CelExpr expr, + String functionName, + CelResolvedOverload resolvedOverload, + PlannedInterpretable target, + PlannedInterpretable index, + CelValueConverter celValueConverter) { + return new EvalOptionalIndex( + expr, functionName, resolvedOverload, target, index, celValueConverter); + } + + private EvalOptionalIndex( + CelExpr expr, + String functionName, + CelResolvedOverload resolvedOverload, + PlannedInterpretable target, + PlannedInterpretable index, + CelValueConverter celValueConverter) { + super(expr); + this.functionName = functionName; + this.resolvedOverload = resolvedOverload; + this.target = target; + this.index = index; + this.celValueConverter = celValueConverter; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index 77f605efc..c56e6e537 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -327,6 +327,24 @@ private PlannedInterpretable planCall(CelExpr expr, PlannerContext ctx) { return EvalUnary.create( expr, functionName, resolvedOverload, evaluatedArgs[0], celValueConverter); case 2: + if (functionName.equals(Operator.INDEX.getFunction())) { + return EvalIndex.create( + expr, + functionName, + resolvedOverload, + evaluatedArgs[0], + evaluatedArgs[1], + celValueConverter); + } + if (functionName.equals(Operator.OPTIONAL_INDEX.getFunction())) { + return EvalOptionalIndex.create( + expr, + functionName, + resolvedOverload, + evaluatedArgs[0], + evaluatedArgs[1], + celValueConverter); + } return EvalBinary.create( expr, functionName, @@ -385,7 +403,7 @@ private Optional maybeInterceptOptionalCalls( break; } - if (Operator.OPTIONAL_SELECT.getFunction().equals(functionName)) { + if (functionName.equals(Operator.OPTIONAL_SELECT.getFunction())) { String field = expr.call().args().get(1).constant().stringValue(); InterpretableAttribute attribute; if (evaluatedArgs[0] instanceof EvalAttribute) { diff --git a/runtime/src/test/java/dev/cel/runtime/CelAttributeTest.java b/runtime/src/test/java/dev/cel/runtime/CelAttributeTest.java index fc6cb3442..dce3e376c 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelAttributeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelAttributeTest.java @@ -143,16 +143,33 @@ public void fromQualifiedIdentifier_parseIdents() { @Test public void fromGeneric_supportedTypes() { + assertThat(Qualifier.fromGeneric(1)).isEqualTo(Qualifier.ofInt(1)); assertThat(Qualifier.fromGeneric(Long.valueOf(1))).isEqualTo(Qualifier.ofInt(1)); assertThat(Qualifier.fromGeneric(UnsignedLong.valueOf(1))).isEqualTo(Qualifier.ofUint(1)); assertThat(Qualifier.fromGeneric("abcd")).isEqualTo(Qualifier.ofString("abcd")); assertThat(Qualifier.fromGeneric(Boolean.valueOf(false))).isEqualTo(Qualifier.ofBool(false)); } + @Test + public void fromGeneric_integerBoundaryValues() { + assertThat(Qualifier.fromGeneric(Integer.MAX_VALUE)) + .isEqualTo(Qualifier.ofInt(Integer.MAX_VALUE)); + assertThat(Qualifier.fromGeneric(Integer.MIN_VALUE)) + .isEqualTo(Qualifier.ofInt(Integer.MIN_VALUE)); + assertThat(Qualifier.fromGeneric(0)).isEqualTo(Qualifier.ofInt(0)); + } + + @Test + public void fromGeneric_nullThrows() { + assertThrows(IllegalArgumentException.class, () -> Qualifier.fromGeneric(null)); + } + @Test public void fromGeneric_unsupportedTypeThrows() { assertThrows( IllegalArgumentException.class, () -> Qualifier.fromGeneric(new ArrayList())); + assertThrows(IllegalArgumentException.class, () -> Qualifier.fromGeneric(1.0)); + assertThrows(IllegalArgumentException.class, () -> Qualifier.fromGeneric(new byte[] {1, 2})); } @Test diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 34e7831a6..a3b1e3596 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -573,6 +573,99 @@ public void plan_call_mapIndex() throws Exception { assertThat(result).isEqualTo(2L); } + @Test + public void plan_call_listIndex() throws Exception { + CelAbstractSyntaxTree ast = compile("[10, 20, 30][1]"); + Program program = PLANNER.plan(ast); + + Long result = (Long) program.eval(); + + assertThat(result).isEqualTo(20L); + } + + @Test + public void plan_call_listIndex_outOfBounds_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("[10, 20, 30][5]"); + Program program = PLANNER.plan(ast); + + assertThrows(CelEvaluationException.class, program::eval); + } + + @Test + public void plan_call_listIndex_negative_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("[10, 20, 30][-1]"); + Program program = PLANNER.plan(ast); + + assertThrows(CelEvaluationException.class, program::eval); + } + + @Test + public void plan_call_mapIndex_missingKey_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var['missing']"); + Program program = PLANNER.plan(ast); + + assertThrows( + CelEvaluationException.class, + () -> program.eval(ImmutableMap.of("map_var", ImmutableMap.of("key", 1L)))); + } + + @Test + public void plan_call_index_withUnknownTarget() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addVar("unk_list", ListType.create(SimpleType.INT)) + .build(); + CelAbstractSyntaxTree ast = compile(compiler, "unk_list[0]"); + Program program = PLANNER.plan(ast); + + CelUnknownSet result = + (CelUnknownSet) program.eval(PartialVars.of(CelAttributePattern.create("unk_list"))); + + assertThat(result) + .isEqualTo( + CelUnknownSet.create( + ImmutableSet.of(CelAttribute.create("unk_list")), ImmutableSet.of(1L))); + } + + @Test + public void plan_call_index_withUnknownIndex() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder().addVar("unk_index", SimpleType.INT).build(); + CelAbstractSyntaxTree ast = compile(compiler, "[10, 20, 30][unk_index]"); + Program program = PLANNER.plan(ast); + + CelUnknownSet result = + (CelUnknownSet) program.eval(PartialVars.of(CelAttributePattern.create("unk_index"))); + + assertThat(result) + .isEqualTo( + CelUnknownSet.create( + ImmutableSet.of(CelAttribute.create("unk_index")), ImmutableSet.of(6L))); + } + + @Test + public void plan_call_index_withMultipleUnknowns_mergesUnknowns() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addVar("unk_map", MapType.create(SimpleType.STRING, SimpleType.INT)) + .addVar("unk_key", SimpleType.STRING) + .build(); + CelAbstractSyntaxTree ast = compile(compiler, "unk_map[unk_key]"); + Program program = PLANNER.plan(ast); + + CelUnknownSet result = + (CelUnknownSet) + program.eval( + PartialVars.of( + CelAttributePattern.create("unk_map"), CelAttributePattern.create("unk_key"))); + + assertThat(result) + .isEqualTo( + CelUnknownSet.create( + ImmutableSet.of(CelAttribute.create("unk_map"), CelAttribute.create("unk_key")), + ImmutableSet.of(1L, 3L))); + } + @Test public void plan_call_noMatchingOverload_throws() throws Exception { CelAbstractSyntaxTree ast = compile("concat(b'abc', dyn_var)");