From 026890266ed0897b4c7ed87e42a1cda17b01eaae Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 10 Jun 2025 13:35:00 -0700 Subject: [PATCH] Move Optional function bindings from standard lib to CelOptionalLibrary PiperOrigin-RevId: 769775644 --- .../main/java/dev/cel/extensions/BUILD.bazel | 2 + .../cel/extensions/CelOptionalLibrary.java | 82 +++++++++- .../src/main/java/dev/cel/runtime/BUILD.bazel | 3 +- .../runtime/CelInternalRuntimeLibrary.java | 35 ++++ .../dev/cel/runtime/CelRuntimeLegacyImpl.java | 15 +- .../dev/cel/runtime/CelStandardFunctions.java | 39 +---- .../java/dev/cel/runtime/standard/BUILD.bazel | 30 ---- .../runtime/standard/OptionalFunction.java | 153 ------------------ runtime/standard/BUILD.bazel | 10 -- 9 files changed, 128 insertions(+), 241 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/CelInternalRuntimeLibrary.java delete mode 100644 runtime/src/main/java/dev/cel/runtime/standard/OptionalFunction.java diff --git a/extensions/src/main/java/dev/cel/extensions/BUILD.bazel b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel index 9eb266750..878988486 100644 --- a/extensions/src/main/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel @@ -155,6 +155,7 @@ java_library( deps = [ "//checker:checker_builder", "//common:compiler_common", + "//common:options", "//common/ast", "//common/types", "//compiler:compiler_builder", @@ -163,6 +164,7 @@ java_library( "//parser:parser_builder", "//runtime", "//runtime:function_binding", + "//runtime:runtime_equality", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", "@maven_android//:com_google_protobuf_protobuf_javalite", diff --git a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java index 79fe06ec6..7a99360cc 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java +++ b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java @@ -19,6 +19,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import com.google.common.collect.ImmutableList; +import com.google.common.primitives.Ints; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.ByteString; import com.google.protobuf.Duration; @@ -28,6 +29,7 @@ import dev.cel.checker.CelCheckerBuilder; import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelIssue; +import dev.cel.common.CelOptions; import dev.cel.common.CelOverloadDecl; import dev.cel.common.CelVarDecl; import dev.cel.common.ast.CelExpr; @@ -43,14 +45,16 @@ import dev.cel.parser.CelParserBuilder; import dev.cel.parser.Operator; import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.CelInternalRuntimeLibrary; import dev.cel.runtime.CelRuntimeBuilder; -import dev.cel.runtime.CelRuntimeLibrary; +import dev.cel.runtime.RuntimeEquality; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Optional; /** Internal implementation of CEL optional values. */ -public final class CelOptionalLibrary implements CelCompilerLibrary, CelRuntimeLibrary { +public final class CelOptionalLibrary implements CelCompilerLibrary, CelInternalRuntimeLibrary { public static final CelOptionalLibrary INSTANCE = new CelOptionalLibrary(); /** Enumerations of function names used for supporting optionals. */ @@ -171,8 +175,14 @@ public void setCheckerOptions(CelCheckerBuilder checkerBuilder) { } @Override - @SuppressWarnings("unchecked") public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder) { + throw new UnsupportedOperationException("Unsupported"); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public void setRuntimeOptions( + CelRuntimeBuilder runtimeBuilder, RuntimeEquality runtimeEquality, CelOptions celOptions) { runtimeBuilder.addFunctionBindings( CelFunctionBinding.from("optional_of", Object.class, Optional::of), CelFunctionBinding.from( @@ -189,7 +199,48 @@ public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder) { CelFunctionBinding.from("optional_none", ImmutableList.of(), val -> Optional.empty()), CelFunctionBinding.from("optional_value", Object.class, val -> ((Optional) val).get()), CelFunctionBinding.from( - "optional_hasValue", Object.class, val -> ((Optional) val).isPresent())); + "optional_hasValue", Object.class, val -> ((Optional) val).isPresent()), + CelFunctionBinding.from( + "select_optional_field", // This only handles map selection. Proto selection is + // special cased inside the interpreter. + Map.class, + String.class, + runtimeEquality::findInMap), + CelFunctionBinding.from( + "map_optindex_optional_value", Map.class, Object.class, runtimeEquality::findInMap), + CelFunctionBinding.from( + "optional_map_optindex_optional_value", + Optional.class, + Object.class, + (Optional optionalMap, Object key) -> + indexOptionalMap(optionalMap, key, runtimeEquality)), + CelFunctionBinding.from( + "optional_map_index_value", + Optional.class, + Object.class, + (Optional optionalMap, Object key) -> + indexOptionalMap(optionalMap, key, runtimeEquality)), + CelFunctionBinding.from( + "optional_list_index_int", + Optional.class, + Long.class, + CelOptionalLibrary::indexOptionalList), + CelFunctionBinding.from( + "list_optindex_optional_int", + List.class, + Long.class, + (List list, Long index) -> { + int castIndex = Ints.checkedCast(index); + if (castIndex < 0 || castIndex >= list.size()) { + return Optional.empty(); + } + return Optional.of(list.get(castIndex)); + }), + CelFunctionBinding.from( + "optional_list_optindex_optional_int", + Optional.class, + Long.class, + CelOptionalLibrary::indexOptionalList)); } private static ImmutableList elideOptionalCollection(Collection> list) { @@ -298,5 +349,28 @@ private static Optional expandOptFlatMap( exprFactory.newGlobalCall(Function.OPTIONAL_NONE.getFunction()))); } + private static Object indexOptionalMap( + Optional optionalMap, Object key, RuntimeEquality runtimeEquality) { + if (!optionalMap.isPresent()) { + return Optional.empty(); + } + + Map map = (Map) optionalMap.get(); + + return runtimeEquality.findInMap(map, key); + } + + private static Object indexOptionalList(Optional optionalList, long index) { + if (!optionalList.isPresent()) { + return Optional.empty(); + } + List list = (List) optionalList.get(); + int castIndex = Ints.checkedCast(index); + if (castIndex < 0 || castIndex >= list.size()) { + return Optional.empty(); + } + return Optional.of(list.get(castIndex)); + } + private CelOptionalLibrary() {} } diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 77dfc4273..754b360ae 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -451,6 +451,7 @@ java_library( # keep sorted RUNTIME_SOURCES = [ + "CelInternalRuntimeLibrary.java", "CelRuntime.java", "CelRuntimeBuilder.java", "CelRuntimeFactory.java", @@ -627,7 +628,6 @@ java_library( "//runtime/standard:multiply", "//runtime/standard:negate", "//runtime/standard:not_equals", - "//runtime/standard:optional", "//runtime/standard:size", "//runtime/standard:standard_function", "//runtime/standard:starts_with", @@ -689,7 +689,6 @@ cel_android_library( "//runtime/standard:multiply_android", "//runtime/standard:negate_android", "//runtime/standard:not_equals_android", - "//runtime/standard:optional_android", "//runtime/standard:size_android", "//runtime/standard:standard_function_android", "//runtime/standard:starts_with_android", diff --git a/runtime/src/main/java/dev/cel/runtime/CelInternalRuntimeLibrary.java b/runtime/src/main/java/dev/cel/runtime/CelInternalRuntimeLibrary.java new file mode 100644 index 000000000..b6a6f02b2 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelInternalRuntimeLibrary.java @@ -0,0 +1,35 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.runtime; + +import dev.cel.common.CelOptions; +import dev.cel.common.annotations.Internal; + +/** + * CelInternalRuntimeLibrary defines the interface to extend functionalities beyond the CEL standard + * functions for {@link CelRuntime}, with access to runtime internals. This is not intended for + * general use. + * + *

CEL Library Internals. Do Not Use. + */ +@Internal +public interface CelInternalRuntimeLibrary extends CelRuntimeLibrary { + + /** + * Configures the runtime to support the library implementation, such as adding function bindings. + */ + void setRuntimeOptions( + CelRuntimeBuilder runtimeBuilder, RuntimeEquality runtimeEquality, CelOptions celOptions); +} diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index bba92c07f..c9a48a01c 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -240,10 +240,6 @@ public CelRuntimeLegacyImpl build() { + " bindings."); } - ImmutableSet runtimeLibraries = celRuntimeLibraries.build(); - // Add libraries, such as extensions - runtimeLibraries.forEach(celLibrary -> celLibrary.setRuntimeOptions(this)); - ImmutableSet fileDescriptors = fileTypes.build(); CelDescriptors celDescriptors = CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( @@ -270,6 +266,17 @@ public CelRuntimeLegacyImpl build() { DynamicProto dynamicProto = DynamicProto.create(runtimeTypeFactory); RuntimeEquality runtimeEquality = ProtoMessageRuntimeEquality.create(dynamicProto, options); + ImmutableSet runtimeLibraries = celRuntimeLibraries.build(); + // Add libraries, such as extensions + for (CelRuntimeLibrary celLibrary : runtimeLibraries) { + if (celLibrary instanceof CelInternalRuntimeLibrary) { + ((CelInternalRuntimeLibrary) celLibrary) + .setRuntimeOptions(this, runtimeEquality, options); + } else { + celLibrary.setRuntimeOptions(this); + } + } + ImmutableMap.Builder functionBindingsBuilder = ImmutableMap.builder(); for (CelFunctionBinding standardFunctionBinding : diff --git a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java index 111f32eae..0f2b74772 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java +++ b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java @@ -99,8 +99,6 @@ import dev.cel.runtime.standard.NegateOperator.NegateOverload; import dev.cel.runtime.standard.NotEqualsOperator; import dev.cel.runtime.standard.NotEqualsOperator.NotEqualsOverload; -import dev.cel.runtime.standard.OptionalFunction; -import dev.cel.runtime.standard.OptionalFunction.OptionalOverload; import dev.cel.runtime.standard.SizeFunction; import dev.cel.runtime.standard.SizeFunction.SizeOverload; import dev.cel.runtime.standard.StartsWithFunction; @@ -155,7 +153,6 @@ public final class CelStandardFunctions { MultiplyOperator.create(), NegateOperator.create(), NotEqualsOperator.create(), - OptionalFunction.create(), SizeFunction.create(), StartsWithFunction.create(), StringFunction.create(), @@ -327,15 +324,7 @@ public enum StandardFunction { Comparison.GREATER_EQUALS_INT64_DOUBLE, Comparison.GREATER_EQUALS_DOUBLE_INT64, Comparison.GREATER_EQUALS_UINT64_DOUBLE, - Comparison.GREATER_EQUALS_DOUBLE_UINT64), - OPTIONAL( - Overload.OptionalValue.SELECT_OPTIONAL_FIELD, - Overload.OptionalValue.MAP_OPTINDEX_OPTIONAL_VALUE, - Overload.OptionalValue.OPTIONAL_MAP_OPTINDEX_OPTIONAL_VALUE, - Overload.OptionalValue.OPTIONAL_MAP_INDEX_VALUE, - Overload.OptionalValue.OPTIONAL_LIST_INDEX_INT, - Overload.OptionalValue.LIST_OPTINDEX_OPTIONAL_INT, - Overload.OptionalValue.OPTIONAL_LIST_OPTINDEX_OPTIONAL_INT); + Comparison.GREATER_EQUALS_DOUBLE_UINT64); /** Container class for CEL standard function overloads. */ public static final class Overload { @@ -708,32 +697,6 @@ public boolean isHeterogeneousComparison() { } } - /** Overloads for optional values. */ - public enum OptionalValue implements StandardOverload { - SELECT_OPTIONAL_FIELD(OptionalOverload.SELECT_OPTIONAL_FIELD::newFunctionBinding), - MAP_OPTINDEX_OPTIONAL_VALUE( - OptionalOverload.MAP_OPTINDEX_OPTIONAL_VALUE::newFunctionBinding), - OPTIONAL_MAP_OPTINDEX_OPTIONAL_VALUE( - OptionalOverload.OPTIONAL_MAP_OPTINDEX_OPTIONAL_VALUE::newFunctionBinding), - OPTIONAL_MAP_INDEX_VALUE(OptionalOverload.OPTIONAL_MAP_INDEX_VALUE::newFunctionBinding), - OPTIONAL_LIST_INDEX_INT(OptionalOverload.OPTIONAL_LIST_INDEX_INT::newFunctionBinding), - LIST_OPTINDEX_OPTIONAL_INT(OptionalOverload.LIST_OPTINDEX_OPTIONAL_INT::newFunctionBinding), - OPTIONAL_LIST_OPTINDEX_OPTIONAL_INT( - OptionalOverload.OPTIONAL_LIST_OPTINDEX_OPTIONAL_INT::newFunctionBinding); - - private final FunctionBindingCreator bindingCreator; - - @Override - public CelFunctionBinding newFunctionBinding( - CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); - } - - OptionalValue(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; - } - } - private Overload() {} } 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 75bb28903..845cf5f9a 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -1226,36 +1226,6 @@ cel_android_library( ], ) -java_library( - name = "optional", - srcs = ["OptionalFunction.java"], - tags = [ - ], - deps = [ - ":standard_overload", - "//common:options", - "//runtime:function_binding", - "//runtime:runtime_equality", - "//runtime/standard:standard_function", - "@maven//:com_google_guava_guava", - ], -) - -cel_android_library( - name = "optional_android", - srcs = ["OptionalFunction.java"], - tags = [ - ], - deps = [ - ":standard_function_android", - ":standard_overload_android", - "//common:options", - "//runtime:function_binding_android", - "//runtime:runtime_equality_android", - "@maven_android//:com_google_guava_guava", - ], -) - java_library( name = "size", srcs = ["SizeFunction.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/standard/OptionalFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/OptionalFunction.java deleted file mode 100644 index 64f05801e..000000000 --- a/runtime/src/main/java/dev/cel/runtime/standard/OptionalFunction.java +++ /dev/null @@ -1,153 +0,0 @@ -// 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 com.google.common.primitives.Ints; -import dev.cel.common.CelOptions; -import dev.cel.runtime.CelFunctionBinding; -import dev.cel.runtime.RuntimeEquality; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * Standard function for optional support. Note that users must also add {@code CelOptionalLibrary} - * as a runtime library to take advantage of CEL optionals. - * - *

TODO: Move into CelOptionalLibrary - */ -public final class OptionalFunction extends CelStandardFunction { - private static final OptionalFunction ALL_OVERLOADS = create(OptionalOverload.values()); - - public static OptionalFunction create() { - return ALL_OVERLOADS; - } - - public static OptionalFunction create(OptionalFunction.OptionalOverload... overloads) { - return create(Arrays.asList(overloads)); - } - - public static OptionalFunction create(Iterable overloads) { - return new OptionalFunction(ImmutableSet.copyOf(overloads)); - } - - /** Overloads for the standard function. */ - public enum OptionalOverload implements CelStandardOverload { - SELECT_OPTIONAL_FIELD( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "select_optional_field", // This only handles map selection. Proto selection is - // special cased inside the interpreter. - Map.class, - String.class, - runtimeEquality::findInMap)), - MAP_OPTINDEX_OPTIONAL_VALUE( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "map_optindex_optional_value", - Map.class, - Object.class, - runtimeEquality::findInMap)), - - @SuppressWarnings("rawtypes") - OPTIONAL_MAP_OPTINDEX_OPTIONAL_VALUE( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "optional_map_optindex_optional_value", - Optional.class, - Object.class, - (Optional optionalMap, Object key) -> - indexOptionalMap(optionalMap, key, runtimeEquality))), - - @SuppressWarnings("rawtypes") - OPTIONAL_MAP_INDEX_VALUE( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "optional_map_index_value", - Optional.class, - Object.class, - (Optional optionalMap, Object key) -> - indexOptionalMap(optionalMap, key, runtimeEquality))), - OPTIONAL_LIST_INDEX_INT( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "optional_list_index_int", - Optional.class, - Long.class, - OptionalFunction::indexOptionalList)), - @SuppressWarnings("rawtypes") - LIST_OPTINDEX_OPTIONAL_INT( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "list_optindex_optional_int", - List.class, - Long.class, - (List list, Long index) -> { - int castIndex = Ints.checkedCast(index); - if (castIndex < 0 || castIndex >= list.size()) { - return Optional.empty(); - } - return Optional.of(list.get(castIndex)); - })), - OPTIONAL_LIST_OPTINDEX_OPTIONAL_INT( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "optional_list_optindex_optional_int", - Optional.class, - Long.class, - OptionalFunction::indexOptionalList)); - - private final FunctionBindingCreator bindingCreator; - - @Override - public CelFunctionBinding newFunctionBinding( - CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); - } - - OptionalOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; - } - } - - private static Object indexOptionalMap( - Optional optionalMap, Object key, RuntimeEquality runtimeEquality) { - if (!optionalMap.isPresent()) { - return Optional.empty(); - } - - Map map = (Map) optionalMap.get(); - - return runtimeEquality.findInMap(map, key); - } - - private static Object indexOptionalList(Optional optionalList, long index) { - if (!optionalList.isPresent()) { - return Optional.empty(); - } - List list = (List) optionalList.get(); - int castIndex = Ints.checkedCast(index); - if (castIndex < 0 || castIndex >= list.size()) { - return Optional.empty(); - } - return Optional.of(list.get(castIndex)); - } - - private OptionalFunction(ImmutableSet overloads) { - super(overloads); - } -} diff --git a/runtime/standard/BUILD.bazel b/runtime/standard/BUILD.bazel index 491d5e1ea..5f84c105f 100644 --- a/runtime/standard/BUILD.bazel +++ b/runtime/standard/BUILD.bazel @@ -356,16 +356,6 @@ cel_android_library( exports = ["//runtime/src/main/java/dev/cel/runtime/standard:not_equals_android"], ) -java_library( - name = "optional", - exports = ["//runtime/src/main/java/dev/cel/runtime/standard:optional"], -) - -cel_android_library( - name = "optional_android", - exports = ["//runtime/src/main/java/dev/cel/runtime/standard:optional_android"], -) - java_library( name = "size", exports = ["//runtime/src/main/java/dev/cel/runtime/standard:size"],