From bf75ce709e920135987cfeeaba6ee21b66d8e0b0 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 6 Jun 2025 17:37:36 -0700 Subject: [PATCH 01/30] Program planner POC --- .../java/dev/cel/common/ast/CelConstant.java | 24 +++++ .../cel/common/values/CelValueConverter.java | 4 +- runtime/BUILD.bazel | 5 + .../src/main/java/dev/cel/runtime/BUILD.bazel | 91 ++++++++++++++++++- .../cel/runtime/CelLiteRuntimeBuilder.java | 3 + .../main/java/dev/cel/runtime/CelRuntime.java | 1 + .../cel/runtime/CelValueInterpretable.java | 17 ++++ .../java/dev/cel/runtime/CelValueProgram.java | 35 +++++++ .../dev/cel/runtime/DefaultInterpreter.java | 3 - .../java/dev/cel/runtime/EvalConstant.java | 21 +++++ .../java/dev/cel/runtime/Interpretable.java | 33 ------- .../java/dev/cel/runtime/LiteRuntimeImpl.java | 21 ++++- .../java/dev/cel/runtime/ProgramPlanner.java | 42 +++++++++ .../src/test/java/dev/cel/runtime/BUILD.bazel | 1 + .../dev/cel/runtime/ProgramPlannerTest.java | 26 ++++++ 15 files changed, 284 insertions(+), 43 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java create mode 100644 runtime/src/main/java/dev/cel/runtime/CelValueProgram.java create mode 100644 runtime/src/main/java/dev/cel/runtime/EvalConstant.java create mode 100644 runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java create mode 100644 runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java diff --git a/common/src/main/java/dev/cel/common/ast/CelConstant.java b/common/src/main/java/dev/cel/common/ast/CelConstant.java index c27f4ff8f..64ed83be8 100644 --- a/common/src/main/java/dev/cel/common/ast/CelConstant.java +++ b/common/src/main/java/dev/cel/common/ast/CelConstant.java @@ -207,4 +207,28 @@ public static CelConstant ofObjectValue(Object value) { throw new IllegalArgumentException("Value is not a CelConstant: " + value); } + * TODO + */ + public Object objectValue() { + switch (getKind()) { + case NULL_VALUE: + return nullValue(); + case BOOLEAN_VALUE: + return booleanValue(); + case INT64_VALUE: + return int64Value(); + case UINT64_VALUE: + return uint64Value(); + case DOUBLE_VALUE: + return doubleValue(); + case STRING_VALUE: + return stringValue(); + case BYTES_VALUE: + return bytesValue(); + default: + throw new IllegalStateException("Unsupported kind: " + getKind()); + } + } + + /** } diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java index a281318f8..f352f7b0b 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -33,7 +33,7 @@ @SuppressWarnings("unchecked") // Unchecked cast of generics due to type-erasure (ex: MapValue). @Internal @Immutable -abstract class CelValueConverter { +public class CelValueConverter { /** Adapts a {@link CelValue} to a plain old Java Object. */ public Object fromCelValueToJavaObject(CelValue celValue) { @@ -145,6 +145,4 @@ private MapValue toMapValue(Map map) { return ImmutableMapValue.create(mapBuilder.buildOrThrow()); } - - protected CelValueConverter() {} } diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index b7d59ce96..4f18bb591 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -220,3 +220,8 @@ cel_android_library( visibility = ["//:internal"], exports = ["//runtime/src/main/java/dev/cel/runtime:lite_runtime_impl_android"], ) + +java_library( + name = "program_planner", + exports = ["//runtime/src/main/java/dev/cel/runtime:program_planner"], +) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 184a01534..0c06239ce 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -41,10 +41,14 @@ LITE_RUNTIME_SOURCES = [ # keep sorted LITE_RUNTIME_IMPL_SOURCES = [ - "LiteProgramImpl.java", "LiteRuntimeImpl.java", ] +# keep sorted +LITE_PROGRAM_IMPL_SOURCES = [ + "LiteProgramImpl.java", +] + # keep sorted FUNCTION_BINDING_SOURCES = [ "CelFunctionBinding.java", @@ -897,7 +901,9 @@ java_library( ":function_resolver", ":interpretable", ":interpreter", + ":lite_program_impl", ":lite_runtime", + ":program_planner", ":runtime_equality", ":runtime_helpers", ":type_resolver", @@ -912,6 +918,24 @@ java_library( ], ) +java_library( + name = "lite_program_impl", + srcs = LITE_PROGRAM_IMPL_SOURCES, + tags = [ + ], + deps = [ + ":activation", + ":evaluation_exception", + ":evaluation_listener", + ":function_resolver", + ":interpretable", + ":interpreter", + ":lite_runtime", + "//:auto_value", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + cel_android_library( name = "lite_runtime_impl_android", srcs = LITE_RUNTIME_IMPL_SOURCES, @@ -926,7 +950,9 @@ cel_android_library( ":function_resolver_android", ":interpretable_android", ":interpreter_android", + ":lite_program_impl", # TODO - android target ":lite_runtime_android", + ":program_planner", # TODO - android target ":runtime_equality_android", ":runtime_helpers_android", ":type_resolver_android", @@ -1165,5 +1191,68 @@ cel_android_library( deps = [ ":unknown_attributes_android", "@maven//:com_google_errorprone_error_prone_annotations", + ] +) + +java_library( + name = "eval_const", + srcs = ["EvalConstant.java"], + deps = [ + ":cel_value_interpretable", + ":evaluation_exception", + ":interpretable", + "//common/values", + "//common/values:cel_value", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "cel_value_interpretable", + srcs = ["CelValueInterpretable.java"], + deps = [ + ":evaluation_exception", + ":interpretable", + "//common/annotations", + "//common/values:cel_value", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "cel_value_program", + srcs = ["CelValueProgram.java"], + tags = [ + ], + deps = [ + ":activation", + ":cel_value_interpretable", + ":evaluation_exception", + ":evaluation_listener", + ":function_resolver", + ":interpretable", + ":interpreter", + ":lite_runtime", + "//:auto_value", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "program_planner", + srcs = ["ProgramPlanner.java"], + deps = [ + ":activation", + ":eval_const", + ":evaluation_exception", + ":function_resolver", + ":interpretable", + ":lite_program_impl", + ":lite_runtime", + "//common:cel_ast", + "//common/ast", + "//common/values", + "//common/values:cel_value", + "@maven//:com_google_errorprone_error_prone_annotations", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/CelLiteRuntimeBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelLiteRuntimeBuilder.java index 48b51274d..1392da66a 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLiteRuntimeBuilder.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLiteRuntimeBuilder.java @@ -62,6 +62,9 @@ CelLiteRuntimeBuilder setStandardFunctions( @CanIgnoreReturnValue CelLiteRuntimeBuilder addLibraries(Iterable libraries); + @CanIgnoreReturnValue + CelLiteRuntimeBuilder enablePlanner(boolean value); + @CheckReturnValue CelLiteRuntime build(); } diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntime.java b/runtime/src/main/java/dev/cel/runtime/CelRuntime.java index d730701e7..cefa2c335 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntime.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntime.java @@ -16,6 +16,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; +import dev.cel.runtime.DefaultInterpreter.DefaultInterpretable; import javax.annotation.concurrent.ThreadSafe; import com.google.protobuf.Message; import dev.cel.common.CelAbstractSyntaxTree; diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java b/runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java new file mode 100644 index 000000000..608b20f68 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java @@ -0,0 +1,17 @@ +package dev.cel.runtime; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.annotations.Internal; +import dev.cel.common.values.CelValue; +/** + * Represent an expression which can be interpreted repeatedly using a given activation. + * + *

CEL Library Internals. Do Not Use. + */ +@Immutable +@Internal +public interface CelValueInterpretable extends Interpretable{ + + @Override + CelValue eval(GlobalResolver resolver) throws CelEvaluationException; +} diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueProgram.java b/runtime/src/main/java/dev/cel/runtime/CelValueProgram.java new file mode 100644 index 000000000..9db5fc27c --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelValueProgram.java @@ -0,0 +1,35 @@ +package dev.cel.runtime; + +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +import java.util.Map; + +@Immutable +@AutoValue +abstract class CelValueProgram implements CelLiteRuntime.Program { + private static final CelValueConverter DEFAULT_VALUE_CONVERTER = new CelValueConverter(); + + abstract CelValueInterpretable interpretable(); + + @Override + public Object eval() throws CelEvaluationException { + CelValue evalResult = interpretable().eval(GlobalResolver.EMPTY); + return DEFAULT_VALUE_CONVERTER.fromCelValueToJavaObject(evalResult); + } + + @Override + public Object eval(Map mapValue) throws CelEvaluationException { + return null; + } + @Override + public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException { + return null; + } + + static CelLiteRuntime.Program create(CelValueInterpretable interpretable) { + return new AutoValue_CelValueProgram(interpretable); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index afdca0e3e..141722e6a 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -146,14 +146,12 @@ public Object eval(GlobalResolver resolver) throws CelEvaluationException { RuntimeUnknownResolver.fromResolver(resolver), Optional.empty(), Optional.empty()); } - @Override public Object eval(GlobalResolver resolver, CelEvaluationListener listener) throws CelEvaluationException { return evalTrackingUnknowns( RuntimeUnknownResolver.fromResolver(resolver), Optional.empty(), Optional.of(listener)); } - @Override public Object eval(GlobalResolver resolver, FunctionResolver lateBoundFunctionResolver) throws CelEvaluationException { return evalTrackingUnknowns( @@ -162,7 +160,6 @@ public Object eval(GlobalResolver resolver, FunctionResolver lateBoundFunctionRe Optional.empty()); } - @Override public Object eval( GlobalResolver resolver, FunctionResolver lateBoundFunctionResolver, diff --git a/runtime/src/main/java/dev/cel/runtime/EvalConstant.java b/runtime/src/main/java/dev/cel/runtime/EvalConstant.java new file mode 100644 index 000000000..10f46f268 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/EvalConstant.java @@ -0,0 +1,21 @@ +package dev.cel.runtime; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; + +@Immutable +final class EvalConstant implements CelValueInterpretable { + + @SuppressWarnings("Immutable") + private final CelValue constant; + + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + return constant; + } + + EvalConstant(CelValue constant) { + this.constant = constant; + } +} \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/Interpretable.java b/runtime/src/main/java/dev/cel/runtime/Interpretable.java index 21e95921d..62b9bb0f2 100644 --- a/runtime/src/main/java/dev/cel/runtime/Interpretable.java +++ b/runtime/src/main/java/dev/cel/runtime/Interpretable.java @@ -28,37 +28,4 @@ public interface Interpretable { /** Runs interpretation with the given activation which supplies name/value bindings. */ Object eval(GlobalResolver resolver) throws CelEvaluationException; - - /** - * Runs interpretation with the given activation which supplies name/value bindings. - * - *

This method allows for evaluation listeners to be provided per-evaluation. - */ - Object eval(GlobalResolver resolver, CelEvaluationListener listener) - throws CelEvaluationException; - - /** - * Runs interpretation with the given activation which supplies name/value bindings. - * - *

This method allows for late-binding functions to be provided per-evaluation, which can be - * useful for binding functions which might have side-effects that are not observable to CEL - * directly such as recording telemetry or evaluation state in a more granular fashion than a more - * general evaluation listener might permit. - */ - Object eval(GlobalResolver resolver, FunctionResolver lateBoundFunctionResolver) - throws CelEvaluationException; - - /** - * Runs interpretation with the given activation which supplies name/value bindings. - * - *

This method allows for late-binding functions to be provided per-evaluation, which can be - * useful for binding functions which might have side-effects that are not observable to CEL - * directly such as recording telemetry or evaluation state in a more granular fashion than a more - * general evaluation listener might permit. - */ - Object eval( - GlobalResolver resolver, - FunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) - throws CelEvaluationException; } diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index 152c96160..611a559c8 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import dev.cel.runtime.DefaultInterpreter.DefaultInterpretable; import javax.annotation.concurrent.ThreadSafe; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelOptions; @@ -37,6 +38,7 @@ final class LiteRuntimeImpl implements CelLiteRuntime { private final ImmutableList customFunctionBindings; private final ImmutableSet celStandardFunctions; private final CelValueProvider celValueProvider; + private final boolean enablePlanner; // This does not affect the evaluation behavior in any manner. // CEL-Internal-4 @@ -45,7 +47,11 @@ final class LiteRuntimeImpl implements CelLiteRuntime { @Override public Program createProgram(CelAbstractSyntaxTree ast) { checkState(ast.isChecked(), "programs must be created from checked expressions"); - return LiteProgramImpl.plan(interpreter.createInterpretable(ast)); + if (enablePlanner) { + return ProgramPlanner.plan(ast); + } else { + return LiteProgramImpl.create(interpreter.createInterpretable(ast)); + } } @Override @@ -72,6 +78,7 @@ static final class Builder implements CelLiteRuntimeBuilder { @VisibleForTesting final ImmutableSet.Builder runtimeLibrariesBuilder; @VisibleForTesting final ImmutableSet.Builder standardFunctionBuilder; @VisibleForTesting CelValueProvider celValueProvider; + @VisibleForTesting boolean enablePlanner; @Override public CelLiteRuntimeBuilder setOptions(CelOptions celOptions) { @@ -118,6 +125,11 @@ public CelLiteRuntimeBuilder addLibraries(Iterable customFunctionBindings, ImmutableSet celStandardFunctions, ImmutableSet runtimeLibraries, - CelValueProvider celValueProvider) { + CelValueProvider celValueProvider, + boolean enablePlanner) { this.interpreter = interpreter; this.celOptions = celOptions; this.customFunctionBindings = ImmutableList.copyOf(customFunctionBindings); this.celStandardFunctions = celStandardFunctions; this.runtimeLibraries = runtimeLibraries; this.celValueProvider = celValueProvider; + this.enablePlanner = enablePlanner; } } diff --git a/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java new file mode 100644 index 000000000..fda258c42 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java @@ -0,0 +1,42 @@ +package dev.cel.runtime; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.ast.CelExpr; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; + +@Immutable +final class ProgramPlanner { + private static final CelValueConverter DEFAULT_VALUE_CONVERTER = new CelValueConverter(); + + private static CelValueInterpretable plan(CelExpr celExpr) { + switch (celExpr.getKind()) { + case CONSTANT: + CelValue constValue = DEFAULT_VALUE_CONVERTER.fromJavaObjectToCelValue(celExpr.constant().objectValue()); + return new EvalConstant(constValue); + case NOT_SET: + break; + case IDENT: + break; + case SELECT: + break; + case CALL: + break; + case LIST: + break; + case STRUCT: + break; + case MAP: + break; + case COMPREHENSION: + break; + } + + throw new IllegalArgumentException("foo"); + } + + static CelLiteRuntime.Program plan(CelAbstractSyntaxTree ast) { + return CelValueProgram.create(plan(ast.getExpr()))); + } +} diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 5137cad39..bf001cfe2 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -72,6 +72,7 @@ java_library( "//runtime:late_function_binding", "//runtime:lite_runtime", "//runtime:lite_runtime_factory", + "//runtime:program_planner", "//runtime:proto_message_activation_factory", "//runtime:proto_message_runtime_equality", "//runtime:proto_message_runtime_helpers", diff --git a/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java new file mode 100644 index 000000000..8f77ddbb0 --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java @@ -0,0 +1,26 @@ +package dev.cel.runtime; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.compiler.CelCompiler; +import dev.cel.compiler.CelCompilerFactory; +import dev.cel.runtime.CelLiteRuntime.Program; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public final class ProgramPlannerTest { + private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder().build(); + + @Test + public void planConst() throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile("1").getAst(); + Program program = ProgramPlanner.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo(1); + } +} From 40cc20c8fe115d0edacbb18be61f7b43592c8faf Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Sun, 8 Jun 2025 09:48:56 -0700 Subject: [PATCH 02/30] Add constant test cases and CelValueConverter adapation logic --- BUILD.bazel | 2 +- .../test/java/dev/cel/bundle/CelImplTest.java | 7 ++-- .../java/dev/cel/common/values/BUILD.bazel | 2 ++ .../cel/common/values/CelValueConverter.java | 10 +++++- .../src/main/java/dev/cel/runtime/BUILD.bazel | 4 +++ .../java/dev/cel/runtime/LiteRuntimeImpl.java | 3 +- .../java/dev/cel/runtime/ProgramPlanner.java | 3 +- .../dev/cel/runtime/ProgramPlannerTest.java | 36 +++++++++++++++++-- 8 files changed, 55 insertions(+), 12 deletions(-) diff --git a/BUILD.bazel b/BUILD.bazel index 06942bc50..41a15f355 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -151,7 +151,7 @@ java_package_configuration( "-Xep:ProtoFieldPreconditionsCheckNotNull:ERROR", "-Xep:ProtocolBufferOrdinal:ERROR", "-Xep:ReferenceEquality:ERROR", - "-Xep:RemoveUnusedImports:ERROR", + # "-Xep:RemoveUnusedImports:ERROR", "-Xep:RequiredModifiers:ERROR", "-Xep:ShortCircuitBoolean:ERROR", "-Xep:SimpleDateFormatConstant:ERROR", diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index 2d337e4cf..fce33972c 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -566,12 +566,13 @@ public void program_withCelValue() throws Exception { .setName("variable") .setIdent(IdentDecl.newBuilder().setType(CelProtoTypes.STRING)) .build()) - .setResultType(SimpleType.BOOL) .build(); - CelRuntime.Program program = cel.createProgram(cel.compile("variable == 'hello'").getAst()); + CelRuntime.Program program = cel.createProgram(cel.compile("b'abc'").getAst()); - assertThat(program.eval(ImmutableMap.of("variable", "hello"))).isEqualTo(true); + + Object foo = program.eval(); + System.out.println(foo); } @Test diff --git a/common/src/main/java/dev/cel/common/values/BUILD.bazel b/common/src/main/java/dev/cel/common/values/BUILD.bazel index 9ffa5bad3..efd23242e 100644 --- a/common/src/main/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel @@ -131,6 +131,7 @@ java_library( "//common/types:type_providers", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", "@maven//:org_jspecify_jspecify", ], ) @@ -152,6 +153,7 @@ cel_android_library( "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:org_jspecify_jspecify", "@maven_android//:com_google_guava_guava", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java index f352f7b0b..e838f3213 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.primitives.UnsignedLong; import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.ByteString; import dev.cel.common.annotations.Internal; import java.util.Map; import java.util.Map.Entry; @@ -76,7 +77,11 @@ public CelValue fromJavaObjectToCelValue(Object value) { return (CelValue) value; } - if (value instanceof Iterable) { + if (value instanceof ByteString) { + // TODO: CelConstant should hold this value instead of adapting it here + return BytesValue.create(CelByteString.of(((ByteString) value).toByteArray())); + } else if (value instanceof Iterable) { + return toListValue((Iterable) value); } else if (value instanceof Map) { return toMapValue((Map) value); @@ -87,6 +92,9 @@ public CelValue fromJavaObjectToCelValue(Object value) { .orElse(OptionalValue.EMPTY); } else if (value instanceof Exception) { return ErrorValue.create((Exception) value); + } else if (value instanceof com.google.protobuf.NullValue) { + // TODO: CelConstant should hold this value instead of adapting it here + return NullValue.NULL_VALUE; } return fromJavaPrimitiveToCelValue(value); diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 0c06239ce..b0be1a0b2 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -1234,6 +1234,8 @@ java_library( ":interpreter", ":lite_runtime", "//:auto_value", + "//common/values", + "//common/values:cel_value", "@maven//:com_google_errorprone_error_prone_annotations", ], ) @@ -1243,6 +1245,8 @@ java_library( srcs = ["ProgramPlanner.java"], deps = [ ":activation", + ":cel_value_interpretable", + ":cel_value_program", ":eval_const", ":evaluation_exception", ":function_resolver", diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index 611a559c8..f518ab277 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -21,8 +21,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import dev.cel.runtime.DefaultInterpreter.DefaultInterpretable; -import javax.annotation.concurrent.ThreadSafe; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelOptions; import dev.cel.common.values.CelValueProvider; @@ -30,6 +28,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Optional; +import javax.annotation.concurrent.ThreadSafe; @ThreadSafe final class LiteRuntimeImpl implements CelLiteRuntime { diff --git a/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java index fda258c42..d8fc71037 100644 --- a/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java @@ -18,7 +18,6 @@ private static CelValueInterpretable plan(CelExpr celExpr) { case NOT_SET: break; case IDENT: - break; case SELECT: break; case CALL: @@ -37,6 +36,6 @@ private static CelValueInterpretable plan(CelExpr celExpr) { } static CelLiteRuntime.Program plan(CelAbstractSyntaxTree ast) { - return CelValueProgram.create(plan(ast.getExpr()))); + return CelValueProgram.create(plan(ast.getExpr())); } } diff --git a/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java index 8f77ddbb0..040936e41 100644 --- a/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java @@ -2,11 +2,16 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.common.primitives.UnsignedLong; +import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.values.CelByteString; +import dev.cel.common.values.NullValue; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; import dev.cel.runtime.CelLiteRuntime.Program; +import java.nio.charset.StandardCharsets; import org.junit.Test; import org.junit.runner.RunWith; @@ -14,13 +19,38 @@ public final class ProgramPlannerTest { private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder().build(); + @SuppressWarnings("ImmutableEnumChecker") // Test only + private enum ConstantTestCase { + NULL( + "null", + NullValue.NULL_VALUE), + BOOLEAN( + "true", + true), + INT64( + "42", 42L), + UINT64("42u", UnsignedLong.valueOf(42)), + DOUBLE("1.5", 1.5d), + STRING("'hello world'", "hello world"), + BYTES( + "b'abc'", CelByteString.of("abc".getBytes(StandardCharsets.UTF_8))); + + private final String expression; + private final Object expected; + + ConstantTestCase(String expression, Object expected) { + this.expression = expression; + this.expected = expected; + } + } + @Test - public void planConst() throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile("1").getAst(); + public void planConst(@TestParameter ConstantTestCase testCase) throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile(testCase.expression).getAst(); Program program = ProgramPlanner.plan(ast); Object result = program.eval(); - assertThat(result).isEqualTo(1); + assertThat(result).isEqualTo(testCase.expected); } } From 21d21784d4db09f97a1532f77d06c1382df2dd51 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Sun, 8 Jun 2025 10:26:43 -0700 Subject: [PATCH 03/30] Plan ident --- BUILD.bazel | 4 +-- .../src/main/java/dev/cel/runtime/BUILD.bazel | 9 +++++ .../java/dev/cel/runtime/EvalAttribute.java | 7 ++++ .../java/dev/cel/runtime/ProgramPlanner.java | 34 +++++++++++++++---- .../dev/cel/runtime/ProgramPlannerTest.java | 16 ++++++++- 5 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/EvalAttribute.java diff --git a/BUILD.bazel b/BUILD.bazel index 41a15f355..7474893e7 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -163,8 +163,8 @@ java_package_configuration( "-Xep:TypeParameterUnusedInFormals:ERROR", "-Xep:URLEqualsHashCode:ERROR", "-Xep:UnsynchronizedOverridesSynchronized:ERROR", - "-Xep:UnusedMethod:ERROR", - "-Xep:UnusedVariable:ERROR", + # "-Xep:UnusedMethod:ERROR", + # "-Xep:UnusedVariable:ERROR", "-Xep:WaitNotInLoop:ERROR", "-Xep:WildcardImport:ERROR", "-XepDisableWarningsInGeneratedCode", diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index b0be1a0b2..4d369c1ca 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -1207,6 +1207,14 @@ java_library( ], ) +java_library( + name = "eval_attribute", + srcs = ["EvalAttribute.java"], + deps = [ + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + java_library( name = "cel_value_interpretable", srcs = ["CelValueInterpretable.java"], @@ -1258,5 +1266,6 @@ java_library( "//common/values", "//common/values:cel_value", "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/EvalAttribute.java new file mode 100644 index 000000000..38946dbb8 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/EvalAttribute.java @@ -0,0 +1,7 @@ +package dev.cel.runtime; + +import com.google.errorprone.annotations.Immutable; + +@Immutable +final class EvalAttribute { +} diff --git a/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java index d8fc71037..68037dabd 100644 --- a/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java @@ -1,8 +1,11 @@ package dev.cel.runtime; +import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; +import dev.cel.common.ast.CelReference; import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; @@ -10,14 +13,13 @@ final class ProgramPlanner { private static final CelValueConverter DEFAULT_VALUE_CONVERTER = new CelValueConverter(); - private static CelValueInterpretable plan(CelExpr celExpr) { + private static CelValueInterpretable plan(CelExpr celExpr, + ImmutableMap referenceMap) { switch (celExpr.getKind()) { case CONSTANT: - CelValue constValue = DEFAULT_VALUE_CONVERTER.fromJavaObjectToCelValue(celExpr.constant().objectValue()); - return new EvalConstant(constValue); - case NOT_SET: - break; + return fromConstExpr(celExpr.constant()); case IDENT: + return planIdent(celExpr, referenceMap); case SELECT: break; case CALL: @@ -30,12 +32,32 @@ private static CelValueInterpretable plan(CelExpr celExpr) { break; case COMPREHENSION: break; + case NOT_SET: + throw new UnsupportedOperationException("Unsupported kind: " + celExpr.getKind()); } throw new IllegalArgumentException("foo"); } + private static CelValueInterpretable planIdent(CelExpr celExpr, + ImmutableMap referenceMap) { + CelReference ref = referenceMap.get(celExpr.id()); + if (ref != null) { + if (ref.value().isPresent()) { + return fromConstExpr(ref.value().get()); + } + } + + return null; + } + static CelLiteRuntime.Program plan(CelAbstractSyntaxTree ast) { - return CelValueProgram.create(plan(ast.getExpr())); + CelValueInterpretable plannedInterpretable = plan(ast.getExpr(), ast.getReferenceMap()); + return CelValueProgram.create(plannedInterpretable); + } + + private static EvalConstant fromConstExpr(CelConstant celConstant) { + CelValue celValue = DEFAULT_VALUE_CONVERTER.fromJavaObjectToCelValue(celConstant.objectValue()); + return new EvalConstant(celValue); } } diff --git a/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java index 040936e41..ed40823f6 100644 --- a/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java @@ -10,6 +10,7 @@ import dev.cel.common.values.NullValue; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; +import dev.cel.expr.conformance.proto2.TestAllTypes; import dev.cel.runtime.CelLiteRuntime.Program; import java.nio.charset.StandardCharsets; import org.junit.Test; @@ -17,7 +18,10 @@ @RunWith(TestParameterInjector.class) public final class ProgramPlannerTest { - private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder().build(); + private static final CelCompiler CEL_COMPILER = + CelCompilerFactory.standardCelCompilerBuilder() + .addMessageTypes(TestAllTypes.getDescriptor()) + .build(); @SuppressWarnings("ImmutableEnumChecker") // Test only private enum ConstantTestCase { @@ -53,4 +57,14 @@ public void planConst(@TestParameter ConstantTestCase testCase) throws Exception assertThat(result).isEqualTo(testCase.expected); } + + @Test + public void planIdentEnum() throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile("cel.expr.conformance.proto2.GlobalEnum.GAR").getAst(); + Program program = ProgramPlanner.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo(1); + } } From 372e40d4f8b6e0198f20aba9d30865aa2d6c7194 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 1 Jul 2025 15:57:07 -0700 Subject: [PATCH 04/30] Add CelTypeProvider as dependency to planner --- .../src/main/java/dev/cel/runtime/BUILD.bazel | 2 + .../cel/runtime/CelValueInterpretable.java | 3 +- .../java/dev/cel/runtime/LiteRuntimeImpl.java | 25 +++++++++-- .../java/dev/cel/runtime/ProgramImpl.java | 10 ++--- .../java/dev/cel/runtime/ProgramPlanner.java | 28 ++++++++---- .../src/test/java/dev/cel/runtime/BUILD.bazel | 1 + .../dev/cel/runtime/ProgramPlannerTest.java | 45 +++++++++++++++++-- 7 files changed, 92 insertions(+), 22 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 4d369c1ca..dd97a6a80 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -910,6 +910,7 @@ java_library( "//:auto_value", "//common:cel_ast", "//common:options", + "//common/types:type_providers", "//common/values:cel_value_provider", "//runtime/standard:standard_function", "@maven//:com_google_code_findbugs_annotations", @@ -1263,6 +1264,7 @@ java_library( ":lite_runtime", "//common:cel_ast", "//common/ast", + "//common/types:type_providers", "//common/values", "//common/values:cel_value", "@maven//:com_google_errorprone_error_prone_annotations", diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java b/runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java index 608b20f68..998acd9c2 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java +++ b/runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java @@ -10,8 +10,7 @@ */ @Immutable @Internal -public interface CelValueInterpretable extends Interpretable{ +public interface CelValueInterpretable { - @Override CelValue eval(GlobalResolver resolver) throws CelEvaluationException; } diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index f518ab277..5a0412952 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -18,16 +18,20 @@ import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelOptions; +import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; import dev.cel.common.values.CelValueProvider; import dev.cel.runtime.standard.CelStandardFunction; import java.util.Arrays; import java.util.HashMap; import java.util.Optional; +import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; @ThreadSafe @@ -37,7 +41,7 @@ final class LiteRuntimeImpl implements CelLiteRuntime { private final ImmutableList customFunctionBindings; private final ImmutableSet celStandardFunctions; private final CelValueProvider celValueProvider; - private final boolean enablePlanner; + private final @Nullable ProgramPlanner planner; // This does not affect the evaluation behavior in any manner. // CEL-Internal-4 @@ -46,8 +50,8 @@ final class LiteRuntimeImpl implements CelLiteRuntime { @Override public Program createProgram(CelAbstractSyntaxTree ast) { checkState(ast.isChecked(), "programs must be created from checked expressions"); - if (enablePlanner) { - return ProgramPlanner.plan(ast); + if (planner != null) { + return planner.plan(ast); } else { return LiteProgramImpl.create(interpreter.createInterpretable(ast)); } @@ -243,6 +247,19 @@ private LiteRuntimeImpl( this.celStandardFunctions = celStandardFunctions; this.runtimeLibraries = runtimeLibraries; this.celValueProvider = celValueProvider; - this.enablePlanner = enablePlanner; + if (enablePlanner) { + this.planner = new ProgramPlanner(new CelTypeProvider() { + @Override + public ImmutableCollection types() { + return null; + } + @Override + public Optional findType(String typeName) { + return Optional.empty(); + } + }); + } else { + this.planner = null; + } } } diff --git a/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java b/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java index d0e64429b..a92dc7175 100644 --- a/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java @@ -158,11 +158,11 @@ private Object evalInternal( lateBoundFunctionResolver, listener); } else { - if (lateBoundFunctionResolver.isPresent() && listener.isPresent()) { - return impl.eval( - context.variableResolver(), lateBoundFunctionResolver.get(), listener.get()); - } else if (lateBoundFunctionResolver.isPresent()) { - return impl.eval(context.variableResolver(), lateBoundFunctionResolver.get()); + return impl.eval(context.variableResolver()); + // if (lateBoundFunctionResolver.isPresent()) { + // return impl.eval(context.variableResolver(), lateBoundFunctionResolver.get(), listener); + // } + // return impl.eval(context.variableResolver(), listener); } else if (listener.isPresent()) { return impl.eval(context.variableResolver(), listener.get()); } diff --git a/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java index 68037dabd..72c47f1af 100644 --- a/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java @@ -6,18 +6,23 @@ import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelReference; +import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; +import dev.cel.common.values.TypeValue; +import java.util.NoSuchElementException; @Immutable final class ProgramPlanner { private static final CelValueConverter DEFAULT_VALUE_CONVERTER = new CelValueConverter(); + private final CelTypeProvider typeProvider; - private static CelValueInterpretable plan(CelExpr celExpr, + private CelValueInterpretable plan(CelExpr celExpr, ImmutableMap referenceMap) { switch (celExpr.getKind()) { case CONSTANT: - return fromConstExpr(celExpr.constant()); + return fromCelConstant(celExpr.constant()); case IDENT: return planIdent(celExpr, referenceMap); case SELECT: @@ -39,25 +44,32 @@ private static CelValueInterpretable plan(CelExpr celExpr, throw new IllegalArgumentException("foo"); } - private static CelValueInterpretable planIdent(CelExpr celExpr, + private CelValueInterpretable planIdent(CelExpr celExpr, ImmutableMap referenceMap) { CelReference ref = referenceMap.get(celExpr.id()); if (ref != null) { if (ref.value().isPresent()) { - return fromConstExpr(ref.value().get()); + return fromCelConstant(ref.value().get()); } + + CelType type = typeProvider.findType(ref.name()).orElseThrow(() -> new NoSuchElementException("Reference to undefined type: " + ref.name())); + return new EvalConstant(TypeValue.create(type)); } return null; } - static CelLiteRuntime.Program plan(CelAbstractSyntaxTree ast) { + private static EvalConstant fromCelConstant(CelConstant celConstant) { + CelValue celValue = DEFAULT_VALUE_CONVERTER.fromJavaObjectToCelValue(celConstant.objectValue()); + return new EvalConstant(celValue); + } + + CelLiteRuntime.Program plan(CelAbstractSyntaxTree ast) { CelValueInterpretable plannedInterpretable = plan(ast.getExpr(), ast.getReferenceMap()); return CelValueProgram.create(plannedInterpretable); } - private static EvalConstant fromConstExpr(CelConstant celConstant) { - CelValue celValue = DEFAULT_VALUE_CONVERTER.fromJavaObjectToCelValue(celConstant.objectValue()); - return new EvalConstant(celValue); + ProgramPlanner(CelTypeProvider typeProvider) { + this.typeProvider = typeProvider; } } diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index bf001cfe2..e7e033fc8 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -49,6 +49,7 @@ java_library( "//common/types", "//common/types:cel_v1alpha1_types", "//common/types:message_type_provider", + "//common/types:type_providers", "//common/values", "//common/values:cel_byte_string", "//common/values:cel_value_provider", diff --git a/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java index ed40823f6..492197c42 100644 --- a/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java @@ -2,10 +2,15 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.common.collect.ImmutableCollection; import com.google.common.primitives.UnsignedLong; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.SimpleType; +import dev.cel.common.types.TypeType; import dev.cel.common.values.CelByteString; import dev.cel.common.values.NullValue; import dev.cel.compiler.CelCompiler; @@ -13,6 +18,7 @@ import dev.cel.expr.conformance.proto2.TestAllTypes; import dev.cel.runtime.CelLiteRuntime.Program; import java.nio.charset.StandardCharsets; +import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; @@ -22,6 +28,18 @@ public final class ProgramPlannerTest { CelCompilerFactory.standardCelCompilerBuilder() .addMessageTypes(TestAllTypes.getDescriptor()) .build(); + private static ProgramPlanner PLANNER = new ProgramPlanner( + new CelTypeProvider() { + @Override + public ImmutableCollection types() { + throw new UnsupportedOperationException(); + } + @Override + public Optional findType(String typeName) { + return Optional.empty(); + } + } + ); @SuppressWarnings("ImmutableEnumChecker") // Test only private enum ConstantTestCase { @@ -51,7 +69,7 @@ private enum ConstantTestCase { @Test public void planConst(@TestParameter ConstantTestCase testCase) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.compile(testCase.expression).getAst(); - Program program = ProgramPlanner.plan(ast); + Program program = PLANNER.plan(ast); Object result = program.eval(); @@ -59,12 +77,33 @@ public void planConst(@TestParameter ConstantTestCase testCase) throws Exception } @Test - public void planIdentEnum() throws Exception { + public void planIdent_enum() throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.compile("cel.expr.conformance.proto2.GlobalEnum.GAR").getAst(); - Program program = ProgramPlanner.plan(ast); + Program program = PLANNER.plan(ast); Object result = program.eval(); assertThat(result).isEqualTo(1); } + + @Test + public void planIdent_typeLiteral() throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile("uint").getAst(); + Program program = PLANNER.plan(ast); + + TypeType result = (TypeType) program.eval(); + + assertThat(result).isEqualTo(TypeType.create(SimpleType.UINT)); + } + + @Test + public void smokeTest() throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.compile("uint").getAst(); + CelRuntime.Program program = CelRuntimeFactory.standardCelRuntimeBuilder().build().createProgram(ast); + + TypeType result = (TypeType) program.eval(); + + assertThat(result).isEqualTo(TypeType.create(SimpleType.UINT)); + + } } From 3fa7689b1429781f8f19f3cd4882dc2e9ee7a8ce Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 3 Jul 2025 00:06:46 -0700 Subject: [PATCH 05/30] Ident planning --- .../java/dev/cel/common/types/BUILD.bazel | 12 ++ .../cel/common/types/DefaultTypeProvider.java | 54 +++++++++ common/types/BUILD.bazel | 5 + .../main/java/dev/cel/runtime/Activation.java | 2 + .../src/main/java/dev/cel/runtime/BUILD.bazel | 7 ++ .../cel/runtime/CelValueInterpretable.java | 2 +- .../java/dev/cel/runtime/CelValueProgram.java | 17 ++- .../java/dev/cel/runtime/EvalAttribute.java | 103 +++++++++++++++++- .../java/dev/cel/runtime/EvalConstant.java | 6 +- .../java/dev/cel/runtime/LiteRuntimeImpl.java | 2 +- .../java/dev/cel/runtime/ProgramPlanner.java | 50 ++++++--- .../src/test/java/dev/cel/runtime/BUILD.bazel | 1 + .../dev/cel/runtime/ProgramPlannerTest.java | 85 +++++++++++---- 13 files changed, 302 insertions(+), 44 deletions(-) create mode 100644 common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java diff --git a/common/src/main/java/dev/cel/common/types/BUILD.bazel b/common/src/main/java/dev/cel/common/types/BUILD.bazel index 579fd31c5..962c351c7 100644 --- a/common/src/main/java/dev/cel/common/types/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/types/BUILD.bazel @@ -183,6 +183,18 @@ java_library( ], ) +java_library( + name = "default_type_provider", + srcs = [ + "DefaultTypeProvider.java", + ], + deps = [ + ":type_providers", + ":types", + "@maven_android//:com_google_guava_guava", + ], +) + cel_android_library( name = "cel_types_android", srcs = ["CelTypes.java"], diff --git a/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java b/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java new file mode 100644 index 000000000..c2fd6d364 --- /dev/null +++ b/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java @@ -0,0 +1,54 @@ +package dev.cel.common.types; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.Optional; + +public class DefaultTypeProvider implements CelTypeProvider { + + private static final ImmutableMap COMMON_TYPES = + ImmutableMap.builder() + .put("bool", TypeType.create(SimpleType.BOOL)) + .put("bytes", TypeType.create(SimpleType.BYTES)) + .put("double", TypeType.create(SimpleType.DOUBLE)) + .put("int", TypeType.create(SimpleType.INT)) + .put("uint", TypeType.create(SimpleType.UINT)) + .put("string", TypeType.create(SimpleType.STRING)) + .put("null_type", TypeType.create(SimpleType.NULL_TYPE)) + .put("dyn",TypeType.create(SimpleType.DYN)) + .put("list", TypeType.create(ListType.create(SimpleType.DYN))) + .put("map", TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN))) + .put("google.protobuf.Duration", TypeType.create(SimpleType.DURATION)) + .put("google.protobuf.Timestamp", TypeType.create(SimpleType.TIMESTAMP)) + .put("optional_type", TypeType.create(OptionalType.create(SimpleType.DYN))) // TODO: Move to CelOptionalLibrary + .buildOrThrow(); + + // private static final ImmutableMap, TypeType> EXTENDABLE_TYPES = + // ImmutableMap., TypeType>builder() + // .put(Collection.class, TypeType.create(ListType.create(SimpleType.DYN))) + // .put(ByteString.class, TypeType.create(SimpleType.BYTES)) + // .put(Map.class, TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN))) + // .buildOrThrow(); + + private static Map.Entry newTypeMapEntry(CelType type) { + return Maps.immutableEntry(type.name(), TypeType.create(type)); + } + + @Override + public ImmutableCollection types() { + return COMMON_TYPES.values(); + } + @Override + public Optional findType(String typeName) { + return Optional.ofNullable(COMMON_TYPES.get(typeName)); + } + + public static DefaultTypeProvider create() { + return new DefaultTypeProvider(); + } + + private DefaultTypeProvider() {} +} diff --git a/common/types/BUILD.bazel b/common/types/BUILD.bazel index 6c2b1a269..3531edd09 100644 --- a/common/types/BUILD.bazel +++ b/common/types/BUILD.bazel @@ -50,6 +50,11 @@ java_library( exports = ["//common/src/main/java/dev/cel/common/types:cel_proto_message_types"], ) +java_library( + name = "default_type_provider", + exports = ["//common/src/main/java/dev/cel/common/types:default_type_provider"], +) + java_library( name = "cel_v1alpha1_types", visibility = ["//:internal"], diff --git a/runtime/src/main/java/dev/cel/runtime/Activation.java b/runtime/src/main/java/dev/cel/runtime/Activation.java index 074a461a2..3fb7ca484 100644 --- a/runtime/src/main/java/dev/cel/runtime/Activation.java +++ b/runtime/src/main/java/dev/cel/runtime/Activation.java @@ -58,6 +58,7 @@ public static Activation of(final String name, Object value) { @Override public @Nullable Object resolve(String theName) { if (theName.equals(name)) { + // TODO: Decouple return RuntimeHelpers.maybeAdaptPrimitive(value); } return null; @@ -65,6 +66,7 @@ public static Activation of(final String name, Object value) { @Override public String toString() { + // TODO: Remove. if (value instanceof ByteString) { ByteString bs = (ByteString) value; StringBuilder val = new StringBuilder(); diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index dd97a6a80..3ea2c70ff 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -1212,7 +1212,13 @@ java_library( name = "eval_attribute", srcs = ["EvalAttribute.java"], deps = [ + ":cel_value_interpretable", + ":evaluation_exception", + ":interpretable", + "//common/values", + "//common/values:cel_value", "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", ], ) @@ -1256,6 +1262,7 @@ java_library( ":activation", ":cel_value_interpretable", ":cel_value_program", + ":eval_attribute", ":eval_const", ":evaluation_exception", ":function_resolver", diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java b/runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java index 998acd9c2..faf226147 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java +++ b/runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java @@ -10,7 +10,7 @@ */ @Immutable @Internal -public interface CelValueInterpretable { +interface CelValueInterpretable { CelValue eval(GlobalResolver resolver) throws CelEvaluationException; } diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueProgram.java b/runtime/src/main/java/dev/cel/runtime/CelValueProgram.java index 9db5fc27c..a93728338 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelValueProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/CelValueProgram.java @@ -9,27 +9,32 @@ @Immutable @AutoValue abstract class CelValueProgram implements CelLiteRuntime.Program { - private static final CelValueConverter DEFAULT_VALUE_CONVERTER = new CelValueConverter(); - abstract CelValueInterpretable interpretable(); + abstract CelValueConverter celValueConverter(); + @Override public Object eval() throws CelEvaluationException { CelValue evalResult = interpretable().eval(GlobalResolver.EMPTY); - return DEFAULT_VALUE_CONVERTER.fromCelValueToJavaObject(evalResult); + return celValueConverter().fromCelValueToJavaObject(evalResult); } @Override public Object eval(Map mapValue) throws CelEvaluationException { - return null; + CelValue evalResult = interpretable().eval(Activation.copyOf(mapValue)); + return celValueConverter().fromCelValueToJavaObject(evalResult); } + @Override public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) throws CelEvaluationException { return null; } - static CelLiteRuntime.Program create(CelValueInterpretable interpretable) { - return new AutoValue_CelValueProgram(interpretable); + static CelLiteRuntime.Program create( + CelValueInterpretable interpretable, + CelValueConverter celValueConverter + ) { + return new AutoValue_CelValueProgram(interpretable, celValueConverter); } } diff --git a/runtime/src/main/java/dev/cel/runtime/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/EvalAttribute.java index 38946dbb8..71ad1d4e0 100644 --- a/runtime/src/main/java/dev/cel/runtime/EvalAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/EvalAttribute.java @@ -1,7 +1,108 @@ package dev.cel.runtime; +import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; @Immutable -final class EvalAttribute { +final class EvalAttribute implements CelValueInterpretable { + + private final long id; + private final CelValueConverter celValueConverter; + private final Attribute attr; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + Object obj = attr.resolve(resolver); + return celValueConverter.fromJavaObjectToCelValue(obj); + } + + static EvalAttribute newAbsoluteAttribute(long id, CelValueConverter celValueConverter, String... names) { + return new EvalAttribute(id, celValueConverter, new AbsoluteAttribute(ImmutableList.copyOf(names))); + } + + static EvalAttribute newMaybeAttribute(long id, CelValueConverter celValueConverter, String container, String... names) { + // TODO: Resolve container names + return new EvalAttribute( + id, + celValueConverter, + new MaybeAttribute( + ImmutableList.of(new NamespacedAttribute(ImmutableList.copyOf(names))) + ) + ); + } + + @Immutable + private interface Attribute { + Object resolve(GlobalResolver ctx); + } + + private static class MaybeAttribute implements Attribute { + private final ImmutableList attributes; + + @Override + public Object resolve(GlobalResolver ctx) { + for (Attribute attr : attributes) { + Object value = attr.resolve(ctx); + if (value != null) { + return value; + } + } + + throw new IllegalArgumentException("no such attribute(s): %s"); + } + + private MaybeAttribute(ImmutableList attributes) { + this.attributes = attributes; + } + } + + private static class NamespacedAttribute implements Attribute { + private final ImmutableList namespacedNames; + + @Override + public Object resolve(GlobalResolver ctx) { + for (String name : namespacedNames) { + Object value = ctx.resolve(name); + if (value != null) { + return value; + } + + // TODO: apply qualifiers + } + + throw new IllegalArgumentException("no such attribute(s): %s"); + } + + private NamespacedAttribute(ImmutableList namespacedNames) { + this.namespacedNames = namespacedNames; + } + } + + private static class AbsoluteAttribute implements Attribute { + private final ImmutableList namespacedNames; + + @Override + public Object resolve(GlobalResolver ctx) { + for (String name : namespacedNames) { + Object value = ctx.resolve(name); + if (value != null) { + return value; + } + } + + throw new IllegalArgumentException("no such attribute(s): %s"); + } + + private AbsoluteAttribute(ImmutableList namespacedNames) { + this.namespacedNames = namespacedNames; + } + } + + private EvalAttribute(long id, CelValueConverter celValueConverter, Attribute attr) { + this.id = id; + this.celValueConverter = celValueConverter; + this.attr = attr; + } } diff --git a/runtime/src/main/java/dev/cel/runtime/EvalConstant.java b/runtime/src/main/java/dev/cel/runtime/EvalConstant.java index 10f46f268..a6f2b8113 100644 --- a/runtime/src/main/java/dev/cel/runtime/EvalConstant.java +++ b/runtime/src/main/java/dev/cel/runtime/EvalConstant.java @@ -15,7 +15,11 @@ public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { return constant; } - EvalConstant(CelValue constant) { + static EvalConstant create(CelValue constant) { + return new EvalConstant(constant); + } + + private EvalConstant(CelValue constant) { this.constant = constant; } } \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index 5a0412952..a8cf41e19 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -257,7 +257,7 @@ public ImmutableCollection types() { public Optional findType(String typeName) { return Optional.empty(); } - }); + }, null); } else { this.planner = null; } diff --git a/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java index 72c47f1af..247950496 100644 --- a/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java @@ -6,6 +6,7 @@ import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelReference; +import dev.cel.common.types.CelKind; import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypeProvider; import dev.cel.common.values.CelValue; @@ -15,16 +16,17 @@ @Immutable final class ProgramPlanner { - private static final CelValueConverter DEFAULT_VALUE_CONVERTER = new CelValueConverter(); private final CelTypeProvider typeProvider; + private final CelValueConverter celValueConverter; private CelValueInterpretable plan(CelExpr celExpr, + ImmutableMap typeMap, ImmutableMap referenceMap) { switch (celExpr.getKind()) { case CONSTANT: return fromCelConstant(celExpr.constant()); case IDENT: - return planIdent(celExpr, referenceMap); + return planIdent(celExpr, typeMap, referenceMap); case SELECT: break; case CALL: @@ -44,32 +46,50 @@ private CelValueInterpretable plan(CelExpr celExpr, throw new IllegalArgumentException("foo"); } - private CelValueInterpretable planIdent(CelExpr celExpr, + private CelValueInterpretable planIdent( + CelExpr celExpr, + ImmutableMap typeMap, ImmutableMap referenceMap) { CelReference ref = referenceMap.get(celExpr.id()); if (ref != null) { - if (ref.value().isPresent()) { - return fromCelConstant(ref.value().get()); - } + return planCheckedIdent(celExpr.id(), ref, typeMap); + } + + return EvalAttribute.newMaybeAttribute(celExpr.id(), celValueConverter, "", celExpr.ident().name()); + } + + private CelValueInterpretable planCheckedIdent( + long id, + CelReference identRef, + ImmutableMap typeMap) { + if (identRef.value().isPresent()) { + return fromCelConstant(identRef.value().get()); + } - CelType type = typeProvider.findType(ref.name()).orElseThrow(() -> new NoSuchElementException("Reference to undefined type: " + ref.name())); - return new EvalConstant(TypeValue.create(type)); + CelType type = typeMap.get(id); + if (type.kind().equals(CelKind.TYPE)) { + CelType identType = typeProvider.findType(identRef.name()).orElseThrow(() -> new NoSuchElementException("Reference to undefined type: " + identRef.name())); + return EvalConstant.create(TypeValue.create(identType)); } - return null; + return EvalAttribute.newAbsoluteAttribute(id, celValueConverter, identRef.name()); } - private static EvalConstant fromCelConstant(CelConstant celConstant) { - CelValue celValue = DEFAULT_VALUE_CONVERTER.fromJavaObjectToCelValue(celConstant.objectValue()); - return new EvalConstant(celValue); + private EvalConstant fromCelConstant(CelConstant celConstant) { + CelValue celValue = celValueConverter.fromJavaObjectToCelValue(celConstant.objectValue()); + return EvalConstant.create(celValue); } CelLiteRuntime.Program plan(CelAbstractSyntaxTree ast) { - CelValueInterpretable plannedInterpretable = plan(ast.getExpr(), ast.getReferenceMap()); - return CelValueProgram.create(plannedInterpretable); + CelValueInterpretable plannedInterpretable = plan(ast.getExpr(), ast.getTypeMap(), ast.getReferenceMap()); + return CelValueProgram.create(plannedInterpretable, celValueConverter); } - ProgramPlanner(CelTypeProvider typeProvider) { + ProgramPlanner( + CelTypeProvider typeProvider, + CelValueConverter celValueConverter + ) { this.typeProvider = typeProvider; + this.celValueConverter = celValueConverter; } } diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index e7e033fc8..446a9e806 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -48,6 +48,7 @@ java_library( "//common/internal:well_known_proto", "//common/types", "//common/types:cel_v1alpha1_types", + "//common/types:default_type_provider", "//common/types:message_type_provider", "//common/types:type_providers", "//common/values", diff --git a/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java index 492197c42..fa09e2390 100644 --- a/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java @@ -2,23 +2,28 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableMap; import com.google.common.primitives.UnsignedLong; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelValidationException; import dev.cel.common.types.CelType; -import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.DefaultTypeProvider; +import dev.cel.common.types.ListType; +import dev.cel.common.types.MapType; +import dev.cel.common.types.OptionalType; import dev.cel.common.types.SimpleType; import dev.cel.common.types.TypeType; import dev.cel.common.values.CelByteString; +import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.NullValue; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto2.TestAllTypes; +import dev.cel.extensions.CelOptionalLibrary; import dev.cel.runtime.CelLiteRuntime.Program; import java.nio.charset.StandardCharsets; -import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,21 +31,17 @@ public final class ProgramPlannerTest { private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() + .addVar("int_var", SimpleType.INT) + .addLibraries(CelOptionalLibrary.INSTANCE) .addMessageTypes(TestAllTypes.getDescriptor()) .build(); private static ProgramPlanner PLANNER = new ProgramPlanner( - new CelTypeProvider() { - @Override - public ImmutableCollection types() { - throw new UnsupportedOperationException(); - } - @Override - public Optional findType(String typeName) { - return Optional.empty(); - } - } + DefaultTypeProvider.create(), + new CelValueConverter() ); + @TestParameter boolean isParseOnly; + @SuppressWarnings("ImmutableEnumChecker") // Test only private enum ConstantTestCase { NULL( @@ -68,7 +69,7 @@ private enum ConstantTestCase { @Test public void planConst(@TestParameter ConstantTestCase testCase) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(testCase.expression).getAst(); + CelAbstractSyntaxTree ast = compile(testCase.expression); Program program = PLANNER.plan(ast); Object result = program.eval(); @@ -78,7 +79,7 @@ public void planConst(@TestParameter ConstantTestCase testCase) throws Exception @Test public void planIdent_enum() throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile("cel.expr.conformance.proto2.GlobalEnum.GAR").getAst(); + CelAbstractSyntaxTree ast = compile("cel.expr.conformance.proto2.GlobalEnum.GAR"); Program program = PLANNER.plan(ast); Object result = program.eval(); @@ -87,23 +88,69 @@ public void planIdent_enum() throws Exception { } @Test - public void planIdent_typeLiteral() throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile("uint").getAst(); + public void planIdent_variable() throws Exception { + CelAbstractSyntaxTree ast = compile("int_var"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(ImmutableMap.of("int_var", 1L)); + + assertThat(result).isEqualTo(1); + } + + + @SuppressWarnings("ImmutableEnumChecker") // Test only + private enum TypeLiteralTestCase { + BOOL("bool", SimpleType.BOOL), + BYTES("bytes", SimpleType.BYTES), + DOUBLE("double", SimpleType.DOUBLE), + INT("int", SimpleType.INT), + UINT("uint", SimpleType.UINT), + STRING("string", SimpleType.STRING), + DYN("dyn", SimpleType.DYN), + LIST("list", ListType.create(SimpleType.DYN)), + MAP("map", MapType.create(SimpleType.DYN, SimpleType.DYN)), + NULL("null_type", SimpleType.NULL_TYPE), + DURATION("google.protobuf.Duration", SimpleType.DURATION), + TIMESTAMP("google.protobuf.Timestamp", SimpleType.TIMESTAMP), + OPTIONAL("optional_type", OptionalType.create(SimpleType.DYN)), + ; + + private final String expression; + private final TypeType type; + + TypeLiteralTestCase(String expression, CelType type) { + this.expression = expression; + this.type = TypeType.create(type); + } + } + + @Test + public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception { + CelAbstractSyntaxTree ast = compile(testCase.expression); Program program = PLANNER.plan(ast); TypeType result = (TypeType) program.eval(); - assertThat(result).isEqualTo(TypeType.create(SimpleType.UINT)); + assertThat(result).isEqualTo(testCase.type); } + @Test public void smokeTest() throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile("uint").getAst(); + CelAbstractSyntaxTree ast = compile("google.protobuf.Duration"); CelRuntime.Program program = CelRuntimeFactory.standardCelRuntimeBuilder().build().createProgram(ast); TypeType result = (TypeType) program.eval(); assertThat(result).isEqualTo(TypeType.create(SimpleType.UINT)); + } + + private CelAbstractSyntaxTree compile(String expression) throws CelValidationException { + CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); + if (isParseOnly) { + return ast; + } + return CEL_COMPILER.check(ast).getAst(); } } From a57e4eb7744921d57b0c146ddcd507a0d73b06f8 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 3 Jul 2025 00:17:39 -0700 Subject: [PATCH 06/30] Move to planner package --- runtime/BUILD.bazel | 5 - runtime/planner/BUILD.bazel | 11 +++ .../src/main/java/dev/cel/runtime/BUILD.bazel | 86 +---------------- .../java/dev/cel/runtime/LiteRuntimeImpl.java | 3 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 92 +++++++++++++++++++ .../{ => planner}/CelValueInterpretable.java | 4 +- .../{ => planner}/CelValueProgram.java | 11 ++- .../runtime/{ => planner}/EvalAttribute.java | 4 +- .../runtime/{ => planner}/EvalConstant.java | 4 +- .../runtime/{ => planner}/ProgramPlanner.java | 18 +++- .../src/test/java/dev/cel/runtime/BUILD.bazel | 2 +- .../dev/cel/runtime/ProgramPlannerTest.java | 3 +- 12 files changed, 140 insertions(+), 103 deletions(-) create mode 100644 runtime/planner/BUILD.bazel create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel rename runtime/src/main/java/dev/cel/runtime/{ => planner}/CelValueInterpretable.java (77%) rename runtime/src/main/java/dev/cel/runtime/{ => planner}/CelValueProgram.java (77%) rename runtime/src/main/java/dev/cel/runtime/{ => planner}/EvalAttribute.java (96%) rename runtime/src/main/java/dev/cel/runtime/{ => planner}/EvalConstant.java (81%) rename runtime/src/main/java/dev/cel/runtime/{ => planner}/ProgramPlanner.java (87%) diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 4f18bb591..b7d59ce96 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -220,8 +220,3 @@ cel_android_library( visibility = ["//:internal"], exports = ["//runtime/src/main/java/dev/cel/runtime:lite_runtime_impl_android"], ) - -java_library( - name = "program_planner", - exports = ["//runtime/src/main/java/dev/cel/runtime:program_planner"], -) diff --git a/runtime/planner/BUILD.bazel b/runtime/planner/BUILD.bazel new file mode 100644 index 000000000..2cc40873d --- /dev/null +++ b/runtime/planner/BUILD.bazel @@ -0,0 +1,11 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//visibility:public"], +) + +java_library( + name = "program_planner", + exports = ["//runtime/src/main/java/dev/cel/runtime/planner:program_planner"], +) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 3ea2c70ff..d29f035b6 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -903,7 +903,6 @@ java_library( ":interpreter", ":lite_program_impl", ":lite_runtime", - ":program_planner", ":runtime_equality", ":runtime_helpers", ":type_resolver", @@ -912,6 +911,7 @@ java_library( "//common:options", "//common/types:type_providers", "//common/values:cel_value_provider", + "//runtime/planner:program_planner", "//runtime/standard:standard_function", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", @@ -1194,87 +1194,3 @@ cel_android_library( "@maven//:com_google_errorprone_error_prone_annotations", ] ) - -java_library( - name = "eval_const", - srcs = ["EvalConstant.java"], - deps = [ - ":cel_value_interpretable", - ":evaluation_exception", - ":interpretable", - "//common/values", - "//common/values:cel_value", - "@maven//:com_google_errorprone_error_prone_annotations", - ], -) - -java_library( - name = "eval_attribute", - srcs = ["EvalAttribute.java"], - deps = [ - ":cel_value_interpretable", - ":evaluation_exception", - ":interpretable", - "//common/values", - "//common/values:cel_value", - "@maven//:com_google_errorprone_error_prone_annotations", - "@maven//:com_google_guava_guava", - ], -) - -java_library( - name = "cel_value_interpretable", - srcs = ["CelValueInterpretable.java"], - deps = [ - ":evaluation_exception", - ":interpretable", - "//common/annotations", - "//common/values:cel_value", - "@maven//:com_google_errorprone_error_prone_annotations", - ], -) - -java_library( - name = "cel_value_program", - srcs = ["CelValueProgram.java"], - tags = [ - ], - deps = [ - ":activation", - ":cel_value_interpretable", - ":evaluation_exception", - ":evaluation_listener", - ":function_resolver", - ":interpretable", - ":interpreter", - ":lite_runtime", - "//:auto_value", - "//common/values", - "//common/values:cel_value", - "@maven//:com_google_errorprone_error_prone_annotations", - ], -) - -java_library( - name = "program_planner", - srcs = ["ProgramPlanner.java"], - deps = [ - ":activation", - ":cel_value_interpretable", - ":cel_value_program", - ":eval_attribute", - ":eval_const", - ":evaluation_exception", - ":function_resolver", - ":interpretable", - ":lite_program_impl", - ":lite_runtime", - "//common:cel_ast", - "//common/ast", - "//common/types:type_providers", - "//common/values", - "//common/values:cel_value", - "@maven//:com_google_errorprone_error_prone_annotations", - "@maven//:com_google_guava_guava", - ], -) diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index a8cf41e19..9d23131db 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -27,6 +27,7 @@ import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypeProvider; import dev.cel.common.values.CelValueProvider; +import dev.cel.runtime.planner.ProgramPlanner; import dev.cel.runtime.standard.CelStandardFunction; import java.util.Arrays; import java.util.HashMap; @@ -248,7 +249,7 @@ private LiteRuntimeImpl( this.runtimeLibraries = runtimeLibraries; this.celValueProvider = celValueProvider; if (enablePlanner) { - this.planner = new ProgramPlanner(new CelTypeProvider() { + this.planner = ProgramPlanner.newPlanner(new CelTypeProvider() { @Override public ImmutableCollection types() { return null; diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel new file mode 100644 index 000000000..7d339d47e --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -0,0 +1,92 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = [ + "//runtime/planner:__pkg__", + ], +) + +java_library( + name = "program_planner", + srcs = ["ProgramPlanner.java"], + deps = [ + ":cel_value_interpretable", + ":cel_value_program", + ":eval_attribute", + ":eval_const", + "//common:cel_ast", + "//common/annotations", + "//common/ast", + "//common/types:type_providers", + "//common/values", + "//common/values:cel_value", + "//runtime:activation", + "//runtime:evaluation_exception", + "//runtime:function_resolver", + "//runtime:interpretable", + "//runtime:lite_runtime", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "cel_value_interpretable", + srcs = ["CelValueInterpretable.java"], + deps = [ + "//common/annotations", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "cel_value_program", + srcs = ["CelValueProgram.java"], + tags = [ + ], + deps = [ + ":cel_value_interpretable", + "//:auto_value", + "//common/values", + "//common/values:cel_value", + "//runtime:activation", + "//runtime:evaluation_exception", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "//runtime:interpreter", + "//runtime:lite_runtime", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "eval_const", + srcs = ["EvalConstant.java"], + deps = [ + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "eval_attribute", + srcs = ["EvalAttribute.java"], + deps = [ + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java b/runtime/src/main/java/dev/cel/runtime/planner/CelValueInterpretable.java similarity index 77% rename from runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java rename to runtime/src/main/java/dev/cel/runtime/planner/CelValueInterpretable.java index faf226147..0c60b52eb 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelValueInterpretable.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/CelValueInterpretable.java @@ -1,8 +1,10 @@ -package dev.cel.runtime; +package dev.cel.runtime.planner; import com.google.errorprone.annotations.Immutable; import dev.cel.common.annotations.Internal; import dev.cel.common.values.CelValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; /** * Represent an expression which can be interpreted repeatedly using a given activation. * diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java similarity index 77% rename from runtime/src/main/java/dev/cel/runtime/CelValueProgram.java rename to runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java index a93728338..514678d30 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelValueProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java @@ -1,14 +1,19 @@ -package dev.cel.runtime; +package dev.cel.runtime.planner; import com.google.auto.value.AutoValue; import com.google.errorprone.annotations.Immutable; import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.Activation; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.CelLiteRuntime.Program; +import dev.cel.runtime.GlobalResolver; import java.util.Map; @Immutable @AutoValue -abstract class CelValueProgram implements CelLiteRuntime.Program { +abstract class CelValueProgram implements Program { abstract CelValueInterpretable interpretable(); abstract CelValueConverter celValueConverter(); @@ -31,7 +36,7 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio return null; } - static CelLiteRuntime.Program create( + static Program create( CelValueInterpretable interpretable, CelValueConverter celValueConverter ) { diff --git a/runtime/src/main/java/dev/cel/runtime/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java similarity index 96% rename from runtime/src/main/java/dev/cel/runtime/EvalAttribute.java rename to runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java index 71ad1d4e0..cf6b5b446 100644 --- a/runtime/src/main/java/dev/cel/runtime/EvalAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java @@ -1,9 +1,11 @@ -package dev.cel.runtime; +package dev.cel.runtime.planner; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; @Immutable final class EvalAttribute implements CelValueInterpretable { diff --git a/runtime/src/main/java/dev/cel/runtime/EvalConstant.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java similarity index 81% rename from runtime/src/main/java/dev/cel/runtime/EvalConstant.java rename to runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java index a6f2b8113..6e07352c2 100644 --- a/runtime/src/main/java/dev/cel/runtime/EvalConstant.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java @@ -1,7 +1,9 @@ -package dev.cel.runtime; +package dev.cel.runtime.planner; import com.google.errorprone.annotations.Immutable; import dev.cel.common.values.CelValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; @Immutable final class EvalConstant implements CelValueInterpretable { diff --git a/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java similarity index 87% rename from runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java rename to runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index 247950496..50eb7c53c 100644 --- a/runtime/src/main/java/dev/cel/runtime/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -1,8 +1,9 @@ -package dev.cel.runtime; +package dev.cel.runtime.planner; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.annotations.Internal; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelReference; @@ -12,10 +13,12 @@ import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.TypeValue; +import dev.cel.runtime.CelLiteRuntime.Program; import java.util.NoSuchElementException; @Immutable -final class ProgramPlanner { +@Internal +public final class ProgramPlanner { private final CelTypeProvider typeProvider; private final CelValueConverter celValueConverter; @@ -80,12 +83,19 @@ private EvalConstant fromCelConstant(CelConstant celConstant) { return EvalConstant.create(celValue); } - CelLiteRuntime.Program plan(CelAbstractSyntaxTree ast) { + public Program plan(CelAbstractSyntaxTree ast) { CelValueInterpretable plannedInterpretable = plan(ast.getExpr(), ast.getTypeMap(), ast.getReferenceMap()); return CelValueProgram.create(plannedInterpretable, celValueConverter); } - ProgramPlanner( + public static ProgramPlanner newPlanner( + CelTypeProvider typeProvider, + CelValueConverter celValueConverter + ) { + return new ProgramPlanner(typeProvider, celValueConverter); + } + + private ProgramPlanner( CelTypeProvider typeProvider, CelValueConverter celValueConverter ) { diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 446a9e806..53272856c 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -74,7 +74,6 @@ java_library( "//runtime:late_function_binding", "//runtime:lite_runtime", "//runtime:lite_runtime_factory", - "//runtime:program_planner", "//runtime:proto_message_activation_factory", "//runtime:proto_message_runtime_equality", "//runtime:proto_message_runtime_helpers", @@ -84,6 +83,7 @@ java_library( "//runtime:type_resolver", "//runtime:unknown_attributes", "//runtime:unknown_options", + "//runtime/planner:program_planner", "//testing/protos:message_with_enum_cel_java_proto", "//testing/protos:message_with_enum_java_proto", "//testing/protos:multi_file_cel_java_proto", diff --git a/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java index fa09e2390..0cf9563f4 100644 --- a/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java @@ -23,6 +23,7 @@ import dev.cel.expr.conformance.proto2.TestAllTypes; import dev.cel.extensions.CelOptionalLibrary; import dev.cel.runtime.CelLiteRuntime.Program; +import dev.cel.runtime.planner.ProgramPlanner; import java.nio.charset.StandardCharsets; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,7 +36,7 @@ public final class ProgramPlannerTest { .addLibraries(CelOptionalLibrary.INSTANCE) .addMessageTypes(TestAllTypes.getDescriptor()) .build(); - private static ProgramPlanner PLANNER = new ProgramPlanner( + private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner( DefaultTypeProvider.create(), new CelValueConverter() ); From de1d63d336b2ccd278551a166cf0a9bd3dd28e4b Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 3 Jul 2025 00:26:49 -0700 Subject: [PATCH 07/30] Add AttributeFactory --- .../dev/cel/runtime/planner/Attribute.java | 63 +++++++++++ .../cel/runtime/planner/AttributeFactory.java | 44 ++++++++ .../java/dev/cel/runtime/planner/BUILD.bazel | 25 +++++ .../cel/runtime/planner/EvalAttribute.java | 83 +------------- .../cel/runtime/planner/ProgramPlanner.java | 10 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 104 ++++++++++++++++++ .../{ => planner}/ProgramPlannerTest.java | 7 +- 7 files changed, 249 insertions(+), 87 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/Attribute.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java create mode 100644 runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel rename runtime/src/test/java/dev/cel/runtime/{ => planner}/ProgramPlannerTest.java (97%) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java new file mode 100644 index 000000000..29d4423e2 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java @@ -0,0 +1,63 @@ +package dev.cel.runtime.planner; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.runtime.GlobalResolver; + +@Immutable +interface Attribute { + Object resolve(GlobalResolver ctx); + + final class MaybeAttribute implements Attribute { + private final ImmutableList attributes; + + @Override + public Object resolve(GlobalResolver ctx) { + for (Attribute attr : attributes) { + Object value = attr.resolve(ctx); + if (value != null) { + return value; + } + } + + throw new IllegalArgumentException("no such attribute(s): %s"); + } + + MaybeAttribute(ImmutableList attributes) { + this.attributes = attributes; + } + } + + final class NamespacedAttribute implements Attribute { + private final ImmutableList namespacedNames; + private final CelTypeProvider typeProvider; + + @Override + public Object resolve(GlobalResolver ctx) { + for (String name : namespacedNames) { + Object value = ctx.resolve(name); + if (value != null) { + // TODO: apply qualifiers + return value; + } + + CelType type = typeProvider.findType(name).orElse(null); + if (type != null) { + return type; + } + } + + throw new IllegalArgumentException("no such attribute(s): %s"); + } + + NamespacedAttribute( + CelTypeProvider typeProvider, + ImmutableList namespacedNames + ) { + this.typeProvider = typeProvider; + this.namespacedNames = namespacedNames; + } + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java new file mode 100644 index 000000000..a20198ea6 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java @@ -0,0 +1,44 @@ +package dev.cel.runtime.planner; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.planner.Attribute.NamespacedAttribute; +import dev.cel.runtime.planner.Attribute.MaybeAttribute; + +@Immutable +final class AttributeFactory { + + private final String container; + private final CelValueConverter celValueConverter; + private final CelTypeProvider typeProvider; + + NamespacedAttribute newAbsoluteAttribute(String... names) { + return new NamespacedAttribute( + typeProvider, + ImmutableList.copyOf(names) + ); + } + + MaybeAttribute newMaybeAttribute(String... names) { + // TODO: Resolve container names + return new MaybeAttribute( + ImmutableList.of(new NamespacedAttribute(typeProvider, ImmutableList.copyOf(names))) + ); + } + + static AttributeFactory newAttributeFactory(String container, CelValueConverter celValueConverter, CelTypeProvider typeProvider) { + return new AttributeFactory(container, celValueConverter, typeProvider); + } + + private AttributeFactory( + String container, + CelValueConverter celValueConverter, + CelTypeProvider typeProvider + ) { + this.container = container; + this.celValueConverter = celValueConverter; + this.typeProvider = typeProvider; + } +} 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 7d339d47e..b9ef67cf2 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -11,6 +11,7 @@ java_library( name = "program_planner", srcs = ["ProgramPlanner.java"], deps = [ + ":attribute_factory", ":cel_value_interpretable", ":cel_value_program", ":eval_attribute", @@ -77,10 +78,34 @@ java_library( ], ) +java_library( + name = "attribute", + srcs = ["Attribute.java"], + deps = [ + "//common/types:type_providers", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "attribute_factory", + srcs = ["AttributeFactory.java"], + deps = [ + ":attribute", + "//common/types:type_providers", + "//common/values", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + java_library( name = "eval_attribute", srcs = ["EvalAttribute.java"], deps = [ + ":attribute", ":cel_value_interpretable", "//common/values", "//common/values:cel_value", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java index cf6b5b446..66be1b4ae 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java @@ -1,6 +1,5 @@ package dev.cel.runtime.planner; -import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; @@ -20,86 +19,8 @@ public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { return celValueConverter.fromJavaObjectToCelValue(obj); } - static EvalAttribute newAbsoluteAttribute(long id, CelValueConverter celValueConverter, String... names) { - return new EvalAttribute(id, celValueConverter, new AbsoluteAttribute(ImmutableList.copyOf(names))); - } - - static EvalAttribute newMaybeAttribute(long id, CelValueConverter celValueConverter, String container, String... names) { - // TODO: Resolve container names - return new EvalAttribute( - id, - celValueConverter, - new MaybeAttribute( - ImmutableList.of(new NamespacedAttribute(ImmutableList.copyOf(names))) - ) - ); - } - - @Immutable - private interface Attribute { - Object resolve(GlobalResolver ctx); - } - - private static class MaybeAttribute implements Attribute { - private final ImmutableList attributes; - - @Override - public Object resolve(GlobalResolver ctx) { - for (Attribute attr : attributes) { - Object value = attr.resolve(ctx); - if (value != null) { - return value; - } - } - - throw new IllegalArgumentException("no such attribute(s): %s"); - } - - private MaybeAttribute(ImmutableList attributes) { - this.attributes = attributes; - } - } - - private static class NamespacedAttribute implements Attribute { - private final ImmutableList namespacedNames; - - @Override - public Object resolve(GlobalResolver ctx) { - for (String name : namespacedNames) { - Object value = ctx.resolve(name); - if (value != null) { - return value; - } - - // TODO: apply qualifiers - } - - throw new IllegalArgumentException("no such attribute(s): %s"); - } - - private NamespacedAttribute(ImmutableList namespacedNames) { - this.namespacedNames = namespacedNames; - } - } - - private static class AbsoluteAttribute implements Attribute { - private final ImmutableList namespacedNames; - - @Override - public Object resolve(GlobalResolver ctx) { - for (String name : namespacedNames) { - Object value = ctx.resolve(name); - if (value != null) { - return value; - } - } - - throw new IllegalArgumentException("no such attribute(s): %s"); - } - - private AbsoluteAttribute(ImmutableList namespacedNames) { - this.namespacedNames = namespacedNames; - } + static EvalAttribute create(long id, CelValueConverter celValueConverter, Attribute attr) { + return new EvalAttribute(id, celValueConverter, attr); } private EvalAttribute(long id, CelValueConverter celValueConverter, Attribute attr) { 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 50eb7c53c..590f5dffd 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -21,6 +21,7 @@ public final class ProgramPlanner { private final CelTypeProvider typeProvider; private final CelValueConverter celValueConverter; + private final AttributeFactory attributeFactory; private CelValueInterpretable plan(CelExpr celExpr, ImmutableMap typeMap, @@ -58,7 +59,11 @@ private CelValueInterpretable planIdent( return planCheckedIdent(celExpr.id(), ref, typeMap); } - return EvalAttribute.newMaybeAttribute(celExpr.id(), celValueConverter, "", celExpr.ident().name()); + return EvalAttribute.create( + celExpr.id(), + celValueConverter, + attributeFactory.newMaybeAttribute(celExpr.ident().name()) + ); } private CelValueInterpretable planCheckedIdent( @@ -75,7 +80,7 @@ private CelValueInterpretable planCheckedIdent( return EvalConstant.create(TypeValue.create(identType)); } - return EvalAttribute.newAbsoluteAttribute(id, celValueConverter, identRef.name()); + return EvalAttribute.create(id, celValueConverter, attributeFactory.newAbsoluteAttribute(identRef.name())); } private EvalConstant fromCelConstant(CelConstant celConstant) { @@ -101,5 +106,6 @@ private ProgramPlanner( ) { this.typeProvider = typeProvider; this.celValueConverter = celValueConverter; + this.attributeFactory = AttributeFactory.newAttributeFactory("", celValueConverter, typeProvider); } } diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel new file mode 100644 index 000000000..35a6a0529 --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -0,0 +1,104 @@ +load("@rules_java//java:defs.bzl", "java_library") +load("//:testing.bzl", "junit4_test_suites") + +package( + default_applicable_licenses = ["//:license"], + default_testonly = True, +) + +java_library( + name = "tests", + testonly = 1, + srcs = glob( + ["*.java"], + ), + deps = [ + "//:auto_value", + "//:java_truth", + "//bundle:cel", + "//common:cel_ast", + "//common:cel_descriptors", + "//common:cel_exception", + "//common:cel_source", + "//common:compiler_common", + "//common:error_codes", + "//common:options", + "//common:proto_v1alpha1_ast", + "//common:runtime_exception", + "//common/ast", + "//common/internal:cel_descriptor_pools", + "//common/internal:converter", + "//common/internal:default_message_factory", + "//common/internal:dynamic_proto", + "//common/internal:proto_message_factory", + "//common/internal:proto_time_utils", + "//common/internal:well_known_proto", + "//common/types", + "//common/types:cel_v1alpha1_types", + "//common/types:default_type_provider", + "//common/types:message_type_provider", + "//common/types:type_providers", + "//common/values", + "//common/values:cel_byte_string", + "//common/values:cel_value_provider", + "//common/values:proto_message_lite_value_provider", + "//compiler", + "//compiler:compiler_builder", + "//extensions:optional_library", + "//parser:macro", + "//parser:unparser", + "//runtime", + "//runtime:activation", + "//runtime:dispatcher", + "//runtime:evaluation_exception_builder", + "//runtime:evaluation_listener", + "//runtime:function_binding", + "//runtime:function_overload_impl", + "//runtime:interpretable", + "//runtime:interpreter", + "//runtime:interpreter_util", + "//runtime:late_function_binding", + "//runtime:lite_runtime", + "//runtime:lite_runtime_factory", + "//runtime:proto_message_activation_factory", + "//runtime:proto_message_runtime_equality", + "//runtime:proto_message_runtime_helpers", + "//runtime:runtime_equality", + "//runtime:runtime_helpers", + "//runtime:standard_functions", + "//runtime:type_resolver", + "//runtime:unknown_attributes", + "//runtime:unknown_options", + "//runtime/planner:program_planner", + "//testing/protos:message_with_enum_cel_java_proto", + "//testing/protos:message_with_enum_java_proto", + "//testing/protos:multi_file_cel_java_proto", + "//testing/protos:multi_file_java_proto", + "//testing/protos:single_file_java_proto", + "//testing/protos:test_all_types_cel_java_proto2", + "//testing/protos:test_all_types_cel_java_proto3", + "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", + "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", + "@com_google_googleapis//google/api/expr/v1alpha1:expr_java_proto", + "@com_google_googleapis//google/rpc/context:attribute_context_java_proto", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:com_google_protobuf_protobuf_java_util", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:com_google_truth_extensions_truth_proto_extension", + "@maven//:junit_junit", + "@maven//:org_jspecify_jspecify", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) + +junit4_test_suites( + name = "test_suites", + sizes = [ + "small", + ], + src_dir = "src/test/java", + deps = [ + ":tests", + ], +) diff --git a/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java similarity index 97% rename from runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java rename to runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 0cf9563f4..8330c2730 100644 --- a/runtime/src/test/java/dev/cel/runtime/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -1,4 +1,4 @@ -package dev.cel.runtime; +package dev.cel.runtime.planner; import static com.google.common.truth.Truth.assertThat; @@ -23,7 +23,8 @@ import dev.cel.expr.conformance.proto2.TestAllTypes; import dev.cel.extensions.CelOptionalLibrary; import dev.cel.runtime.CelLiteRuntime.Program; -import dev.cel.runtime.planner.ProgramPlanner; +import dev.cel.runtime.CelRuntime; +import dev.cel.runtime.CelRuntimeFactory; import java.nio.charset.StandardCharsets; import org.junit.Test; import org.junit.runner.RunWith; @@ -98,7 +99,6 @@ public void planIdent_variable() throws Exception { assertThat(result).isEqualTo(1); } - @SuppressWarnings("ImmutableEnumChecker") // Test only private enum TypeLiteralTestCase { BOOL("bool", SimpleType.BOOL), @@ -135,7 +135,6 @@ public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) t assertThat(result).isEqualTo(testCase.type); } - @Test public void smokeTest() throws Exception { CelAbstractSyntaxTree ast = compile("google.protobuf.Duration"); From e51846682efa9080bb0585dce6815194782633d4 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 3 Jul 2025 01:08:49 -0700 Subject: [PATCH 08/30] Plan call --- .../java/dev/cel/runtime/planner/BUILD.bazel | 12 +++++++++++ .../cel/runtime/planner/EvalAttribute.java | 3 +-- .../dev/cel/runtime/planner/EvalCall.java | 20 +++++++++++++++++++ .../dev/cel/runtime/planner/EvalConstant.java | 2 +- .../cel/runtime/planner/ProgramPlanner.java | 4 ++++ 5 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalCall.java 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 b9ef67cf2..2e52eef15 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -15,6 +15,7 @@ java_library( ":cel_value_interpretable", ":cel_value_program", ":eval_attribute", + ":eval_call", ":eval_const", "//common:cel_ast", "//common/annotations", @@ -115,3 +116,14 @@ java_library( "@maven//:com_google_guava_guava", ], ) + +java_library( + name = "eval_call", + srcs = ["EvalCall.java"], + deps = [ + ":cel_value_interpretable", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:interpretable", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java index 66be1b4ae..887e707e9 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java @@ -3,7 +3,6 @@ import com.google.errorprone.annotations.Immutable; import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; -import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.GlobalResolver; @Immutable @@ -14,7 +13,7 @@ final class EvalAttribute implements CelValueInterpretable { private final Attribute attr; @Override - public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + public CelValue eval(GlobalResolver resolver) { Object obj = attr.resolve(resolver); return celValueConverter.fromJavaObjectToCelValue(obj); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCall.java new file mode 100644 index 000000000..c686ff187 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCall.java @@ -0,0 +1,20 @@ +package dev.cel.runtime.planner; + +import dev.cel.common.values.CelValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; + +final class EvalCall implements CelValueInterpretable { + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + return null; + } + + static EvalCall create() { + return new EvalCall(); + } + + private EvalCall() { + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java index 6e07352c2..5d8cadab9 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java @@ -13,7 +13,7 @@ final class EvalConstant implements CelValueInterpretable { @Override - public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + public CelValue eval(GlobalResolver resolver) { return constant; } 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 590f5dffd..36314d4c9 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -88,6 +88,10 @@ private EvalConstant fromCelConstant(CelConstant celConstant) { return EvalConstant.create(celValue); } + private EvalCall planCall(CelExpr celExpr) { + return EvalCall.create(); + } + public Program plan(CelAbstractSyntaxTree ast) { CelValueInterpretable plannedInterpretable = plan(ast.getExpr(), ast.getTypeMap(), ast.getReferenceMap()); return CelValueProgram.create(plannedInterpretable, celValueConverter); From cfda434b039a97f44bd6b4e38669ff932d0e0b33 Mon Sep 17 00:00:00 2001 From: swh Date: Fri, 4 Jul 2025 09:57:53 +0100 Subject: [PATCH 09/30] Add dispatcher to planner --- .../src/main/java/dev/cel/runtime/BUILD.bazel | 1 + .../dev/cel/runtime/DefaultDispatcher.java | 5 ++- .../main/java/dev/cel/runtime/Dispatcher.java | 2 +- .../java/dev/cel/runtime/LiteRuntimeImpl.java | 2 +- .../dev/cel/runtime/ResolvedOverload.java | 5 ++- .../java/dev/cel/runtime/planner/BUILD.bazel | 2 + .../cel/runtime/planner/ProgramPlanner.java | 38 +++++++++++++++---- .../runtime/planner/ProgramPlannerTest.java | 27 ++++++++++++- 8 files changed, 69 insertions(+), 13 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index d29f035b6..66e3975c6 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -506,6 +506,7 @@ java_library( ":function_overload_impl", ":function_resolver", "//:auto_value", + "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index 1b89939ea..efa7613bb 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -22,6 +22,8 @@ import javax.annotation.concurrent.ThreadSafe; import com.google.errorprone.annotations.concurrent.GuardedBy; import dev.cel.common.CelErrorCode; +import dev.cel.common.annotations.Internal; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -34,7 +36,8 @@ *

Should be final, do not mock; mocking {@link Dispatcher} instead. */ @ThreadSafe -final class DefaultDispatcher implements Dispatcher, Registrar { +@Internal +public final class DefaultDispatcher implements Dispatcher, Registrar { public static DefaultDispatcher create() { return new DefaultDispatcher(); } diff --git a/runtime/src/main/java/dev/cel/runtime/Dispatcher.java b/runtime/src/main/java/dev/cel/runtime/Dispatcher.java index 017e76685..6482290f3 100644 --- a/runtime/src/main/java/dev/cel/runtime/Dispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/Dispatcher.java @@ -25,7 +25,7 @@ */ @ThreadSafe @Internal -interface Dispatcher extends FunctionResolver { +public interface Dispatcher extends FunctionResolver { /** * Returns an {@link ImmutableCopy} from current instance. diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index 9d23131db..c5d402f29 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -258,7 +258,7 @@ public ImmutableCollection types() { public Optional findType(String typeName) { return Optional.empty(); } - }, null); + }, null, null); } else { this.planner = null; } diff --git a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java index f71748814..635f999ec 100644 --- a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java @@ -16,6 +16,8 @@ import com.google.errorprone.annotations.Immutable; import com.google.protobuf.MessageLite; +import dev.cel.common.annotations.Internal; + import java.util.List; import java.util.Map; @@ -24,7 +26,8 @@ * and a function definition. */ @Immutable -interface ResolvedOverload { +@Internal +public interface ResolvedOverload { /** The overload id of the function. */ String getOverloadId(); 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 2e52eef15..c400ff8bd 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -24,9 +24,11 @@ java_library( "//common/values", "//common/values:cel_value", "//runtime:activation", + "//runtime:dispatcher", "//runtime:evaluation_exception", "//runtime:function_resolver", "//runtime:interpretable", + "//runtime:late_function_binding", "//runtime:lite_runtime", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", 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 36314d4c9..a761c95fc 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -1,11 +1,12 @@ package dev.cel.runtime.planner; import com.google.common.collect.ImmutableMap; -import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.ThreadSafe; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.annotations.Internal; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; +import dev.cel.common.ast.CelExpr.CelCall; import dev.cel.common.ast.CelReference; import dev.cel.common.types.CelKind; import dev.cel.common.types.CelType; @@ -14,13 +15,17 @@ import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.TypeValue; import dev.cel.runtime.CelLiteRuntime.Program; +import dev.cel.runtime.CelResolvedOverload; +import dev.cel.runtime.Dispatcher; + import java.util.NoSuchElementException; -@Immutable +@ThreadSafe @Internal public final class ProgramPlanner { private final CelTypeProvider typeProvider; private final CelValueConverter celValueConverter; + private final Dispatcher dispatcher; private final AttributeFactory attributeFactory; private CelValueInterpretable plan(CelExpr celExpr, @@ -34,7 +39,7 @@ private CelValueInterpretable plan(CelExpr celExpr, case SELECT: break; case CALL: - break; + return planCall(celExpr, referenceMap); case LIST: break; case STRUCT: @@ -88,10 +93,24 @@ private EvalConstant fromCelConstant(CelConstant celConstant) { return EvalConstant.create(celValue); } - private EvalCall planCall(CelExpr celExpr) { + private EvalCall planCall(CelExpr celExpr, ImmutableMap referenceMap) { + CelCall call = celExpr.call(); + if (call.target().isPresent()) { + + } + + CelReference reference = referenceMap.get(celExpr.id()); + if (reference != null) { + + } + return EvalCall.create(); } + private CelResolvedOverload resolveFunction() { + return null; + } + public Program plan(CelAbstractSyntaxTree ast) { CelValueInterpretable plannedInterpretable = plan(ast.getExpr(), ast.getTypeMap(), ast.getReferenceMap()); return CelValueProgram.create(plannedInterpretable, celValueConverter); @@ -99,17 +118,20 @@ public Program plan(CelAbstractSyntaxTree ast) { public static ProgramPlanner newPlanner( CelTypeProvider typeProvider, - CelValueConverter celValueConverter + CelValueConverter celValueConverter, + Dispatcher dispatcher ) { - return new ProgramPlanner(typeProvider, celValueConverter); + return new ProgramPlanner(typeProvider, celValueConverter, dispatcher); } private ProgramPlanner( CelTypeProvider typeProvider, - CelValueConverter celValueConverter + CelValueConverter celValueConverter, + Dispatcher dispatcher ) { this.typeProvider = typeProvider; this.celValueConverter = celValueConverter; - this.attributeFactory = AttributeFactory.newAttributeFactory("", celValueConverter, typeProvider); + this.dispatcher = dispatcher; + this.attributeFactory = AttributeFactory.newAttributeFactory("", celValueConverter, typeProvider); } } 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 8330c2730..0d045d1d7 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -7,6 +7,8 @@ import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelFunctionDecl; +import dev.cel.common.CelOverloadDecl; import dev.cel.common.CelValidationException; import dev.cel.common.types.CelType; import dev.cel.common.types.DefaultTypeProvider; @@ -26,6 +28,9 @@ import dev.cel.runtime.CelRuntime; import dev.cel.runtime.CelRuntimeFactory; import java.nio.charset.StandardCharsets; + +import dev.cel.runtime.DefaultDispatcher; +import dev.cel.runtime.Dispatcher; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,14 +39,22 @@ public final class ProgramPlannerTest { private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() .addVar("int_var", SimpleType.INT) + .addFunctionDeclarations(CelFunctionDecl.newFunctionDeclaration( + "zero", CelOverloadDecl.newGlobalOverload("zero", SimpleType.INT) + )) .addLibraries(CelOptionalLibrary.INSTANCE) .addMessageTypes(TestAllTypes.getDescriptor()) .build(); private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner( DefaultTypeProvider.create(), - new CelValueConverter() + new CelValueConverter(), + newDispatcher() ); + private static Dispatcher newDispatcher() { + return DefaultDispatcher.create(); + } + @TestParameter boolean isParseOnly; @SuppressWarnings("ImmutableEnumChecker") // Test only @@ -135,6 +148,18 @@ public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) t assertThat(result).isEqualTo(testCase.type); } + + @Test + public void planCall_zeroArgs() throws Exception { + CelAbstractSyntaxTree ast = compile("zero()"); + Program program = PLANNER.plan(ast); + + Long result = (Long) program.eval(); + + assertThat(result).isEqualTo(0L); + } + + @Test public void smokeTest() throws Exception { CelAbstractSyntaxTree ast = compile("google.protobuf.Duration"); From 9f1ab783a7db246e82aad0fdc02f7074d306c8b3 Mon Sep 17 00:00:00 2001 From: swh Date: Mon, 7 Jul 2025 08:50:22 +0100 Subject: [PATCH 10/30] Rename existing dispatcher to legacy. Add a new immutable defaultdispatcher --- runtime/BUILD.bazel | 12 +- .../src/main/java/dev/cel/runtime/BUILD.bazel | 35 ++- .../cel/runtime/CelLateFunctionBindings.java | 2 +- .../dev/cel/runtime/CelRuntimeLegacyImpl.java | 2 +- .../dev/cel/runtime/DefaultDispatcher.java | 270 ++++++------------ .../main/java/dev/cel/runtime/Dispatcher.java | 2 +- .../dev/cel/runtime/FunctionOverload.java | 4 +- .../dev/cel/runtime/LegacyDispatcher.java | 177 ++++++++++++ .../java/dev/cel/runtime/LiteRuntimeImpl.java | 2 +- .../dev/cel/runtime/ResolvedOverload.java | 2 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 12 +- .../dev/cel/runtime/planner/EvalCall.java | 20 -- .../cel/runtime/planner/EvalZeroArity.java | 28 ++ .../cel/runtime/planner/ProgramPlanner.java | 86 +++++- .../src/test/java/dev/cel/runtime/BUILD.bazel | 2 +- .../cel/runtime/DefaultInterpreterTest.java | 2 +- ...herTest.java => LegacyDispatcherTest.java} | 120 ++++---- .../java/dev/cel/runtime/planner/BUILD.bazel | 3 +- .../runtime/planner/ProgramPlannerTest.java | 47 +-- 19 files changed, 515 insertions(+), 313 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/LegacyDispatcher.java delete mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalCall.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java rename runtime/src/test/java/dev/cel/runtime/{DefaultDispatcherTest.java => LegacyDispatcherTest.java} (90%) diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index b7d59ce96..d81019444 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -20,10 +20,18 @@ java_library( ) java_library( - name = "dispatcher", + name = "default_dispatcher", visibility = ["//:internal"], exports = [ - "//runtime/src/main/java/dev/cel/runtime:dispatcher", + "//runtime/src/main/java/dev/cel/runtime:default_dispatcher", + ], +) + +java_library( + name = "legacy_dispatcher", + visibility = ["//:internal"], + exports = [ + "//runtime/src/main/java/dev/cel/runtime:legacy_dispatcher", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 66e3975c6..7775e252b 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -70,8 +70,8 @@ INTERPRABLE_SOURCES = [ # keep sorted DISPATCHER_SOURCES = [ - "DefaultDispatcher.java", "Dispatcher.java", + "LegacyDispatcher.java", ] java_library( @@ -124,7 +124,22 @@ java_library( ) java_library( - name = "dispatcher", + name = "default_dispatcher", + srcs = ["DefaultDispatcher.java"], + tags = [ + ], + deps = [ + ":base", + ":function_overload_impl", + "//:auto_value", + "//common/annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "legacy_dispatcher", srcs = DISPATCHER_SOURCES, tags = [ ], @@ -143,7 +158,7 @@ java_library( ) cel_android_library( - name = "dispatcher_android", + name = "legacy_dispatcher_android", srcs = DISPATCHER_SOURCES, visibility = ["//visibility:private"], deps = [ @@ -288,13 +303,13 @@ java_library( ":accumulated_unknowns", ":base", ":concatenated_list_view", - ":dispatcher", ":evaluation_exception", ":evaluation_exception_builder", ":evaluation_listener", ":function_overload_impl", ":interpretable", ":interpreter_util", + ":legacy_dispatcher", ":metadata", ":runtime_helpers", ":runtime_type_provider", @@ -326,13 +341,13 @@ cel_android_library( ":accumulated_unknowns_android", ":base_android", ":concatenated_list_view", - ":dispatcher_android", ":evaluation_exception", ":evaluation_exception_builder", ":evaluation_listener_android", ":function_overload_impl_android", ":interpretable_android", ":interpreter_util_android", + ":legacy_dispatcher_android", ":metadata", ":runtime_helpers_android", ":runtime_type_provider_android", @@ -499,12 +514,12 @@ java_library( tags = [ ], deps = [ - ":dispatcher", ":evaluation_exception", ":function_binding", ":function_overload", ":function_overload_impl", ":function_resolver", + ":legacy_dispatcher", "//:auto_value", "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", @@ -518,12 +533,12 @@ cel_android_library( tags = [ ], deps = [ - ":dispatcher_android", ":evaluation_exception", ":function_binding_android", ":function_overload_android", ":function_overload_impl_android", ":function_resolver_android", + ":legacy_dispatcher_android", "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", @@ -832,13 +847,13 @@ java_library( ":cel_value_runtime_type_provider", ":descriptor_message_provider", ":descriptor_type_resolver", - ":dispatcher", ":evaluation_exception", ":evaluation_listener", ":function_binding", ":function_resolver", ":interpretable", ":interpreter", + ":legacy_dispatcher", ":lite_runtime", ":proto_message_activation_factory", ":proto_message_runtime_equality", @@ -896,12 +911,12 @@ java_library( deps = [ ":activation", ":cel_value_runtime_type_provider", - ":dispatcher", ":evaluation_exception", ":function_binding", ":function_resolver", ":interpretable", ":interpreter", + ":legacy_dispatcher", ":lite_program_impl", ":lite_runtime", ":runtime_equality", @@ -946,12 +961,12 @@ cel_android_library( deps = [ ":activation_android", ":cel_value_runtime_type_provider_android", - ":dispatcher_android", ":evaluation_exception", ":function_binding_android", ":function_resolver_android", ":interpretable_android", ":interpreter_android", + ":legacy_dispatcher_android", ":lite_program_impl", # TODO - android target ":lite_runtime_android", ":program_planner", # TODO - android target diff --git a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java index 7f83e38fd..9948590b4 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java @@ -38,7 +38,7 @@ private CelLateFunctionBindings(ImmutableMap functions @Override public Optional findOverload( String functionName, List overloadIds, Object[] args) throws CelEvaluationException { - return DefaultDispatcher.findOverload(functionName, overloadIds, functions, args); + return LegacyDispatcher.findOverload(functionName, overloadIds, functions, args); } public static CelLateFunctionBindings from(CelFunctionBinding... functions) { diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index 2947445d1..e7d9ca8eb 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -287,7 +287,7 @@ public CelRuntimeLegacyImpl build() { functionBindingsBuilder.putAll(customFunctionBindings); - DefaultDispatcher dispatcher = DefaultDispatcher.create(); + LegacyDispatcher dispatcher = LegacyDispatcher.create(); functionBindingsBuilder .buildOrThrow() .forEach( diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index efa7613bb..9a7befa61 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -1,177 +1,93 @@ -// Copyright 2022 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.auto.value.AutoValue; -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.errorprone.annotations.Immutable; -import javax.annotation.concurrent.ThreadSafe; -import com.google.errorprone.annotations.concurrent.GuardedBy; -import dev.cel.common.CelErrorCode; -import dev.cel.common.annotations.Internal; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * Default implementation of {@link Dispatcher}. - * - *

Should be final, do not mock; mocking {@link Dispatcher} instead. - */ -@ThreadSafe -@Internal -public final class DefaultDispatcher implements Dispatcher, Registrar { - public static DefaultDispatcher create() { - return new DefaultDispatcher(); - } - - @GuardedBy("this") - private final Map overloads = new HashMap<>(); - - @Override - @SuppressWarnings("unchecked") - public synchronized void add( - String overloadId, Class argType, final Registrar.UnaryFunction function) { - overloads.put( - overloadId, - ResolvedOverloadImpl.of( - overloadId, new Class[] {argType}, args -> function.apply((T) args[0]))); - } - - @Override - @SuppressWarnings("unchecked") - public synchronized void add( - String overloadId, - Class argType1, - Class argType2, - final Registrar.BinaryFunction function) { - overloads.put( - overloadId, - ResolvedOverloadImpl.of( - overloadId, - new Class[] {argType1, argType2}, - args -> function.apply((T1) args[0], (T2) args[1]))); - } - - @Override - public synchronized void add( - String overloadId, List> argTypes, Registrar.Function function) { - overloads.put( - overloadId, - ResolvedOverloadImpl.of(overloadId, argTypes.toArray(new Class[0]), function)); - } - - @Override - public synchronized Optional findOverload( - String functionName, List overloadIds, Object[] args) throws CelEvaluationException { - return DefaultDispatcher.findOverload(functionName, overloadIds, overloads, args); - } - - /** Finds the overload that matches the given function name, overload IDs, and arguments. */ - public static Optional findOverload( - String functionName, - List overloadIds, - Map overloads, - Object[] args) - throws CelEvaluationException { - int matchingOverloadCount = 0; - ResolvedOverload match = null; - List candidates = null; - for (String overloadId : overloadIds) { - ResolvedOverload overload = overloads.get(overloadId); - // If the overload is null, it means that the function was not registered; however, it is - // possible that the overload refers to a late-bound function. - if (overload != null && overload.canHandle(args)) { - if (++matchingOverloadCount > 1) { - if (candidates == null) { - candidates = new ArrayList<>(); - candidates.add(match.getOverloadId()); - } - candidates.add(overloadId); - } - match = overload; - } - } - - if (matchingOverloadCount > 1) { - throw CelEvaluationExceptionBuilder.newBuilder( - "Ambiguous overloads for function '%s'. Matching candidates: %s", - functionName, Joiner.on(", ").join(candidates)) - .setErrorCode(CelErrorCode.AMBIGUOUS_OVERLOAD) - .build(); - } - return Optional.ofNullable(match); - } - - @Override - public synchronized Dispatcher.ImmutableCopy immutableCopy() { - return new ImmutableCopy(overloads); - } - - @Immutable - private static final class ImmutableCopy implements Dispatcher.ImmutableCopy { - private final ImmutableMap overloads; - - private ImmutableCopy(Map overloads) { - this.overloads = ImmutableMap.copyOf(overloads); - } - - @Override - public Optional findOverload( - String functionName, List overloadIds, Object[] args) - throws CelEvaluationException { - return DefaultDispatcher.findOverload(functionName, overloadIds, overloads, args); - } - - @Override - public Dispatcher.ImmutableCopy immutableCopy() { - return this; - } - } - - private DefaultDispatcher() {} - - @AutoValue - @Immutable - abstract static class ResolvedOverloadImpl implements ResolvedOverload { - /** The overload id of the function. */ - @Override - public abstract String getOverloadId(); - - /** The types of the function parameters. */ - @Override - public abstract ImmutableList> getParameterTypes(); - - /** The function definition. */ - @Override - public abstract FunctionOverload getDefinition(); - - static ResolvedOverload of( - String overloadId, Class[] parameterTypes, FunctionOverload definition) { - return of(overloadId, ImmutableList.copyOf(parameterTypes), definition); - } - - static ResolvedOverload of( - String overloadId, ImmutableList> parameterTypes, FunctionOverload definition) { - return new AutoValue_DefaultDispatcher_ResolvedOverloadImpl( - overloadId, parameterTypes, definition); - } - } -} +package dev.cel.runtime; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.CheckReturnValue; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.annotations.Internal; + +import java.util.List; +import java.util.Optional; + +@Internal +@AutoValue +public abstract class DefaultDispatcher { + + abstract ImmutableMap overloads(); + + public Optional findOverload(String overloadId) { + return Optional.ofNullable(overloads().get(overloadId)); + } + + public static Builder newBuilder() { + return new AutoValue_DefaultDispatcher.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + abstract ImmutableMap.Builder overloadsBuilder(); + + abstract ImmutableMap overloads(); + + @CanIgnoreReturnValue + public Builder add(String overloadId, Class argType, Registrar.UnaryFunction function) { + overloadsBuilder().put( + overloadId, + ResolvedOverloadImpl.of( + overloadId, new Class[] {argType}, args -> function.apply((T) args[0]))); + return this; + } + + @CanIgnoreReturnValue + public Builder add(String overloadId, Class argType1, Class argType2, Registrar.BinaryFunction function) { + overloadsBuilder().put( + overloadId, + ResolvedOverloadImpl.of( + overloadId, + new Class[] {argType1, argType2}, + args -> function.apply((T1) args[0], (T2) args[1]))); + return this; + } + + @CanIgnoreReturnValue + public Builder add(String overloadId, List> argTypes, Registrar.Function function) { + overloadsBuilder().put( + overloadId, + ResolvedOverloadImpl.of(overloadId, argTypes.toArray(new Class[0]), function)); + return this; + } + + @CheckReturnValue + public abstract DefaultDispatcher build(); + } + + // TODO: Refactor to reuse across LegacyDispatcher + @AutoValue + @Immutable + abstract static class ResolvedOverloadImpl implements ResolvedOverload { + /** The overload id of the function. */ + @Override + public abstract String getOverloadId(); + + /** The types of the function parameters. */ + @Override + public abstract ImmutableList> getParameterTypes(); + + /** The function definition. */ + @Override + public abstract FunctionOverload getDefinition(); + + static ResolvedOverload of( + String overloadId, Class[] parameterTypes, FunctionOverload definition) { + return of(overloadId, ImmutableList.copyOf(parameterTypes), definition); + } + + static ResolvedOverload of( + String overloadId, ImmutableList> parameterTypes, FunctionOverload definition) { + return new AutoValue_DefaultDispatcher_ResolvedOverloadImpl( + overloadId, parameterTypes, definition); + } + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/Dispatcher.java b/runtime/src/main/java/dev/cel/runtime/Dispatcher.java index 6482290f3..c0e6897c5 100644 --- a/runtime/src/main/java/dev/cel/runtime/Dispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/Dispatcher.java @@ -35,7 +35,7 @@ public interface Dispatcher extends FunctionResolver { ImmutableCopy immutableCopy(); /** - * An {@link Immutable} copy of a {@link Dispatcher}. Currently {@link DefaultDispatcher} + * An {@link Immutable} copy of a {@link Dispatcher}. Currently {@link LegacyDispatcher} * implementation implements both {@link Dispatcher} and {@link Registrar} and cannot be annotated * as {@link Immutable}. * diff --git a/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java b/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java index bf825f004..3e05af66a 100644 --- a/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java @@ -15,11 +15,13 @@ package dev.cel.runtime; import com.google.errorprone.annotations.Immutable; +import dev.cel.common.annotations.Internal; /** Interface describing the general signature of all CEL custom function implementations. */ @FunctionalInterface @Immutable -interface FunctionOverload { +@Internal +public interface FunctionOverload { /** Evaluate a set of arguments throwing a {@code CelException} on error. */ Object apply(Object[] args) throws CelEvaluationException; diff --git a/runtime/src/main/java/dev/cel/runtime/LegacyDispatcher.java b/runtime/src/main/java/dev/cel/runtime/LegacyDispatcher.java new file mode 100644 index 000000000..91aa87181 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/LegacyDispatcher.java @@ -0,0 +1,177 @@ +// Copyright 2022 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.auto.value.AutoValue; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; +import javax.annotation.concurrent.ThreadSafe; +import com.google.errorprone.annotations.concurrent.GuardedBy; +import dev.cel.common.CelErrorCode; +import dev.cel.common.annotations.Internal; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Default implementation of {@link Dispatcher}. + * + *

Should be final, do not mock; mocking {@link Dispatcher} instead. + */ +@ThreadSafe +@Internal +public final class LegacyDispatcher implements Dispatcher, Registrar { + public static LegacyDispatcher create() { + return new LegacyDispatcher(); + } + + @GuardedBy("this") + private final Map overloads = new HashMap<>(); + + @Override + @SuppressWarnings("unchecked") + public synchronized void add( + String overloadId, Class argType, final Registrar.UnaryFunction function) { + overloads.put( + overloadId, + ResolvedOverloadImpl.of( + overloadId, new Class[] {argType}, args -> function.apply((T) args[0]))); + } + + @Override + @SuppressWarnings("unchecked") + public synchronized void add( + String overloadId, + Class argType1, + Class argType2, + final Registrar.BinaryFunction function) { + overloads.put( + overloadId, + ResolvedOverloadImpl.of( + overloadId, + new Class[] {argType1, argType2}, + args -> function.apply((T1) args[0], (T2) args[1]))); + } + + @Override + public synchronized void add( + String overloadId, List> argTypes, Registrar.Function function) { + overloads.put( + overloadId, + ResolvedOverloadImpl.of(overloadId, argTypes.toArray(new Class[0]), function)); + } + + @Override + public synchronized Optional findOverload( + String functionName, List overloadIds, Object[] args) throws CelEvaluationException { + return LegacyDispatcher.findOverload(functionName, overloadIds, overloads, args); + } + + /** Finds the overload that matches the given function name, overload IDs, and arguments. */ + public static Optional findOverload( + String functionName, + List overloadIds, + Map overloads, + Object[] args) + throws CelEvaluationException { + int matchingOverloadCount = 0; + ResolvedOverload match = null; + List candidates = null; + for (String overloadId : overloadIds) { + ResolvedOverload overload = overloads.get(overloadId); + // If the overload is null, it means that the function was not registered; however, it is + // possible that the overload refers to a late-bound function. + if (overload != null && overload.canHandle(args)) { + if (++matchingOverloadCount > 1) { + if (candidates == null) { + candidates = new ArrayList<>(); + candidates.add(match.getOverloadId()); + } + candidates.add(overloadId); + } + match = overload; + } + } + + if (matchingOverloadCount > 1) { + throw CelEvaluationExceptionBuilder.newBuilder( + "Ambiguous overloads for function '%s'. Matching candidates: %s", + functionName, Joiner.on(", ").join(candidates)) + .setErrorCode(CelErrorCode.AMBIGUOUS_OVERLOAD) + .build(); + } + return Optional.ofNullable(match); + } + + @Override + public synchronized Dispatcher.ImmutableCopy immutableCopy() { + return new ImmutableCopy(overloads); + } + + @Immutable + private static final class ImmutableCopy implements Dispatcher.ImmutableCopy { + private final ImmutableMap overloads; + + private ImmutableCopy(Map overloads) { + this.overloads = ImmutableMap.copyOf(overloads); + } + + @Override + public Optional findOverload( + String functionName, List overloadIds, Object[] args) + throws CelEvaluationException { + return LegacyDispatcher.findOverload(functionName, overloadIds, overloads, args); + } + + @Override + public Dispatcher.ImmutableCopy immutableCopy() { + return this; + } + } + + private LegacyDispatcher() {} + + @AutoValue + @Immutable + abstract static class ResolvedOverloadImpl implements ResolvedOverload { + /** The overload id of the function. */ + @Override + public abstract String getOverloadId(); + + /** The types of the function parameters. */ + @Override + public abstract ImmutableList> getParameterTypes(); + + /** The function definition. */ + @Override + public abstract FunctionOverload getDefinition(); + + static ResolvedOverload of( + String overloadId, Class[] parameterTypes, FunctionOverload definition) { + return of(overloadId, ImmutableList.copyOf(parameterTypes), definition); + } + + static ResolvedOverload of( + String overloadId, ImmutableList> parameterTypes, FunctionOverload definition) { + return new AutoValue_LegacyDispatcher_ResolvedOverloadImpl( + overloadId, parameterTypes, definition); + } + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index c5d402f29..2b3703137 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -192,7 +192,7 @@ public CelLiteRuntime build() { functionBindingsBuilder.putAll(customFunctionBindings); - DefaultDispatcher dispatcher = DefaultDispatcher.create(); + LegacyDispatcher dispatcher = LegacyDispatcher.create(); functionBindingsBuilder .buildOrThrow() .forEach( diff --git a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java index 635f999ec..1a30aa91f 100644 --- a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java @@ -52,7 +52,7 @@ default boolean canHandle(Object[] arguments) { if (arg == null) { // null can be assigned to messages, maps, and to objects. if (paramType != Object.class - && !MessageLite.class.isAssignableFrom(paramType) + && !MessageLite.class.isAssignableFrom(paramType) // TODO: Move to impl. && !Map.class.isAssignableFrom(paramType)) { return false; } 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 c400ff8bd..a1fee44c8 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -15,8 +15,9 @@ java_library( ":cel_value_interpretable", ":cel_value_program", ":eval_attribute", - ":eval_call", ":eval_const", + ":eval_zero_arity", + "//:auto_value", "//common:cel_ast", "//common/annotations", "//common/ast", @@ -24,8 +25,9 @@ java_library( "//common/values", "//common/values:cel_value", "//runtime:activation", - "//runtime:dispatcher", + "//runtime:default_dispatcher", "//runtime:evaluation_exception", + "//runtime:function_overload_impl", "//runtime:function_resolver", "//runtime:interpretable", "//runtime:late_function_binding", @@ -120,12 +122,14 @@ java_library( ) java_library( - name = "eval_call", - srcs = ["EvalCall.java"], + name = "eval_zero_arity", + srcs = ["EvalZeroArity.java"], deps = [ ":cel_value_interpretable", + "//common/values", "//common/values:cel_value", "//runtime:evaluation_exception", + "//runtime:function_overload_impl", "//runtime:interpretable", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCall.java deleted file mode 100644 index c686ff187..000000000 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCall.java +++ /dev/null @@ -1,20 +0,0 @@ -package dev.cel.runtime.planner; - -import dev.cel.common.values.CelValue; -import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.GlobalResolver; - -final class EvalCall implements CelValueInterpretable { - - @Override - public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { - return null; - } - - static EvalCall create() { - return new EvalCall(); - } - - private EvalCall() { - } -} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java new file mode 100644 index 000000000..ee56eaf47 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java @@ -0,0 +1,28 @@ +package dev.cel.runtime.planner; + +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.ResolvedOverload; + +final class EvalZeroArity implements CelValueInterpretable { + + private final ResolvedOverload resolvedOverload; + private final CelValueConverter celValueConverter; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + Object result = resolvedOverload.getDefinition().apply(new Object[0]); + return celValueConverter.fromJavaObjectToCelValue(result); + } + + static EvalZeroArity create(ResolvedOverload resolvedOverload, CelValueConverter celValueConverter) { + return new EvalZeroArity(resolvedOverload, celValueConverter); + } + + private EvalZeroArity(ResolvedOverload resolvedOverload, CelValueConverter celValueConverter) { + this.resolvedOverload = resolvedOverload; + 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 a761c95fc..efb1104e0 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -1,6 +1,8 @@ package dev.cel.runtime.planner; +import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.ThreadSafe; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.annotations.Internal; @@ -15,17 +17,18 @@ import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.TypeValue; import dev.cel.runtime.CelLiteRuntime.Program; -import dev.cel.runtime.CelResolvedOverload; -import dev.cel.runtime.Dispatcher; +import dev.cel.runtime.DefaultDispatcher; +import dev.cel.runtime.ResolvedOverload; import java.util.NoSuchElementException; +import java.util.Optional; @ThreadSafe @Internal public final class ProgramPlanner { private final CelTypeProvider typeProvider; private final CelValueConverter celValueConverter; - private final Dispatcher dispatcher; + private final DefaultDispatcher dispatcher; private final AttributeFactory attributeFactory; private CelValueInterpretable plan(CelExpr celExpr, @@ -93,22 +96,77 @@ private EvalConstant fromCelConstant(CelConstant celConstant) { return EvalConstant.create(celValue); } - private EvalCall planCall(CelExpr celExpr, ImmutableMap referenceMap) { - CelCall call = celExpr.call(); - if (call.target().isPresent()) { + private EvalZeroArity planCall(CelExpr expr, ImmutableMap referenceMap) { + ResolvedFunction resolvedFunction = resolveFunction(expr, referenceMap); + // TODO: Handle args + int argCount = expr.call().args().size(); + // TODO: Handle specialized calls (logical operators, index, conditionals, equals etc) + + ResolvedOverload resolvedOverload = null; + + if (resolvedFunction.overloadId().isPresent()) { + resolvedOverload = dispatcher.findOverload(resolvedFunction.overloadId().get()).orElse(null); + } + + if (resolvedOverload == null) { + resolvedOverload = dispatcher.findOverload(resolvedFunction.functionName()).orElseThrow(() -> new NoSuchElementException("TODO: Overload not found")); + } + + switch (argCount) { + case 0: + return EvalZeroArity.create(resolvedOverload, celValueConverter); + default: + break; } - CelReference reference = referenceMap.get(celExpr.id()); + throw new UnsupportedOperationException("Unimplemented"); + } + + /** + * resolveFunction determines the call target, function name, and overload name (when unambiguous) from the given call expr. + */ + private ResolvedFunction resolveFunction(CelExpr expr, ImmutableMap referenceMap) { + CelCall call = expr.call(); + + CelReference reference = referenceMap.get(expr.id()); if (reference != null) { + if (reference.overloadIds().size() == 1) { + ResolvedFunction.Builder builder = ResolvedFunction.newBuilder() + .setFunctionName(call.function()) + .setOverloadId(reference.overloadIds().get(0)); + call.target().ifPresent(builder::setTarget); + + return builder.build(); + } } - return EvalCall.create(); + throw new UnsupportedOperationException("Unimplemented"); } - private CelResolvedOverload resolveFunction() { - return null; + @AutoValue + static abstract class ResolvedFunction { + + abstract String functionName(); + + abstract Optional target(); + + abstract Optional overloadId(); + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setFunctionName(String functionName); + abstract Builder setTarget(CelExpr target); + abstract Builder setOverloadId(String overloadId); + + @CheckReturnValue + abstract ResolvedFunction build(); + } + + private static Builder newBuilder() { + return new AutoValue_ProgramPlanner_ResolvedFunction.Builder(); + } } public Program plan(CelAbstractSyntaxTree ast) { @@ -119,7 +177,7 @@ public Program plan(CelAbstractSyntaxTree ast) { public static ProgramPlanner newPlanner( CelTypeProvider typeProvider, CelValueConverter celValueConverter, - Dispatcher dispatcher + DefaultDispatcher dispatcher ) { return new ProgramPlanner(typeProvider, celValueConverter, dispatcher); } @@ -127,11 +185,11 @@ public static ProgramPlanner newPlanner( private ProgramPlanner( CelTypeProvider typeProvider, CelValueConverter celValueConverter, - Dispatcher dispatcher + DefaultDispatcher dispatcher ) { this.typeProvider = typeProvider; this.celValueConverter = celValueConverter; - this.dispatcher = dispatcher; - this.attributeFactory = AttributeFactory.newAttributeFactory("", celValueConverter, typeProvider); + this.dispatcher = dispatcher; + this.attributeFactory = AttributeFactory.newAttributeFactory("", celValueConverter, typeProvider); } } diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 53272856c..5321e2331 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -63,7 +63,7 @@ java_library( "//parser:unparser", "//runtime", "//runtime:activation", - "//runtime:dispatcher", + "//runtime:legacy_dispatcher", "//runtime:evaluation_exception_builder", "//runtime:evaluation_listener", "//runtime:function_binding", diff --git a/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java index 4f3501d41..b4206f757 100644 --- a/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java @@ -73,7 +73,7 @@ public Object adapt(String messageName, Object message) { } }; CelAbstractSyntaxTree ast = celCompiler.compile("[1].all(x, [2].all(y, error()))").getAst(); - DefaultDispatcher dispatcher = DefaultDispatcher.create(); + LegacyDispatcher dispatcher = LegacyDispatcher.create(); dispatcher.add("error", long.class, (args) -> new IllegalArgumentException("Always throws")); DefaultInterpreter defaultInterpreter = new DefaultInterpreter(new TypeResolver(), emptyProvider, dispatcher, CelOptions.DEFAULT); diff --git a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java b/runtime/src/test/java/dev/cel/runtime/LegacyDispatcherTest.java similarity index 90% rename from runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java rename to runtime/src/test/java/dev/cel/runtime/LegacyDispatcherTest.java index 556d6945c..9ed82a260 100644 --- a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java +++ b/runtime/src/test/java/dev/cel/runtime/LegacyDispatcherTest.java @@ -1,60 +1,60 @@ -// Copyright 2024 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 aj -// -// 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 static com.google.common.truth.Truth.assertThat; - -import com.google.common.collect.ImmutableList; -import java.util.HashMap; -import java.util.Map; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Tests for {@link DefaultDispatcher}. */ -@RunWith(JUnit4.class) -public final class DefaultDispatcherTest { - - private Map overloads; - - @Before - public void setup() { - overloads = new HashMap<>(); - overloads.put( - "overload_1", - CelResolvedOverload.of( - "overload_1", new Class[] {Long.class}, args -> (Long) args[0] + 1)); - overloads.put( - "overload_2", - CelResolvedOverload.of( - "overload_2", new Class[] {Long.class}, args -> (Long) args[0] + 2)); - } - - @Test - public void findOverload_multipleMatches_throwsException() { - CelEvaluationException e = - Assert.assertThrows( - CelEvaluationException.class, - () -> - DefaultDispatcher.findOverload( - "overloads", - ImmutableList.of("overload_1", "overload_2"), - overloads, - new Object[] {1L})); - assertThat(e).hasMessageThat().contains("Matching candidates: overload_1, overload_2"); - } -} +// Copyright 2024 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 aj +// +// 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 static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import java.util.HashMap; +import java.util.Map; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link LegacyDispatcher}. */ +@RunWith(JUnit4.class) +public final class LegacyDispatcherTest { + + private Map overloads; + + @Before + public void setup() { + overloads = new HashMap<>(); + overloads.put( + "overload_1", + CelResolvedOverload.of( + "overload_1", new Class[] {Long.class}, args -> (Long) args[0] + 1)); + overloads.put( + "overload_2", + CelResolvedOverload.of( + "overload_2", new Class[] {Long.class}, args -> (Long) args[0] + 2)); + } + + @Test + public void findOverload_multipleMatches_throwsException() { + CelEvaluationException e = + Assert.assertThrows( + CelEvaluationException.class, + () -> + LegacyDispatcher.findOverload( + "overloads", + ImmutableList.of("overload_1", "overload_2"), + overloads, + new Object[] {1L})); + assertThat(e).hasMessageThat().contains("Matching candidates: overload_1, overload_2"); + } +} diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 35a6a0529..1eed332bb 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -49,7 +49,7 @@ java_library( "//parser:unparser", "//runtime", "//runtime:activation", - "//runtime:dispatcher", + "//runtime:default_dispatcher", "//runtime:evaluation_exception_builder", "//runtime:evaluation_listener", "//runtime:function_binding", @@ -58,6 +58,7 @@ java_library( "//runtime:interpreter", "//runtime:interpreter_util", "//runtime:late_function_binding", + "//runtime:legacy_dispatcher", "//runtime:lite_runtime", "//runtime:lite_runtime_factory", "//runtime:proto_message_activation_factory", 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 0d045d1d7..b4ed7ed2b 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -2,6 +2,7 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.primitives.UnsignedLong; import com.google.testing.junit.testparameterinjector.TestParameter; @@ -30,29 +31,31 @@ import java.nio.charset.StandardCharsets; import dev.cel.runtime.DefaultDispatcher; -import dev.cel.runtime.Dispatcher; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) public final class ProgramPlannerTest { private static final CelCompiler CEL_COMPILER = - CelCompilerFactory.standardCelCompilerBuilder() - .addVar("int_var", SimpleType.INT) - .addFunctionDeclarations(CelFunctionDecl.newFunctionDeclaration( - "zero", CelOverloadDecl.newGlobalOverload("zero", SimpleType.INT) - )) - .addLibraries(CelOptionalLibrary.INSTANCE) - .addMessageTypes(TestAllTypes.getDescriptor()) - .build(); + CelCompilerFactory.standardCelCompilerBuilder() + .addVar("int_var", SimpleType.INT) + .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.DYN)) + .addFunctionDeclarations(CelFunctionDecl.newFunctionDeclaration( + "zero", CelOverloadDecl.newGlobalOverload("zero", SimpleType.INT) + )) + .addLibraries(CelOptionalLibrary.INSTANCE) + .addMessageTypes(TestAllTypes.getDescriptor()) + .build(); private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner( - DefaultTypeProvider.create(), - new CelValueConverter(), - newDispatcher() + DefaultTypeProvider.create(), + new CelValueConverter(), + newDispatcher() ); - private static Dispatcher newDispatcher() { - return DefaultDispatcher.create(); + private static DefaultDispatcher newDispatcher() { + return DefaultDispatcher.newBuilder() + .add("zero", ImmutableList.of(), args -> 0L) + .build(); } @TestParameter boolean isParseOnly; @@ -148,7 +151,6 @@ public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) t assertThat(result).isEqualTo(testCase.type); } - @Test public void planCall_zeroArgs() throws Exception { CelAbstractSyntaxTree ast = compile("zero()"); @@ -159,13 +161,24 @@ public void planCall_zeroArgs() throws Exception { assertThat(result).isEqualTo(0L); } + @Test + public void planCall_mapIndex() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var['key'][1]"); + Program program = PLANNER.plan(ast); + ImmutableMap mapVarPayload = ImmutableMap.of("key", ImmutableList.of(1L, 2L)); + + Long result = (Long) program.eval(ImmutableMap.of("map_var", mapVarPayload)); + + assertThat(result).isEqualTo(1L); + } @Test public void smokeTest() throws Exception { - CelAbstractSyntaxTree ast = compile("google.protobuf.Duration"); + CelAbstractSyntaxTree ast = compile("map_var['key'][1]"); + ImmutableMap mapVarPayload = ImmutableMap.of("key", ImmutableList.of(1L, 2L)); CelRuntime.Program program = CelRuntimeFactory.standardCelRuntimeBuilder().build().createProgram(ast); - TypeType result = (TypeType) program.eval(); + Long result = (Long) program.eval(ImmutableMap.of("map_var", mapVarPayload)); assertThat(result).isEqualTo(TypeType.create(SimpleType.UINT)); } From 1b3a8e79e2785aca0d2257b6005fe7a736e655c6 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 8 Jul 2025 00:17:00 -0700 Subject: [PATCH 11/30] Handle unary --- .../src/main/java/dev/cel/runtime/BUILD.bazel | 3 +- .../dev/cel/runtime/CelFunctionBinding.java | 20 +++++ .../dev/cel/runtime/DefaultDispatcher.java | 61 ++------------ .../java/dev/cel/runtime/planner/BUILD.bazel | 17 +++- .../dev/cel/runtime/planner/EvalUnary.java | 31 +++++++ .../cel/runtime/planner/EvalZeroArity.java | 8 +- .../cel/runtime/planner/ProgramPlanner.java | 66 +++++++++++---- .../runtime/planner/ProgramPlannerTest.java | 82 +++++++++++++++++-- 8 files changed, 205 insertions(+), 83 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 7775e252b..638de457b 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -130,7 +130,8 @@ java_library( ], deps = [ ":base", - ":function_overload_impl", + ":function_binding", + ":function_overload", "//:auto_value", "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java index 8fe2b8a2e..7898eb9a7 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java @@ -17,6 +17,7 @@ import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; import dev.cel.common.annotations.Internal; +import java.util.List; /** * Binding consisting of an overload id, a Java-native argument signature, and an overload @@ -68,4 +69,23 @@ static CelFunctionBinding from( String overloadId, Iterable> argTypes, CelFunctionOverload impl) { return new FunctionBindingImpl(overloadId, ImmutableList.copyOf(argTypes), impl); } + + default boolean canHandle(Object[] arguments) { + ImmutableList> parameterTypes = getArgTypes(); + if (parameterTypes.size() != arguments.length) { + return false; + } + for (int i = 0; i < parameterTypes.size(); i++) { + Class paramType = parameterTypes.get(i); + Object arg = arguments[i]; + if (arg == null) { + // Reject nulls. CEL-Java in general is not designed to handle nullability of objects. + return false; + } + if (!paramType.isAssignableFrom(arg.getClass())) { + return false; + } + } + return true; + } } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index 9a7befa61..d0ab19503 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -5,19 +5,17 @@ import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; -import com.google.errorprone.annotations.Immutable; import dev.cel.common.annotations.Internal; -import java.util.List; import java.util.Optional; @Internal @AutoValue public abstract class DefaultDispatcher { - abstract ImmutableMap overloads(); + abstract ImmutableMap overloads(); - public Optional findOverload(String overloadId) { + public Optional findOverload(String overloadId) { return Optional.ofNullable(overloads().get(overloadId)); } @@ -27,67 +25,26 @@ public static Builder newBuilder() { @AutoValue.Builder public abstract static class Builder { - abstract ImmutableMap.Builder overloadsBuilder(); - - abstract ImmutableMap overloads(); + public abstract ImmutableMap.Builder overloadsBuilder(); @CanIgnoreReturnValue - public Builder add(String overloadId, Class argType, Registrar.UnaryFunction function) { + public Builder addOverload(CelFunctionBinding functionBinding) { overloadsBuilder().put( - overloadId, - ResolvedOverloadImpl.of( - overloadId, new Class[] {argType}, args -> function.apply((T) args[0]))); + functionBinding.getOverloadId(), + functionBinding); return this; } @CanIgnoreReturnValue - public Builder add(String overloadId, Class argType1, Class argType2, Registrar.BinaryFunction function) { + public Builder addFunction(String functionName, CelFunctionOverload definition) { overloadsBuilder().put( - overloadId, - ResolvedOverloadImpl.of( - overloadId, - new Class[] {argType1, argType2}, - args -> function.apply((T1) args[0], (T2) args[1]))); - return this; - } + functionName, CelFunctionBinding.from(functionName, ImmutableList.of(), definition) + ); - @CanIgnoreReturnValue - public Builder add(String overloadId, List> argTypes, Registrar.Function function) { - overloadsBuilder().put( - overloadId, - ResolvedOverloadImpl.of(overloadId, argTypes.toArray(new Class[0]), function)); return this; } @CheckReturnValue public abstract DefaultDispatcher build(); } - - // TODO: Refactor to reuse across LegacyDispatcher - @AutoValue - @Immutable - abstract static class ResolvedOverloadImpl implements ResolvedOverload { - /** The overload id of the function. */ - @Override - public abstract String getOverloadId(); - - /** The types of the function parameters. */ - @Override - public abstract ImmutableList> getParameterTypes(); - - /** The function definition. */ - @Override - public abstract FunctionOverload getDefinition(); - - static ResolvedOverload of( - String overloadId, Class[] parameterTypes, FunctionOverload definition) { - return of(overloadId, ImmutableList.copyOf(parameterTypes), definition); - } - - static ResolvedOverload of( - String overloadId, ImmutableList> parameterTypes, FunctionOverload definition) { - return new AutoValue_DefaultDispatcher_ResolvedOverloadImpl( - overloadId, parameterTypes, definition); - } - } } 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 a1fee44c8..5530cfc86 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -16,6 +16,7 @@ java_library( ":cel_value_program", ":eval_attribute", ":eval_const", + ":eval_unary", ":eval_zero_arity", "//:auto_value", "//common:cel_ast", @@ -27,6 +28,7 @@ java_library( "//runtime:activation", "//runtime:default_dispatcher", "//runtime:evaluation_exception", + "//runtime:function_binding", "//runtime:function_overload_impl", "//runtime:function_resolver", "//runtime:interpretable", @@ -129,7 +131,20 @@ java_library( "//common/values", "//common/values:cel_value", "//runtime:evaluation_exception", - "//runtime:function_overload_impl", + "//runtime:function_binding", + "//runtime:interpretable", + ], +) + +java_library( + name = "eval_unary", + srcs = ["EvalUnary.java"], + deps = [ + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:function_binding", "//runtime:interpretable", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java new file mode 100644 index 000000000..de2321e58 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java @@ -0,0 +1,31 @@ +package dev.cel.runtime.planner; + +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.CelFunctionBinding; + +final class EvalUnary implements CelValueInterpretable { + + private final CelFunctionBinding resolvedOverload; + private final CelValueConverter celValueConverter; + private final CelValueInterpretable arg; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + CelValue argVal = arg.eval(resolver); + Object result = resolvedOverload.getDefinition().apply(new Object[] {argVal.value()}); + return celValueConverter.fromJavaObjectToCelValue(result); + } + + static EvalUnary create(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter, CelValueInterpretable arg) { + return new EvalUnary(resolvedOverload, celValueConverter, arg); + } + + private EvalUnary(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter, CelValueInterpretable arg) { + this.resolvedOverload = resolvedOverload; + this.celValueConverter = celValueConverter; + this.arg = arg; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java index ee56eaf47..476c9d836 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java @@ -3,12 +3,12 @@ import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.ResolvedOverload; final class EvalZeroArity implements CelValueInterpretable { - private final ResolvedOverload resolvedOverload; + private final CelFunctionBinding resolvedOverload; private final CelValueConverter celValueConverter; @Override @@ -17,11 +17,11 @@ public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { return celValueConverter.fromJavaObjectToCelValue(result); } - static EvalZeroArity create(ResolvedOverload resolvedOverload, CelValueConverter celValueConverter) { + static EvalZeroArity create(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter) { return new EvalZeroArity(resolvedOverload, celValueConverter); } - private EvalZeroArity(ResolvedOverload resolvedOverload, CelValueConverter celValueConverter) { + private EvalZeroArity(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter) { this.resolvedOverload = resolvedOverload; 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 efb1104e0..2b666125e 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -1,6 +1,7 @@ package dev.cel.runtime.planner; import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.ThreadSafe; @@ -16,9 +17,9 @@ import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.TypeValue; +import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelLiteRuntime.Program; import dev.cel.runtime.DefaultDispatcher; -import dev.cel.runtime.ResolvedOverload; import java.util.NoSuchElementException; import java.util.Optional; @@ -31,18 +32,19 @@ public final class ProgramPlanner { private final DefaultDispatcher dispatcher; private final AttributeFactory attributeFactory; - private CelValueInterpretable plan(CelExpr celExpr, - ImmutableMap typeMap, - ImmutableMap referenceMap) { + private CelValueInterpretable plan( + CelExpr celExpr, + PlannerContext ctx + ) { switch (celExpr.getKind()) { case CONSTANT: return fromCelConstant(celExpr.constant()); case IDENT: - return planIdent(celExpr, typeMap, referenceMap); + return planIdent(celExpr, ctx); case SELECT: break; case CALL: - return planCall(celExpr, referenceMap); + return planCall(celExpr, ctx); case LIST: break; case STRUCT: @@ -60,11 +62,10 @@ private CelValueInterpretable plan(CelExpr celExpr, private CelValueInterpretable planIdent( CelExpr celExpr, - ImmutableMap typeMap, - ImmutableMap referenceMap) { - CelReference ref = referenceMap.get(celExpr.id()); + PlannerContext ctx) { + CelReference ref = ctx.referenceMap().get(celExpr.id()); if (ref != null) { - return planCheckedIdent(celExpr.id(), ref, typeMap); + return planCheckedIdent(celExpr.id(), ref, ctx.typeMap()); } return EvalAttribute.create( @@ -96,14 +97,18 @@ private EvalConstant fromCelConstant(CelConstant celConstant) { return EvalConstant.create(celValue); } - private EvalZeroArity planCall(CelExpr expr, ImmutableMap referenceMap) { - ResolvedFunction resolvedFunction = resolveFunction(expr, referenceMap); - // TODO: Handle args + private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { + ResolvedFunction resolvedFunction = resolveFunction(expr, ctx.referenceMap()); int argCount = expr.call().args().size(); + ImmutableList.Builder evaluatedArgBuilder = ImmutableList.builder(); + for (CelExpr argExpr : expr.call().args()) { + evaluatedArgBuilder.add(plan(argExpr, ctx)); + } + ImmutableList evaluatedArgs = evaluatedArgBuilder.build(); // TODO: Handle specialized calls (logical operators, index, conditionals, equals etc) - ResolvedOverload resolvedOverload = null; + CelFunctionBinding resolvedOverload = null; if (resolvedFunction.overloadId().isPresent()) { resolvedOverload = dispatcher.findOverload(resolvedFunction.overloadId().get()).orElse(null); @@ -116,6 +121,8 @@ private EvalZeroArity planCall(CelExpr expr, ImmutableMap re switch (argCount) { case 0: return EvalZeroArity.create(resolvedOverload, celValueConverter); + case 1: + return EvalUnary.create(resolvedOverload, celValueConverter, evaluatedArgs.get(0)); default: break; } @@ -128,20 +135,34 @@ private EvalZeroArity planCall(CelExpr expr, ImmutableMap re */ private ResolvedFunction resolveFunction(CelExpr expr, ImmutableMap referenceMap) { CelCall call = expr.call(); + Optional target = call.target(); + String functionName = call.function(); CelReference reference = referenceMap.get(expr.id()); if (reference != null) { + // Checked expression if (reference.overloadIds().size() == 1) { ResolvedFunction.Builder builder = ResolvedFunction.newBuilder() - .setFunctionName(call.function()) + .setFunctionName(functionName) .setOverloadId(reference.overloadIds().get(0)); - call.target().ifPresent(builder::setTarget); + target.ifPresent(builder::setTarget); return builder.build(); } } + // Parse-only from this point on + if (!target.isPresent()) { + // TODO: Handle containers. + CelFunctionBinding resolvedOverload = dispatcher.findOverload(functionName) + .orElseThrow(() -> new NoSuchElementException(String.format("Function %s not found", call.function()))); + + return ResolvedFunction.newBuilder() + .setFunctionName(functionName) + .build(); + } + throw new UnsupportedOperationException("Unimplemented"); } @@ -169,8 +190,19 @@ private static Builder newBuilder() { } } + @AutoValue + static abstract class PlannerContext { + + abstract ImmutableMap referenceMap(); + abstract ImmutableMap typeMap(); + + private static PlannerContext create(CelAbstractSyntaxTree ast) { + return new AutoValue_ProgramPlanner_PlannerContext(ast.getReferenceMap(), ast.getTypeMap()); + } + } + public Program plan(CelAbstractSyntaxTree ast) { - CelValueInterpretable plannedInterpretable = plan(ast.getExpr(), ast.getTypeMap(), ast.getReferenceMap()); + CelValueInterpretable plannedInterpretable = plan(ast.getExpr(), PlannerContext.create(ast)); return CelValueProgram.create(plannedInterpretable, celValueConverter); } 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 b4ed7ed2b..55378999d 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -1,6 +1,8 @@ package dev.cel.runtime.planner; import static com.google.common.truth.Truth.assertThat; +import static dev.cel.common.CelFunctionDecl.*; +import static dev.cel.common.CelOverloadDecl.*; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -8,8 +10,6 @@ import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelAbstractSyntaxTree; -import dev.cel.common.CelFunctionDecl; -import dev.cel.common.CelOverloadDecl; import dev.cel.common.CelValidationException; import dev.cel.common.types.CelType; import dev.cel.common.types.DefaultTypeProvider; @@ -25,6 +25,8 @@ import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto2.TestAllTypes; import dev.cel.extensions.CelOptionalLibrary; +import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.CelFunctionOverload; import dev.cel.runtime.CelLiteRuntime.Program; import dev.cel.runtime.CelRuntime; import dev.cel.runtime.CelRuntimeFactory; @@ -40,12 +42,17 @@ public final class ProgramPlannerTest { CelCompilerFactory.standardCelCompilerBuilder() .addVar("int_var", SimpleType.INT) .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.DYN)) - .addFunctionDeclarations(CelFunctionDecl.newFunctionDeclaration( - "zero", CelOverloadDecl.newGlobalOverload("zero", SimpleType.INT) - )) + .addFunctionDeclarations( + newFunctionDeclaration("zero", newGlobalOverload("zero_overload", SimpleType.INT)), + newFunctionDeclaration("neg", + newGlobalOverload("neg_int", SimpleType.INT, SimpleType.INT), + newGlobalOverload("neg_double", SimpleType.DOUBLE, SimpleType.DOUBLE) + ) + ) .addLibraries(CelOptionalLibrary.INSTANCE) .addMessageTypes(TestAllTypes.getDescriptor()) .build(); + private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner( DefaultTypeProvider.create(), new CelValueConverter(), @@ -53,9 +60,48 @@ public final class ProgramPlannerTest { ); private static DefaultDispatcher newDispatcher() { - return DefaultDispatcher.newBuilder() - .add("zero", ImmutableList.of(), args -> 0L) - .build(); + DefaultDispatcher.Builder builder = DefaultDispatcher.newBuilder(); + + addBindings(builder, "zero", CelFunctionBinding.from("zero_overload", ImmutableList.of(), args -> 0L)); + addBindings(builder, "neg", + CelFunctionBinding.from("neg_int", Long.class, arg -> -arg), + CelFunctionBinding.from("neg_double", Double.class, arg -> -arg) + ); + + return builder.build(); + } + + private static void addBindings(DefaultDispatcher.Builder builder, String functionName, CelFunctionBinding... functionBindings) { + addBindings(builder, functionName, ImmutableList.copyOf(functionBindings)); + } + + private static void addBindings(DefaultDispatcher.Builder builder, String functionName, ImmutableList overloadBindings) { + if (overloadBindings.isEmpty()) { + throw new IllegalArgumentException("Invalid bindings"); + } + // TODO: Runtime top-level APIs currently does not allow grouping overloads with the function name. This capability will have to be added. + if (overloadBindings.size() == 1) { + CelFunctionBinding singleBinding = overloadBindings.get(0); + builder.addOverload( + CelFunctionBinding.from( + functionName, singleBinding.getArgTypes(), singleBinding.getDefinition()) + ); + } else { + overloadBindings.forEach(builder::addOverload); + + // Setup dynamic dispatch + CelFunctionOverload dynamicDispatchDef = args -> { + for (CelFunctionBinding overload : overloadBindings) { + if (overload.canHandle(args)) { + return overload.getDefinition().apply(args); + } + } + + throw new IllegalArgumentException("Overload not found: " + functionName); + }; + + builder.addFunction(functionName, dynamicDispatchDef); + } } @TestParameter boolean isParseOnly; @@ -161,6 +207,26 @@ public void planCall_zeroArgs() throws Exception { assertThat(result).isEqualTo(0L); } + @Test + public void planCall_oneArg_int() throws Exception { + CelAbstractSyntaxTree ast = compile("neg(1)"); + Program program = PLANNER.plan(ast); + + Long result = (Long) program.eval(); + + assertThat(result).isEqualTo(-1L); + } + + @Test + public void planCall_oneArg_double() throws Exception { + CelAbstractSyntaxTree ast = compile("neg(2.5)"); + Program program = PLANNER.plan(ast); + + Double result = (Double) program.eval(); + + assertThat(result).isEqualTo(-2.5d); + } + @Test public void planCall_mapIndex() throws Exception { CelAbstractSyntaxTree ast = compile("map_var['key'][1]"); From e0951f0b16e5f0092181b1e94e936501ed997e45 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 8 Jul 2025 00:48:19 -0700 Subject: [PATCH 12/30] Handle var arg calls --- .../java/dev/cel/runtime/planner/BUILD.bazel | 15 ++++++ .../cel/runtime/planner/EvalVarArgsCall.java | 36 +++++++++++++ .../cel/runtime/planner/ProgramPlanner.java | 23 +++++--- .../runtime/planner/ProgramPlannerTest.java | 52 +++++++++++++++---- 4 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java 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 5530cfc86..410231864 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -17,6 +17,7 @@ java_library( ":eval_attribute", ":eval_const", ":eval_unary", + ":eval_var_args_call", ":eval_zero_arity", "//:auto_value", "//common:cel_ast", @@ -148,3 +149,17 @@ java_library( "//runtime:interpretable", ], ) + +java_library( + name = "eval_var_args_call", + srcs = ["EvalVarArgsCall.java"], + deps = [ + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:function_binding", + "//runtime:interpretable", + "@maven//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java new file mode 100644 index 000000000..8b508cd28 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java @@ -0,0 +1,36 @@ +package dev.cel.runtime.planner; + +import com.google.common.collect.ImmutableList; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.GlobalResolver; +final class EvalVarArgsCall implements CelValueInterpretable{ + + private final CelFunctionBinding resolvedOverload; + private final CelValueConverter celValueConverter; + private final ImmutableList args; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + ImmutableList.Builder argValBuilder = ImmutableList.builder(); + for (CelValueInterpretable arg : args) { + argValBuilder.add(arg.eval(resolver).value()); + } + ImmutableList argVals = argValBuilder.build(); + + Object result = resolvedOverload.getDefinition().apply(argVals.toArray()); + return celValueConverter.fromJavaObjectToCelValue(result); + } + + static EvalVarArgsCall create(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter, ImmutableList s) { + return new EvalVarArgsCall(resolvedOverload, celValueConverter, s); + } + + private EvalVarArgsCall(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter, ImmutableList args) { + this.resolvedOverload = resolvedOverload; + this.celValueConverter = celValueConverter; + this.args = args; + } +} 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 2b666125e..a36186817 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -101,6 +101,12 @@ private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { ResolvedFunction resolvedFunction = resolveFunction(expr, ctx.referenceMap()); int argCount = expr.call().args().size(); ImmutableList.Builder evaluatedArgBuilder = ImmutableList.builder(); + + if (resolvedFunction.target().isPresent()) { + argCount++; + evaluatedArgBuilder.add(plan(resolvedFunction.target().get(), ctx)); + } + for (CelExpr argExpr : expr.call().args()) { evaluatedArgBuilder.add(plan(argExpr, ctx)); } @@ -124,10 +130,8 @@ private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { case 1: return EvalUnary.create(resolvedOverload, celValueConverter, evaluatedArgs.get(0)); default: - break; + return EvalVarArgsCall.create(resolvedOverload, celValueConverter, evaluatedArgs); } - - throw new UnsupportedOperationException("Unimplemented"); } /** @@ -153,17 +157,22 @@ private ResolvedFunction resolveFunction(CelExpr expr, ImmutableMap new NoSuchElementException(String.format("Function %s not found", call.function()))); + if (!target.isPresent()) { // TODO: Handle containers. - CelFunctionBinding resolvedOverload = dispatcher.findOverload(functionName) - .orElseThrow(() -> new NoSuchElementException(String.format("Function %s not found", call.function()))); return ResolvedFunction.newBuilder() .setFunctionName(functionName) .build(); + } else { + // TODO: Handle qualifications + return ResolvedFunction.newBuilder() + .setFunctionName(functionName) + .setTarget(target.get()) + .build(); } - - throw new UnsupportedOperationException("Unimplemented"); } @AutoValue 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 55378999d..e1c9df780 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -28,8 +28,6 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelFunctionOverload; import dev.cel.runtime.CelLiteRuntime.Program; -import dev.cel.runtime.CelRuntime; -import dev.cel.runtime.CelRuntimeFactory; import java.nio.charset.StandardCharsets; import dev.cel.runtime.DefaultDispatcher; @@ -47,6 +45,10 @@ public final class ProgramPlannerTest { newFunctionDeclaration("neg", newGlobalOverload("neg_int", SimpleType.INT, SimpleType.INT), newGlobalOverload("neg_double", SimpleType.DOUBLE, SimpleType.DOUBLE) + ), + newFunctionDeclaration("concat", + newGlobalOverload("concat_bytes_bytes", SimpleType.BYTES, SimpleType.BYTES, SimpleType.BYTES), + newMemberOverload("bytes_concat_bytes", SimpleType.BYTES, SimpleType.BYTES, SimpleType.BYTES) ) ) .addLibraries(CelOptionalLibrary.INSTANCE) @@ -67,6 +69,9 @@ private static DefaultDispatcher newDispatcher() { CelFunctionBinding.from("neg_int", Long.class, arg -> -arg), CelFunctionBinding.from("neg_double", Double.class, arg -> -arg) ); + addBindings(builder, "concat", + CelFunctionBinding.from("concat_bytes_bytes", CelByteString.class, CelByteString.class, ProgramPlannerTest::concatenateByteArrays), + CelFunctionBinding.from("bytes_concat_bytes", CelByteString.class, CelByteString.class,ProgramPlannerTest::concatenateByteArrays)); return builder.build(); } @@ -228,25 +233,35 @@ public void planCall_oneArg_double() throws Exception { } @Test - public void planCall_mapIndex() throws Exception { - CelAbstractSyntaxTree ast = compile("map_var['key'][1]"); + public void planCall_twoArgs_global() throws Exception { + CelAbstractSyntaxTree ast = compile("concat(b'abc', b'def')"); Program program = PLANNER.plan(ast); - ImmutableMap mapVarPayload = ImmutableMap.of("key", ImmutableList.of(1L, 2L)); - Long result = (Long) program.eval(ImmutableMap.of("map_var", mapVarPayload)); + CelByteString result = (CelByteString) program.eval(); - assertThat(result).isEqualTo(1L); + assertThat(result).isEqualTo(CelByteString.of("abcdef".getBytes(StandardCharsets.UTF_8))); + } + + @Test + public void planCall_twoArgs_receiver() throws Exception { + CelAbstractSyntaxTree ast = compile("b'abc'.concat(b'def')"); + Program program = PLANNER.plan(ast); + + CelByteString result = (CelByteString) program.eval(); + + assertThat(result).isEqualTo(CelByteString.of("abcdef".getBytes(StandardCharsets.UTF_8))); } + @Test - public void smokeTest() throws Exception { + public void planCall_mapIndex() throws Exception { CelAbstractSyntaxTree ast = compile("map_var['key'][1]"); + Program program = PLANNER.plan(ast); ImmutableMap mapVarPayload = ImmutableMap.of("key", ImmutableList.of(1L, 2L)); - CelRuntime.Program program = CelRuntimeFactory.standardCelRuntimeBuilder().build().createProgram(ast); Long result = (Long) program.eval(ImmutableMap.of("map_var", mapVarPayload)); - assertThat(result).isEqualTo(TypeType.create(SimpleType.UINT)); + assertThat(result).isEqualTo(1L); } private CelAbstractSyntaxTree compile(String expression) throws CelValidationException { @@ -257,4 +272,21 @@ private CelAbstractSyntaxTree compile(String expression) throws CelValidationExc return CEL_COMPILER.check(ast).getAst(); } + + private static byte[] concatenateByteArrays(CelByteString bytes1, CelByteString bytes2) { + byte[] array1 = bytes1.toByteArray(); + byte[] array2 = bytes2.toByteArray(); + // Handle null or empty arrays gracefully + if (array1 == null || array1.length == 0) { + return array2; + } + if (array2 == null || array2.length == 0) { + return array1; + } + + byte[] combined = new byte[array1.length + array2.length]; + System.arraycopy(array1, 0, combined, 0, array1.length); + System.arraycopy(array2, 0, combined, array1.length, array2.length); + return combined; + } } From 64ec8ba4bca7b7149ea124de10fa8c465040f182 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 8 Jul 2025 14:07:24 -0700 Subject: [PATCH 13/30] Handle index --- .../cel/runtime/planner/EvalVarArgsCall.java | 3 +- .../cel/runtime/planner/ProgramPlanner.java | 5 +-- .../java/dev/cel/runtime/planner/BUILD.bazel | 3 ++ .../runtime/planner/ProgramPlannerTest.java | 32 ++++++++++++++++--- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java index 8b508cd28..1fbb55dd9 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java @@ -16,7 +16,8 @@ final class EvalVarArgsCall implements CelValueInterpretable{ public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { ImmutableList.Builder argValBuilder = ImmutableList.builder(); for (CelValueInterpretable arg : args) { - argValBuilder.add(arg.eval(resolver).value()); + Object evalValue = celValueConverter.fromCelValueToJavaObject(arg.eval(resolver)); + argValBuilder.add(evalValue); } ImmutableList argVals = argValBuilder.build(); 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 a36186817..43a1d4f50 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -112,7 +112,7 @@ private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { } ImmutableList evaluatedArgs = evaluatedArgBuilder.build(); - // TODO: Handle specialized calls (logical operators, index, conditionals, equals etc) + // TODO: Handle specialized calls (logical operators, conditionals, equals etc) CelFunctionBinding resolvedOverload = null; @@ -129,6 +129,7 @@ private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { return EvalZeroArity.create(resolvedOverload, celValueConverter); case 1: return EvalUnary.create(resolvedOverload, celValueConverter, evaluatedArgs.get(0)); + // TODO: Handle binary default: return EvalVarArgsCall.create(resolvedOverload, celValueConverter, evaluatedArgs); } @@ -157,7 +158,7 @@ private ResolvedFunction resolveFunction(CelExpr expr, ImmutableMap new NoSuchElementException(String.format("Function %s not found", call.function()))); if (!target.isPresent()) { diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 1eed332bb..c4bc76746 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -46,6 +46,7 @@ java_library( "//compiler:compiler_builder", "//extensions:optional_library", "//parser:macro", + "//parser:operator", "//parser:unparser", "//runtime", "//runtime:activation", @@ -71,6 +72,8 @@ java_library( "//runtime:unknown_attributes", "//runtime:unknown_options", "//runtime/planner:program_planner", + "//runtime/standard:index", + "//runtime/standard:standard_function", "//testing/protos:message_with_enum_cel_java_proto", "//testing/protos:message_with_enum_java_proto", "//testing/protos:multi_file_cel_java_proto", 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 e1c9df780..8434730ed 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -4,12 +4,16 @@ import static dev.cel.common.CelFunctionDecl.*; import static dev.cel.common.CelOverloadDecl.*; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.primitives.UnsignedLong; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelOptions; import dev.cel.common.CelValidationException; import dev.cel.common.types.CelType; import dev.cel.common.types.DefaultTypeProvider; @@ -25,17 +29,25 @@ import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto2.TestAllTypes; import dev.cel.extensions.CelOptionalLibrary; +import dev.cel.parser.Operator; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelFunctionOverload; import dev.cel.runtime.CelLiteRuntime.Program; +import dev.cel.runtime.RuntimeEquality; +import dev.cel.runtime.RuntimeHelpers; +import dev.cel.runtime.standard.CelStandardFunction; +import dev.cel.runtime.standard.IndexOperator; import java.nio.charset.StandardCharsets; import dev.cel.runtime.DefaultDispatcher; +import java.util.Collection; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) public final class ProgramPlannerTest { + private static final CelOptions CEL_OPTIONS = CelOptions.DEFAULT; + private static final RuntimeEquality RUNTIME_EQUALITY = RuntimeEquality.create(RuntimeHelpers.create(), CEL_OPTIONS); private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() .addVar("int_var", SimpleType.INT) @@ -61,9 +73,16 @@ public final class ProgramPlannerTest { newDispatcher() ); + /** + * Configure dispatcher for testing purposes. This is done manually here, but this should be driven by the top-level runtime APIs in the future + */ private static DefaultDispatcher newDispatcher() { DefaultDispatcher.Builder builder = DefaultDispatcher.newBuilder(); + // Subsetted StdLib + addBindings(builder, Operator.INDEX.getFunction(), fromStandardFunction(IndexOperator.create())); + + // Custom functions addBindings(builder, "zero", CelFunctionBinding.from("zero_overload", ImmutableList.of(), args -> 0L)); addBindings(builder, "neg", CelFunctionBinding.from("neg_int", Long.class, arg -> -arg), @@ -76,17 +95,21 @@ private static DefaultDispatcher newDispatcher() { return builder.build(); } + private static ImmutableSet fromStandardFunction(CelStandardFunction standardFunction) { + return standardFunction.newFunctionBindings(CEL_OPTIONS, RUNTIME_EQUALITY); + } + private static void addBindings(DefaultDispatcher.Builder builder, String functionName, CelFunctionBinding... functionBindings) { - addBindings(builder, functionName, ImmutableList.copyOf(functionBindings)); + addBindings(builder, functionName, ImmutableSet.copyOf(functionBindings)); } - private static void addBindings(DefaultDispatcher.Builder builder, String functionName, ImmutableList overloadBindings) { + private static void addBindings(DefaultDispatcher.Builder builder, String functionName, ImmutableCollection overloadBindings) { if (overloadBindings.isEmpty()) { throw new IllegalArgumentException("Invalid bindings"); } // TODO: Runtime top-level APIs currently does not allow grouping overloads with the function name. This capability will have to be added. if (overloadBindings.size() == 1) { - CelFunctionBinding singleBinding = overloadBindings.get(0); + CelFunctionBinding singleBinding = Iterables.getOnlyElement(overloadBindings); builder.addOverload( CelFunctionBinding.from( functionName, singleBinding.getArgTypes(), singleBinding.getDefinition()) @@ -252,7 +275,6 @@ public void planCall_twoArgs_receiver() throws Exception { assertThat(result).isEqualTo(CelByteString.of("abcdef".getBytes(StandardCharsets.UTF_8))); } - @Test public void planCall_mapIndex() throws Exception { CelAbstractSyntaxTree ast = compile("map_var['key'][1]"); @@ -261,7 +283,7 @@ public void planCall_mapIndex() throws Exception { Long result = (Long) program.eval(ImmutableMap.of("map_var", mapVarPayload)); - assertThat(result).isEqualTo(1L); + assertThat(result).isEqualTo(2L); } private CelAbstractSyntaxTree compile(String expression) throws CelValidationException { From c7f35d410d504e1e174a5236d32b65c042c22791 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 8 Jul 2025 14:26:55 -0700 Subject: [PATCH 14/30] Use ProtoLiteCelValueConverter --- .../cel/runtime/planner/CelValueProgram.java | 2 +- .../cel/runtime/planner/EvalVarArgsCall.java | 3 ++- .../java/dev/cel/runtime/planner/BUILD.bazel | 2 ++ .../runtime/planner/ProgramPlannerTest.java | 23 +++++++++++++++++-- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java index 514678d30..0074f3d9f 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java @@ -33,7 +33,7 @@ public Object eval(Map mapValue) throws CelEvaluationException { @Override public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) throws CelEvaluationException { - return null; + throw new UnsupportedOperationException("Late bound functions not supported yet"); } static Program create( diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java index 1fbb55dd9..17637e555 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java @@ -6,7 +6,8 @@ import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.GlobalResolver; -final class EvalVarArgsCall implements CelValueInterpretable{ + +final class EvalVarArgsCall implements CelValueInterpretable { private final CelFunctionBinding resolvedOverload; private final CelValueConverter celValueConverter; diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index c4bc76746..1dd43e4df 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -28,6 +28,7 @@ java_library( "//common/ast", "//common/internal:cel_descriptor_pools", "//common/internal:converter", + "//common/internal:default_lite_descriptor_pool", "//common/internal:default_message_factory", "//common/internal:dynamic_proto", "//common/internal:proto_message_factory", @@ -41,6 +42,7 @@ java_library( "//common/values", "//common/values:cel_byte_string", "//common/values:cel_value_provider", + "//common/values:proto_message_lite_value", "//common/values:proto_message_lite_value_provider", "//compiler", "//compiler:compiler_builder", 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 8434730ed..d2f2e60a7 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -12,9 +12,11 @@ import com.google.common.primitives.UnsignedLong; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelOptions; import dev.cel.common.CelValidationException; +import dev.cel.common.internal.DefaultLiteDescriptorPool; import dev.cel.common.types.CelType; import dev.cel.common.types.DefaultTypeProvider; import dev.cel.common.types.ListType; @@ -25,6 +27,7 @@ import dev.cel.common.values.CelByteString; import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.NullValue; +import dev.cel.common.values.ProtoLiteCelValueConverter; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto2.TestAllTypes; @@ -40,7 +43,6 @@ import java.nio.charset.StandardCharsets; import dev.cel.runtime.DefaultDispatcher; -import java.util.Collection; import org.junit.Test; import org.junit.runner.RunWith; @@ -69,7 +71,8 @@ public final class ProgramPlannerTest { private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner( DefaultTypeProvider.create(), - new CelValueConverter(), + // new CelValueConverter(), + ProtoLiteCelValueConverter.newInstance(DefaultLiteDescriptorPool.newInstance()), newDispatcher() ); @@ -286,6 +289,22 @@ public void planCall_mapIndex() throws Exception { assertThat(result).isEqualTo(2L); } + @Test + @TestParameters("{expression: 'true || true', expectedResult: true}") + @TestParameters("{expression: 'true || false', expectedResult: true}") + @TestParameters("{expression: 'false || true', expectedResult: true}") + @TestParameters("{expression: 'false || false', expectedResult: false}") + @TestParameters("{expression: 'true || (1 / 0 > 2)', expectedResult: true}") + @TestParameters("{expression: '(1 / 0 > 2) || true', expectedResult: true}") + public void planCall_logicalOr_shortCircuit(String expression, boolean expectedResult) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + boolean result = (boolean) program.eval(); + + assertThat(result).isEqualTo(expectedResult); + } + private CelAbstractSyntaxTree compile(String expression) throws CelValidationException { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); if (isParseOnly) { From 20635eceded644a963a912c1a83f6fdfc54c05f5 Mon Sep 17 00:00:00 2001 From: swh Date: Wed, 9 Jul 2025 09:16:27 +0100 Subject: [PATCH 15/30] Handle logical ors/ands with errors --- .../java/dev/cel/runtime/planner/BUILD.bazel | 48 +++++++++++ .../cel/runtime/planner/CelValueProgram.java | 28 ++++++- .../java/dev/cel/runtime/planner/EvalAnd.java | 48 +++++++++++ .../dev/cel/runtime/planner/EvalHelpers.java | 18 +++++ .../java/dev/cel/runtime/planner/EvalOr.java | 48 +++++++++++ .../cel/runtime/planner/EvalVarArgsCall.java | 4 +- .../cel/runtime/planner/ProgramPlanner.java | 14 +++- .../java/dev/cel/runtime/planner/BUILD.bazel | 2 + .../runtime/planner/ProgramPlannerTest.java | 79 +++++++++++++++++-- 9 files changed, 274 insertions(+), 15 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java 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 410231864..ab4011e52 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -14,8 +14,10 @@ java_library( ":attribute_factory", ":cel_value_interpretable", ":cel_value_program", + ":eval_and", ":eval_attribute", ":eval_const", + ":eval_or", ":eval_unary", ":eval_var_args_call", ":eval_zero_arity", @@ -60,10 +62,12 @@ java_library( deps = [ ":cel_value_interpretable", "//:auto_value", + "//common:runtime_exception", "//common/values", "//common/values:cel_value", "//runtime:activation", "//runtime:evaluation_exception", + "//runtime:evaluation_exception_builder", "//runtime:evaluation_listener", "//runtime:function_resolver", "//runtime:interpretable", @@ -163,3 +167,47 @@ java_library( "@maven//:com_google_guava_guava", ], ) + +java_library( + name = "eval_or", + srcs = ["EvalOr.java"], + deps = [ + ":cel_value_interpretable", + ":eval_helpers", + "//common/values", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:function_binding", + "//runtime:interpretable", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_and", + srcs = ["EvalAnd.java"], + deps = [ + ":cel_value_interpretable", + ":eval_helpers", + "//common/values", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:function_binding", + "//runtime:interpretable", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_helpers", + srcs = ["EvalHelpers.java"], + deps = [ + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:function_binding", + "//runtime:interpretable", + "@maven//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java index 0074f3d9f..454bed653 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java @@ -2,10 +2,13 @@ import com.google.auto.value.AutoValue; import com.google.errorprone.annotations.Immutable; +import dev.cel.common.CelRuntimeException; import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; +import dev.cel.common.values.ErrorValue; import dev.cel.runtime.Activation; import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelEvaluationExceptionBuilder; import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.CelLiteRuntime.Program; import dev.cel.runtime.GlobalResolver; @@ -20,13 +23,13 @@ abstract class CelValueProgram implements Program { @Override public Object eval() throws CelEvaluationException { - CelValue evalResult = interpretable().eval(GlobalResolver.EMPTY); + CelValue evalResult = evalOrThrow(interpretable(), GlobalResolver.EMPTY); return celValueConverter().fromCelValueToJavaObject(evalResult); } @Override public Object eval(Map mapValue) throws CelEvaluationException { - CelValue evalResult = interpretable().eval(Activation.copyOf(mapValue)); + CelValue evalResult = evalOrThrow(interpretable(), Activation.copyOf(mapValue)); return celValueConverter().fromCelValueToJavaObject(evalResult); } @@ -36,6 +39,27 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio throw new UnsupportedOperationException("Late bound functions not supported yet"); } + private static CelValue evalOrThrow(CelValueInterpretable interpretable, GlobalResolver resolver) throws CelEvaluationException { + CelValue evalResult = interpretable.eval(resolver); + if (evalResult instanceof ErrorValue) { + ErrorValue errorValue = (ErrorValue) evalResult; + Exception e = errorValue.value(); + + + CelEvaluationExceptionBuilder builder; + if (e instanceof CelRuntimeException) { + builder = CelEvaluationExceptionBuilder + .newBuilder((CelRuntimeException) e); + } else { + builder = CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e); + } + + throw builder.build(); + } + + return evalResult; + } + static Program create( CelValueInterpretable interpretable, CelValueConverter celValueConverter diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java new file mode 100644 index 000000000..12caada8d --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java @@ -0,0 +1,48 @@ +package dev.cel.runtime.planner; + +import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import dev.cel.common.values.BoolValue; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.ErrorValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; + + +final class EvalAnd implements CelValueInterpretable { + + private final ImmutableList args; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + ErrorValue errorValue = null; + for (CelValueInterpretable arg : args) { + CelValue argVal = evalNonstrictly(arg, resolver); + if (argVal instanceof BoolValue) { + // Short-circuit on false + if (!((boolean) argVal.value())) { + return argVal; + } + } else if (argVal instanceof ErrorValue) { + errorValue = (ErrorValue) argVal; + } + } + + if (errorValue != null) { + return errorValue; + } + + return BoolValue.create(true); + } + + static EvalAnd create(ImmutableList args) { + return new EvalAnd(args); + } + + private EvalAnd(ImmutableList args) { + Preconditions.checkArgument(args.size() == 2); + this.args = args; + } +} \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java new file mode 100644 index 000000000..e0184db70 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -0,0 +1,18 @@ +package dev.cel.runtime.planner; + +import dev.cel.common.values.CelValue; +import dev.cel.common.values.ErrorValue; +import dev.cel.runtime.GlobalResolver; + +final class EvalHelpers { + + static CelValue evalNonstrictly(CelValueInterpretable interpretable, GlobalResolver resolver) { + try { + return interpretable.eval(resolver); + } catch (Exception e) { + return ErrorValue.create(e); + } + } + + private EvalHelpers() {} +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java new file mode 100644 index 000000000..fed426438 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java @@ -0,0 +1,48 @@ +package dev.cel.runtime.planner; + +import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import dev.cel.common.values.BoolValue; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.ErrorValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; + + +final class EvalOr implements CelValueInterpretable { + + private final ImmutableList args; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + ErrorValue errorValue = null; + for (CelValueInterpretable arg : args) { + CelValue argVal = evalNonstrictly(arg, resolver); + if (argVal instanceof BoolValue) { + // Short-circuit on true + if (((boolean) argVal.value())) { + return argVal; + } + } else if (argVal instanceof ErrorValue) { + errorValue = (ErrorValue) argVal; + } + } + + if (errorValue != null) { + return errorValue; + } + + return BoolValue.create(false); + } + + static EvalOr create(ImmutableList args) { + return new EvalOr(args); + } + + private EvalOr(ImmutableList args) { + Preconditions.checkArgument(args.size() == 2); + this.args = args; + } +} \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java index 17637e555..e86f250b7 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java @@ -26,8 +26,8 @@ public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { return celValueConverter.fromJavaObjectToCelValue(result); } - static EvalVarArgsCall create(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter, ImmutableList s) { - return new EvalVarArgsCall(resolvedOverload, celValueConverter, s); + static EvalVarArgsCall create(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter, ImmutableList args) { + return new EvalVarArgsCall(resolvedOverload, celValueConverter, args); } private EvalVarArgsCall(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter, ImmutableList args) { 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 43a1d4f50..6668eabee 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -113,6 +113,14 @@ private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { ImmutableList evaluatedArgs = evaluatedArgBuilder.build(); // TODO: Handle specialized calls (logical operators, conditionals, equals etc) + String functionName = resolvedFunction.functionName(); + switch (functionName) { + // TODO: Move Operator.java out to common package and use that instead. + case "_||_": + return EvalOr.create(evaluatedArgs); + case "_&&_": + return EvalAnd.create(evaluatedArgs); + } CelFunctionBinding resolvedOverload = null; @@ -121,7 +129,7 @@ private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { } if (resolvedOverload == null) { - resolvedOverload = dispatcher.findOverload(resolvedFunction.functionName()).orElseThrow(() -> new NoSuchElementException("TODO: Overload not found")); + resolvedOverload = dispatcher.findOverload(functionName).orElseThrow(() -> new NoSuchElementException("TODO: Overload not found")); } switch (argCount) { @@ -158,8 +166,8 @@ private ResolvedFunction resolveFunction(CelExpr expr, ImmutableMap new NoSuchElementException(String.format("Function %s not found", call.function()))); +// dispatcher.findOverload(functionName) +// .orElseThrow(() -> new NoSuchElementException(String.format("Function %s not found", call.function()))); if (!target.isPresent()) { // TODO: Handle containers. diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 1dd43e4df..880428c60 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -74,6 +74,8 @@ java_library( "//runtime:unknown_attributes", "//runtime:unknown_options", "//runtime/planner:program_planner", + "//runtime/standard:divide", + "//runtime/standard:greater", "//runtime/standard:index", "//runtime/standard:standard_function", "//testing/protos:message_with_enum_cel_java_proto", 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 d2f2e60a7..9277121da 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -1,8 +1,10 @@ package dev.cel.runtime.planner; import static com.google.common.truth.Truth.assertThat; -import static dev.cel.common.CelFunctionDecl.*; -import static dev.cel.common.CelOverloadDecl.*; +import static dev.cel.common.CelFunctionDecl.newFunctionDeclaration; +import static dev.cel.common.CelOverloadDecl.newGlobalOverload; +import static dev.cel.common.CelOverloadDecl.newMemberOverload; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; @@ -14,9 +16,8 @@ import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelValidationException; -import dev.cel.common.internal.DefaultLiteDescriptorPool; import dev.cel.common.types.CelType; import dev.cel.common.types.DefaultTypeProvider; import dev.cel.common.types.ListType; @@ -27,18 +28,20 @@ import dev.cel.common.values.CelByteString; import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.NullValue; -import dev.cel.common.values.ProtoLiteCelValueConverter; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto2.TestAllTypes; import dev.cel.extensions.CelOptionalLibrary; import dev.cel.parser.Operator; +import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelFunctionOverload; import dev.cel.runtime.CelLiteRuntime.Program; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; import dev.cel.runtime.standard.CelStandardFunction; +import dev.cel.runtime.standard.DivideOperator; +import dev.cel.runtime.standard.GreaterOperator; import dev.cel.runtime.standard.IndexOperator; import java.nio.charset.StandardCharsets; @@ -56,6 +59,7 @@ public final class ProgramPlannerTest { .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.DYN)) .addFunctionDeclarations( newFunctionDeclaration("zero", newGlobalOverload("zero_overload", SimpleType.INT)), + newFunctionDeclaration("error", newGlobalOverload("error_overload", SimpleType.INT)), newFunctionDeclaration("neg", newGlobalOverload("neg_int", SimpleType.INT, SimpleType.INT), newGlobalOverload("neg_double", SimpleType.DOUBLE, SimpleType.DOUBLE) @@ -71,8 +75,7 @@ public final class ProgramPlannerTest { private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner( DefaultTypeProvider.create(), - // new CelValueConverter(), - ProtoLiteCelValueConverter.newInstance(DefaultLiteDescriptorPool.newInstance()), + new CelValueConverter(), newDispatcher() ); @@ -84,9 +87,12 @@ private static DefaultDispatcher newDispatcher() { // Subsetted StdLib addBindings(builder, Operator.INDEX.getFunction(), fromStandardFunction(IndexOperator.create())); + addBindings(builder, Operator.GREATER.getFunction(), fromStandardFunction(GreaterOperator.create())); + addBindings(builder, Operator.DIVIDE.getFunction(), fromStandardFunction(DivideOperator.create())); // Custom functions addBindings(builder, "zero", CelFunctionBinding.from("zero_overload", ImmutableList.of(), args -> 0L)); + addBindings(builder, "error", CelFunctionBinding.from("error_overload", ImmutableList.of(), args -> { throw new IllegalArgumentException("Intentional error"); })); addBindings(builder, "neg", CelFunctionBinding.from("neg_int", Long.class, arg -> -arg), CelFunctionBinding.from("neg_double", Double.class, arg -> -arg) @@ -238,6 +244,16 @@ public void planCall_zeroArgs() throws Exception { assertThat(result).isEqualTo(0L); } + @Test + public void planCall_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("error()"); + Program program = PLANNER.plan(ast); + + Long result = (Long) program.eval(); + + assertThat(result).isEqualTo(0L); + } + @Test public void planCall_oneArg_int() throws Exception { CelAbstractSyntaxTree ast = compile("neg(1)"); @@ -305,7 +321,54 @@ public void planCall_logicalOr_shortCircuit(String expression, boolean expectedR assertThat(result).isEqualTo(expectedResult); } - private CelAbstractSyntaxTree compile(String expression) throws CelValidationException { + @Test + @TestParameters("{expression: '(1 / 0 > 2) || (1 / 0 > 2)'}") + @TestParameters("{expression: 'false || (1 / 0 > 2)'}") + @TestParameters("{expression: '(1 / 0 > 2) || false'}") + public void planCall_logicalOr_throws(String expression) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> program.eval()); + // TODO: Tag metadata (source loc) + assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero"); + assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); + } + + @Test + @TestParameters("{expression: 'true && true', expectedResult: true}") + @TestParameters("{expression: 'true && false', expectedResult: false}") + @TestParameters("{expression: 'false && true', expectedResult: false}") + @TestParameters("{expression: 'false && false', expectedResult: false}") + @TestParameters("{expression: 'false && (1 / 0 > 2)', expectedResult: false}") + @TestParameters("{expression: '(1 / 0 > 2) && false', expectedResult: false}") + public void evaluate_logicalAndShortCircuits_success(String expression, boolean expectedResult) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + boolean result = (boolean) program.eval(); + + assertThat(result).isEqualTo(expectedResult); + } + + @Test + @TestParameters("{expression: '(1 / 0 > 2) && (1 / 0 > 2)'}") + @TestParameters("{expression: 'true && (1 / 0 > 2)'}") + @TestParameters("{expression: '(1 / 0 > 2) && true'}") + public void planCall_logicalAnd_throws(String expression) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> program.eval()); + // TODO: Tag metadata (source loc) + assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero"); + assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); + } + + + private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); if (isParseOnly) { return ast; From d1ff1b005d687976ae64ca478b04549325c3d91f Mon Sep 17 00:00:00 2001 From: swh Date: Wed, 9 Jul 2025 09:23:51 +0100 Subject: [PATCH 16/30] Handle runtime exceptions from arbitrary locations --- .../java/dev/cel/runtime/LiteProgramImpl.java | 3 +- .../java/dev/cel/runtime/ProgramImpl.java | 9 +++-- .../cel/runtime/planner/CelValueProgram.java | 34 +++++++++++-------- .../runtime/planner/ProgramPlannerTest.java | 6 ++-- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java index 3002f7c7c..a889eb668 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java @@ -37,7 +37,8 @@ public Object eval(Map mapValue) throws CelEvaluationException { @Override public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) throws CelEvaluationException { - return interpretable().eval(Activation.copyOf(mapValue), lateBoundFunctionResolver); + throw new UnsupportedOperationException("foo"); + // return interpretable().eval(Activation.copyOf(mapValue), lateBoundFunctionResolver); } static CelLiteRuntime.Program plan(Interpretable interpretable) { diff --git a/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java b/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java index a92dc7175..55e80bb52 100644 --- a/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java @@ -163,12 +163,11 @@ private Object evalInternal( // return impl.eval(context.variableResolver(), lateBoundFunctionResolver.get(), listener); // } // return impl.eval(context.variableResolver(), listener); - } else if (listener.isPresent()) { - return impl.eval(context.variableResolver(), listener.get()); - } - - return impl.eval(context.variableResolver()); + } else if (listener.isPresent()) { + // return impl.eval(context.variableResolver(), listener.get()); } + + return impl.eval(context.variableResolver()); } /** Get the underlying {@link Interpretable} for the {@code Program}. */ diff --git a/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java index 454bed653..fc8381eed 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/CelValueProgram.java @@ -40,24 +40,30 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio } private static CelValue evalOrThrow(CelValueInterpretable interpretable, GlobalResolver resolver) throws CelEvaluationException { - CelValue evalResult = interpretable.eval(resolver); - if (evalResult instanceof ErrorValue) { - ErrorValue errorValue = (ErrorValue) evalResult; - Exception e = errorValue.value(); - - - CelEvaluationExceptionBuilder builder; - if (e instanceof CelRuntimeException) { - builder = CelEvaluationExceptionBuilder - .newBuilder((CelRuntimeException) e); - } else { - builder = CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e); + try { + CelValue evalResult = interpretable.eval(resolver); + if (evalResult instanceof ErrorValue) { + ErrorValue errorValue = (ErrorValue) evalResult; + Exception e = errorValue.value(); + throw newCelEvaluationException(e); } - throw builder.build(); + return evalResult; + } catch (RuntimeException e) { + throw newCelEvaluationException(e); + } + } + + private static CelEvaluationException newCelEvaluationException(Exception e) { + CelEvaluationExceptionBuilder builder; + if (e instanceof CelRuntimeException) { + builder = CelEvaluationExceptionBuilder + .newBuilder((CelRuntimeException) e); + } else { + builder = CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e); } - return evalResult; + return builder.build(); } static Program create( 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 9277121da..ebf6dc7f1 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -249,9 +249,9 @@ public void planCall_throws() throws Exception { CelAbstractSyntaxTree ast = compile("error()"); Program program = PLANNER.plan(ast); - Long result = (Long) program.eval(); - - assertThat(result).isEqualTo(0L); + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); + assertThat(e).hasMessageThat().contains("evaluation error: Intentional error"); + assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); } @Test From 4b446975eb6bbdfe6ceee3695697a10c6a3d8774 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 14 Aug 2025 13:07:55 -0700 Subject: [PATCH 17/30] Fix rebase issues --- runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java | 2 +- runtime/src/main/java/dev/cel/runtime/ProgramImpl.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index 2b3703137..97699fdae 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -54,7 +54,7 @@ public Program createProgram(CelAbstractSyntaxTree ast) { if (planner != null) { return planner.plan(ast); } else { - return LiteProgramImpl.create(interpreter.createInterpretable(ast)); + return LiteProgramImpl.plan(interpreter.createInterpretable(ast)); } } diff --git a/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java b/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java index 55e80bb52..a1d0976ec 100644 --- a/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java @@ -157,9 +157,7 @@ private Object evalInternal( .build(), lateBoundFunctionResolver, listener); - } else { - return impl.eval(context.variableResolver()); - // if (lateBoundFunctionResolver.isPresent()) { + } else if (lateBoundFunctionResolver.isPresent()) { // return impl.eval(context.variableResolver(), lateBoundFunctionResolver.get(), listener); // } // return impl.eval(context.variableResolver(), listener); From bdc7af491f266a65acc0ea2114f57c7e834e22a7 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 14 Aug 2025 16:30:49 -0700 Subject: [PATCH 18/30] Support create object --- .../java/dev/cel/runtime/LiteRuntimeImpl.java | 3 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 19 ++++++++++ .../cel/runtime/planner/EvalCreateObject.java | 35 +++++++++++++++++++ .../cel/runtime/planner/ProgramPlanner.java | 20 +++++++++-- .../java/dev/cel/runtime/planner/BUILD.bazel | 1 + .../runtime/planner/ProgramPlannerTest.java | 33 ++++++++++++++--- 6 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalCreateObject.java diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index 97699fdae..238783955 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -249,6 +249,7 @@ private LiteRuntimeImpl( this.runtimeLibraries = runtimeLibraries; this.celValueProvider = celValueProvider; if (enablePlanner) { + // TODO this.planner = ProgramPlanner.newPlanner(new CelTypeProvider() { @Override public ImmutableCollection types() { @@ -258,7 +259,7 @@ public ImmutableCollection types() { public Optional findType(String typeName) { return Optional.empty(); } - }, null, null); + }, null, null, null); } else { this.planner = null; } 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 ab4011e52..82cf354e5 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -17,6 +17,7 @@ java_library( ":eval_and", ":eval_attribute", ":eval_const", + ":eval_create_object", ":eval_or", ":eval_unary", ":eval_var_args_call", @@ -28,6 +29,7 @@ java_library( "//common/types:type_providers", "//common/values", "//common/values:cel_value", + "//common/values:cel_value_provider", "//runtime:activation", "//runtime:default_dispatcher", "//runtime:evaluation_exception", @@ -198,6 +200,22 @@ java_library( ], ) +java_library( + name = "eval_create_object", + srcs = ["EvalCreateObject.java"], + deps = [ + ":attribute", + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//common/values:cel_value_provider", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + java_library( name = "eval_helpers", srcs = ["EvalHelpers.java"], @@ -205,6 +223,7 @@ java_library( ":cel_value_interpretable", "//common/values", "//common/values:cel_value", + "//common/values:cel_value_provider", "//runtime:evaluation_exception", "//runtime:function_binding", "//runtime:interpretable", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateObject.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateObject.java new file mode 100644 index 000000000..16fefd316 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateObject.java @@ -0,0 +1,35 @@ +package dev.cel.runtime.planner; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueProvider; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; +import java.util.HashMap; + +@Immutable +final class EvalCreateObject implements CelValueInterpretable { + + private final CelValueProvider valueProvider; + private final String typeName; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + return valueProvider.newValue(typeName, new HashMap<>()).orElseThrow(() -> new IllegalArgumentException("Type name not found: " + typeName)); + } + + static EvalCreateObject create( + CelValueProvider valueProvider, + String typeName + ) { + return new EvalCreateObject(valueProvider, typeName); + } + + private EvalCreateObject( + CelValueProvider valueProvider, + String typeName + ) { + this.valueProvider = valueProvider; + this.typeName = typeName; + } +} 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 6668eabee..4e1fee817 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -10,17 +10,20 @@ import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelCall; +import dev.cel.common.ast.CelExpr.CelStruct; import dev.cel.common.ast.CelReference; import dev.cel.common.types.CelKind; import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypeProvider; import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; +import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.TypeValue; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelLiteRuntime.Program; import dev.cel.runtime.DefaultDispatcher; +import java.util.HashMap; import java.util.NoSuchElementException; import java.util.Optional; @@ -28,6 +31,7 @@ @Internal public final class ProgramPlanner { private final CelTypeProvider typeProvider; + private final CelValueProvider valueProvider; private final CelValueConverter celValueConverter; private final DefaultDispatcher dispatcher; private final AttributeFactory attributeFactory; @@ -48,7 +52,7 @@ private CelValueInterpretable plan( case LIST: break; case STRUCT: - break; + return planCreateObject(celExpr, ctx); case MAP: break; case COMPREHENSION: @@ -143,6 +147,15 @@ private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { } } + private CelValueInterpretable planCreateObject(CelExpr celExpr, PlannerContext ctx) { + CelStruct struct = celExpr.struct(); + // TODO: maybe do this via type provider? + valueProvider.newValue(struct.messageName(), new HashMap<>()) + .orElseThrow(() -> new IllegalArgumentException("Undefined type name: " + struct.messageName())); + + return EvalCreateObject.create(valueProvider, struct.messageName()); + } + /** * resolveFunction determines the call target, function name, and overload name (when unambiguous) from the given call expr. */ @@ -226,18 +239,21 @@ public Program plan(CelAbstractSyntaxTree ast) { public static ProgramPlanner newPlanner( CelTypeProvider typeProvider, + CelValueProvider valueProvider, CelValueConverter celValueConverter, DefaultDispatcher dispatcher ) { - return new ProgramPlanner(typeProvider, celValueConverter, dispatcher); + return new ProgramPlanner(typeProvider, valueProvider, celValueConverter, dispatcher); } private ProgramPlanner( CelTypeProvider typeProvider, + CelValueProvider valueProvider, CelValueConverter celValueConverter, DefaultDispatcher dispatcher ) { this.typeProvider = typeProvider; + this.valueProvider = valueProvider; this.celValueConverter = celValueConverter; this.dispatcher = dispatcher; this.attributeFactory = AttributeFactory.newAttributeFactory("", celValueConverter, typeProvider); diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 880428c60..72f5015c0 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -21,6 +21,7 @@ java_library( "//common:cel_exception", "//common:cel_source", "//common:compiler_common", + "//common:container", "//common:error_codes", "//common:options", "//common:proto_v1alpha1_ast", 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 ebf6dc7f1..c17beb09c 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -28,9 +28,11 @@ import dev.cel.common.values.CelByteString; import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.NullValue; +import dev.cel.common.values.ProtoMessageLiteValueProvider; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; -import dev.cel.expr.conformance.proto2.TestAllTypes; +import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.expr.conformance.proto3.TestAllTypesCelDescriptor; import dev.cel.extensions.CelOptionalLibrary; import dev.cel.parser.Operator; import dev.cel.runtime.CelEvaluationException; @@ -74,9 +76,12 @@ public final class ProgramPlannerTest { .build(); private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner( - DefaultTypeProvider.create(), - new CelValueConverter(), - newDispatcher() + DefaultTypeProvider.create(), + ProtoMessageLiteValueProvider.newInstance( + TestAllTypesCelDescriptor.getDescriptor() + ), + new CelValueConverter(), + newDispatcher() ); /** @@ -198,6 +203,26 @@ public void planIdent_variable() throws Exception { assertThat(result).isEqualTo(1); } + @Test + public void planCreateObject() throws Exception { + CelAbstractSyntaxTree ast = compile("cel.expr.conformance.proto3.TestAllTypes{}"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo(TestAllTypes.getDefaultInstance()); + } + + @Test + public void planCreateObject_withFields() throws Exception { + CelAbstractSyntaxTree ast = compile("cel.expr.conformance.proto3.TestAllTypes{single_string: 'foo'}"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo(TestAllTypes.newBuilder().setSingleString("foo").build()); + } + @SuppressWarnings("ImmutableEnumChecker") // Test only private enum TypeLiteralTestCase { BOOL("bool", SimpleType.BOOL), From f968d4dd62f38aedd164891bc29395524eccb211 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 17 Sep 2025 13:20:05 -0700 Subject: [PATCH 19/30] Plan message creation --- .../java/dev/cel/common/ast/CelConstant.java | 5 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 6 +-- .../cel/runtime/planner/EvalCreateObject.java | 35 ------------- .../cel/runtime/planner/EvalCreateStruct.java | 52 +++++++++++++++++++ .../cel/runtime/planner/ProgramPlanner.java | 11 +++- .../java/dev/cel/runtime/planner/BUILD.bazel | 1 + .../runtime/planner/ProgramPlannerTest.java | 15 ++++-- 7 files changed, 78 insertions(+), 47 deletions(-) delete mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalCreateObject.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java diff --git a/common/src/main/java/dev/cel/common/ast/CelConstant.java b/common/src/main/java/dev/cel/common/ast/CelConstant.java index 64ed83be8..e63693602 100644 --- a/common/src/main/java/dev/cel/common/ast/CelConstant.java +++ b/common/src/main/java/dev/cel/common/ast/CelConstant.java @@ -207,8 +207,7 @@ public static CelConstant ofObjectValue(Object value) { throw new IllegalArgumentException("Value is not a CelConstant: " + value); } - * TODO - */ + public Object objectValue() { switch (getKind()) { case NULL_VALUE: @@ -229,6 +228,4 @@ public Object objectValue() { throw new IllegalStateException("Unsupported kind: " + getKind()); } } - - /** } 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 82cf354e5..174ff784e 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -17,7 +17,7 @@ java_library( ":eval_and", ":eval_attribute", ":eval_const", - ":eval_create_object", + ":eval_create_struct", ":eval_or", ":eval_unary", ":eval_var_args_call", @@ -201,8 +201,8 @@ java_library( ) java_library( - name = "eval_create_object", - srcs = ["EvalCreateObject.java"], + name = "eval_create_struct", + srcs = ["EvalCreateStruct.java"], deps = [ ":attribute", ":cel_value_interpretable", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateObject.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateObject.java deleted file mode 100644 index 16fefd316..000000000 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateObject.java +++ /dev/null @@ -1,35 +0,0 @@ -package dev.cel.runtime.planner; - -import com.google.errorprone.annotations.Immutable; -import dev.cel.common.values.CelValue; -import dev.cel.common.values.CelValueProvider; -import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.GlobalResolver; -import java.util.HashMap; - -@Immutable -final class EvalCreateObject implements CelValueInterpretable { - - private final CelValueProvider valueProvider; - private final String typeName; - - @Override - public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { - return valueProvider.newValue(typeName, new HashMap<>()).orElseThrow(() -> new IllegalArgumentException("Type name not found: " + typeName)); - } - - static EvalCreateObject create( - CelValueProvider valueProvider, - String typeName - ) { - return new EvalCreateObject(valueProvider, typeName); - } - - private EvalCreateObject( - CelValueProvider valueProvider, - String typeName - ) { - this.valueProvider = valueProvider; - this.typeName = typeName; - } -} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java new file mode 100644 index 000000000..931ea8a00 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java @@ -0,0 +1,52 @@ +package dev.cel.runtime.planner; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueProvider; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +@Immutable +final class EvalCreateStruct implements CelValueInterpretable { + + private final CelValueProvider valueProvider; + private final String typeName; + + // Regular hashmap used for performance. Planner must not mutate the map post-construction. + @SuppressWarnings("Immutable") + private final Map fields; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + Map fieldValues = new HashMap<>(); + for (Entry entry : fields.entrySet()) { + Object value = entry.getValue().eval(resolver).value(); + fieldValues.put(entry.getKey(), value); + } + + return valueProvider.newValue(typeName, Collections.unmodifiableMap(fieldValues)) + .orElseThrow(() -> new IllegalArgumentException("Type name not found: " + typeName)); + } + + static EvalCreateStruct create( + CelValueProvider valueProvider, + String typeName, + Map fields + ) { + return new EvalCreateStruct(valueProvider, typeName, fields); + } + + private EvalCreateStruct( + CelValueProvider valueProvider, + String typeName, + Map fields + ) { + this.valueProvider = valueProvider; + this.typeName = typeName; + this.fields = fields; + } +} 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 4e1fee817..ed0ad45fe 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -11,6 +11,7 @@ import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelCall; import dev.cel.common.ast.CelExpr.CelStruct; +import dev.cel.common.ast.CelExpr.CelStruct.Entry; import dev.cel.common.ast.CelReference; import dev.cel.common.types.CelKind; import dev.cel.common.types.CelType; @@ -23,6 +24,7 @@ import dev.cel.runtime.CelLiteRuntime.Program; import dev.cel.runtime.DefaultDispatcher; +import java.util.Collections; import java.util.HashMap; import java.util.NoSuchElementException; import java.util.Optional; @@ -153,7 +155,14 @@ private CelValueInterpretable planCreateObject(CelExpr celExpr, PlannerContext c valueProvider.newValue(struct.messageName(), new HashMap<>()) .orElseThrow(() -> new IllegalArgumentException("Undefined type name: " + struct.messageName())); - return EvalCreateObject.create(valueProvider, struct.messageName()); + HashMap fieldMap = new HashMap<>(); + for (Entry entry : struct.entries()) { + CelValueInterpretable value = plan(entry.value(), ctx); + fieldMap.put(entry.fieldKey(), value); + } + + return EvalCreateStruct.create(valueProvider, struct.messageName(), + Collections.unmodifiableMap(fieldMap)); } /** diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 72f5015c0..e5b21f7b3 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -45,6 +45,7 @@ java_library( "//common/values:cel_value_provider", "//common/values:proto_message_lite_value", "//common/values:proto_message_lite_value_provider", + "//common/values:proto_message_value_provider", "//compiler", "//compiler:compiler_builder", "//extensions:optional_library", 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 c17beb09c..e77125817 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -16,8 +16,13 @@ import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; +import dev.cel.common.internal.CelDescriptorPool; +import dev.cel.common.internal.DefaultDescriptorPool; +import dev.cel.common.internal.DefaultMessageFactory; +import dev.cel.common.internal.DynamicProto; import dev.cel.common.types.CelType; import dev.cel.common.types.DefaultTypeProvider; import dev.cel.common.types.ListType; @@ -28,11 +33,10 @@ import dev.cel.common.values.CelByteString; import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.NullValue; -import dev.cel.common.values.ProtoMessageLiteValueProvider; +import dev.cel.common.values.ProtoMessageValueProvider; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto3.TestAllTypes; -import dev.cel.expr.conformance.proto3.TestAllTypesCelDescriptor; import dev.cel.extensions.CelOptionalLibrary; import dev.cel.parser.Operator; import dev.cel.runtime.CelEvaluationException; @@ -75,10 +79,13 @@ public final class ProgramPlannerTest { .addMessageTypes(TestAllTypes.getDescriptor()) .build(); + private static final CelDescriptorPool DESCRIPTOR_POOL = + DefaultDescriptorPool.create(CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(TestAllTypes.getDescriptor().getFile())); private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner( DefaultTypeProvider.create(), - ProtoMessageLiteValueProvider.newInstance( - TestAllTypesCelDescriptor.getDescriptor() + ProtoMessageValueProvider.newInstance( + CEL_OPTIONS, + DynamicProto.create(DefaultMessageFactory.create(DESCRIPTOR_POOL)) ), new CelValueConverter(), newDispatcher() From 1f3e08fd42cd9b45bf29a905af1ee18b5174da51 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 17 Sep 2025 14:17:10 -0700 Subject: [PATCH 20/30] Plan map creation --- .../java/dev/cel/runtime/planner/BUILD.bazel | 17 +++++++ .../cel/runtime/planner/EvalCreateMap.java | 43 ++++++++++++++++++ .../cel/runtime/planner/EvalCreateStruct.java | 26 ++++++----- .../cel/runtime/planner/ProgramPlanner.java | 44 ++++++++++++++----- .../runtime/planner/ProgramPlannerTest.java | 23 ++++++++-- 5 files changed, 127 insertions(+), 26 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java 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 174ff784e..adf07c742 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -17,6 +17,7 @@ java_library( ":eval_and", ":eval_attribute", ":eval_const", + ":eval_create_map", ":eval_create_struct", ":eval_or", ":eval_unary", @@ -216,6 +217,22 @@ java_library( ], ) +java_library( + name = "eval_create_map", + srcs = ["EvalCreateMap.java"], + deps = [ + ":attribute", + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//common/values:cel_value_provider", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + java_library( name = "eval_helpers", srcs = ["EvalHelpers.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java new file mode 100644 index 000000000..2ba7e6b4c --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java @@ -0,0 +1,43 @@ +package dev.cel.runtime.planner; + +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.ImmutableMapValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; + +@Immutable +final class EvalCreateMap implements CelValueInterpretable { + + + @SuppressWarnings("Immutable") + private final CelValueInterpretable[] keys; + + @SuppressWarnings("Immutable") + private final CelValueInterpretable[] values; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (int i = 0; i < keys.length; i++) { + builder.put(keys[i].eval(resolver), values[i].eval(resolver)); + } + return ImmutableMapValue.create(builder.build()); + } + + static EvalCreateMap create( + CelValueInterpretable[] keys, + CelValueInterpretable[] values + ) { + return new EvalCreateMap(keys, values); + } + + private EvalCreateMap( + CelValueInterpretable[] keys, + CelValueInterpretable[] values + ) { + this.keys = keys; + this.values = values; + } +} \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java index 931ea8a00..7ff2f5537 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java @@ -8,7 +8,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; @Immutable final class EvalCreateStruct implements CelValueInterpretable { @@ -16,16 +15,20 @@ final class EvalCreateStruct implements CelValueInterpretable { private final CelValueProvider valueProvider; private final String typeName; - // Regular hashmap used for performance. Planner must not mutate the map post-construction. + + @SuppressWarnings("Immutable") + private final String[] keys; + @SuppressWarnings("Immutable") - private final Map fields; + private final CelValueInterpretable[] values; + @Override public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { Map fieldValues = new HashMap<>(); - for (Entry entry : fields.entrySet()) { - Object value = entry.getValue().eval(resolver).value(); - fieldValues.put(entry.getKey(), value); + for (int i = 0; i < keys.length; i++) { + Object value = values[i].eval(resolver).value(); + fieldValues.put(keys[i], value); } return valueProvider.newValue(typeName, Collections.unmodifiableMap(fieldValues)) @@ -35,18 +38,21 @@ public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { static EvalCreateStruct create( CelValueProvider valueProvider, String typeName, - Map fields + String[] keys, + CelValueInterpretable[] values ) { - return new EvalCreateStruct(valueProvider, typeName, fields); + return new EvalCreateStruct(valueProvider, typeName, keys, values); } private EvalCreateStruct( CelValueProvider valueProvider, String typeName, - Map fields + String[] keys, + CelValueInterpretable[] values ) { this.valueProvider = valueProvider; this.typeName = typeName; - this.fields = fields; + this.keys = keys; + this.values = values; } } 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 ed0ad45fe..871d74f92 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -10,6 +10,7 @@ import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelCall; +import dev.cel.common.ast.CelExpr.CelMap; import dev.cel.common.ast.CelExpr.CelStruct; import dev.cel.common.ast.CelExpr.CelStruct.Entry; import dev.cel.common.ast.CelReference; @@ -24,8 +25,8 @@ import dev.cel.runtime.CelLiteRuntime.Program; import dev.cel.runtime.DefaultDispatcher; -import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; @@ -54,16 +55,16 @@ private CelValueInterpretable plan( case LIST: break; case STRUCT: - return planCreateObject(celExpr, ctx); + return planCreateStruct(celExpr, ctx); case MAP: - break; + return planCreateMap(celExpr, ctx); case COMPREHENSION: break; case NOT_SET: throw new UnsupportedOperationException("Unsupported kind: " + celExpr.getKind()); } - throw new IllegalArgumentException("foo"); + throw new IllegalArgumentException("Not yet implemented"); } private CelValueInterpretable planIdent( @@ -149,20 +150,39 @@ private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { } } - private CelValueInterpretable planCreateObject(CelExpr celExpr, PlannerContext ctx) { + private CelValueInterpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) { CelStruct struct = celExpr.struct(); - // TODO: maybe do this via type provider? + // TODO: maybe perform the check via type provider? valueProvider.newValue(struct.messageName(), new HashMap<>()) .orElseThrow(() -> new IllegalArgumentException("Undefined type name: " + struct.messageName())); - HashMap fieldMap = new HashMap<>(); - for (Entry entry : struct.entries()) { - CelValueInterpretable value = plan(entry.value(), ctx); - fieldMap.put(entry.fieldKey(), value); + List entries = struct.entries(); + String[] keys = new String[entries.size()]; + CelValueInterpretable[] values = new CelValueInterpretable[entries.size()]; + + for (int i = 0; i < entries.size(); i++) { + Entry entry = entries.get(i); + keys[i] = entry.fieldKey(); + values[i] = plan(entry.value(), ctx); + } + + return EvalCreateStruct.create(valueProvider, struct.messageName(), keys, values); + } + + private CelValueInterpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) { + CelMap map = celExpr.map(); + + List entries = map.entries(); + CelValueInterpretable[] keys = new CelValueInterpretable[entries.size()]; + CelValueInterpretable[] values = new CelValueInterpretable[entries.size()]; + + for (int i = 0; i < entries.size(); i++) { + CelMap.Entry entry = entries.get(i); + keys[i] = plan(entry.key(), ctx); + values[i] = plan(entry.value(), ctx); } - return EvalCreateStruct.create(valueProvider, struct.messageName(), - Collections.unmodifiableMap(fieldMap)); + return EvalCreateMap.create(keys, values); } /** 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 e77125817..3d16107f1 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -52,6 +52,7 @@ import java.nio.charset.StandardCharsets; import dev.cel.runtime.DefaultDispatcher; +import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; @@ -211,7 +212,7 @@ public void planIdent_variable() throws Exception { } @Test - public void planCreateObject() throws Exception { + public void planCreateStruct() throws Exception { CelAbstractSyntaxTree ast = compile("cel.expr.conformance.proto3.TestAllTypes{}"); Program program = PLANNER.plan(ast); @@ -221,13 +222,27 @@ public void planCreateObject() throws Exception { } @Test - public void planCreateObject_withFields() throws Exception { - CelAbstractSyntaxTree ast = compile("cel.expr.conformance.proto3.TestAllTypes{single_string: 'foo'}"); + public void planCreateStruct_withFields() throws Exception { + CelAbstractSyntaxTree ast = compile("cel.expr.conformance.proto3.TestAllTypes{" + + "single_string: 'foo'," + + "single_bool: true" + + "}"); Program program = PLANNER.plan(ast); Object result = program.eval(); - assertThat(result).isEqualTo(TestAllTypes.newBuilder().setSingleString("foo").build()); + assertThat(result).isEqualTo(TestAllTypes.newBuilder().setSingleString("foo").setSingleBool(true).build()); + } + + @Test + public void planCreateMap() throws Exception { + CelAbstractSyntaxTree ast = compile("{'foo': 1, true: 'bar'}"); + + Program program = PLANNER.plan(ast); + + Map result = (Map) program.eval(); + + assertThat(result).containsExactly("foo", 1L, true, "bar").inOrder(); } @SuppressWarnings("ImmutableEnumChecker") // Test only From 9f749defc1aeecfc48c8ee6644fb9dabff2b317c Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 17 Sep 2025 14:33:32 -0700 Subject: [PATCH 21/30] Plan create list --- .../java/dev/cel/runtime/planner/BUILD.bazel | 17 +++++++++ .../cel/runtime/planner/EvalCreateList.java | 37 +++++++++++++++++++ .../cel/runtime/planner/ProgramPlanner.java | 16 +++++++- .../runtime/planner/ProgramPlannerTest.java | 12 ++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java 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 adf07c742..03640b170 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -17,6 +17,7 @@ java_library( ":eval_and", ":eval_attribute", ":eval_const", + ":eval_create_list", ":eval_create_map", ":eval_create_struct", ":eval_or", @@ -217,6 +218,22 @@ java_library( ], ) +java_library( + name = "eval_create_list", + srcs = ["EvalCreateList.java"], + deps = [ + ":attribute", + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//common/values:cel_value_provider", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + java_library( name = "eval_create_map", srcs = ["EvalCreateMap.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java new file mode 100644 index 000000000..16015b5a2 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java @@ -0,0 +1,37 @@ +package dev.cel.runtime.planner; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.ImmutableListValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; + +@Immutable +final class EvalCreateList implements CelValueInterpretable { + + @SuppressWarnings("Immutable") + private final CelValueInterpretable[] values; + + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + ImmutableList.Builder builder = ImmutableList.builder(); + for (int i = 0; i < values.length; i++) { + builder.add(values[i].eval(resolver)); + } + return ImmutableListValue.create(builder.build()); + } + + static EvalCreateList create( + CelValueInterpretable[] values + ) { + return new EvalCreateList(values); + } + + private EvalCreateList( + CelValueInterpretable[] values + ) { + this.values = values; + } +} \ No newline at end of file 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 871d74f92..0cbda441e 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -10,6 +10,7 @@ import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelCall; +import dev.cel.common.ast.CelExpr.CelList; import dev.cel.common.ast.CelExpr.CelMap; import dev.cel.common.ast.CelExpr.CelStruct; import dev.cel.common.ast.CelExpr.CelStruct.Entry; @@ -53,7 +54,7 @@ private CelValueInterpretable plan( case CALL: return planCall(celExpr, ctx); case LIST: - break; + return planCreateList(celExpr, ctx); case STRUCT: return planCreateStruct(celExpr, ctx); case MAP: @@ -169,6 +170,19 @@ private CelValueInterpretable planCreateStruct(CelExpr celExpr, PlannerContext c return EvalCreateStruct.create(valueProvider, struct.messageName(), keys, values); } + private CelValueInterpretable planCreateList(CelExpr celExpr, PlannerContext ctx) { + CelList list = celExpr.list(); + + List elements = list.elements(); + CelValueInterpretable[] values = new CelValueInterpretable[elements.size()]; + + for (int i = 0; i < elements.size(); i++) { + values[i] = plan(elements.get(i), ctx); + } + + return EvalCreateList.create(values); + } + private CelValueInterpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) { CelMap map = celExpr.map(); 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 3d16107f1..7b30a8b2b 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -52,6 +52,7 @@ import java.nio.charset.StandardCharsets; import dev.cel.runtime.DefaultDispatcher; +import java.util.List; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; @@ -234,6 +235,17 @@ public void planCreateStruct_withFields() throws Exception { assertThat(result).isEqualTo(TestAllTypes.newBuilder().setSingleString("foo").setSingleBool(true).build()); } + @Test + public void planCreateList() throws Exception { + CelAbstractSyntaxTree ast = compile("[1, 'foo', true]"); + + Program program = PLANNER.plan(ast); + + List result = (List) program.eval(); + + assertThat(result).containsExactly(1L, "foo", true).inOrder(); + } + @Test public void planCreateMap() throws Exception { CelAbstractSyntaxTree ast = compile("{'foo': 1, true: 'bar'}"); From 6df998810d31649abf7066299ca232014ff66906 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 17 Sep 2025 14:51:14 -0700 Subject: [PATCH 22/30] Plan fold --- .../dev/cel/runtime/planner/Attribute.java | 7 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 18 +++ .../dev/cel/runtime/planner/EvalFold.java | 137 ++++++++++++++++++ .../cel/runtime/planner/ProgramPlanner.java | 26 +++- .../java/dev/cel/runtime/standard/BUILD.bazel | 36 +++++ .../standard/NotStrictlyFalseFunction.java | 61 ++++++++ .../java/dev/cel/runtime/planner/BUILD.bazel | 6 + .../runtime/planner/ProgramPlannerTest.java | 46 +++++- runtime/standard/BUILD.bazel | 10 ++ 9 files changed, 341 insertions(+), 6 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java create mode 100644 runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java index 29d4423e2..be5e7e112 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java @@ -5,6 +5,7 @@ import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypeProvider; import dev.cel.runtime.GlobalResolver; +import java.util.stream.Collectors; @Immutable interface Attribute { @@ -22,7 +23,9 @@ public Object resolve(GlobalResolver ctx) { } } - throw new IllegalArgumentException("no such attribute(s): %s"); + throw new IllegalArgumentException(String.format("no such attribute(s): %s", attributes.stream() + .map(Attribute::toString) + .collect(Collectors.joining(",")))); } MaybeAttribute(ImmutableList attributes) { @@ -49,7 +52,7 @@ public Object resolve(GlobalResolver ctx) { } } - throw new IllegalArgumentException("no such attribute(s): %s"); + throw new IllegalArgumentException(String.format("no such attribute(s): %s", String.join(",", namespacedNames))); } NamespacedAttribute( 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 03640b170..11c18a775 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -20,6 +20,7 @@ java_library( ":eval_create_list", ":eval_create_map", ":eval_create_struct", + ":eval_fold", ":eval_or", ":eval_unary", ":eval_var_args_call", @@ -250,6 +251,23 @@ java_library( ], ) +java_library( + name = "eval_fold", + srcs = ["EvalFold.java"], + deps = [ + ":attribute", + ":cel_value_interpretable", + "//common/values", + "//common/values:cel_value", + "//common/values:cel_value_provider", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:org_jspecify_jspecify", + ], +) + java_library( name = "eval_helpers", srcs = ["EvalHelpers.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java new file mode 100644 index 000000000..b0435cf55 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java @@ -0,0 +1,137 @@ +package dev.cel.runtime.planner; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.IntValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; +import java.util.Collection; +import java.util.Iterator; +import org.jspecify.annotations.Nullable; + +@Immutable +final class EvalFold implements CelValueInterpretable { + + private final String accuVar; + private final CelValueInterpretable accuInit; + private final String iterVar; + private final String iterVar2; + private final CelValueInterpretable iterRange; + private final CelValueInterpretable condition; + private final CelValueInterpretable loopStep; + private final CelValueInterpretable result; + + @Override + public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { + // TODO: Consider creating a folder abstraction like in cel-go. This requires some legwork in attribute qualification. + Collection foldRange = (Collection) iterRange.eval(resolver); + + Folder folder = new Folder( + resolver, + accuVar, + iterVar, + iterVar2 + ); + + folder.accuVal = accuInit.eval(folder); + + // TODO: Implement scoping + long index = 0; + for (Iterator iterator = foldRange.iterator(); iterator.hasNext(); ) { + if (iterVar2.isEmpty()) { + folder.iterVarVal = iterator.next(); + } else { + folder.iterVarVal = IntValue.create(index); + folder.iterVar2Val = iterator.next(); + } + folder.accuVal = loopStep.eval(folder); + index++; + } + + CelValue resultValue = result.eval(folder); + // CelValue resultValue = resolveName("@result", resolver); + + return resultValue; + } + + private static class Folder implements GlobalResolver { + private final GlobalResolver resolver; + private final String accuVar; + private final String iterVar; + private final String iterVar2; + + private CelValue iterVarVal; + private CelValue iterVar2Val; + private CelValue accuVal; + + private Folder( + GlobalResolver resolver, + String accuVar, + String iterVar, + String iterVar2 + ) { + this.resolver = resolver; + this.accuVar = accuVar; + this.iterVar = iterVar; + this.iterVar2 = iterVar2; + } + + @Override + public @Nullable Object resolve(String name) { + if (name.equals(accuVar)) { + return accuVal; + } + + // Todo: !f.computeResult check + if (name.equals(iterVar)) { + return this.iterVarVal; + } + + if (name.equals(iterVar2)) { + return this.iterVar2Val; + } + + return resolver.resolve(name); + } + } + + static EvalFold create( + String accuVar, + CelValueInterpretable accuInit, + String iterVar, + String iterVar2, + CelValueInterpretable iterRange, + CelValueInterpretable condition, + CelValueInterpretable loopStep, + CelValueInterpretable result) { + return new EvalFold( + accuVar, + accuInit, + iterVar, + iterVar2, + iterRange, + condition, + loopStep, + result + ); + } + + private EvalFold( + String accuVar, + CelValueInterpretable accuInit, + String iterVar, + String iterVar2, + CelValueInterpretable iterRange, + CelValueInterpretable condition, + CelValueInterpretable loopStep, + CelValueInterpretable result) { + this.accuVar = accuVar; + this.accuInit = accuInit; + this.iterVar = iterVar; + this.iterVar2 = iterVar2; + this.iterRange = iterRange; + this.condition = condition; + this.loopStep = loopStep; + this.result = result; + } +} \ No newline at end of file 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 0cbda441e..55e12d3c9 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -10,6 +10,7 @@ import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelCall; +import dev.cel.common.ast.CelExpr.CelComprehension; import dev.cel.common.ast.CelExpr.CelList; import dev.cel.common.ast.CelExpr.CelMap; import dev.cel.common.ast.CelExpr.CelStruct; @@ -60,7 +61,7 @@ private CelValueInterpretable plan( case MAP: return planCreateMap(celExpr, ctx); case COMPREHENSION: - break; + return planComprehension(celExpr, ctx); case NOT_SET: throw new UnsupportedOperationException("Unsupported kind: " + celExpr.getKind()); } @@ -137,7 +138,7 @@ private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { } if (resolvedOverload == null) { - resolvedOverload = dispatcher.findOverload(functionName).orElseThrow(() -> new NoSuchElementException("TODO: Overload not found")); + resolvedOverload = dispatcher.findOverload(functionName).orElseThrow(() -> new NoSuchElementException("Overload not found: " + functionName)); } switch (argCount) { @@ -199,6 +200,27 @@ private CelValueInterpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) return EvalCreateMap.create(keys, values); } + private CelValueInterpretable planComprehension(CelExpr expr, PlannerContext ctx) { + CelComprehension comprehension = expr.comprehension(); + + CelValueInterpretable accuInit = plan(comprehension.accuInit(), ctx); + CelValueInterpretable iterRange = plan(comprehension.iterRange(), ctx); + CelValueInterpretable loopCondition = plan(comprehension.loopCondition(), ctx); + CelValueInterpretable loopStep = plan(comprehension.loopStep(), ctx); + CelValueInterpretable result = plan(comprehension.result(), ctx); + + return EvalFold.create( + comprehension.accuVar(), + accuInit, + comprehension.iterVar(), + comprehension.iterVar2(), + iterRange, + loopCondition, + loopStep, + result + ); + } + /** * resolveFunction determines the call target, function name, and overload name (when unambiguous) from the given call expr. */ 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 08177a474..1833106c0 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,42 @@ cel_android_library( ], ) +java_library( + name = "not_strictly_false", + srcs = ["NotStrictlyFalseFunction.java"], + tags = [ + ], + deps = [ + ":standard_overload", + "//common:error_codes", + "//common:options", + "//common:runtime_exception", + "//runtime:function_binding", + "//runtime:runtime_equality", + "//runtime:runtime_helpers", + "//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:error_codes", + "//common:options", + "//common:runtime_exception", + "//runtime:function_binding_android", + "//runtime:runtime_equality_android", + "//runtime:runtime_helpers_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..dd3c11214 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java @@ -0,0 +1,61 @@ +// 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.RuntimeEquality; + +/** Standard function for {@code matches}. */ +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) -> + CelFunctionBinding.from( + "not_strictly_false", + Object.class, + (Object value) -> { + if (value instanceof Boolean) { + return value; + } + + return true; + })), + ; + + private final FunctionBindingCreator bindingCreator; + + @Override + public CelFunctionBinding newFunctionBinding( + CelOptions celOptions, RuntimeEquality runtimeEquality) { + return bindingCreator.create(celOptions, runtimeEquality); + } + + NotStrictlyFalseOverload(FunctionBindingCreator bindingCreator) { + this.bindingCreator = bindingCreator; + } + } + + private NotStrictlyFalseFunction(ImmutableSet overloads) { + super(overloads); + } +} \ No newline at end of file diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index e5b21f7b3..32c411ee2 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -48,6 +48,7 @@ java_library( "//common/values:proto_message_value_provider", "//compiler", "//compiler:compiler_builder", + "//extensions", "//extensions:optional_library", "//parser:macro", "//parser:operator", @@ -77,8 +78,13 @@ java_library( "//runtime:unknown_options", "//runtime/planner:program_planner", "//runtime/standard:divide", + "//runtime/standard:equals", "//runtime/standard:greater", + "//runtime/standard:greater_equals", "//runtime/standard:index", + "//runtime/standard:less", + "//runtime/standard:logical_not", + "//runtime/standard:not_strictly_false", "//runtime/standard:standard_function", "//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/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 7b30a8b2b..94378fde9 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -29,6 +29,7 @@ import dev.cel.common.types.MapType; import dev.cel.common.types.OptionalType; import dev.cel.common.types.SimpleType; +import dev.cel.common.types.StructTypeReference; import dev.cel.common.types.TypeType; import dev.cel.common.values.CelByteString; import dev.cel.common.values.CelValueConverter; @@ -37,7 +38,9 @@ import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.extensions.CelExtensions; import dev.cel.extensions.CelOptionalLibrary; +import dev.cel.parser.CelStandardMacro; import dev.cel.parser.Operator; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; @@ -47,8 +50,13 @@ import dev.cel.runtime.RuntimeHelpers; import dev.cel.runtime.standard.CelStandardFunction; import dev.cel.runtime.standard.DivideOperator; +import dev.cel.runtime.standard.EqualsOperator; +import dev.cel.runtime.standard.GreaterEqualsOperator; import dev.cel.runtime.standard.GreaterOperator; +import dev.cel.runtime.standard.LessOperator; import dev.cel.runtime.standard.IndexOperator; +import dev.cel.runtime.standard.LogicalNotOperator; +import dev.cel.runtime.standard.NotStrictlyFalseFunction; import java.nio.charset.StandardCharsets; import dev.cel.runtime.DefaultDispatcher; @@ -63,8 +71,10 @@ public final class ProgramPlannerTest { private static final RuntimeEquality RUNTIME_EQUALITY = RuntimeEquality.create(RuntimeHelpers.create(), CEL_OPTIONS); private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() + .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) .addVar("int_var", SimpleType.INT) .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.DYN)) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) .addFunctionDeclarations( newFunctionDeclaration("zero", newGlobalOverload("zero_overload", SimpleType.INT)), newFunctionDeclaration("error", newGlobalOverload("error_overload", SimpleType.INT)), @@ -77,7 +87,7 @@ public final class ProgramPlannerTest { newMemberOverload("bytes_concat_bytes", SimpleType.BYTES, SimpleType.BYTES, SimpleType.BYTES) ) ) - .addLibraries(CelOptionalLibrary.INSTANCE) + .addLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.comprehensions()) .addMessageTypes(TestAllTypes.getDescriptor()) .build(); @@ -101,8 +111,15 @@ private static DefaultDispatcher newDispatcher() { // Subsetted StdLib addBindings(builder, Operator.INDEX.getFunction(), fromStandardFunction(IndexOperator.create())); + addBindings(builder, Operator.LOGICAL_NOT.getFunction(), fromStandardFunction(LogicalNotOperator.create())); addBindings(builder, Operator.GREATER.getFunction(), fromStandardFunction(GreaterOperator.create())); + addBindings(builder, Operator.GREATER_EQUALS.getFunction(), fromStandardFunction( + GreaterEqualsOperator.create())); + addBindings(builder, Operator.LESS.getFunction(), fromStandardFunction(LessOperator.create())); addBindings(builder, Operator.DIVIDE.getFunction(), fromStandardFunction(DivideOperator.create())); + addBindings(builder, Operator.EQUALS.getFunction(), fromStandardFunction(EqualsOperator.create())); + addBindings(builder, Operator.NOT_STRICTLY_FALSE.getFunction(), fromStandardFunction( + NotStrictlyFalseFunction.create())); // Custom functions addBindings(builder, "zero", CelFunctionBinding.from("zero_overload", ImmutableList.of(), args -> 0L)); @@ -402,7 +419,7 @@ public void planCall_logicalOr_throws(String expression) throws Exception { @TestParameters("{expression: 'false && false', expectedResult: false}") @TestParameters("{expression: 'false && (1 / 0 > 2)', expectedResult: false}") @TestParameters("{expression: '(1 / 0 > 2) && false', expectedResult: false}") - public void evaluate_logicalAndShortCircuits_success(String expression, boolean expectedResult) throws Exception { + public void planCall_logicalAnd_shortCircuit(String expression, boolean expectedResult) throws Exception { CelAbstractSyntaxTree ast = compile(expression); Program program = PLANNER.plan(ast); @@ -426,6 +443,31 @@ public void planCall_logicalAnd_throws(String expression) throws Exception { assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); } + @Test + public void planSelect() throws Exception { + CelAbstractSyntaxTree ast = compile("msg.single_string"); + Program program = PLANNER.plan(ast); + + String result = (String) program.eval( + ImmutableMap.of("msg", TestAllTypes.newBuilder().setSingleString("foo").build()) + ); + + assertThat(result).isEqualTo("foo"); + } + + @Test + @TestParameters("{expression: '[1,2,3].exists(x, x > 0) == true'}") + @TestParameters("{expression: '[1,2,3].exists(x, x < 0) == false'}") + @TestParameters("{expression: '[1,2,3].exists(i, v, i >= 0 && v > 0) == true'}") + @TestParameters("{expression: '[1,2,3].exists(i, v, i < 0 || v < 0) == false'}") + public void planComprehension(String expression) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + boolean result = (boolean) program.eval(); + + assertThat(result).isTrue(); + } private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); diff --git a/runtime/standard/BUILD.bazel b/runtime/standard/BUILD.bazel index 5f84c105f..d6bddec66 100644 --- a/runtime/standard/BUILD.bazel +++ b/runtime/standard/BUILD.bazel @@ -405,3 +405,13 @@ cel_android_library( name = "uint_android", 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"], +) From 8cd3d770722afafeaa07c4884a378db3ec186345 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 18 Sep 2025 17:35:43 -0700 Subject: [PATCH 23/30] Implement mutable list --- .../java/dev/cel/common/values/BUILD.bazel | 22 ++++++ .../cel/common/values/MutableListValue.java | 78 +++++++++++++++++++ .../dev/cel/runtime/planner/EvalFold.java | 9 +-- .../java/dev/cel/runtime/planner/BUILD.bazel | 1 + .../runtime/planner/ProgramPlannerTest.java | 27 +++++-- 5 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 common/src/main/java/dev/cel/common/values/MutableListValue.java diff --git a/common/src/main/java/dev/cel/common/values/BUILD.bazel b/common/src/main/java/dev/cel/common/values/BUILD.bazel index efd23242e..fe148999d 100644 --- a/common/src/main/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel @@ -157,6 +157,28 @@ cel_android_library( ], ) +java_library( + name = "mutable_list_value", + srcs = ["MutableListValue.java"], + tags = [ + ], + deps = [ + ":cel_byte_string", + ":cel_value", + ":values", + "//:auto_value", + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + "//common/types", + "//common/types:type_providers", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:org_jspecify_jspecify", + ], +) + java_library( name = "cel_byte_string", srcs = ["CelByteString.java"], diff --git a/common/src/main/java/dev/cel/common/values/MutableListValue.java b/common/src/main/java/dev/cel/common/values/MutableListValue.java new file mode 100644 index 000000000..e4b9a4ed1 --- /dev/null +++ b/common/src/main/java/dev/cel/common/values/MutableListValue.java @@ -0,0 +1,78 @@ +package dev.cel.common.values; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +public final class MutableListValue extends ListValue { + + private List mutableList; + + public static MutableListValue create(int size) { + return new MutableListValue<>(size); + } + + private MutableListValue(int size) { + this.mutableList = new ArrayList<>(size); + } + + @Override + public List value() { + return List.of(); + } + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + @Override + public boolean contains(Object o) { + return false; + } + @Override + public Iterator iterator() { + return null; + } + @Override + public Object[] toArray() { + return new Object[0]; + } + @Override + public T[] toArray(T[] a) { + return null; + } + @Override + public boolean containsAll(Collection c) { + return false; + } + @Override + public E get(int index) { + return null; + } + @Override + public int indexOf(Object o) { + return 0; + } + @Override + public int lastIndexOf(Object o) { + return 0; + } + @Override + public ListIterator listIterator() { + return null; + } + @Override + public ListIterator listIterator(int index) { + return null; + } + @Override + public List subList(int fromIndex, int toIndex) { + return List.of(); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java index b0435cf55..cd9583a70 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java @@ -3,7 +3,9 @@ import com.google.errorprone.annotations.Immutable; import dev.cel.common.values.CelValue; import dev.cel.common.values.IntValue; +import dev.cel.common.values.ListValue; import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.ConcatenatedListView; import dev.cel.runtime.GlobalResolver; import java.util.Collection; import java.util.Iterator; @@ -35,9 +37,9 @@ public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { folder.accuVal = accuInit.eval(folder); - // TODO: Implement scoping long index = 0; for (Iterator iterator = foldRange.iterator(); iterator.hasNext(); ) { + // TODO: Implement condition if (iterVar2.isEmpty()) { folder.iterVarVal = iterator.next(); } else { @@ -48,10 +50,7 @@ public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { index++; } - CelValue resultValue = result.eval(folder); - // CelValue resultValue = resolveName("@result", resolver); - - return resultValue; + return result.eval(folder); } private static class Folder implements GlobalResolver { diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 32c411ee2..334714b66 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -77,6 +77,7 @@ java_library( "//runtime:unknown_attributes", "//runtime:unknown_options", "//runtime/planner:program_planner", + "//runtime/standard:add", "//runtime/standard:divide", "//runtime/standard:equals", "//runtime/standard:greater", 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 94378fde9..cddfc84b3 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -48,6 +48,7 @@ import dev.cel.runtime.CelLiteRuntime.Program; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import dev.cel.runtime.standard.AddOperator; import dev.cel.runtime.standard.CelStandardFunction; import dev.cel.runtime.standard.DivideOperator; import dev.cel.runtime.standard.EqualsOperator; @@ -112,6 +113,7 @@ private static DefaultDispatcher newDispatcher() { // Subsetted StdLib addBindings(builder, Operator.INDEX.getFunction(), fromStandardFunction(IndexOperator.create())); addBindings(builder, Operator.LOGICAL_NOT.getFunction(), fromStandardFunction(LogicalNotOperator.create())); + addBindings(builder, Operator.ADD.getFunction(), fromStandardFunction(AddOperator.create())); addBindings(builder, Operator.GREATER.getFunction(), fromStandardFunction(GreaterOperator.create())); addBindings(builder, Operator.GREATER_EQUALS.getFunction(), fromStandardFunction( GreaterEqualsOperator.create())); @@ -456,11 +458,12 @@ public void planSelect() throws Exception { } @Test - @TestParameters("{expression: '[1,2,3].exists(x, x > 0) == true'}") - @TestParameters("{expression: '[1,2,3].exists(x, x < 0) == false'}") - @TestParameters("{expression: '[1,2,3].exists(i, v, i >= 0 && v > 0) == true'}") - @TestParameters("{expression: '[1,2,3].exists(i, v, i < 0 || v < 0) == false'}") - public void planComprehension(String expression) throws Exception { + // @TestParameters("{expression: '[1,2,3].exists(x, x > 0) == true'}") + // @TestParameters("{expression: '[1,2,3].exists(x, x < 0) == false'}") + // @TestParameters("{expression: '[1,2,3].exists(i, v, i >= 0 && v > 0) == true'}") + // @TestParameters("{expression: '[1,2,3].exists(i, v, i < 0 || v < 0) == false'}") + @TestParameters("{expression: '[1,2,3].map(x, x + 1) == [2,3,4]'}") + public void planComprehension_lists(String expression) throws Exception { CelAbstractSyntaxTree ast = compile(expression); Program program = PLANNER.plan(ast); @@ -469,6 +472,20 @@ public void planComprehension(String expression) throws Exception { assertThat(result).isTrue(); } + // @Test + // @TestParameters("{expression: '[1,2,3].exists(x, x > 0) == true'}") + // @TestParameters("{expression: '[1,2,3].exists(x, x < 0) == false'}") + // @TestParameters("{expression: '[1,2,3].exists(i, v, i >= 0 && v > 0) == true'}") + // @TestParameters("{expression: '[1,2,3].exists(i, v, i < 0 || v < 0) == false'}") + // public void planComprehension_maps(String expression) throws Exception { + // CelAbstractSyntaxTree ast = compile(expression); + // Program program = PLANNER.plan(ast); + // + // boolean result = (boolean) program.eval(); + // + // assertThat(result).isTrue(); + // } + private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); if (isParseOnly) { From 6b5c84c6ee1e8f95f95cdc72e688f64f10ff6aac Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 20 Oct 2025 15:04:38 -0700 Subject: [PATCH 24/30] Move ConcatenatedListView to common/internal --- common/internal/BUILD.bazel | 5 +++++ .../java/dev/cel/common/internal/BUILD.bazel | 7 +++++++ .../common/internal}/ConcatenatedListView.java | 8 +++++--- .../src/main/java/dev/cel/runtime/BUILD.bazel | 17 +++++------------ .../dev/cel/runtime/DefaultInterpreter.java | 6 +++--- .../java/dev/cel/runtime/RuntimeHelpers.java | 2 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 1 + .../java/dev/cel/runtime/planner/EvalFold.java | 2 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 1 + 9 files changed, 29 insertions(+), 20 deletions(-) rename {runtime/src/main/java/dev/cel/runtime => common/src/main/java/dev/cel/common/internal}/ConcatenatedListView.java (92%) diff --git a/common/internal/BUILD.bazel b/common/internal/BUILD.bazel index c69f26b3e..e2a5125f5 100644 --- a/common/internal/BUILD.bazel +++ b/common/internal/BUILD.bazel @@ -147,3 +147,8 @@ cel_android_library( name = "date_time_helpers_android", exports = ["//common/src/main/java/dev/cel/common/internal:date_time_helpers_android"], ) + +java_library( + name = "concatenated_list_view", + exports = ["//common/src/main/java/dev/cel/common/internal:concatenated_list_view"], +) diff --git a/common/src/main/java/dev/cel/common/internal/BUILD.bazel b/common/src/main/java/dev/cel/common/internal/BUILD.bazel index 690b1cc75..359ddffed 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -461,3 +461,10 @@ cel_android_library( "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) + +java_library( + name = "concatenated_list_view", + srcs = ["ConcatenatedListView.java"], + deps = ["//common/annotations"], + # used_by_android +) diff --git a/runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java b/common/src/main/java/dev/cel/common/internal/ConcatenatedListView.java similarity index 92% rename from runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java rename to common/src/main/java/dev/cel/common/internal/ConcatenatedListView.java index ac7696751..1fbe779f6 100644 --- a/runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java +++ b/common/src/main/java/dev/cel/common/internal/ConcatenatedListView.java @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package dev.cel.runtime; +package dev.cel.common.internal; +import dev.cel.common.annotations.Internal; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; @@ -28,7 +29,8 @@ * *

This does not support any of the standard list operations from {@link java.util.List}. */ -final class ConcatenatedListView extends AbstractList { +@Internal +public final class ConcatenatedListView extends AbstractList { private final List> sourceLists; private int totalSize = 0; @@ -36,7 +38,7 @@ final class ConcatenatedListView extends AbstractList { this.sourceLists = new ArrayList<>(); } - ConcatenatedListView(Collection collection) { + public ConcatenatedListView(Collection collection) { this(); addAll(collection); } diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 638de457b..14ac2e2c4 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -303,7 +303,6 @@ java_library( deps = [ ":accumulated_unknowns", ":base", - ":concatenated_list_view", ":evaluation_exception", ":evaluation_exception_builder", ":evaluation_listener", @@ -323,6 +322,7 @@ java_library( "//common:runtime_exception", "//common/annotations", "//common/ast", + "//common/internal:concatenated_list_view", "//common/types", "//common/types:type_providers", "//common/values:cel_byte_string", @@ -341,7 +341,6 @@ cel_android_library( deps = [ ":accumulated_unknowns_android", ":base_android", - ":concatenated_list_view", ":evaluation_exception", ":evaluation_exception_builder", ":evaluation_listener_android", @@ -361,6 +360,7 @@ cel_android_library( "//common:runtime_exception", "//common/annotations", "//common/ast:ast_android", + "//common/internal:concatenated_list_view", "//common/types:type_providers_android", "//common/types:types_android", "//common/values:cel_byte_string", @@ -436,11 +436,11 @@ cel_android_library( tags = [ ], deps = [ - ":concatenated_list_view", "//common:error_codes", "//common:options", "//common:runtime_exception", "//common/annotations", + "//common/internal:concatenated_list_view", "//common/internal:converter", "//common/values:values_android", "@maven//:com_google_errorprone_error_prone_annotations", @@ -459,11 +459,11 @@ java_library( tags = [ ], deps = [ - ":concatenated_list_view", "//common:error_codes", "//common:options", "//common:runtime_exception", "//common/annotations", + "//common/internal:concatenated_list_view", "//common/internal:converter", "//common/values", "@maven//:com_google_errorprone_error_prone_annotations", @@ -1185,13 +1185,6 @@ cel_android_library( ], ) -java_library( - name = "concatenated_list_view", - srcs = ["ConcatenatedListView.java"], - # used_by_android - visibility = ["//visibility:private"], -) - java_library( name = "accumulated_unknowns", srcs = ["AccumulatedUnknowns.java"], @@ -1209,5 +1202,5 @@ cel_android_library( deps = [ ":unknown_attributes_android", "@maven//:com_google_errorprone_error_prone_annotations", - ] + ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index 141722e6a..ef2986e29 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -937,14 +937,14 @@ private IntermediateResult maybeAdaptToListView(IntermediateResult accuValue) { return accuValue; } - ConcatenatedListView lv = - new ConcatenatedListView<>((List) accuValue.value()); + dev.cel.common.internal.ConcatenatedListView lv = + new dev.cel.common.internal.ConcatenatedListView<>((List) accuValue.value()); return IntermediateResult.create(lv); } @SuppressWarnings("unchecked") // All type-erased elements are object compatible private IntermediateResult maybeAdaptViewToList(IntermediateResult accuValue) { - if ((accuValue.value() instanceof ConcatenatedListView)) { + if ((accuValue.value() instanceof dev.cel.common.internal.ConcatenatedListView)) { // Materialize view back into a list to facilitate O(1) lookups. List copiedList = new ArrayList<>((List) accuValue.value()); diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java index 25f01ef59..3ca8bd7a6 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java @@ -96,7 +96,7 @@ public static boolean matches(String string, String regexp, CelOptions celOption /** Concatenates two lists into a new list. */ public static List concat(List first, List second) { - if (first instanceof ConcatenatedListView) { + if (first instanceof dev.cel.common.internal.ConcatenatedListView) { // Comprehensions use a more efficient list view for performing O(1) concatenation first.addAll(second); return first; 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 11c18a775..568ef8821 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -257,6 +257,7 @@ java_library( deps = [ ":attribute", ":cel_value_interpretable", + "//common/internal:concatenated_list_view", "//common/values", "//common/values:cel_value", "//common/values:cel_value_provider", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java index cd9583a70..634c72111 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java @@ -5,7 +5,7 @@ import dev.cel.common.values.IntValue; import dev.cel.common.values.ListValue; import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.ConcatenatedListView; +import dev.cel.common.internal.ConcatenatedListView; import dev.cel.runtime.GlobalResolver; import java.util.Collection; import java.util.Iterator; diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 334714b66..acf77f88f 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -17,6 +17,7 @@ java_library( "//:java_truth", "//bundle:cel", "//common:cel_ast", + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common:cel_exception", "//common:cel_source", From 9a0f88239247f3744af060868afcbd95210d5475 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 20 Oct 2025 17:11:26 -0700 Subject: [PATCH 25/30] EvalVarArgs native array --- .../dev/cel/runtime/planner/EvalVarArgsCall.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java index e86f250b7..5a27d70ed 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java @@ -15,14 +15,14 @@ final class EvalVarArgsCall implements CelValueInterpretable { @Override public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { - ImmutableList.Builder argValBuilder = ImmutableList.builder(); - for (CelValueInterpretable arg : args) { - Object evalValue = celValueConverter.fromCelValueToJavaObject(arg.eval(resolver)); - argValBuilder.add(evalValue); + Object[] argVals = new Object[args.size()]; + for (int i = 0; i < args.size(); i++) { + CelValueInterpretable arg = args.get(i); + CelValue resolved = arg.eval(resolver); + argVals[i] = celValueConverter.fromCelValueToJavaObject(resolved); } - ImmutableList argVals = argValBuilder.build(); - Object result = resolvedOverload.getDefinition().apply(argVals.toArray()); + Object result = resolvedOverload.getDefinition().apply(argVals); return celValueConverter.fromJavaObjectToCelValue(result); } From 72b51efe0875f06b5a5cad3e6f287fb14fb7ac3e Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 23 Oct 2025 13:54:27 -0700 Subject: [PATCH 26/30] Remove internal annotation from CelFunctionBidning --- runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java index 7898eb9a7..75140cfd4 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java @@ -16,8 +16,6 @@ import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; -import dev.cel.common.annotations.Internal; -import java.util.List; /** * Binding consisting of an overload id, a Java-native argument signature, and an overload @@ -37,7 +35,6 @@ * *

Examples: string_startsWith_string, mathMax_list, lessThan_money_money */ -@Internal @Immutable public interface CelFunctionBinding { String getOverloadId(); From 4dd379407de3f73921c5b4d4c19ef7ab1344a3fd Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 23 Oct 2025 18:33:37 -0700 Subject: [PATCH 27/30] Dispatch using CelValue types --- .../cel/common/values/CelValueConverter.java | 1 - runtime/BUILD.bazel | 10 + .../src/main/java/dev/cel/runtime/BUILD.bazel | 32 +++- .../dev/cel/runtime/CelFunctionBinding.java | 19 -- .../cel/runtime/CelValueFunctionBinding.java | 67 +++++++ .../cel/runtime/CelValueFunctionOverload.java | 31 ++++ .../dev/cel/runtime/DefaultDispatcher.java | 14 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 4 + .../java/dev/cel/runtime/planner/EvalAnd.java | 10 +- .../java/dev/cel/runtime/planner/EvalOr.java | 10 +- .../dev/cel/runtime/planner/EvalUnary.java | 15 +- .../cel/runtime/planner/EvalVarArgsCall.java | 29 ++- .../cel/runtime/planner/EvalZeroArity.java | 14 +- .../cel/runtime/planner/ProgramPlanner.java | 30 +-- .../java/dev/cel/runtime/planner/BUILD.bazel | 3 + .../runtime/planner/ProgramPlannerTest.java | 172 +++++++++++++----- 16 files changed, 326 insertions(+), 135 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/CelValueFunctionBinding.java create mode 100644 runtime/src/main/java/dev/cel/runtime/CelValueFunctionOverload.java diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java index e838f3213..4deb84583 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -81,7 +81,6 @@ public CelValue fromJavaObjectToCelValue(Object value) { // TODO: CelConstant should hold this value instead of adapting it here return BytesValue.create(CelByteString.of(((ByteString) value).toByteArray())); } else if (value instanceof Iterable) { - return toListValue((Iterable) value); } else if (value instanceof Map) { return toMapValue((Map) value); diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index d81019444..78892a3ef 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -228,3 +228,13 @@ cel_android_library( visibility = ["//:internal"], exports = ["//runtime/src/main/java/dev/cel/runtime:lite_runtime_impl_android"], ) + +java_library( + name = "cel_value_function_binding", + exports = ["//runtime/src/main/java/dev/cel/runtime:cel_value_function_binding"], +) + +java_library( + name = "cel_value_function_overload", + exports = ["//runtime/src/main/java/dev/cel/runtime:cel_value_function_overload"], +) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 14ac2e2c4..f73473a4e 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -130,8 +130,8 @@ java_library( ], deps = [ ":base", - ":function_binding", - ":function_overload", + ":cel_value_function_binding", + ":cel_value_function_overload", "//:auto_value", "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", @@ -1204,3 +1204,31 @@ cel_android_library( "@maven//:com_google_errorprone_error_prone_annotations", ], ) + +java_library( + name = "cel_value_function_binding", + srcs = ["CelValueFunctionBinding.java"], + tags = [ + ], + deps = [ + ":cel_value_function_overload", + "//common/values:cel_value", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "cel_value_function_overload", + srcs = [ + "CelValueFunctionOverload.java", + ], + tags = [ + ], + deps = [ + ":evaluation_exception", + "//common/values:cel_value", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java index 75140cfd4..7d7c47428 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java @@ -66,23 +66,4 @@ static CelFunctionBinding from( String overloadId, Iterable> argTypes, CelFunctionOverload impl) { return new FunctionBindingImpl(overloadId, ImmutableList.copyOf(argTypes), impl); } - - default boolean canHandle(Object[] arguments) { - ImmutableList> parameterTypes = getArgTypes(); - if (parameterTypes.size() != arguments.length) { - return false; - } - for (int i = 0; i < parameterTypes.size(); i++) { - Class paramType = parameterTypes.get(i); - Object arg = arguments[i]; - if (arg == null) { - // Reject nulls. CEL-Java in general is not designed to handle nullability of objects. - return false; - } - if (!paramType.isAssignableFrom(arg.getClass())) { - return false; - } - } - return true; - } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueFunctionBinding.java b/runtime/src/main/java/dev/cel/runtime/CelValueFunctionBinding.java new file mode 100644 index 000000000..1fbd06c81 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelValueFunctionBinding.java @@ -0,0 +1,67 @@ +package dev.cel.runtime; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; + +@Immutable +public final class CelValueFunctionBinding { + + private final String overloadId; + private final ImmutableList> argTypes; + private final CelValueFunctionOverload definition; + + public String overloadId() { + return overloadId; + } + + public ImmutableList> argTypes() { + return argTypes; + } + + public CelValueFunctionOverload definition() { + return definition; + } + + public static CelValueFunctionBinding from(String overloadId, CelValueFunctionOverload.Nullary impl) { + return from(overloadId, ImmutableList.of(), (args) -> impl.apply()); + } + + public static CelValueFunctionBinding from( + String overloadId, Class argType, CelValueFunctionOverload.Unary impl) { + return from(overloadId, ImmutableList.of(argType), (args) -> impl.apply((T) args[0])); + } + + public static CelValueFunctionBinding from(String overloadId, Class argType1, Class argType2, CelValueFunctionOverload.Binary impl) { + return from(overloadId, ImmutableList.of(argType1, argType2), (args) -> impl.apply((T1) args[0], (T2) args[1])); + } + + public static CelValueFunctionBinding from(String overloadId, Class argType1, Class argType2, Class argType3, CelValueFunctionOverload.Ternary impl) { + return from(overloadId, ImmutableList.of(argType1, argType2, argType3), (args) -> impl.apply((T1) args[0], (T2) args[1], (T3) args[2])); + } + + public static CelValueFunctionBinding from(String overloadId, ImmutableList> argTypes, CelValueFunctionOverload impl) { + return new CelValueFunctionBinding(overloadId, argTypes, impl); + } + + public boolean canHandle(CelValue[] arguments) { + if (argTypes().size() != arguments.length) { + return false; + } + + for (int i = 0; i < argTypes().size(); i++) { + Class paramType = argTypes().get(i); + CelValue arg = arguments[i]; + if (!paramType.isInstance(arg)) { + return false; + } + } + return true; + } + + private CelValueFunctionBinding(String overloadId, ImmutableList> argTypes, CelValueFunctionOverload definition) { + this.overloadId = overloadId; + this.argTypes = argTypes; + this.definition = definition; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueFunctionOverload.java b/runtime/src/main/java/dev/cel/runtime/CelValueFunctionOverload.java new file mode 100644 index 000000000..8ac8d256f --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelValueFunctionOverload.java @@ -0,0 +1,31 @@ +package dev.cel.runtime; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; + +@Immutable +@FunctionalInterface +public interface CelValueFunctionOverload { + + CelValue apply(CelValue... args) throws CelEvaluationException; + + @Immutable + interface Nullary { + CelValue apply() throws CelEvaluationException; + } + + @Immutable + interface Unary { + CelValue apply(T arg) throws CelEvaluationException; + } + + @Immutable + interface Binary { + CelValue apply(T1 arg1, T2 arg2) throws CelEvaluationException; + } + + @Immutable + interface Ternary { + CelValue apply(T1 arg1, T2 arg2, T3 arg3) throws CelEvaluationException; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index d0ab19503..56528d4f3 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -13,9 +13,9 @@ @AutoValue public abstract class DefaultDispatcher { - abstract ImmutableMap overloads(); + abstract ImmutableMap overloads(); - public Optional findOverload(String overloadId) { + public Optional findOverload(String overloadId) { return Optional.ofNullable(overloads().get(overloadId)); } @@ -25,20 +25,20 @@ public static Builder newBuilder() { @AutoValue.Builder public abstract static class Builder { - public abstract ImmutableMap.Builder overloadsBuilder(); + public abstract ImmutableMap.Builder overloadsBuilder(); @CanIgnoreReturnValue - public Builder addOverload(CelFunctionBinding functionBinding) { + public Builder addOverload(CelValueFunctionBinding functionBinding) { overloadsBuilder().put( - functionBinding.getOverloadId(), + functionBinding.overloadId(), functionBinding); return this; } @CanIgnoreReturnValue - public Builder addFunction(String functionName, CelFunctionOverload definition) { + public Builder addDynamicDispatchOverload(String functionName, CelValueFunctionOverload definition) { overloadsBuilder().put( - functionName, CelFunctionBinding.from(functionName, ImmutableList.of(), definition) + functionName, CelValueFunctionBinding.from(functionName, ImmutableList.of(), definition) ); return this; 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 568ef8821..77e857aab 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -34,6 +34,7 @@ java_library( "//common/values:cel_value", "//common/values:cel_value_provider", "//runtime:activation", + "//runtime:cel_value_function_binding", "//runtime:default_dispatcher", "//runtime:evaluation_exception", "//runtime:function_binding", @@ -140,6 +141,7 @@ java_library( ":cel_value_interpretable", "//common/values", "//common/values:cel_value", + "//runtime:cel_value_function_binding", "//runtime:evaluation_exception", "//runtime:function_binding", "//runtime:interpretable", @@ -153,6 +155,7 @@ java_library( ":cel_value_interpretable", "//common/values", "//common/values:cel_value", + "//runtime:cel_value_function_binding", "//runtime:evaluation_exception", "//runtime:function_binding", "//runtime:interpretable", @@ -166,6 +169,7 @@ java_library( ":cel_value_interpretable", "//common/values", "//common/values:cel_value", + "//runtime:cel_value_function_binding", "//runtime:evaluation_exception", "//runtime:function_binding", "//runtime:interpretable", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java index 12caada8d..04266f063 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java @@ -3,7 +3,6 @@ import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; import dev.cel.common.values.BoolValue; import dev.cel.common.values.CelValue; import dev.cel.common.values.ErrorValue; @@ -13,7 +12,8 @@ final class EvalAnd implements CelValueInterpretable { - private final ImmutableList args; + @SuppressWarnings("Immutable") + private final CelValueInterpretable[] args; @Override public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { @@ -37,12 +37,12 @@ public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { return BoolValue.create(true); } - static EvalAnd create(ImmutableList args) { + static EvalAnd create(CelValueInterpretable[] args) { return new EvalAnd(args); } - private EvalAnd(ImmutableList args) { - Preconditions.checkArgument(args.size() == 2); + private EvalAnd(CelValueInterpretable[] args) { + Preconditions.checkArgument(args.length == 2); this.args = args; } } \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java index fed426438..7181e8e7b 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java @@ -3,7 +3,6 @@ import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; import dev.cel.common.values.BoolValue; import dev.cel.common.values.CelValue; import dev.cel.common.values.ErrorValue; @@ -13,7 +12,8 @@ final class EvalOr implements CelValueInterpretable { - private final ImmutableList args; + @SuppressWarnings("Immutable") + private final CelValueInterpretable[] args; @Override public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { @@ -37,12 +37,12 @@ public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { return BoolValue.create(false); } - static EvalOr create(ImmutableList args) { + static EvalOr create(CelValueInterpretable[] args) { return new EvalOr(args); } - private EvalOr(ImmutableList args) { - Preconditions.checkArgument(args.size() == 2); + private EvalOr(CelValueInterpretable[] args) { + Preconditions.checkArgument(args.length == 2); this.args = args; } } \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java index de2321e58..06aefe45e 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java @@ -4,28 +4,25 @@ import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.CelValueFunctionBinding; final class EvalUnary implements CelValueInterpretable { - private final CelFunctionBinding resolvedOverload; - private final CelValueConverter celValueConverter; + private final CelValueFunctionBinding resolvedOverload; private final CelValueInterpretable arg; @Override public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { CelValue argVal = arg.eval(resolver); - Object result = resolvedOverload.getDefinition().apply(new Object[] {argVal.value()}); - return celValueConverter.fromJavaObjectToCelValue(result); + return resolvedOverload.definition().apply(argVal); } - static EvalUnary create(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter, CelValueInterpretable arg) { - return new EvalUnary(resolvedOverload, celValueConverter, arg); + static EvalUnary create(CelValueFunctionBinding resolvedOverload, CelValueInterpretable arg) { + return new EvalUnary(resolvedOverload, arg); } - private EvalUnary(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter, CelValueInterpretable arg) { + private EvalUnary(CelValueFunctionBinding resolvedOverload, CelValueInterpretable arg) { this.resolvedOverload = resolvedOverload; - this.celValueConverter = celValueConverter; this.arg = arg; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java index 5a27d70ed..6b3822de2 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java @@ -1,38 +1,33 @@ package dev.cel.runtime.planner; -import com.google.common.collect.ImmutableList; import dev.cel.common.values.CelValue; -import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.CelValueFunctionBinding; import dev.cel.runtime.GlobalResolver; +@SuppressWarnings("Immutable") final class EvalVarArgsCall implements CelValueInterpretable { - private final CelFunctionBinding resolvedOverload; - private final CelValueConverter celValueConverter; - private final ImmutableList args; + private final CelValueFunctionBinding resolvedOverload; + private final CelValueInterpretable[] args; @Override public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { - Object[] argVals = new Object[args.size()]; - for (int i = 0; i < args.size(); i++) { - CelValueInterpretable arg = args.get(i); - CelValue resolved = arg.eval(resolver); - argVals[i] = celValueConverter.fromCelValueToJavaObject(resolved); + CelValue[] argVals = new CelValue[args.length]; + for (int i = 0; i < args.length; i++) { + CelValueInterpretable arg = args[i]; + argVals[i] = arg.eval(resolver); } - Object result = resolvedOverload.getDefinition().apply(argVals); - return celValueConverter.fromJavaObjectToCelValue(result); + return resolvedOverload.definition().apply(argVals); } - static EvalVarArgsCall create(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter, ImmutableList args) { - return new EvalVarArgsCall(resolvedOverload, celValueConverter, args); + static EvalVarArgsCall create(CelValueFunctionBinding resolvedOverload, CelValueInterpretable[] args) { + return new EvalVarArgsCall(resolvedOverload, args); } - private EvalVarArgsCall(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter, ImmutableList args) { + private EvalVarArgsCall(CelValueFunctionBinding resolvedOverload, CelValueInterpretable[] args) { this.resolvedOverload = resolvedOverload; - this.celValueConverter = celValueConverter; this.args = args; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java index 476c9d836..0d9f57b49 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java @@ -4,25 +4,23 @@ import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.CelValueFunctionBinding; import dev.cel.runtime.GlobalResolver; final class EvalZeroArity implements CelValueInterpretable { - private final CelFunctionBinding resolvedOverload; - private final CelValueConverter celValueConverter; + private final CelValueFunctionBinding resolvedOverload; @Override public CelValue eval(GlobalResolver resolver) throws CelEvaluationException { - Object result = resolvedOverload.getDefinition().apply(new Object[0]); - return celValueConverter.fromJavaObjectToCelValue(result); + return resolvedOverload.definition().apply(); } - static EvalZeroArity create(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter) { - return new EvalZeroArity(resolvedOverload, celValueConverter); + static EvalZeroArity create(CelValueFunctionBinding resolvedOverload) { + return new EvalZeroArity(resolvedOverload); } - private EvalZeroArity(CelFunctionBinding resolvedOverload, CelValueConverter celValueConverter) { + private EvalZeroArity(CelValueFunctionBinding resolvedOverload) { this.resolvedOverload = resolvedOverload; - 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 55e12d3c9..bcdcb91a5 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -23,8 +23,8 @@ import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.TypeValue; -import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelLiteRuntime.Program; +import dev.cel.runtime.CelValueFunctionBinding; import dev.cel.runtime.DefaultDispatcher; import java.util.HashMap; @@ -108,18 +108,24 @@ private EvalConstant fromCelConstant(CelConstant celConstant) { private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { ResolvedFunction resolvedFunction = resolveFunction(expr, ctx.referenceMap()); + CelExpr target = resolvedFunction.target().orElse(null); int argCount = expr.call().args().size(); - ImmutableList.Builder evaluatedArgBuilder = ImmutableList.builder(); - - if (resolvedFunction.target().isPresent()) { + if (target != null) { argCount++; - evaluatedArgBuilder.add(plan(resolvedFunction.target().get(), ctx)); } - for (CelExpr argExpr : expr.call().args()) { - evaluatedArgBuilder.add(plan(argExpr, ctx)); + CelValueInterpretable[] evaluatedArgs = new CelValueInterpretable[argCount]; + + int offset = 0; + if (target != null) { + evaluatedArgs[0] = plan(target, ctx); + offset++; + } + + ImmutableList args = expr.call().args(); + for (int argIndex = 0; argIndex < args.size(); argIndex++) { + evaluatedArgs[argIndex + offset] = plan(args.get(argIndex), ctx); } - ImmutableList evaluatedArgs = evaluatedArgBuilder.build(); // TODO: Handle specialized calls (logical operators, conditionals, equals etc) String functionName = resolvedFunction.functionName(); @@ -131,7 +137,7 @@ private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { return EvalAnd.create(evaluatedArgs); } - CelFunctionBinding resolvedOverload = null; + CelValueFunctionBinding resolvedOverload = null; if (resolvedFunction.overloadId().isPresent()) { resolvedOverload = dispatcher.findOverload(resolvedFunction.overloadId().get()).orElse(null); @@ -143,12 +149,12 @@ private CelValueInterpretable planCall(CelExpr expr, PlannerContext ctx) { switch (argCount) { case 0: - return EvalZeroArity.create(resolvedOverload, celValueConverter); + return EvalZeroArity.create(resolvedOverload); case 1: - return EvalUnary.create(resolvedOverload, celValueConverter, evaluatedArgs.get(0)); + return EvalUnary.create(resolvedOverload, evaluatedArgs[0]); // TODO: Handle binary default: - return EvalVarArgsCall.create(resolvedOverload, celValueConverter, evaluatedArgs); + return EvalVarArgsCall.create(resolvedOverload, evaluatedArgs); } } diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index acf77f88f..819067fd0 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -43,6 +43,7 @@ java_library( "//common/types:type_providers", "//common/values", "//common/values:cel_byte_string", + "//common/values:cel_value", "//common/values:cel_value_provider", "//common/values:proto_message_lite_value", "//common/values:proto_message_lite_value_provider", @@ -56,6 +57,8 @@ java_library( "//parser:unparser", "//runtime", "//runtime:activation", + "//runtime:cel_value_function_binding", + "//runtime:cel_value_function_overload", "//runtime:default_dispatcher", "//runtime:evaluation_exception_builder", "//runtime:evaluation_listener", 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 cddfc84b3..65df68b3c 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -25,18 +25,29 @@ import dev.cel.common.internal.DynamicProto; import dev.cel.common.types.CelType; import dev.cel.common.types.DefaultTypeProvider; -import dev.cel.common.types.ListType; import dev.cel.common.types.MapType; -import dev.cel.common.types.OptionalType; import dev.cel.common.types.SimpleType; import dev.cel.common.types.StructTypeReference; import dev.cel.common.types.TypeType; +import dev.cel.common.values.BoolValue; +import dev.cel.common.values.BytesValue; import dev.cel.common.values.CelByteString; +import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; +import dev.cel.common.values.DoubleValue; +import dev.cel.common.values.DurationValue; +import dev.cel.common.values.IntValue; +import dev.cel.common.values.ListValue; +import dev.cel.common.values.MapValue; import dev.cel.common.values.NullValue; import dev.cel.common.values.ProtoMessageValueProvider; +import dev.cel.common.values.StringValue; +import dev.cel.common.values.TimestampValue; +import dev.cel.common.values.TypeValue; +import dev.cel.common.values.UintValue; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; +import dev.cel.expr.conformance.proto3.GlobalEnum; import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.extensions.CelExtensions; import dev.cel.extensions.CelOptionalLibrary; @@ -44,8 +55,9 @@ import dev.cel.parser.Operator; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; -import dev.cel.runtime.CelFunctionOverload; import dev.cel.runtime.CelLiteRuntime.Program; +import dev.cel.runtime.CelValueFunctionBinding; +import dev.cel.runtime.CelValueFunctionOverload; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; import dev.cel.runtime.standard.AddOperator; @@ -54,13 +66,15 @@ import dev.cel.runtime.standard.EqualsOperator; import dev.cel.runtime.standard.GreaterEqualsOperator; import dev.cel.runtime.standard.GreaterOperator; -import dev.cel.runtime.standard.LessOperator; import dev.cel.runtime.standard.IndexOperator; +import dev.cel.runtime.standard.LessOperator; import dev.cel.runtime.standard.LogicalNotOperator; import dev.cel.runtime.standard.NotStrictlyFalseFunction; import java.nio.charset.StandardCharsets; import dev.cel.runtime.DefaultDispatcher; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Map; import org.junit.Test; @@ -68,7 +82,7 @@ @RunWith(TestParameterInjector.class) public final class ProgramPlannerTest { - private static final CelOptions CEL_OPTIONS = CelOptions.DEFAULT; + private static final CelOptions CEL_OPTIONS = CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build(); private static final RuntimeEquality RUNTIME_EQUALITY = RuntimeEquality.create(RuntimeHelpers.create(), CEL_OPTIONS); private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() @@ -91,16 +105,16 @@ public final class ProgramPlannerTest { .addLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.comprehensions()) .addMessageTypes(TestAllTypes.getDescriptor()) .build(); - private static final CelDescriptorPool DESCRIPTOR_POOL = DefaultDescriptorPool.create(CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(TestAllTypes.getDescriptor().getFile())); + private static final CelValueConverter CEL_VALUE_CONVERTER = new CelValueConverter(); private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner( DefaultTypeProvider.create(), ProtoMessageValueProvider.newInstance( CEL_OPTIONS, DynamicProto.create(DefaultMessageFactory.create(DESCRIPTOR_POOL)) ), - new CelValueConverter(), + CEL_VALUE_CONVERTER, newDispatcher() ); @@ -112,7 +126,8 @@ private static DefaultDispatcher newDispatcher() { // Subsetted StdLib addBindings(builder, Operator.INDEX.getFunction(), fromStandardFunction(IndexOperator.create())); - addBindings(builder, Operator.LOGICAL_NOT.getFunction(), fromStandardFunction(LogicalNotOperator.create())); + addBindings(builder, Operator.LOGICAL_NOT.getFunction(), fromStandardFunction( + LogicalNotOperator.create())); addBindings(builder, Operator.ADD.getFunction(), fromStandardFunction(AddOperator.create())); addBindings(builder, Operator.GREATER.getFunction(), fromStandardFunction(GreaterOperator.create())); addBindings(builder, Operator.GREATER_EQUALS.getFunction(), fromStandardFunction( @@ -124,53 +139,50 @@ private static DefaultDispatcher newDispatcher() { NotStrictlyFalseFunction.create())); // Custom functions - addBindings(builder, "zero", CelFunctionBinding.from("zero_overload", ImmutableList.of(), args -> 0L)); - addBindings(builder, "error", CelFunctionBinding.from("error_overload", ImmutableList.of(), args -> { throw new IllegalArgumentException("Intentional error"); })); + addBindings(builder, "zero", CelValueFunctionBinding.from("zero_overload", () -> IntValue.create(0L))); + addBindings(builder, "error", CelValueFunctionBinding.from("error_overload", () -> { throw new IllegalArgumentException("Intentional error"); })); addBindings(builder, "neg", - CelFunctionBinding.from("neg_int", Long.class, arg -> -arg), - CelFunctionBinding.from("neg_double", Double.class, arg -> -arg) + CelValueFunctionBinding.from("neg_int", IntValue.class, arg -> IntValue.create(-arg.longValue())), + CelValueFunctionBinding.from("neg_double", DoubleValue.class, arg -> DoubleValue.create(-arg.doubleValue())) ); addBindings(builder, "concat", - CelFunctionBinding.from("concat_bytes_bytes", CelByteString.class, CelByteString.class, ProgramPlannerTest::concatenateByteArrays), - CelFunctionBinding.from("bytes_concat_bytes", CelByteString.class, CelByteString.class,ProgramPlannerTest::concatenateByteArrays)); + CelValueFunctionBinding.from("concat_bytes_bytes", BytesValue.class, BytesValue.class, ProgramPlannerTest::concatenateByteArrays), + CelValueFunctionBinding.from("bytes_concat_bytes", BytesValue.class, BytesValue.class,ProgramPlannerTest::concatenateByteArrays)); return builder.build(); } - private static ImmutableSet fromStandardFunction(CelStandardFunction standardFunction) { - return standardFunction.newFunctionBindings(CEL_OPTIONS, RUNTIME_EQUALITY); - } - private static void addBindings(DefaultDispatcher.Builder builder, String functionName, CelFunctionBinding... functionBindings) { + private static void addBindings(DefaultDispatcher.Builder builder, String functionName, CelValueFunctionBinding... functionBindings) { addBindings(builder, functionName, ImmutableSet.copyOf(functionBindings)); } - private static void addBindings(DefaultDispatcher.Builder builder, String functionName, ImmutableCollection overloadBindings) { + private static void addBindings(DefaultDispatcher.Builder builder, String functionName, ImmutableCollection overloadBindings) { if (overloadBindings.isEmpty()) { throw new IllegalArgumentException("Invalid bindings"); } // TODO: Runtime top-level APIs currently does not allow grouping overloads with the function name. This capability will have to be added. if (overloadBindings.size() == 1) { - CelFunctionBinding singleBinding = Iterables.getOnlyElement(overloadBindings); + CelValueFunctionBinding singleBinding = Iterables.getOnlyElement(overloadBindings); builder.addOverload( - CelFunctionBinding.from( - functionName, singleBinding.getArgTypes(), singleBinding.getDefinition()) + CelValueFunctionBinding.from( + functionName, singleBinding.argTypes(), singleBinding.definition()) ); } else { overloadBindings.forEach(builder::addOverload); // Setup dynamic dispatch - CelFunctionOverload dynamicDispatchDef = args -> { - for (CelFunctionBinding overload : overloadBindings) { + CelValueFunctionOverload dynamicDispatchDef = args -> { + for (CelValueFunctionBinding overload : overloadBindings) { if (overload.canHandle(args)) { - return overload.getDefinition().apply(args); + return overload.definition().apply(args); } } throw new IllegalArgumentException("Overload not found: " + functionName); }; - builder.addFunction(functionName, dynamicDispatchDef); + builder.addDynamicDispatchOverload(functionName, dynamicDispatchDef); } } @@ -213,7 +225,7 @@ public void planConst(@TestParameter ConstantTestCase testCase) throws Exception @Test public void planIdent_enum() throws Exception { - CelAbstractSyntaxTree ast = compile("cel.expr.conformance.proto2.GlobalEnum.GAR"); + CelAbstractSyntaxTree ast = compile(GlobalEnum.getDescriptor().getFullName() + "." + GlobalEnum.GAR); Program program = PLANNER.plan(ast); Object result = program.eval(); @@ -278,19 +290,19 @@ public void planCreateMap() throws Exception { @SuppressWarnings("ImmutableEnumChecker") // Test only private enum TypeLiteralTestCase { - BOOL("bool", SimpleType.BOOL), - BYTES("bytes", SimpleType.BYTES), - DOUBLE("double", SimpleType.DOUBLE), - INT("int", SimpleType.INT), - UINT("uint", SimpleType.UINT), - STRING("string", SimpleType.STRING), - DYN("dyn", SimpleType.DYN), - LIST("list", ListType.create(SimpleType.DYN)), - MAP("map", MapType.create(SimpleType.DYN, SimpleType.DYN)), - NULL("null_type", SimpleType.NULL_TYPE), + // BOOL("bool", SimpleType.BOOL), + // BYTES("bytes", SimpleType.BYTES), + // DOUBLE("double", SimpleType.DOUBLE), + // INT("int", SimpleType.INT), + // UINT("uint", SimpleType.UINT), + // STRING("string", SimpleType.STRING), + // DYN("dyn", SimpleType.DYN), + // LIST("list", ListType.create(SimpleType.DYN)), + // MAP("map", MapType.create(SimpleType.DYN, SimpleType.DYN)), + // NULL("null_type", SimpleType.NULL_TYPE), DURATION("google.protobuf.Duration", SimpleType.DURATION), TIMESTAMP("google.protobuf.Timestamp", SimpleType.TIMESTAMP), - OPTIONAL("optional_type", OptionalType.create(SimpleType.DYN)), + // OPTIONAL("optional_type", OptionalType.create(SimpleType.DYN)), ; private final String expression; @@ -495,20 +507,80 @@ private CelAbstractSyntaxTree compile(String expression) throws Exception { return CEL_COMPILER.check(ast).getAst(); } - private static byte[] concatenateByteArrays(CelByteString bytes1, CelByteString bytes2) { - byte[] array1 = bytes1.toByteArray(); - byte[] array2 = bytes2.toByteArray(); - // Handle null or empty arrays gracefully - if (array1 == null || array1.length == 0) { - return array2; + private static BytesValue concatenateByteArrays(BytesValue bytes1, BytesValue bytes2) { + if (bytes1.isZeroValue()) { + return bytes2; } - if (array2 == null || array2.length == 0) { - return array1; + + if (bytes2.isZeroValue()) { + return bytes1; + } + + CelByteString combined = bytes1.value().concat(bytes2.value()); + return BytesValue.create(combined); + } + + // TODO: The following native -> CelValue function binding will need to be an adapter. + private static ImmutableSet fromStandardFunction(CelStandardFunction standardFunction) { + ImmutableSet functionBindings = standardFunction.newFunctionBindings(CEL_OPTIONS, RUNTIME_EQUALITY); + ImmutableSet.Builder builder = ImmutableSet.builder(); + + for (CelFunctionBinding functionBinding : functionBindings) { + CelValueFunctionBinding adaptedBinding = CelValueFunctionBinding.from(functionBinding.getOverloadId(), adaptArgumentTypes(functionBinding.getArgTypes()), celValueArgs -> { + Object[] nativeArgs = new Object[celValueArgs.length]; + for (int i = 0; i < celValueArgs.length; i++) { + nativeArgs[i] = CEL_VALUE_CONVERTER.fromCelValueToJavaObject(celValueArgs[i]); + } + + Object nativeResult = functionBinding.getDefinition().apply(nativeArgs); + return CEL_VALUE_CONVERTER.fromJavaObjectToCelValue(nativeResult); + }); + builder.add(adaptedBinding); } - byte[] combined = new byte[array1.length + array2.length]; - System.arraycopy(array1, 0, combined, 0, array1.length); - System.arraycopy(array2, 0, combined, array1.length, array2.length); - return combined; + return builder.build(); } + + private static ImmutableList> adaptArgumentTypes(ImmutableList> argTypes) { + ImmutableList.Builder> builder = ImmutableList.builder(); + + for (Class argType : argTypes) { + if (argType.equals(String.class)) { + builder.add(StringValue.class); + } else if (argType.equals(Long.class)) { + builder.add(IntValue.class); + } else if (argType.equals(Double.class)) { + builder.add(DoubleValue.class); + } else if (argType.equals(Boolean.class)) { + builder.add(BoolValue.class); + } else if (argType.equals(UnsignedLong.class)) { + builder.add(UintValue.class); + } else if (argType.equals(CelByteString.class)) { + builder.add(BytesValue.class); + } else if (argType.equals(Instant.class)) { + builder.add(TimestampValue.class); + } else if (argType.equals(Duration.class)) { + builder.add(DurationValue.class); + } else if (List.class.isAssignableFrom(argType)) { + builder.add(ListValue.class); + } else if (Map.class.isAssignableFrom(argType)) { + builder.add(MapValue.class); + } else if (CelType.class.isAssignableFrom(argType)) { + builder.add(TypeValue.class); + } else if (argType.equals(NullValue.class)) { + builder.add(NullValue.class); + } else if ( + argType.equals(Object.class) || + // Using Number.class was probably a mistake (see index_list). This particular overload will benefit from a concrete definition. + argType.equals(Number.class) + ) { + builder.add(CelValue.class); + } else { + // In all likelihood -- we should probably do an OpaqueValue here + throw new IllegalArgumentException("Unknown argument type: " + argType); + } + } + return builder.build(); + } + } From a599d6d3aaeef9bfe5005d92a5dc5eb271e65a61 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 27 Oct 2025 14:54:17 -0700 Subject: [PATCH 28/30] Class renames --- .../src/main/java/dev/cel/runtime/BUILD.bazel | 4 +- .../cel/runtime/CelLateFunctionBindings.java | 2 +- .../dev/cel/runtime/CelRuntimeLegacyImpl.java | 2 +- .../dev/cel/runtime/CelValueDispatcher.java | 50 ++++ .../dev/cel/runtime/DefaultDispatcher.java | 227 ++++++++++++++---- .../main/java/dev/cel/runtime/Dispatcher.java | 2 +- .../dev/cel/runtime/LegacyDispatcher.java | 177 -------------- .../java/dev/cel/runtime/LiteRuntimeImpl.java | 2 +- .../cel/runtime/planner/ProgramPlanner.java | 8 +- ...erTest.java => DefaultDispatcherTest.java} | 6 +- .../cel/runtime/DefaultInterpreterTest.java | 2 +- .../runtime/planner/ProgramPlannerTest.java | 10 +- 12 files changed, 246 insertions(+), 246 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/CelValueDispatcher.java delete mode 100644 runtime/src/main/java/dev/cel/runtime/LegacyDispatcher.java rename runtime/src/test/java/dev/cel/runtime/{LegacyDispatcherTest.java => DefaultDispatcherTest.java} (90%) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index f73473a4e..5fbeb579a 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -70,8 +70,8 @@ INTERPRABLE_SOURCES = [ # keep sorted DISPATCHER_SOURCES = [ + "DefaultDispatcher.java", "Dispatcher.java", - "LegacyDispatcher.java", ] java_library( @@ -125,7 +125,7 @@ java_library( java_library( name = "default_dispatcher", - srcs = ["DefaultDispatcher.java"], + srcs = ["CelValueDispatcher.java"], tags = [ ], deps = [ diff --git a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java index 9948590b4..7f83e38fd 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java @@ -38,7 +38,7 @@ private CelLateFunctionBindings(ImmutableMap functions @Override public Optional findOverload( String functionName, List overloadIds, Object[] args) throws CelEvaluationException { - return LegacyDispatcher.findOverload(functionName, overloadIds, functions, args); + return DefaultDispatcher.findOverload(functionName, overloadIds, functions, args); } public static CelLateFunctionBindings from(CelFunctionBinding... functions) { diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index e7d9ca8eb..2947445d1 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -287,7 +287,7 @@ public CelRuntimeLegacyImpl build() { functionBindingsBuilder.putAll(customFunctionBindings); - LegacyDispatcher dispatcher = LegacyDispatcher.create(); + DefaultDispatcher dispatcher = DefaultDispatcher.create(); functionBindingsBuilder .buildOrThrow() .forEach( diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueDispatcher.java b/runtime/src/main/java/dev/cel/runtime/CelValueDispatcher.java new file mode 100644 index 000000000..403b48253 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelValueDispatcher.java @@ -0,0 +1,50 @@ +package dev.cel.runtime; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.CheckReturnValue; +import dev.cel.common.annotations.Internal; + +import java.util.Optional; + +@Internal +@AutoValue +public abstract class CelValueDispatcher { + + abstract ImmutableMap overloads(); + + public Optional findOverload(String overloadId) { + return Optional.ofNullable(overloads().get(overloadId)); + } + + public static Builder newBuilder() { + return new AutoValue_CelValueDispatcher.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract ImmutableMap.Builder overloadsBuilder(); + + @CanIgnoreReturnValue + public Builder addOverload(CelValueFunctionBinding functionBinding) { + overloadsBuilder().put( + functionBinding.overloadId(), + functionBinding); + return this; + } + + @CanIgnoreReturnValue + public Builder addDynamicDispatchOverload(String functionName, CelValueFunctionOverload definition) { + overloadsBuilder().put( + functionName, CelValueFunctionBinding.from(functionName, ImmutableList.of(), definition) + ); + + return this; + } + + @CheckReturnValue + public abstract CelValueDispatcher build(); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index 56528d4f3..efa7613bb 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -1,50 +1,177 @@ -package dev.cel.runtime; - -import com.google.auto.value.AutoValue; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.errorprone.annotations.CheckReturnValue; -import dev.cel.common.annotations.Internal; - -import java.util.Optional; - -@Internal -@AutoValue -public abstract class DefaultDispatcher { - - abstract ImmutableMap overloads(); - - public Optional findOverload(String overloadId) { - return Optional.ofNullable(overloads().get(overloadId)); - } - - public static Builder newBuilder() { - return new AutoValue_DefaultDispatcher.Builder(); - } - - @AutoValue.Builder - public abstract static class Builder { - public abstract ImmutableMap.Builder overloadsBuilder(); - - @CanIgnoreReturnValue - public Builder addOverload(CelValueFunctionBinding functionBinding) { - overloadsBuilder().put( - functionBinding.overloadId(), - functionBinding); - return this; - } - - @CanIgnoreReturnValue - public Builder addDynamicDispatchOverload(String functionName, CelValueFunctionOverload definition) { - overloadsBuilder().put( - functionName, CelValueFunctionBinding.from(functionName, ImmutableList.of(), definition) - ); - - return this; - } - - @CheckReturnValue - public abstract DefaultDispatcher build(); - } -} +// Copyright 2022 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.auto.value.AutoValue; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; +import javax.annotation.concurrent.ThreadSafe; +import com.google.errorprone.annotations.concurrent.GuardedBy; +import dev.cel.common.CelErrorCode; +import dev.cel.common.annotations.Internal; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Default implementation of {@link Dispatcher}. + * + *

Should be final, do not mock; mocking {@link Dispatcher} instead. + */ +@ThreadSafe +@Internal +public final class DefaultDispatcher implements Dispatcher, Registrar { + public static DefaultDispatcher create() { + return new DefaultDispatcher(); + } + + @GuardedBy("this") + private final Map overloads = new HashMap<>(); + + @Override + @SuppressWarnings("unchecked") + public synchronized void add( + String overloadId, Class argType, final Registrar.UnaryFunction function) { + overloads.put( + overloadId, + ResolvedOverloadImpl.of( + overloadId, new Class[] {argType}, args -> function.apply((T) args[0]))); + } + + @Override + @SuppressWarnings("unchecked") + public synchronized void add( + String overloadId, + Class argType1, + Class argType2, + final Registrar.BinaryFunction function) { + overloads.put( + overloadId, + ResolvedOverloadImpl.of( + overloadId, + new Class[] {argType1, argType2}, + args -> function.apply((T1) args[0], (T2) args[1]))); + } + + @Override + public synchronized void add( + String overloadId, List> argTypes, Registrar.Function function) { + overloads.put( + overloadId, + ResolvedOverloadImpl.of(overloadId, argTypes.toArray(new Class[0]), function)); + } + + @Override + public synchronized Optional findOverload( + String functionName, List overloadIds, Object[] args) throws CelEvaluationException { + return DefaultDispatcher.findOverload(functionName, overloadIds, overloads, args); + } + + /** Finds the overload that matches the given function name, overload IDs, and arguments. */ + public static Optional findOverload( + String functionName, + List overloadIds, + Map overloads, + Object[] args) + throws CelEvaluationException { + int matchingOverloadCount = 0; + ResolvedOverload match = null; + List candidates = null; + for (String overloadId : overloadIds) { + ResolvedOverload overload = overloads.get(overloadId); + // If the overload is null, it means that the function was not registered; however, it is + // possible that the overload refers to a late-bound function. + if (overload != null && overload.canHandle(args)) { + if (++matchingOverloadCount > 1) { + if (candidates == null) { + candidates = new ArrayList<>(); + candidates.add(match.getOverloadId()); + } + candidates.add(overloadId); + } + match = overload; + } + } + + if (matchingOverloadCount > 1) { + throw CelEvaluationExceptionBuilder.newBuilder( + "Ambiguous overloads for function '%s'. Matching candidates: %s", + functionName, Joiner.on(", ").join(candidates)) + .setErrorCode(CelErrorCode.AMBIGUOUS_OVERLOAD) + .build(); + } + return Optional.ofNullable(match); + } + + @Override + public synchronized Dispatcher.ImmutableCopy immutableCopy() { + return new ImmutableCopy(overloads); + } + + @Immutable + private static final class ImmutableCopy implements Dispatcher.ImmutableCopy { + private final ImmutableMap overloads; + + private ImmutableCopy(Map overloads) { + this.overloads = ImmutableMap.copyOf(overloads); + } + + @Override + public Optional findOverload( + String functionName, List overloadIds, Object[] args) + throws CelEvaluationException { + return DefaultDispatcher.findOverload(functionName, overloadIds, overloads, args); + } + + @Override + public Dispatcher.ImmutableCopy immutableCopy() { + return this; + } + } + + private DefaultDispatcher() {} + + @AutoValue + @Immutable + abstract static class ResolvedOverloadImpl implements ResolvedOverload { + /** The overload id of the function. */ + @Override + public abstract String getOverloadId(); + + /** The types of the function parameters. */ + @Override + public abstract ImmutableList> getParameterTypes(); + + /** The function definition. */ + @Override + public abstract FunctionOverload getDefinition(); + + static ResolvedOverload of( + String overloadId, Class[] parameterTypes, FunctionOverload definition) { + return of(overloadId, ImmutableList.copyOf(parameterTypes), definition); + } + + static ResolvedOverload of( + String overloadId, ImmutableList> parameterTypes, FunctionOverload definition) { + return new AutoValue_DefaultDispatcher_ResolvedOverloadImpl( + overloadId, parameterTypes, definition); + } + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/Dispatcher.java b/runtime/src/main/java/dev/cel/runtime/Dispatcher.java index c0e6897c5..6482290f3 100644 --- a/runtime/src/main/java/dev/cel/runtime/Dispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/Dispatcher.java @@ -35,7 +35,7 @@ public interface Dispatcher extends FunctionResolver { ImmutableCopy immutableCopy(); /** - * An {@link Immutable} copy of a {@link Dispatcher}. Currently {@link LegacyDispatcher} + * An {@link Immutable} copy of a {@link Dispatcher}. Currently {@link DefaultDispatcher} * implementation implements both {@link Dispatcher} and {@link Registrar} and cannot be annotated * as {@link Immutable}. * diff --git a/runtime/src/main/java/dev/cel/runtime/LegacyDispatcher.java b/runtime/src/main/java/dev/cel/runtime/LegacyDispatcher.java deleted file mode 100644 index 91aa87181..000000000 --- a/runtime/src/main/java/dev/cel/runtime/LegacyDispatcher.java +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2022 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.auto.value.AutoValue; -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.errorprone.annotations.Immutable; -import javax.annotation.concurrent.ThreadSafe; -import com.google.errorprone.annotations.concurrent.GuardedBy; -import dev.cel.common.CelErrorCode; -import dev.cel.common.annotations.Internal; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * Default implementation of {@link Dispatcher}. - * - *

Should be final, do not mock; mocking {@link Dispatcher} instead. - */ -@ThreadSafe -@Internal -public final class LegacyDispatcher implements Dispatcher, Registrar { - public static LegacyDispatcher create() { - return new LegacyDispatcher(); - } - - @GuardedBy("this") - private final Map overloads = new HashMap<>(); - - @Override - @SuppressWarnings("unchecked") - public synchronized void add( - String overloadId, Class argType, final Registrar.UnaryFunction function) { - overloads.put( - overloadId, - ResolvedOverloadImpl.of( - overloadId, new Class[] {argType}, args -> function.apply((T) args[0]))); - } - - @Override - @SuppressWarnings("unchecked") - public synchronized void add( - String overloadId, - Class argType1, - Class argType2, - final Registrar.BinaryFunction function) { - overloads.put( - overloadId, - ResolvedOverloadImpl.of( - overloadId, - new Class[] {argType1, argType2}, - args -> function.apply((T1) args[0], (T2) args[1]))); - } - - @Override - public synchronized void add( - String overloadId, List> argTypes, Registrar.Function function) { - overloads.put( - overloadId, - ResolvedOverloadImpl.of(overloadId, argTypes.toArray(new Class[0]), function)); - } - - @Override - public synchronized Optional findOverload( - String functionName, List overloadIds, Object[] args) throws CelEvaluationException { - return LegacyDispatcher.findOverload(functionName, overloadIds, overloads, args); - } - - /** Finds the overload that matches the given function name, overload IDs, and arguments. */ - public static Optional findOverload( - String functionName, - List overloadIds, - Map overloads, - Object[] args) - throws CelEvaluationException { - int matchingOverloadCount = 0; - ResolvedOverload match = null; - List candidates = null; - for (String overloadId : overloadIds) { - ResolvedOverload overload = overloads.get(overloadId); - // If the overload is null, it means that the function was not registered; however, it is - // possible that the overload refers to a late-bound function. - if (overload != null && overload.canHandle(args)) { - if (++matchingOverloadCount > 1) { - if (candidates == null) { - candidates = new ArrayList<>(); - candidates.add(match.getOverloadId()); - } - candidates.add(overloadId); - } - match = overload; - } - } - - if (matchingOverloadCount > 1) { - throw CelEvaluationExceptionBuilder.newBuilder( - "Ambiguous overloads for function '%s'. Matching candidates: %s", - functionName, Joiner.on(", ").join(candidates)) - .setErrorCode(CelErrorCode.AMBIGUOUS_OVERLOAD) - .build(); - } - return Optional.ofNullable(match); - } - - @Override - public synchronized Dispatcher.ImmutableCopy immutableCopy() { - return new ImmutableCopy(overloads); - } - - @Immutable - private static final class ImmutableCopy implements Dispatcher.ImmutableCopy { - private final ImmutableMap overloads; - - private ImmutableCopy(Map overloads) { - this.overloads = ImmutableMap.copyOf(overloads); - } - - @Override - public Optional findOverload( - String functionName, List overloadIds, Object[] args) - throws CelEvaluationException { - return LegacyDispatcher.findOverload(functionName, overloadIds, overloads, args); - } - - @Override - public Dispatcher.ImmutableCopy immutableCopy() { - return this; - } - } - - private LegacyDispatcher() {} - - @AutoValue - @Immutable - abstract static class ResolvedOverloadImpl implements ResolvedOverload { - /** The overload id of the function. */ - @Override - public abstract String getOverloadId(); - - /** The types of the function parameters. */ - @Override - public abstract ImmutableList> getParameterTypes(); - - /** The function definition. */ - @Override - public abstract FunctionOverload getDefinition(); - - static ResolvedOverload of( - String overloadId, Class[] parameterTypes, FunctionOverload definition) { - return of(overloadId, ImmutableList.copyOf(parameterTypes), definition); - } - - static ResolvedOverload of( - String overloadId, ImmutableList> parameterTypes, FunctionOverload definition) { - return new AutoValue_LegacyDispatcher_ResolvedOverloadImpl( - overloadId, parameterTypes, definition); - } - } -} diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index 238783955..d55528fbc 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -192,7 +192,7 @@ public CelLiteRuntime build() { functionBindingsBuilder.putAll(customFunctionBindings); - LegacyDispatcher dispatcher = LegacyDispatcher.create(); + DefaultDispatcher dispatcher = DefaultDispatcher.create(); functionBindingsBuilder .buildOrThrow() .forEach( 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 bcdcb91a5..e74140de0 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -25,7 +25,7 @@ import dev.cel.common.values.TypeValue; import dev.cel.runtime.CelLiteRuntime.Program; import dev.cel.runtime.CelValueFunctionBinding; -import dev.cel.runtime.DefaultDispatcher; +import dev.cel.runtime.CelValueDispatcher; import java.util.HashMap; import java.util.List; @@ -38,7 +38,7 @@ public final class ProgramPlanner { private final CelTypeProvider typeProvider; private final CelValueProvider valueProvider; private final CelValueConverter celValueConverter; - private final DefaultDispatcher dispatcher; + private final CelValueDispatcher dispatcher; private final AttributeFactory attributeFactory; private CelValueInterpretable plan( @@ -312,7 +312,7 @@ public static ProgramPlanner newPlanner( CelTypeProvider typeProvider, CelValueProvider valueProvider, CelValueConverter celValueConverter, - DefaultDispatcher dispatcher + CelValueDispatcher dispatcher ) { return new ProgramPlanner(typeProvider, valueProvider, celValueConverter, dispatcher); } @@ -321,7 +321,7 @@ private ProgramPlanner( CelTypeProvider typeProvider, CelValueProvider valueProvider, CelValueConverter celValueConverter, - DefaultDispatcher dispatcher + CelValueDispatcher dispatcher ) { this.typeProvider = typeProvider; this.valueProvider = valueProvider; diff --git a/runtime/src/test/java/dev/cel/runtime/LegacyDispatcherTest.java b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java similarity index 90% rename from runtime/src/test/java/dev/cel/runtime/LegacyDispatcherTest.java rename to runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java index 9ed82a260..4d0b3f2b3 100644 --- a/runtime/src/test/java/dev/cel/runtime/LegacyDispatcherTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java @@ -25,9 +25,9 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** Tests for {@link LegacyDispatcher}. */ +/** Tests for {@link DefaultDispatcher}. */ @RunWith(JUnit4.class) -public final class LegacyDispatcherTest { +public final class DefaultDispatcherTest { private Map overloads; @@ -50,7 +50,7 @@ public void findOverload_multipleMatches_throwsException() { Assert.assertThrows( CelEvaluationException.class, () -> - LegacyDispatcher.findOverload( + DefaultDispatcher.findOverload( "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 b4206f757..4f3501d41 100644 --- a/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java @@ -73,7 +73,7 @@ public Object adapt(String messageName, Object message) { } }; CelAbstractSyntaxTree ast = celCompiler.compile("[1].all(x, [2].all(y, error()))").getAst(); - LegacyDispatcher dispatcher = LegacyDispatcher.create(); + DefaultDispatcher dispatcher = DefaultDispatcher.create(); dispatcher.add("error", long.class, (args) -> new IllegalArgumentException("Always throws")); DefaultInterpreter defaultInterpreter = new DefaultInterpreter(new TypeResolver(), emptyProvider, dispatcher, CelOptions.DEFAULT); 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 65df68b3c..6fa1d084e 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -72,7 +72,7 @@ import dev.cel.runtime.standard.NotStrictlyFalseFunction; import java.nio.charset.StandardCharsets; -import dev.cel.runtime.DefaultDispatcher; +import dev.cel.runtime.CelValueDispatcher; import java.time.Duration; import java.time.Instant; import java.util.List; @@ -121,8 +121,8 @@ public final class ProgramPlannerTest { /** * Configure dispatcher for testing purposes. This is done manually here, but this should be driven by the top-level runtime APIs in the future */ - private static DefaultDispatcher newDispatcher() { - DefaultDispatcher.Builder builder = DefaultDispatcher.newBuilder(); + private static CelValueDispatcher newDispatcher() { + CelValueDispatcher.Builder builder = CelValueDispatcher.newBuilder(); // Subsetted StdLib addBindings(builder, Operator.INDEX.getFunction(), fromStandardFunction(IndexOperator.create())); @@ -153,11 +153,11 @@ private static DefaultDispatcher newDispatcher() { } - private static void addBindings(DefaultDispatcher.Builder builder, String functionName, CelValueFunctionBinding... functionBindings) { + private static void addBindings(CelValueDispatcher.Builder builder, String functionName, CelValueFunctionBinding... functionBindings) { addBindings(builder, functionName, ImmutableSet.copyOf(functionBindings)); } - private static void addBindings(DefaultDispatcher.Builder builder, String functionName, ImmutableCollection overloadBindings) { + private static void addBindings(CelValueDispatcher.Builder builder, String functionName, ImmutableCollection overloadBindings) { if (overloadBindings.isEmpty()) { throw new IllegalArgumentException("Invalid bindings"); } From 7f93040cf7cb65ec309a4b754c264c445db1b9c4 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 27 Oct 2025 16:00:47 -0700 Subject: [PATCH 29/30] Remove mutablelist for now --- .../java/dev/cel/common/values/BUILD.bazel | 22 ------ .../cel/common/values/MutableListValue.java | 78 ------------------- .../runtime/planner/ProgramPlannerTest.java | 34 ++++---- 3 files changed, 17 insertions(+), 117 deletions(-) delete mode 100644 common/src/main/java/dev/cel/common/values/MutableListValue.java diff --git a/common/src/main/java/dev/cel/common/values/BUILD.bazel b/common/src/main/java/dev/cel/common/values/BUILD.bazel index fe148999d..efd23242e 100644 --- a/common/src/main/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel @@ -157,28 +157,6 @@ cel_android_library( ], ) -java_library( - name = "mutable_list_value", - srcs = ["MutableListValue.java"], - tags = [ - ], - deps = [ - ":cel_byte_string", - ":cel_value", - ":values", - "//:auto_value", - "//common:error_codes", - "//common:runtime_exception", - "//common/annotations", - "//common/types", - "//common/types:type_providers", - "@maven//:com_google_errorprone_error_prone_annotations", - "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", - "@maven//:org_jspecify_jspecify", - ], -) - java_library( name = "cel_byte_string", srcs = ["CelByteString.java"], diff --git a/common/src/main/java/dev/cel/common/values/MutableListValue.java b/common/src/main/java/dev/cel/common/values/MutableListValue.java deleted file mode 100644 index e4b9a4ed1..000000000 --- a/common/src/main/java/dev/cel/common/values/MutableListValue.java +++ /dev/null @@ -1,78 +0,0 @@ -package dev.cel.common.values; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; - -public final class MutableListValue extends ListValue { - - private List mutableList; - - public static MutableListValue create(int size) { - return new MutableListValue<>(size); - } - - private MutableListValue(int size) { - this.mutableList = new ArrayList<>(size); - } - - @Override - public List value() { - return List.of(); - } - @Override - public int size() { - return 0; - } - - @Override - public boolean isEmpty() { - return false; - } - @Override - public boolean contains(Object o) { - return false; - } - @Override - public Iterator iterator() { - return null; - } - @Override - public Object[] toArray() { - return new Object[0]; - } - @Override - public T[] toArray(T[] a) { - return null; - } - @Override - public boolean containsAll(Collection c) { - return false; - } - @Override - public E get(int index) { - return null; - } - @Override - public int indexOf(Object o) { - return 0; - } - @Override - public int lastIndexOf(Object o) { - return 0; - } - @Override - public ListIterator listIterator() { - return null; - } - @Override - public ListIterator listIterator(int index) { - return null; - } - @Override - public List subList(int fromIndex, int toIndex) { - return List.of(); - } -} 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 6fa1d084e..a9fa00926 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -470,10 +470,10 @@ public void planSelect() throws Exception { } @Test - // @TestParameters("{expression: '[1,2,3].exists(x, x > 0) == true'}") - // @TestParameters("{expression: '[1,2,3].exists(x, x < 0) == false'}") - // @TestParameters("{expression: '[1,2,3].exists(i, v, i >= 0 && v > 0) == true'}") - // @TestParameters("{expression: '[1,2,3].exists(i, v, i < 0 || v < 0) == false'}") + @TestParameters("{expression: '[1,2,3].exists(x, x > 0) == true'}") + @TestParameters("{expression: '[1,2,3].exists(x, x < 0) == false'}") + @TestParameters("{expression: '[1,2,3].exists(i, v, i >= 0 && v > 0) == true'}") + @TestParameters("{expression: '[1,2,3].exists(i, v, i < 0 || v < 0) == false'}") @TestParameters("{expression: '[1,2,3].map(x, x + 1) == [2,3,4]'}") public void planComprehension_lists(String expression) throws Exception { CelAbstractSyntaxTree ast = compile(expression); @@ -484,19 +484,19 @@ public void planComprehension_lists(String expression) throws Exception { assertThat(result).isTrue(); } - // @Test - // @TestParameters("{expression: '[1,2,3].exists(x, x > 0) == true'}") - // @TestParameters("{expression: '[1,2,3].exists(x, x < 0) == false'}") - // @TestParameters("{expression: '[1,2,3].exists(i, v, i >= 0 && v > 0) == true'}") - // @TestParameters("{expression: '[1,2,3].exists(i, v, i < 0 || v < 0) == false'}") - // public void planComprehension_maps(String expression) throws Exception { - // CelAbstractSyntaxTree ast = compile(expression); - // Program program = PLANNER.plan(ast); - // - // boolean result = (boolean) program.eval(); - // - // assertThat(result).isTrue(); - // } + @Test + @TestParameters("{expression: '[1,2,3].exists(x, x > 0) == true'}") + @TestParameters("{expression: '[1,2,3].exists(x, x < 0) == false'}") + @TestParameters("{expression: '[1,2,3].exists(i, v, i >= 0 && v > 0) == true'}") + @TestParameters("{expression: '[1,2,3].exists(i, v, i < 0 || v < 0) == false'}") + public void planComprehension_maps(String expression) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + boolean result = (boolean) program.eval(); + + assertThat(result).isTrue(); + } private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); From 6a4fd446a4d4950f178a04872ab298a1c7c19b39 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 27 Oct 2025 16:05:28 -0700 Subject: [PATCH 30/30] CelValueConverter cleanups --- .../dev/cel/common/values/CelValueConverter.java | 13 ++++--------- .../test/java/dev/cel/runtime/planner/BUILD.bazel | 1 + .../dev/cel/runtime/planner/ProgramPlannerTest.java | 10 +++++++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java index 4deb84583..359f2ef5b 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -19,7 +19,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.primitives.UnsignedLong; import com.google.errorprone.annotations.Immutable; -import com.google.protobuf.ByteString; import dev.cel.common.annotations.Internal; import java.util.Map; import java.util.Map.Entry; @@ -34,7 +33,7 @@ @SuppressWarnings("unchecked") // Unchecked cast of generics due to type-erasure (ex: MapValue). @Internal @Immutable -public class CelValueConverter { +public abstract class CelValueConverter { /** Adapts a {@link CelValue} to a plain old Java Object. */ public Object fromCelValueToJavaObject(CelValue celValue) { @@ -77,10 +76,7 @@ public CelValue fromJavaObjectToCelValue(Object value) { return (CelValue) value; } - if (value instanceof ByteString) { - // TODO: CelConstant should hold this value instead of adapting it here - return BytesValue.create(CelByteString.of(((ByteString) value).toByteArray())); - } else if (value instanceof Iterable) { + if (value instanceof Iterable) { return toListValue((Iterable) value); } else if (value instanceof Map) { return toMapValue((Map) value); @@ -91,9 +87,6 @@ public CelValue fromJavaObjectToCelValue(Object value) { .orElse(OptionalValue.EMPTY); } else if (value instanceof Exception) { return ErrorValue.create((Exception) value); - } else if (value instanceof com.google.protobuf.NullValue) { - // TODO: CelConstant should hold this value instead of adapting it here - return NullValue.NULL_VALUE; } return fromJavaPrimitiveToCelValue(value); @@ -152,4 +145,6 @@ private MapValue toMapValue(Map map) { return ImmutableMapValue.create(mapBuilder.buildOrThrow()); } + + protected CelValueConverter() {} } diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 819067fd0..447d76a60 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -47,6 +47,7 @@ java_library( "//common/values:cel_value_provider", "//common/values:proto_message_lite_value", "//common/values:proto_message_lite_value_provider", + "//common/values:proto_message_value", "//common/values:proto_message_value_provider", "//compiler", "//compiler:compiler_builder", 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 a9fa00926..a14ac6e53 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -40,6 +40,7 @@ import dev.cel.common.values.ListValue; import dev.cel.common.values.MapValue; import dev.cel.common.values.NullValue; +import dev.cel.common.values.ProtoCelValueConverter; import dev.cel.common.values.ProtoMessageValueProvider; import dev.cel.common.values.StringValue; import dev.cel.common.values.TimestampValue; @@ -105,14 +106,17 @@ public final class ProgramPlannerTest { .addLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.comprehensions()) .addMessageTypes(TestAllTypes.getDescriptor()) .build(); + + // Note that the following deps are ordinarily built from top-level builder APIs private static final CelDescriptorPool DESCRIPTOR_POOL = DefaultDescriptorPool.create(CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(TestAllTypes.getDescriptor().getFile())); - private static final CelValueConverter CEL_VALUE_CONVERTER = new CelValueConverter(); + private static final DynamicProto DYNAMIC_PROTO = DynamicProto.create(DefaultMessageFactory.create(DESCRIPTOR_POOL)); + private static final CelValueConverter CEL_VALUE_CONVERTER = ProtoCelValueConverter.newInstance(DESCRIPTOR_POOL, + DYNAMIC_PROTO); private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner( DefaultTypeProvider.create(), ProtoMessageValueProvider.newInstance( - CEL_OPTIONS, - DynamicProto.create(DefaultMessageFactory.create(DESCRIPTOR_POOL)) + CEL_OPTIONS, DYNAMIC_PROTO ), CEL_VALUE_CONVERTER, newDispatcher()