diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 3bcb86766..22926832a 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -232,3 +232,15 @@ java_library( visibility = ["//:internal"], exports = ["//runtime/src/main/java/dev/cel/runtime:resolved_overload_internal"], ) + +java_library( + name = "internal_function_binder", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:internal_function_binder"], +) + +cel_android_library( + name = "internal_function_binder_android", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:internal_function_binder_andriod"], +) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 20d49c476..90808a8d0 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -644,6 +644,7 @@ java_library( "//runtime/standard:multiply", "//runtime/standard:negate", "//runtime/standard:not_equals", + "//runtime/standard:not_strictly_false", "//runtime/standard:size", "//runtime/standard:standard_function", "//runtime/standard:standard_overload", @@ -700,6 +701,7 @@ cel_android_library( "//runtime/standard:multiply_android", "//runtime/standard:negate_android", "//runtime/standard:not_equals_android", + "//runtime/standard:not_strictly_false_android", "//runtime/standard:size_android", "//runtime/standard:standard_function_android", "//runtime/standard:standard_overload_android", @@ -794,6 +796,7 @@ java_library( ], deps = [ ":function_overload", + ":unknown_attributes", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_protobuf_protobuf_java", @@ -806,6 +809,7 @@ cel_android_library( visibility = ["//visibility:private"], deps = [ ":function_overload_android", + ":unknown_attributes_android", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_protobuf_protobuf_javalite", @@ -1190,3 +1194,29 @@ cel_android_library( "@maven_android//:com_google_guava_guava", ], ) + +java_library( + name = "internal_function_binder", + srcs = ["InternalFunctionBinder.java"], + tags = [ + ], + deps = [ + ":function_overload", + "//common/annotations", + "//runtime:function_binding", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "internal_function_binder_andriod", + srcs = ["InternalFunctionBinder.java"], + tags = [ + ], + deps = [ + ":function_overload_android", + "//common/annotations", + "//runtime:function_binding_android", + "@maven_android//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java index 8df7fd0dc..8f00eebb3 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java @@ -35,6 +35,6 @@ public interface CelFunctionResolver { * @return an optional value of the resolved overload. * @throws CelEvaluationException if the overload resolution is ambiguous, */ - Optional findOverload( + Optional findOverloadMatchingArgs( String functionName, List overloadIds, Object[] args) throws CelEvaluationException; } diff --git a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java index 6762feec0..75be39e92 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java @@ -36,9 +36,9 @@ private CelLateFunctionBindings(ImmutableMap functions } @Override - public Optional findOverload( + public Optional findOverloadMatchingArgs( String functionName, List overloadIds, Object[] args) throws CelEvaluationException { - return DefaultDispatcher.findOverload(functionName, overloadIds, functions, args); + return DefaultDispatcher.findOverloadMatchingArgs(functionName, overloadIds, functions, args); } public static CelLateFunctionBindings from(CelFunctionBinding... functions) { diff --git a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java index 6742dc42f..98e4c8636 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java +++ b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java @@ -100,6 +100,8 @@ import dev.cel.runtime.standard.NegateOperator.NegateOverload; import dev.cel.runtime.standard.NotEqualsOperator; import dev.cel.runtime.standard.NotEqualsOperator.NotEqualsOverload; +import dev.cel.runtime.standard.NotStrictlyFalseFunction; +import dev.cel.runtime.standard.NotStrictlyFalseFunction.NotStrictlyFalseOverload; import dev.cel.runtime.standard.SizeFunction; import dev.cel.runtime.standard.SizeFunction.SizeOverload; import dev.cel.runtime.standard.StartsWithFunction; @@ -159,7 +161,8 @@ public final class CelStandardFunctions { StringFunction.create(), SubtractOperator.create(), TimestampFunction.create(), - UintFunction.create()); + UintFunction.create(), + NotStrictlyFalseFunction.create()); /** * Enumeration of Standard Function bindings. @@ -170,6 +173,7 @@ public final class CelStandardFunctions { public enum StandardFunction { LOGICAL_NOT(BooleanOperator.LOGICAL_NOT), IN(InternalOperator.IN_LIST, InternalOperator.IN_MAP), + NOT_STRICTLY_FALSE(InternalOperator.NOT_STRICTLY_FALSE), EQUALS(Relation.EQUALS), NOT_EQUALS(Relation.NOT_EQUALS), BOOL(Conversions.BOOL_TO_BOOL, Conversions.STRING_TO_BOOL), @@ -331,10 +335,14 @@ public enum StandardFunction { /** Container class for CEL standard function overloads. */ public static final class Overload { - /** Overloads for internal functions that may have been rewritten by macros (ex: @in) */ + /** + * Overloads for internal functions that may have been rewritten by macros + * (ex: @in, @not_strictly_false) + */ public enum InternalOperator implements CelStandardOverload { IN_LIST(InOverload.IN_LIST), - IN_MAP(InOverload.IN_MAP); + IN_MAP(InOverload.IN_MAP), + NOT_STRICTLY_FALSE(NotStrictlyFalseOverload.NOT_STRICTLY_FALSE); private final CelStandardOverload standardOverload; diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index 2643ddf75..35340abc5 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -34,13 +34,13 @@ final class DefaultDispatcher implements CelFunctionResolver { private final ImmutableMap overloads; @Override - public Optional findOverload( + public Optional findOverloadMatchingArgs( String functionName, List overloadIds, Object[] args) throws CelEvaluationException { - return findOverload(functionName, overloadIds, overloads, args); + return findOverloadMatchingArgs(functionName, overloadIds, overloads, args); } /** Finds the overload that matches the given function name, overload IDs, and arguments. */ - public static Optional findOverload( + static Optional findOverloadMatchingArgs( String functionName, List overloadIds, Map overloads, @@ -75,6 +75,32 @@ public static Optional findOverload( return Optional.ofNullable(match); } + /** + * Finds the single registered overload iff it's marked as a non-strict function. + * + *

The intent behind this function is to provide an at-parity behavior with existing + * DefaultInterpreter, where it historically special-cased locating a single overload for certain + * non-strict functions, such as not_strictly_false. This method should not be used outside of + * this specific context. + * + * @throws IllegalStateException if there are multiple overloads that are marked non-strict. + */ + Optional findSingleNonStrictOverload(List overloadIds) { + for (String overloadId : overloadIds) { + ResolvedOverload overload = overloads.get(overloadId); + if (overload != null && !overload.isStrict()) { + if (overloadIds.size() > 1) { + throw new IllegalStateException( + String.format( + "%d overloads found for a non-strict function. Expected 1.", overloadIds.size())); + } + return Optional.of(overload); + } + } + + return Optional.empty(); + } + static Builder newBuilder() { return new AutoBuilder_DefaultDispatcher_Builder(); } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index f40960e74..931194d6e 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -421,8 +421,6 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall return evalLogicalAnd(frame, callExpr); case "logical_or": return evalLogicalOr(frame, callExpr); - case "not_strictly_false": - return evalNotStrictlyFalse(frame, callExpr); case "type": return evalType(frame, callExpr); case "optional_or_optional": @@ -441,8 +439,18 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall break; } - // Delegate handling of call to dispatcher. + boolean isNonStrictCall = + dispatcher.findSingleNonStrictOverload(reference.overloadIds()).isPresent(); + return dispatchCall(frame, expr, callExpr, reference, isNonStrictCall); + } + private IntermediateResult dispatchCall( + ExecutionFrame frame, + CelExpr expr, + CelCall callExpr, + CelReference reference, + boolean isNonStrict) + throws CelEvaluationException { List callArgs = new ArrayList<>(); callExpr.target().ifPresent(callArgs::add); @@ -450,9 +458,16 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall IntermediateResult[] argResults = new IntermediateResult[callArgs.size()]; for (int i = 0; i < argResults.length; i++) { - // Default evaluation is strict so errors will propagate (via thrown Java exception) before - // unknowns. - argResults[i] = evalInternal(frame, callArgs.get(i)); + IntermediateResult result; + try { + result = evalInternal(frame, callArgs.get(i)); + } catch (Exception e) { + if (!isNonStrict) { + throw e; + } + result = IntermediateResult.create(e); + } + argResults[i] = result; } Optional indexAttr = @@ -469,7 +484,7 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall indexAttr.isPresent() ? CallArgumentChecker.createAcceptingPartial(frame.getResolver()) : CallArgumentChecker.create(frame.getResolver()); - for (DefaultInterpreter.IntermediateResult element : argResults) { + for (IntermediateResult element : argResults) { argChecker.checkArg(element); } Optional unknowns = argChecker.maybeUnknowns(); @@ -511,7 +526,7 @@ private ResolvedOverload findOverloadOrThrow( throws CelEvaluationException { try { Optional funcImpl = - dispatcher.findOverload(functionName, overloadIds, args); + dispatcher.findOverloadMatchingArgs(functionName, overloadIds, args); if (funcImpl.isPresent()) { return funcImpl.get(); } @@ -684,20 +699,6 @@ private IntermediateResult evalLogicalAnd(ExecutionFrame frame, CelCall callExpr return mergeBooleanUnknowns(left, right); } - // Returns true unless the expression evaluates to false, in which case it returns false. - // True is also returned if evaluation yields an error or an unknown set. - private IntermediateResult evalNotStrictlyFalse(ExecutionFrame frame, CelCall callExpr) { - try { - IntermediateResult value = evalBooleanStrict(frame, callExpr.args().get(0)); - if (value.value() instanceof Boolean) { - return value; - } - } catch (Exception e) { - /*nothing to do*/ - } - return IntermediateResult.create(true); - } - private IntermediateResult evalType(ExecutionFrame frame, CelCall callExpr) throws CelEvaluationException { CelExpr typeExprArg = callExpr.args().get(0); @@ -1134,7 +1135,9 @@ private RuntimeUnknownResolver getResolver() { private Optional findOverload( String function, List overloadIds, Object[] args) throws CelEvaluationException { if (lateBoundFunctionResolver.isPresent()) { - return lateBoundFunctionResolver.get().findOverload(function, overloadIds, args); + return lateBoundFunctionResolver + .get() + .findOverloadMatchingArgs(function, overloadIds, args); } return Optional.empty(); } diff --git a/runtime/src/main/java/dev/cel/runtime/InternalFunctionBinder.java b/runtime/src/main/java/dev/cel/runtime/InternalFunctionBinder.java new file mode 100644 index 000000000..5a063ee6c --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/InternalFunctionBinder.java @@ -0,0 +1,49 @@ +// Copyright 2025 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; + +import com.google.common.collect.ImmutableList; +import dev.cel.common.annotations.Internal; + +/** + * A helper to create CelFunctionBinding instances with sensitive controls, such as to toggle the + * strictness of the function. + * + *

CEL Library Internals. Do Not Use. + */ +@Internal +public final class InternalFunctionBinder { + + /** + * Create a unary function binding from the {@code overloadId}, {@code arg}, {@code impl}, and + * {@code} isStrict. + */ + @SuppressWarnings("unchecked") + public static CelFunctionBinding from( + String overloadId, Class arg, CelFunctionOverload.Unary impl, boolean isStrict) { + return from(overloadId, ImmutableList.of(arg), (args) -> impl.apply((T) args[0]), isStrict); + } + + /** + * Create a function binding from the {@code overloadId}, {@code argTypes}, {@code impl} and + * {@code isStrict}. + */ + public static CelFunctionBinding from( + String overloadId, Iterable> argTypes, CelFunctionOverload impl, boolean isStrict) { + return new FunctionBindingImpl(overloadId, ImmutableList.copyOf(argTypes), impl, isStrict); + } + + private InternalFunctionBinder() {} +} diff --git a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java index d0b4d2f77..a46a92fae 100644 --- a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java @@ -70,6 +70,14 @@ default boolean canHandle(Object[] arguments) { } continue; } + + if (arg instanceof Exception || arg instanceof CelUnknownSet) { + if (!isStrict()) { + // Only non-strict functions can accept errors/unknowns as arguments to a function + return true; + } + } + if (!paramType.isAssignableFrom(arg.getClass())) { return false; } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel index 851f375a4..041d07dea 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -1419,6 +1419,38 @@ cel_android_library( ], ) +java_library( + name = "not_strictly_false", + srcs = ["NotStrictlyFalseFunction.java"], + tags = [ + ], + deps = [ + ":standard_overload", + "//common:options", + "//runtime:function_binding", + "//runtime:internal_function_binder", + "//runtime:runtime_equality", + "//runtime/standard:standard_function", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "not_strictly_false_android", + srcs = ["NotStrictlyFalseFunction.java"], + tags = [ + ], + deps = [ + ":standard_function_android", + ":standard_overload_android", + "//common:options", + "//runtime:function_binding_android", + "//runtime:internal_function_binder_android", + "//runtime:runtime_equality_android", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "standard_overload", srcs = ["CelStandardOverload.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java new file mode 100644 index 000000000..af7d1a3c2 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java @@ -0,0 +1,68 @@ +// Copyright 2025 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.standard; + +import com.google.common.collect.ImmutableSet; +import dev.cel.common.CelOptions; +import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.InternalFunctionBinder; +import dev.cel.runtime.RuntimeEquality; + +/** + * Standard function for {@code @not_strictly_false}. This is an internal function used within + * comprehensions to coerce the result into true if an evaluation yields an error or an unknown set. + */ +public final class NotStrictlyFalseFunction extends CelStandardFunction { + private static final NotStrictlyFalseFunction ALL_OVERLOADS = + new NotStrictlyFalseFunction(ImmutableSet.copyOf(NotStrictlyFalseOverload.values())); + + public static NotStrictlyFalseFunction create() { + return ALL_OVERLOADS; + } + + /** Overloads for the standard function. */ + public enum NotStrictlyFalseOverload implements CelStandardOverload { + NOT_STRICTLY_FALSE( + (celOptions, runtimeEquality) -> + InternalFunctionBinder.from( + "not_strictly_false", + Object.class, + (Object value) -> { + if (value instanceof Boolean) { + return value; + } + + return true; + }, + /* isStrict= */ false)), + ; + + private final CelStandardOverload bindingCreator; + + @Override + public CelFunctionBinding newFunctionBinding( + CelOptions celOptions, RuntimeEquality runtimeEquality) { + return bindingCreator.newFunctionBinding(celOptions, runtimeEquality); + } + + NotStrictlyFalseOverload(CelStandardOverload bindingCreator) { + this.bindingCreator = bindingCreator; + } + } + + private NotStrictlyFalseFunction(ImmutableSet overloads) { + super(overloads); + } +} diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 81e206fdb..4fda11d9b 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -82,6 +82,7 @@ java_library( "//runtime:type_resolver", "//runtime:unknown_attributes", "//runtime:unknown_options", + "//runtime/standard:not_strictly_false", "//runtime/standard:standard_overload", "//testing/protos:message_with_enum_cel_java_proto", "//testing/protos:message_with_enum_java_proto", diff --git a/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java b/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java index 03bed8ae6..3f9e1e105 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java @@ -38,7 +38,7 @@ public void findOverload_singleMatchingFunction_isPresent() throws Exception { CelFunctionBinding.from( "increment_uint", UnsignedLong.class, (arg) -> arg.plus(UnsignedLong.ONE))); Optional overload = - bindings.findOverload( + bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_int", "increment_uint"), new Object[] {1L}); assertThat(overload).isPresent(); assertThat(overload.get().getOverloadId()).isEqualTo("increment_int"); @@ -54,7 +54,7 @@ public void findOverload_noMatchingFunctionSameArgCount_isEmpty() throws Excepti CelFunctionBinding.from( "increment_uint", UnsignedLong.class, (arg) -> arg.plus(UnsignedLong.ONE))); Optional overload = - bindings.findOverload( + bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_int", "increment_uint"), new Object[] {1.0}); assertThat(overload).isEmpty(); } @@ -67,7 +67,7 @@ public void findOverload_noMatchingFunctionDifferentArgCount_isEmpty() throws Ex CelFunctionBinding.from( "increment_uint", UnsignedLong.class, (arg) -> arg.plus(UnsignedLong.ONE))); Optional overload = - bindings.findOverload( + bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_int", "increment_uint"), new Object[] {1.0, 1.0}); @@ -89,7 +89,7 @@ public void findOverload_badInput_throwsException() throws Exception { return arg.plus(UnsignedLong.ONE); })); Optional overload = - bindings.findOverload( + bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_uint"), new Object[] {UnsignedLong.MAX_VALUE}); assertThat(overload).isPresent(); assertThat(overload.get().getOverloadId()).isEqualTo("increment_uint"); @@ -111,7 +111,7 @@ public void findOverload_multipleMatchingFunctions_throwsException() throws Exce Assert.assertThrows( CelEvaluationException.class, () -> - bindings.findOverload( + bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_int", "increment_uint"), new Object[] {1L})); @@ -124,7 +124,8 @@ public void findOverload_nullPrimitiveArg_isEmpty() throws Exception { CelLateFunctionBindings.from( CelFunctionBinding.from("identity_int", Long.class, (arg) -> arg)); Optional overload = - bindings.findOverload("identity", ImmutableList.of("identity_int"), new Object[] {null}); + bindings.findOverloadMatchingArgs( + "identity", ImmutableList.of("identity_int"), new Object[] {null}); assertThat(overload).isEmpty(); } @@ -134,7 +135,8 @@ public void findOverload_nullMessageArg_returnsOverload() throws Exception { CelLateFunctionBindings.from( CelFunctionBinding.from("identity_msg", TestAllTypes.class, (arg) -> arg)); Optional overload = - bindings.findOverload("identity", ImmutableList.of("identity_msg"), new Object[] {null}); + bindings.findOverloadMatchingArgs( + "identity", ImmutableList.of("identity_msg"), new Object[] {null}); assertThat(overload).isPresent(); assertThat(overload.get().getOverloadId()).isEqualTo("identity_msg"); assertThat(overload.get().getParameterTypes()).containsExactly(TestAllTypes.class); diff --git a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java index fb6831201..fbcfbd813 100644 --- a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java @@ -50,7 +50,7 @@ public void findOverload_multipleMatches_throwsException() { Assert.assertThrows( CelEvaluationException.class, () -> - DefaultDispatcher.findOverload( + DefaultDispatcher.findOverloadMatchingArgs( "overloads", ImmutableList.of("overload_1", "overload_2"), overloads, diff --git a/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java index b70bffdb6..1a8f45161 100644 --- a/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java @@ -29,6 +29,7 @@ import dev.cel.parser.CelStandardMacro; import dev.cel.runtime.DefaultInterpreter.DefaultInterpretable; import dev.cel.runtime.DefaultInterpreter.ExecutionFrame; +import dev.cel.runtime.standard.NotStrictlyFalseFunction.NotStrictlyFalseOverload; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; @@ -80,6 +81,15 @@ public Object adapt(String messageName, Object message) { ImmutableList.of(long.class), /* isStrict= */ true, (args) -> new IllegalArgumentException("Always throws")); + CelFunctionBinding notStrictlyFalseBinding = + NotStrictlyFalseOverload.NOT_STRICTLY_FALSE.newFunctionBinding( + CelOptions.DEFAULT, + RuntimeEquality.create(RuntimeHelpers.create(), CelOptions.DEFAULT)); + dispatcherBuilder.addOverload( + notStrictlyFalseBinding.getOverloadId(), + notStrictlyFalseBinding.getArgTypes(), + notStrictlyFalseBinding.isStrict(), + notStrictlyFalseBinding.getDefinition()); DefaultInterpreter defaultInterpreter = new DefaultInterpreter( new TypeResolver(), emptyProvider, dispatcherBuilder.build(), CelOptions.DEFAULT); diff --git a/runtime/src/test/resources/nonstrictQuantifierTests.baseline b/runtime/src/test/resources/nonstrictQuantifierTests.baseline index 3a7a05b2b..728e4bfc9 100644 --- a/runtime/src/test/resources/nonstrictQuantifierTests.baseline +++ b/runtime/src/test/resources/nonstrictQuantifierTests.baseline @@ -38,4 +38,12 @@ declare four { } =====> bindings: {four=4} +result: true + +Source: [0, 1].exists(x, x > four || true) +declare four { + value int +} +=====> +bindings: {} result: true \ No newline at end of file diff --git a/runtime/standard/BUILD.bazel b/runtime/standard/BUILD.bazel index a69f69887..bc6ab9ed3 100644 --- a/runtime/standard/BUILD.bazel +++ b/runtime/standard/BUILD.bazel @@ -406,6 +406,16 @@ cel_android_library( exports = ["//runtime/src/main/java/dev/cel/runtime/standard:uint_android"], ) +java_library( + name = "not_strictly_false", + exports = ["//runtime/src/main/java/dev/cel/runtime/standard:not_strictly_false"], +) + +cel_android_library( + name = "not_strictly_false_android", + exports = ["//runtime/src/main/java/dev/cel/runtime/standard:not_strictly_false_android"], +) + java_library( name = "standard_overload", exports = ["//runtime/src/main/java/dev/cel/runtime/standard:standard_overload"], diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 24ed1ea8d..4be456fb7 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -1439,6 +1439,10 @@ public void nonstrictQuantifierTests() { source = "![0, 2, four].all(x, four/x != 2 && four/(four-x) != 2)"; runTest(ImmutableMap.of("four", 4L)); + + // Unknown argument + source = "[0, 1].exists(x, x > four || true)"; + runTest(); } @Test