diff --git a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java index fab444750..650c01526 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java @@ -54,7 +54,7 @@ import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelRuntime; -import dev.cel.runtime.InterpreterUtil; +import dev.cel.runtime.CelUnknownSet; import dev.cel.runtime.PartialVars; import java.time.Duration; import java.time.Instant; @@ -937,7 +937,7 @@ public void optionalIndex_onMapWithUnknownInput_returnsUnknownResult(String sour cel.createProgram(ast) .eval(PartialVars.of(CelAttributePattern.fromQualifiedIdentifier("x"))); - assertThat(InterpreterUtil.isUnknown(result)).isTrue(); + assertThat(result).isInstanceOf(CelUnknownSet.class); } @Test @@ -1029,7 +1029,7 @@ public void optionalIndex_onListWithUnknownInput_returnsUnknownResult() throws E cel.createProgram(ast) .eval(PartialVars.of(CelAttributePattern.fromQualifiedIdentifier("x"))); - assertThat(InterpreterUtil.isUnknown(result)).isTrue(); + assertThat(result).isInstanceOf(CelUnknownSet.class); } @Test @@ -1066,7 +1066,7 @@ public void optionalFieldSelect_fieldMarkedUnknown_returnsUnknownSet() throws Ex ImmutableMap.of("msg", TestAllTypes.newBuilder().setSingleInt32(42).build()), CelAttributePattern.fromQualifiedIdentifier("msg.single_int32"))); - assertThat(InterpreterUtil.isUnknown(result)).isTrue(); + assertThat(result).isInstanceOf(CelUnknownSet.class); } @Test @@ -1089,7 +1089,7 @@ public void optionalChainedFunctions_lhsIsUnknown_returnsUnknown(String expressi cel.createProgram(ast) .eval(PartialVars.of(CelAttributePattern.fromQualifiedIdentifier("optx"))); - assertThat(InterpreterUtil.isUnknown(result)).isTrue(); + assertThat(result).isInstanceOf(CelUnknownSet.class); } @Test diff --git a/runtime/src/main/java/dev/cel/runtime/CallArgumentChecker.java b/runtime/src/main/java/dev/cel/runtime/CallArgumentChecker.java index 76a942927..7ce8fb006 100644 --- a/runtime/src/main/java/dev/cel/runtime/CallArgumentChecker.java +++ b/runtime/src/main/java/dev/cel/runtime/CallArgumentChecker.java @@ -73,7 +73,7 @@ void checkArg(DefaultInterpreter.IntermediateResult arg) { unknowns = mergeOptionalUnknowns(unknowns, argUnknowns); // support for ExprValue unknowns. - if (InterpreterUtil.isAccumulatedUnknowns(arg.value())) { + if (arg.value() instanceof AccumulatedUnknowns) { AccumulatedUnknowns unknownSet = (AccumulatedUnknowns) arg.value(); exprIds.addAll(unknownSet.exprIds()); } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index fdab71c3d..fa22b1d00 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -282,7 +282,7 @@ private IntermediateResult evalInternal(ExecutionFrame frame, CelExpr expr) } private static boolean isUnknownValue(Object value) { - return InterpreterUtil.isAccumulatedUnknowns(value); + return value instanceof AccumulatedUnknowns; } private static boolean isUnknownOrError(Object value) { diff --git a/runtime/src/main/java/dev/cel/runtime/InterpreterUtil.java b/runtime/src/main/java/dev/cel/runtime/InterpreterUtil.java index 73607cefd..8c817d055 100644 --- a/runtime/src/main/java/dev/cel/runtime/InterpreterUtil.java +++ b/runtime/src/main/java/dev/cel/runtime/InterpreterUtil.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CheckReturnValue; +import com.google.errorprone.annotations.InlineMe; import dev.cel.common.annotations.Internal; import org.jspecify.annotations.Nullable; @@ -51,15 +52,14 @@ public static Object strict(Object valueOrThrowable) throws CelEvaluationExcepti * * @param obj Object to check. * @return boolean value if object is unknown. + * @deprecated Perform {@code obj instanceof CelUnknownSet} directly instead. */ + @Deprecated + @InlineMe(replacement = "obj instanceof CelUnknownSet", imports = "dev.cel.runtime.CelUnknownSet") public static boolean isUnknown(Object obj) { return obj instanceof CelUnknownSet; } - public static boolean isAccumulatedUnknowns(Object obj) { - return obj instanceof AccumulatedUnknowns; - } - /** If the argument is {@link CelUnknownSet}, adapts it into {@link AccumulatedUnknowns} */ public static Object maybeAdaptToAccumulatedUnknowns(Object val) { if (!(val instanceof CelUnknownSet)) { @@ -102,7 +102,7 @@ public static Object enforceStrictness(Object left, Object right) throws CelEval public static Object valueOrUnknown(@Nullable Object valueOrThrowable, Long id) { // Handle the unknown value case. - if (isAccumulatedUnknowns(valueOrThrowable)) { + if (valueOrThrowable instanceof AccumulatedUnknowns) { return AccumulatedUnknowns.create(id); } // Handle the null value case. 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 bdac6c95a..e05fca9b4 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -342,9 +342,12 @@ java_library( "//common:error_codes", "//common/exceptions:runtime_exception", "//common/values", + "//runtime:accumulated_unknowns", "//runtime:evaluation_exception", "//runtime:interpretable", + "//runtime:interpreter_util", "//runtime:resolved_overload", + "//runtime:unknown_attributes", "@maven//:com_google_guava_guava", ], ) @@ -851,9 +854,12 @@ cel_android_library( "//common:error_codes", "//common/exceptions:runtime_exception", "//common/values:values_android", + "//runtime:accumulated_unknowns_android", "//runtime:evaluation_exception", "//runtime:interpretable_android", + "//runtime:interpreter_util_android", "//runtime:resolved_overload_android", + "//runtime:unknown_attributes_android", "@maven_android//:com_google_guava_guava", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java index f9812793e..1b8d61234 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -19,9 +19,12 @@ import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.ErrorValue; +import dev.cel.runtime.AccumulatedUnknowns; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelResolvedOverload; +import dev.cel.runtime.CelUnknownSet; import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.InterpreterUtil; final class EvalHelpers { @@ -63,7 +66,7 @@ static Object dispatch( throws CelEvaluationException { try { Object result = overload.invoke(args); - return valueConverter.maybeUnwrap(valueConverter.toRuntimeValue(result)); + return convertAndAdaptResult(valueConverter, result); } catch (RuntimeException e) { throw handleDispatchException(e, overload, args); } @@ -77,7 +80,7 @@ static Object dispatch( throws CelEvaluationException { try { Object result = overload.invoke(arg); - return valueConverter.maybeUnwrap(valueConverter.toRuntimeValue(result)); + return convertAndAdaptResult(valueConverter, result); } catch (RuntimeException e) { throw handleDispatchException(e, overload, arg); } @@ -92,12 +95,22 @@ static Object dispatch( throws CelEvaluationException { try { Object result = overload.invoke(arg1, arg2); - return valueConverter.maybeUnwrap(valueConverter.toRuntimeValue(result)); + return convertAndAdaptResult(valueConverter, result); } catch (RuntimeException e) { throw handleDispatchException(e, overload, arg1, arg2); } } + /** + * Converts the raw invocation result into a CEL runtime value, unwraps it if necessary, and + * adapts any public {@link CelUnknownSet} instances into internal {@link AccumulatedUnknowns} for + * AST evaluation. + */ + private static Object convertAndAdaptResult(CelValueConverter valueConverter, Object result) { + return InterpreterUtil.maybeAdaptToAccumulatedUnknowns( + valueConverter.maybeUnwrap(valueConverter.toRuntimeValue(result))); + } + private static RuntimeException handleDispatchException( RuntimeException e, CelResolvedOverload overload, Object... args) { if (e instanceof CelRuntimeException) { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java index 95a4489fd..01673923d 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java @@ -184,6 +184,9 @@ public NamespacedAttribute addQualifier(Qualifier qualifier) { private static Object applyQualifiers( Object value, CelValueConverter celValueConverter, ImmutableList qualifiers) { + if (value instanceof AccumulatedUnknowns) { + return value; + } Object obj = celValueConverter.toRuntimeValue(value); // Avoid enhanced for loop to prevent UnmodifiableIterator from being allocated diff --git a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java index 13d5dd550..d7247f8a1 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java @@ -522,7 +522,7 @@ public void trace_shortCircuitingDisabledWithUnknownsAndedToFalse_returnsFalse(S (expr, res) -> { if (expr.constantOrDefault().getKind().equals(CelConstant.Kind.BOOLEAN_VALUE) || expr.identOrDefault().name().equals("x")) { - if (InterpreterUtil.isUnknown(res)) { + if (res instanceof CelUnknownSet) { branchResults.add("x"); // Swap unknown result with a sentinel value for testing } else { branchResults.add(res); @@ -577,7 +577,7 @@ public void trace_shortCircuitingDisabledWithUnknownAndedToTrue_returnsUnknown(S PartialVars partialVars = PartialVars.of(CelAttributePattern.create("x")); Object unknownResult = cel.createProgram(ast).trace(partialVars, listener); - assertThat(InterpreterUtil.isUnknown(unknownResult)).isTrue(); + assertThat(unknownResult).isInstanceOf(CelUnknownSet.class); assertThat(branchResults.build()).containsExactly(true, true, unknownResult); } @@ -653,7 +653,7 @@ public void trace_shortCircuitingDisabledWithUnknownsOredToFalse_returnsUnknown( PartialVars partialVars = PartialVars.of(CelAttributePattern.create("x")); Object unknownResult = cel.createProgram(ast).trace(partialVars, listener); - assertThat(InterpreterUtil.isUnknown(unknownResult)).isTrue(); + assertThat(unknownResult).isInstanceOf(CelUnknownSet.class); assertThat(branchResults.build()).containsExactly(false, false, unknownResult); } @@ -668,7 +668,7 @@ public void trace_shortCircuitingDisabledWithUnknownOredToTrue_returnsTrue(Strin (expr, res) -> { if (expr.constantOrDefault().getKind().equals(CelConstant.Kind.BOOLEAN_VALUE) || expr.identOrDefault().name().equals("x")) { - if (InterpreterUtil.isUnknown(res)) { + if (res instanceof CelUnknownSet) { branchResults.add("x"); // Swap unknown result with a sentinel value for testing } else { branchResults.add(res); @@ -748,7 +748,7 @@ public void trace_shortCircuitingDisabled_ternaryWithUnknowns(String source) thr PartialVars partialVars = PartialVars.of(CelAttributePattern.create("x")); Object unknownResult = cel.createProgram(ast).trace(partialVars, listener); - assertThat(InterpreterUtil.isUnknown(unknownResult)).isTrue(); + assertThat(unknownResult).isInstanceOf(CelUnknownSet.class); assertThat(branchResults.build()).containsExactly(false, unknownResult, true); } @@ -944,4 +944,151 @@ public void trace_shortCircuitingDisabled_logicalOrPrefersFirstError() throws Ex CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> program.eval()); assertThat(e).hasCauseThat().hasMessageThat().contains("error 1"); } + + @Test + public void evaluate_customFunctionReturningCelUnknownSet_propagatesUnknown( + @TestParameter({ + // Field selection + "getMsg().single_int32", + "getMsg().single_nested_message.bb", + // Binary & unary operators + "getMsg().single_int32 == 100", + "getMsg().single_int32 + 5 == 10", + "-getMsg().single_int32 == -10", + // Boolean operators & ternary + "true && (getMsg().single_int32 == 100)", + "false || (getMsg().single_int32 == 100)", + "(getMsg().single_int32 == 100) ? 'match' : 'no-match'", + // Comprehensions + "[1, 2, 3].exists(x, x == getMsg().single_int32)", + "[1, 2, 3].all(x, x > 0 && getMsg().single_int32 > 0)", + "[1, 2, 3].map(x, x + getMsg().single_int32)", + "[1, 2, 3].filter(x, x == getMsg().single_int32)", + }) + String expression) + throws Exception { + Cel cel = + runtimeFlavor + .builder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addMessageTypes(TestAllTypes.getDescriptor()) + .addFunctionDeclarations( + CelFunctionDecl.newFunctionDeclaration( + "getMsg", + CelOverloadDecl.newGlobalOverload( + "getMsg_overload", + StructTypeReference.create(TestAllTypes.getDescriptor().getFullName()), + ImmutableList.of()))) + .addFunctionBindings( + CelFunctionBinding.from( + "getMsg_overload", + ImmutableList.of(), + args -> CelUnknownSet.create(CelAttribute.create("custom_msg")))) + .build(); + + Object result = cel.createProgram(cel.compile(expression).getAst()).eval(); + + assertThat(result).isInstanceOf(CelUnknownSet.class); + } + + @Test + // Short-circuited boolean operators + @TestParameters("{expression: 'false && (getMsg().single_int32 == 100)', expected: false}") + @TestParameters("{expression: 'true || (getMsg().single_int32 == 100)', expected: true}") + // Short-circuited comprehensions + @TestParameters( + "{expression: '[1, 2, 3].exists(x, x == 1 || x == getMsg().single_int32)', expected: true}") + @TestParameters( + "{expression: '[1, 2, 3].all(x, x == 0 && getMsg().single_int32 > 0)', expected: false}") + public void evaluate_customFunctionReturningCelUnknownSet_shortCircuits( + String expression, boolean expected) throws Exception { + Cel cel = + runtimeFlavor + .builder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addMessageTypes(TestAllTypes.getDescriptor()) + .addFunctionDeclarations( + CelFunctionDecl.newFunctionDeclaration( + "getMsg", + CelOverloadDecl.newGlobalOverload( + "getMsg_overload", + StructTypeReference.create(TestAllTypes.getDescriptor().getFullName()), + ImmutableList.of()))) + .addFunctionBindings( + CelFunctionBinding.from( + "getMsg_overload", + ImmutableList.of(), + args -> CelUnknownSet.create(CelAttribute.create("custom_msg")))) + .build(); + + Object result = cel.createProgram(cel.compile(expression).getAst()).eval(); + + assertThat(result).isEqualTo(expected); + } + + @Test + public void evaluate_customFunctionReturningCelUnknownSet_differentArities() throws Exception { + Cel cel = + runtimeFlavor + .builder() + .addFunctionDeclarations( + CelFunctionDecl.newFunctionDeclaration( + "unkZero", + CelOverloadDecl.newGlobalOverload( + "unk_zero", SimpleType.INT, ImmutableList.of())), + CelFunctionDecl.newFunctionDeclaration( + "unkUnary", + CelOverloadDecl.newGlobalOverload("unk_unary", SimpleType.INT, SimpleType.INT)), + CelFunctionDecl.newFunctionDeclaration( + "unkBinary", + CelOverloadDecl.newGlobalOverload( + "unk_binary", SimpleType.INT, SimpleType.INT, SimpleType.INT)), + CelFunctionDecl.newFunctionDeclaration( + "unkMember", + CelOverloadDecl.newMemberOverload( + "unk_member", SimpleType.INT, SimpleType.STRING, SimpleType.INT)), + CelFunctionDecl.newFunctionDeclaration( + "unkVarargs", + CelOverloadDecl.newGlobalOverload( + "unk_varargs", + SimpleType.INT, + SimpleType.INT, + SimpleType.INT, + SimpleType.INT))) + .addFunctionBindings( + CelFunctionBinding.from( + "unk_zero", + ImmutableList.of(), + args -> CelUnknownSet.create(CelAttribute.create("attr_zero"))), + CelFunctionBinding.from( + "unk_unary", + Long.class, + arg -> CelUnknownSet.create(CelAttribute.create("attr_unary"))), + CelFunctionBinding.from( + "unk_binary", + Long.class, + Long.class, + (a, b) -> CelUnknownSet.create(CelAttribute.create("attr_binary"))), + CelFunctionBinding.from( + "unk_member", + String.class, + Long.class, + (target, arg) -> CelUnknownSet.create(CelAttribute.create("attr_member"))), + CelFunctionBinding.from( + "unk_varargs", + ImmutableList.of(Long.class, Long.class, Long.class), + args -> CelUnknownSet.create(CelAttribute.create("attr_varargs")))) + .build(); + + assertThat(cel.createProgram(cel.compile("unkZero() + 1").getAst()).eval()) + .isEqualTo(CelUnknownSet.create(CelAttribute.create("attr_zero"))); + assertThat(cel.createProgram(cel.compile("unkUnary(1) + 1").getAst()).eval()) + .isEqualTo(CelUnknownSet.create(CelAttribute.create("attr_unary"))); + assertThat(cel.createProgram(cel.compile("unkBinary(1, 2) + 1").getAst()).eval()) + .isEqualTo(CelUnknownSet.create(CelAttribute.create("attr_binary"))); + assertThat(cel.createProgram(cel.compile("'target'.unkMember(1) + 1").getAst()).eval()) + .isEqualTo(CelUnknownSet.create(CelAttribute.create("attr_member"))); + assertThat(cel.createProgram(cel.compile("unkVarargs(1, 2, 3) + 1").getAst()).eval()) + .isEqualTo(CelUnknownSet.create(CelAttribute.create("attr_varargs"))); + } } 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 57fa72162..34e7831a6 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -1253,6 +1253,123 @@ public void localDoubleShadowIdentifier_withGlobalDisambiguation() throws Except assertThat(result).isTrue(); } + @Test + public void plan_customFunctionReturningUnknown_fieldSelection() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addMessageTypes(TestAllTypes.getDescriptor()) + .addFunctionDeclarations( + newFunctionDeclaration( + "getMsg", + newGlobalOverload( + "getMsg_overload", + StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())))) + .build(); + CelAbstractSyntaxTree ast = compile(compiler, "getMsg().single_int32"); + + DefaultDispatcher.Builder dispatcherBuilder = DefaultDispatcher.newBuilder(); + addBindingsToDispatcher( + dispatcherBuilder, + CelFunctionBinding.fromOverloads( + "getMsg", + CelFunctionBinding.from( + "getMsg_overload", + ImmutableList.of(), + (unused) -> CelUnknownSet.create(CelAttribute.create("custom_msg"))))); + ProgramPlanner planner = + ProgramPlanner.newPlanner( + TYPE_PROVIDER, + VALUE_PROVIDER, + dispatcherBuilder.build(), + CEL_VALUE_CONVERTER, + CEL_CONTAINER, + CEL_OPTIONS, + ImmutableSet.of()); + + Program program = planner.plan(ast); + + Object result = program.eval(); + assertThat(((CelUnknownSet) result).attributes()) + .containsExactly(CelAttribute.create("custom_msg")); + } + + @Test + public void plan_customFunctionReturningUnknown_binaryOperation() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addFunctionDeclarations( + newFunctionDeclaration( + "getUnknownInt", newGlobalOverload("getUnknownInt_overload", SimpleType.INT))) + .build(); + CelAbstractSyntaxTree ast = compile(compiler, "getUnknownInt() == 100"); + + DefaultDispatcher.Builder dispatcherBuilder = DefaultDispatcher.newBuilder(); + CelStandardFunctions stdFunctions = + CelStandardFunctions.newBuilder().includeFunctions(StandardFunction.EQUALS).build(); + addBindingsToDispatcher( + dispatcherBuilder, stdFunctions.newFunctionBindings(RUNTIME_EQUALITY, CEL_OPTIONS)); + addBindingsToDispatcher( + dispatcherBuilder, + CelFunctionBinding.fromOverloads( + "getUnknownInt", + CelFunctionBinding.from( + "getUnknownInt_overload", + ImmutableList.of(), + (unused) -> CelUnknownSet.create(CelAttribute.create("custom_int"))))); + ProgramPlanner planner = + ProgramPlanner.newPlanner( + TYPE_PROVIDER, + VALUE_PROVIDER, + dispatcherBuilder.build(), + CEL_VALUE_CONVERTER, + CEL_CONTAINER, + CEL_OPTIONS, + ImmutableSet.of()); + + Program program = planner.plan(ast); + + Object result = program.eval(); + assertThat(((CelUnknownSet) result).attributes()) + .containsExactly(CelAttribute.create("custom_int")); + } + + @Test + public void plan_variableAsCelUnknownSet_propagatesUnknown() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addMessageTypes(TestAllTypes.getDescriptor()) + .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) + .addVar("x", SimpleType.INT) + .build(); + CelAbstractSyntaxTree ast1 = compile(compiler, "x + 1"); + CelAbstractSyntaxTree ast2 = compile(compiler, "msg.single_int32"); + + DefaultDispatcher.Builder dispatcherBuilder = DefaultDispatcher.newBuilder(); + CelStandardFunctions stdFunctions = + CelStandardFunctions.newBuilder().includeFunctions(StandardFunction.ADD).build(); + addBindingsToDispatcher( + dispatcherBuilder, stdFunctions.newFunctionBindings(RUNTIME_EQUALITY, CEL_OPTIONS)); + ProgramPlanner planner = + ProgramPlanner.newPlanner( + TYPE_PROVIDER, + VALUE_PROVIDER, + dispatcherBuilder.build(), + CEL_VALUE_CONVERTER, + CEL_CONTAINER, + CEL_OPTIONS, + ImmutableSet.of()); + + ImmutableMap vars = + ImmutableMap.of( + "msg", CelUnknownSet.create(CelAttribute.create("custom_msg")), + "x", CelUnknownSet.create(CelAttribute.create("custom_x"))); + + assertThat(planner.plan(ast1).eval(vars)) + .isEqualTo(CelUnknownSet.create(CelAttribute.create("custom_x"))); + assertThat(planner.plan(ast2).eval(vars)) + .isEqualTo(CelUnknownSet.create(CelAttribute.create("custom_msg"))); + } + private CelAbstractSyntaxTree compile(String expression) throws Exception { return compile(CEL_COMPILER, expression); }