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 a35a897b8..f758b8e86 100644 --- a/common/src/main/java/dev/cel/common/types/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/types/BUILD.bazel @@ -193,10 +193,26 @@ java_library( deps = [ ":type_providers", ":types", + "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], ) +cel_android_library( + name = "default_type_provider_android", + srcs = [ + "DefaultTypeProvider.java", + ], + tags = [ + ], + deps = [ + ":type_providers_android", + ":types_android", + "@maven//:com_google_errorprone_error_prone_annotations", + "@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 index 84e6c9ede..372a50e73 100644 --- a/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java +++ b/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java @@ -16,9 +16,11 @@ import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; import java.util.Optional; /** {@code DefaultTypeProvider} is a registry of common CEL types. */ +@Immutable public class DefaultTypeProvider implements CelTypeProvider { private static final DefaultTypeProvider INSTANCE = new DefaultTypeProvider(); @@ -43,6 +45,7 @@ private DefaultTypeProvider() { typeMapBuilder.putAll(SimpleType.TYPE_MAP); typeMapBuilder.put("list", ListType.create(SimpleType.DYN)); typeMapBuilder.put("map", MapType.create(SimpleType.DYN, SimpleType.DYN)); + typeMapBuilder.put("type", TypeType.create(SimpleType.DYN)); typeMapBuilder.put( "optional_type", // TODO: Move to CelOptionalLibrary and register it on demand diff --git a/common/types/BUILD.bazel b/common/types/BUILD.bazel index 41d3d59b2..df249ddbc 100644 --- a/common/types/BUILD.bazel +++ b/common/types/BUILD.bazel @@ -81,3 +81,8 @@ cel_android_library( name = "cel_proto_types_android", exports = ["//common/src/main/java/dev/cel/common/types:cel_proto_types_android"], ) + +cel_android_library( + name = "default_type_provider_android", + exports = ["//common/src/main/java/dev/cel/common/types:default_type_provider_android"], +) diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index a42ee7fb4..074ef2059 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -9,14 +9,31 @@ package( java_library( name = "runtime", exports = [ + ":descriptor_message_provider", ":evaluation_exception", + ":function_overload", ":late_function_binding", ":metadata", + ":runtime_factory", + ":runtime_legacy_impl", + ":variable_resolver", "//runtime/src/main/java/dev/cel/runtime", - "//runtime/src/main/java/dev/cel/runtime:descriptor_message_provider", - "//runtime/src/main/java/dev/cel/runtime:function_overload", "//runtime/src/main/java/dev/cel/runtime:runtime_type_provider", - "//runtime/src/main/java/dev/cel/runtime:variable_resolver", + ], +) + +java_library( + name = "runtime_factory", + exports = [ + "//runtime/src/main/java/dev/cel/runtime:runtime_factory", + ], +) + +java_library( + name = "runtime_legacy_impl", + visibility = ["//:internal"], + exports = [ + "//runtime/src/main/java/dev/cel/runtime:runtime_legacy_impl", ], ) @@ -28,6 +45,14 @@ java_library( ], ) +cel_android_library( + name = "dispatcher_android", + visibility = ["//:internal"], + exports = [ + "//runtime/src/main/java/dev/cel/runtime:dispatcher_android", + ], +) + java_library( name = "standard_functions", exports = [ @@ -43,6 +68,14 @@ java_library( ], ) +cel_android_library( + name = "activation_android", + visibility = ["//:internal"], + exports = [ + "//runtime/src/main/java/dev/cel/runtime:activation_android", + ], +) + java_library( name = "proto_message_activation_factory", visibility = ["//:internal"], @@ -79,6 +112,7 @@ cel_android_library( java_library( name = "evaluation_exception_builder", + # used_by_android exports = ["//runtime/src/main/java/dev/cel/runtime:evaluation_exception_builder"], ) @@ -112,6 +146,12 @@ java_library( exports = ["//runtime/src/main/java/dev/cel/runtime:interpretable"], ) +cel_android_library( + name = "interpretable_android", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:interpretable_android"], +) + java_library( name = "runtime_helpers", visibility = ["//:internal"], @@ -154,6 +194,18 @@ java_library( exports = ["//runtime/src/main/java/dev/cel/runtime:type_resolver"], ) +cel_android_library( + name = "type_resolver_android", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:type_resolver_android"], +) + +java_library( + name = "descriptor_type_resolver", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:descriptor_type_resolver"], +) + java_library( name = "unknown_attributes", exports = ["//runtime/src/main/java/dev/cel/runtime:unknown_attributes"], @@ -253,12 +305,20 @@ cel_android_library( java_library( name = "metadata", + # used_by_android visibility = ["//:internal"], exports = ["//runtime/src/main/java/dev/cel/runtime:metadata"], ) +java_library( + name = "variable_resolver", + # used_by_android + exports = ["//runtime/src/main/java/dev/cel/runtime:variable_resolver"], +) + java_library( name = "concatenated_list_view", + # used_by_android visibility = ["//:internal"], exports = [ "//runtime/src/main/java/dev/cel/runtime:concatenated_list_view", @@ -266,10 +326,14 @@ java_library( ) java_library( - name = "variable_resolver", - exports = [ - "//runtime/src/main/java/dev/cel/runtime:variable_resolver", - ], + name = "function_overload", + exports = ["//runtime/src/main/java/dev/cel/runtime:function_overload"], +) + +java_library( + name = "descriptor_message_provider", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:descriptor_message_provider"], ) java_library( diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index f77c48d96..0746a5b83 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -139,7 +139,8 @@ java_library( cel_android_library( name = "dispatcher_android", srcs = DISPATCHER_SOURCES, - visibility = ["//visibility:private"], + tags = [ + ], deps = [ ":evaluation_exception", ":evaluation_exception_builder", @@ -175,7 +176,8 @@ java_library( cel_android_library( name = "activation_android", srcs = ["Activation.java"], - visibility = ["//visibility:private"], + tags = [ + ], deps = [ ":interpretable_android", ":runtime_helpers_android", @@ -207,8 +209,10 @@ java_library( tags = [ ], deps = [ + "//common/annotations", "//common/types", "//common/types:type_providers", + "//common/values", "//common/values:cel_byte_string", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -219,11 +223,14 @@ java_library( cel_android_library( name = "type_resolver_android", srcs = ["TypeResolver.java"], - visibility = ["//visibility:private"], + tags = [ + ], deps = [ + "//common/annotations", "//common/types:type_providers_android", "//common/types:types_android", "//common/values:cel_byte_string", + "//common/values:values_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven_android//:com_google_guava_guava", @@ -234,14 +241,17 @@ cel_android_library( java_library( name = "descriptor_type_resolver", srcs = ["DescriptorTypeResolver.java"], - visibility = ["//visibility:private"], + tags = [ + ], deps = [ ":type_resolver", + "//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", ], ) @@ -476,8 +486,6 @@ RUNTIME_SOURCES = [ "CelInternalRuntimeLibrary.java", "CelRuntime.java", "CelRuntimeBuilder.java", - "CelRuntimeFactory.java", - "CelRuntimeLegacyImpl.java", "CelRuntimeLibrary.java", "ProgramImpl.java", "UnknownContext.java", @@ -570,6 +578,8 @@ java_library( name = "metadata", srcs = ["Metadata.java"], # used_by_android + tags = [ + ], deps = [ "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", @@ -579,6 +589,8 @@ java_library( java_library( name = "interpretable", srcs = INTERPRABLE_SOURCES, + tags = [ + ], deps = [ ":evaluation_exception", ":evaluation_listener", @@ -592,7 +604,8 @@ java_library( cel_android_library( name = "interpretable_android", srcs = INTERPRABLE_SOURCES, - visibility = ["//visibility:private"], + tags = [ + ], deps = [ ":evaluation_exception", ":evaluation_listener_android", @@ -782,7 +795,7 @@ java_library( ], deps = [ ":evaluation_exception", - "//runtime:unknown_attributes", + ":unknown_attributes", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], @@ -795,7 +808,7 @@ cel_android_library( ], deps = [ ":evaluation_exception", - "//runtime:unknown_attributes_android", + ":unknown_attributes_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", ], @@ -803,11 +816,22 @@ cel_android_library( java_library( name = "runtime_planner_impl", - testonly = 1, srcs = ["CelRuntimeImpl.java"], tags = [ ], deps = [ + ":descriptor_type_resolver", + ":dispatcher", + ":evaluation_exception", + ":evaluation_listener", + ":function_binding", + ":function_resolver", + ":program", + ":proto_message_runtime_helpers", + ":runtime", + ":runtime_equality", + ":standard_functions", + ":variable_resolver", "//:auto_value", "//common:cel_ast", "//common:cel_descriptor_util", @@ -825,17 +849,8 @@ java_library( "//common/values:cel_value_provider", "//common/values:combined_cel_value_provider", "//common/values:proto_message_value_provider", - "//runtime", - "//runtime:dispatcher", - "//runtime:evaluation_listener", - "//runtime:function_binding", - "//runtime:function_resolver", - "//runtime:program", - "//runtime:proto_message_runtime_helpers", - "//runtime:runtime_equality", - "//runtime:standard_functions", - "//runtime:variable_resolver", "//runtime/planner:program_planner", + "//runtime/standard:type", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", @@ -844,30 +859,22 @@ java_library( ) java_library( - name = "runtime", - srcs = RUNTIME_SOURCES, + name = "runtime_legacy_impl", + srcs = ["CelRuntimeLegacyImpl.java"], tags = [ ], deps = [ - ":activation", ":cel_value_runtime_type_provider", ":descriptor_message_provider", ":descriptor_type_resolver", ":dispatcher", - ":evaluation_exception", - ":evaluation_listener", ":function_binding", - ":function_resolver", - ":interpretable", ":interpreter", - ":program", - ":proto_message_activation_factory", ":proto_message_runtime_equality", + ":runtime", ":runtime_equality", ":runtime_type_provider", ":standard_functions", - ":unknown_attributes", - "//:auto_value", "//common:cel_ast", "//common:cel_descriptor_util", "//common:cel_descriptors", @@ -882,7 +889,6 @@ java_library( "//common/types:type_providers", "//common/values:cel_value_provider", "//common/values:proto_message_value_provider", - "//runtime:variable_resolver", "//runtime/standard:add", "//runtime/standard:int", "//runtime/standard:timestamp", @@ -894,6 +900,52 @@ java_library( ], ) +java_library( + name = "runtime_factory", + srcs = ["CelRuntimeFactory.java"], + tags = [ + ], + deps = [ + ":runtime", + ":runtime_legacy_impl", + "//common:options", + ], +) + +java_library( + name = "runtime", + srcs = RUNTIME_SOURCES, + tags = [ + ], + deps = [ + ":activation", + ":evaluation_exception", + ":evaluation_listener", + ":function_binding", + ":function_resolver", + ":interpretable", + ":interpreter", + ":program", + ":proto_message_activation_factory", + ":runtime_equality", + ":standard_functions", + ":unknown_attributes", + ":variable_resolver", + "//:auto_value", + "//common:cel_ast", + "//common:container", + "//common:options", + "//common/annotations", + "//common/types:type_providers", + "//common/values:cel_value_provider", + "@maven//:com_google_code_findbugs_annotations", + "@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 = "lite_runtime", srcs = LITE_RUNTIME_SOURCES, @@ -950,8 +1002,8 @@ java_library( ":function_resolver", ":interpretable", ":program", + ":variable_resolver", "//:auto_value", - "//runtime:variable_resolver", "@maven//:com_google_errorprone_error_prone_annotations", ], ) @@ -1253,19 +1305,6 @@ cel_android_library( ], ) -java_library( - name = "variable_resolver", - srcs = [ - "CelVariableResolver.java", - "HierarchicalVariableResolver.java", - ], - # used_by_android - tags = [ - ], - deps = [ - ], -) - java_library( name = "program", srcs = ["Program.java"], @@ -1318,3 +1357,14 @@ cel_android_library( "@maven_android//:com_google_guava_guava", ], ) + +java_library( + name = "variable_resolver", + srcs = [ + "CelVariableResolver.java", + "HierarchicalVariableResolver.java", + ], + # used_by_android + tags = [ + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java index 3f3245c84..27bd1b3fc 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java @@ -46,6 +46,7 @@ import dev.cel.common.values.CombinedCelValueProvider; import dev.cel.common.values.ProtoMessageValueProvider; import dev.cel.runtime.planner.ProgramPlanner; +import dev.cel.runtime.standard.TypeFunction; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -446,12 +447,20 @@ public CelRuntime build() { CelTypeProvider combinedTypeProvider = new CelTypeProvider.CombinedCelTypeProvider( - messageTypeProvider, DefaultTypeProvider.getInstance()); + DefaultTypeProvider.getInstance(), messageTypeProvider); if (typeProvider() != null) { combinedTypeProvider = new CelTypeProvider.CombinedCelTypeProvider(combinedTypeProvider, typeProvider()); } + DescriptorTypeResolver descriptorTypeResolver = + DescriptorTypeResolver.create(combinedTypeProvider); + TypeFunction typeFunction = TypeFunction.create(descriptorTypeResolver); + for (CelFunctionBinding binding : + typeFunction.newFunctionBindings(options(), runtimeEquality)) { + mutableFunctionBindings.put(binding.getOverloadId(), binding); + } + DefaultDispatcher dispatcher = newDispatcher( standardFunctions(), mutableFunctionBindings.values(), runtimeEquality, options()); diff --git a/runtime/src/main/java/dev/cel/runtime/DescriptorTypeResolver.java b/runtime/src/main/java/dev/cel/runtime/DescriptorTypeResolver.java index 46ad0d231..63fcb87b6 100644 --- a/runtime/src/main/java/dev/cel/runtime/DescriptorTypeResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/DescriptorTypeResolver.java @@ -18,24 +18,46 @@ import com.google.errorprone.annotations.Immutable; import com.google.protobuf.MessageOrBuilder; +import dev.cel.common.annotations.Internal; import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; import dev.cel.common.types.StructTypeReference; import dev.cel.common.types.TypeType; +import java.util.NoSuchElementException; import java.util.Optional; +import org.jspecify.annotations.Nullable; /** * {@code DescriptorTypeResolver} extends {@link TypeResolver} and additionally resolves incoming * protobuf message types using descriptors. + * + *

CEL Library Internals. Do Not Use. */ @Immutable -final class DescriptorTypeResolver extends TypeResolver { +@Internal +public final class DescriptorTypeResolver extends TypeResolver { - static DescriptorTypeResolver create() { + private final @Nullable CelTypeProvider typeProvider; + + /** + * Creates a {@code DescriptorTypeResolver}. All protobuf messages are resolved as a type of + * {@link StructTypeReference}. + */ + public static DescriptorTypeResolver create() { return new DescriptorTypeResolver(); } + /** + * Creates a {@code DescriptorTypeResolver}. If the protobuf message to be resolved can be found + * in the provided {@link CelTypeProvider}, the message is resolved as a concrete {@code + * ProtoMessageType} instead of a {@link StructTypeReference}. + */ + public static DescriptorTypeResolver create(CelTypeProvider typeProvider) { + return new DescriptorTypeResolver(typeProvider); + } + @Override - TypeType resolveObjectType(Object obj, CelType typeCheckedType) { + public TypeType resolveObjectType(Object obj, CelType typeCheckedType) { checkNotNull(obj); Optional wellKnownTypeType = resolveWellKnownObjectType(obj); @@ -45,11 +67,25 @@ TypeType resolveObjectType(Object obj, CelType typeCheckedType) { if (obj instanceof MessageOrBuilder) { MessageOrBuilder msg = (MessageOrBuilder) obj; - return TypeType.create(StructTypeReference.create(msg.getDescriptorForType().getFullName())); + String typeName = msg.getDescriptorForType().getFullName(); + if (typeProvider != null) { + return typeProvider + .findType(typeName) + .map(TypeType::create) + .orElseThrow(() -> new NoSuchElementException("Could not find type: " + typeName)); + } else { + return TypeType.create(StructTypeReference.create(typeName)); + } } return super.resolveObjectType(obj, typeCheckedType); } - private DescriptorTypeResolver() {} + private DescriptorTypeResolver() { + this(null); + } + + private DescriptorTypeResolver(@Nullable CelTypeProvider typeProvider) { + this.typeProvider = typeProvider; + } } diff --git a/runtime/src/main/java/dev/cel/runtime/TypeResolver.java b/runtime/src/main/java/dev/cel/runtime/TypeResolver.java index 905120988..c2ebf521c 100644 --- a/runtime/src/main/java/dev/cel/runtime/TypeResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/TypeResolver.java @@ -26,6 +26,7 @@ import com.google.protobuf.MessageLiteOrBuilder; import com.google.protobuf.NullValue; import com.google.protobuf.Timestamp; +import dev.cel.common.annotations.Internal; import dev.cel.common.types.CelType; import dev.cel.common.types.ListType; import dev.cel.common.types.MapType; @@ -45,9 +46,12 @@ /** * {@code TypeResolver} resolves incoming {@link CelType} into {@link TypeType}., either as part of * a type call (type('foo'), type(1), etc.) or as a type literal (type, int, string, etc.) + * + *

CEL Library Internals. Do Not Use. */ @Immutable -class TypeResolver { +@Internal +public class TypeResolver { static TypeResolver create() { return new TypeResolver(); @@ -65,6 +69,7 @@ static TypeResolver create() { .put(UnsignedLong.class, TypeType.create(SimpleType.UINT)) .put(String.class, TypeType.create(SimpleType.STRING)) .put(NullValue.class, TypeType.create(SimpleType.NULL_TYPE)) + .put(dev.cel.common.values.NullValue.class, TypeType.create(SimpleType.NULL_TYPE)) .put(java.time.Duration.class, TypeType.create(SimpleType.DURATION)) .put(Instant.class, TypeType.create(SimpleType.TIMESTAMP)) .put( @@ -135,7 +140,7 @@ Optional resolveWellKnownObjectType(Object obj) { } /** Resolve the CEL type of the {@code obj}. */ - TypeType resolveObjectType(Object obj, CelType typeCheckedType) { + public TypeType resolveObjectType(Object obj, CelType typeCheckedType) { checkNotNull(obj); Optional wellKnownTypeType = resolveWellKnownObjectType(obj); if (wellKnownTypeType.isPresent()) { @@ -188,5 +193,5 @@ private static CelType adaptStructType(StructType typeOfType) { return newTypeOfType; } - TypeResolver() {} + protected TypeResolver() {} } 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 99273f2fd..c7e4103af 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -69,7 +69,6 @@ java_library( "//common:options", "//common/exceptions:runtime_exception", "//common/values", - "//runtime", "//runtime:activation", "//runtime:evaluation_exception", "//runtime:evaluation_exception_builder", @@ -77,6 +76,7 @@ java_library( "//runtime:interpretable", "//runtime:program", "//runtime:resolved_overload", + "//runtime:variable_resolver", "@maven//:com_google_errorprone_error_prone_annotations", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java index 5ce3208f8..6f3a9d7ff 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java @@ -25,8 +25,6 @@ abstract class PlannedInterpretable { /** Runs interpretation with the given activation which supplies name/value bindings. */ abstract Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException; - // TODO: Implement support for late-bound functions and evaluation listener - long exprId() { return exprId; } 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 b5d43728b..7f8b8a0e0 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -40,6 +40,7 @@ import dev.cel.common.types.CelKind; import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.SimpleType; import dev.cel.common.types.StructType; import dev.cel.common.types.TypeType; import dev.cel.common.values.CelValueConverter; @@ -60,7 +61,6 @@ @Immutable @Internal public final class ProgramPlanner { - private final CelTypeProvider typeProvider; private final CelValueProvider valueProvider; private final DefaultDispatcher dispatcher; @@ -182,7 +182,13 @@ private PlannedInterpretable planCheckedIdent( TypeType identType = typeProvider .findType(identRef.name()) - .map(TypeType::create) + .map( + t -> + (t instanceof TypeType) + // Coalesce all type(foo) "type" into a sentinel runtime type to allow for + // erasure based type comparisons + ? TypeType.create(SimpleType.DYN) + : TypeType.create(t)) .orElseThrow( () -> new NoSuchElementException( 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 5853220ae..0a76b6135 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -1389,6 +1389,40 @@ cel_android_library( ], ) +java_library( + name = "type", + srcs = ["TypeFunction.java"], + tags = [ + ], + deps = [ + ":standard_function", + ":standard_overload", + "//common:options", + "//common/types", + "//runtime:function_binding", + "//runtime:runtime_equality", + "//runtime:type_resolver", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "type_android", + srcs = ["TypeFunction.java"], + tags = [ + ], + deps = [ + ":standard_function_android", + ":standard_overload_android", + "//common:options", + "//common/types:types_android", + "//runtime:function_binding_android", + "//runtime:runtime_equality_android", + "//runtime:type_resolver_android", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "uint", srcs = ["UintFunction.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/standard/TypeFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/TypeFunction.java new file mode 100644 index 000000000..b325c0e9c --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/standard/TypeFunction.java @@ -0,0 +1,71 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.runtime.standard; + +import com.google.common.collect.ImmutableSet; +import dev.cel.common.CelOptions; +import dev.cel.common.types.SimpleType; +import dev.cel.common.types.TypeType; +import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.RuntimeEquality; +import dev.cel.runtime.TypeResolver; + +/** + * Standard function for the {@code type} function. + * + *

The {@code type} function returns the CEL type of its argument. It accepts a + * {@link TypeResolver} so that different runtimes can supply the appropriate resolver (e.g. a + * descriptor-based resolver for full proto, or a base resolver for lite proto). + */ +public final class TypeFunction extends CelStandardFunction { + + private final TypeResolver typeResolver; + + public static TypeFunction create(TypeResolver typeResolver) { + return new TypeFunction(typeResolver); + } + + /** Overloads for the standard {@code type} function. */ + public enum TypeOverload implements CelStandardOverload { + TYPE; + + @Override + public CelFunctionBinding newFunctionBinding( + CelOptions celOptions, RuntimeEquality runtimeEquality) { + // This overload is not used directly. The binding is created in TypeFunction via the + // TypeResolver instance. + // TODO: Instantiate from CelStandardFunctions. + throw new UnsupportedOperationException( + "TypeOverload bindings must be created through TypeFunction.create(TypeResolver)"); + } + } + + @Override + public ImmutableSet newFunctionBindings( + CelOptions celOptions, RuntimeEquality runtimeEquality) { + CelFunctionBinding binding = + CelFunctionBinding.from( + "type", + Object.class, + arg -> typeResolver.resolveObjectType(arg, TypeType.create(SimpleType.DYN))); + + return CelFunctionBinding.fromOverloads("type", ImmutableSet.of(binding)); + } + + private TypeFunction(TypeResolver typeResolver) { + super("type", ImmutableSet.copyOf(TypeOverload.values())); + this.typeResolver = typeResolver; + } +} \ No newline at end of file diff --git a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java index 00e72a884..ea8e52d0c 100644 --- a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java @@ -46,12 +46,6 @@ public void unknownResultSet() { skipBaselineVerification(); } - @Override - public void typeComparisons() { - // TODO: type() standard function needs to be implemented first. - skipBaselineVerification(); - } - @Override public void jsonFieldNames() throws Exception { // TODO: Support JSON field names for planner 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 6749be24f..fb05b0b31 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -40,6 +40,7 @@ java_library( "//extensions", "//parser:macro", "//runtime", + "//runtime:descriptor_type_resolver", "//runtime:dispatcher", "//runtime:function_binding", "//runtime:program", @@ -47,6 +48,7 @@ java_library( "//runtime:runtime_helpers", "//runtime:standard_functions", "//runtime/planner:program_planner", + "//runtime/standard:type", "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_testparameterinjector_test_parameter_injector", 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 9154b5d8b..e8f5c6662 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -71,9 +71,11 @@ import dev.cel.runtime.CelStandardFunctions; import dev.cel.runtime.CelStandardFunctions.StandardFunction; import dev.cel.runtime.DefaultDispatcher; +import dev.cel.runtime.DescriptorTypeResolver; import dev.cel.runtime.Program; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import dev.cel.runtime.standard.TypeFunction; import org.junit.Test; import org.junit.runner.RunWith; @@ -173,6 +175,10 @@ private static DefaultDispatcher newDispatcher() { addBindingsToDispatcher( builder, stdFunctions.newFunctionBindings(RUNTIME_EQUALITY, CEL_OPTIONS)); + TypeFunction typeFunction = TypeFunction.create(DescriptorTypeResolver.create(TYPE_PROVIDER)); + addBindingsToDispatcher( + builder, typeFunction.newFunctionBindings(CEL_OPTIONS, RUNTIME_EQUALITY)); + // Custom functions addBindingsToDispatcher( builder, @@ -400,9 +406,14 @@ public void plan_call_throws() throws Exception { String expectedOverloadId = isParseOnly ? "error" : "error_overload"; CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); - assertThat(e).hasMessageThat().contains("evaluation error at :5: Function '" + expectedOverloadId + "' failed with arg(s) ''"); - assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class); - assertThat(e.getCause().getMessage()).contains("Intentional error"); + assertThat(e) + .hasMessageThat() + .contains( + "evaluation error at :5: Function '" + + expectedOverloadId + + "' failed with arg(s) ''"); + assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + assertThat(e.getCause()).hasMessageThat().contains("Intentional error"); } @Test @@ -603,6 +614,17 @@ public void plan_call_lateBoundFunction() throws Exception { assertThat(result).isEqualTo("test_resolved"); } + @Test + public void plan_call_typeResolution(@TestParameter TypeObjectTestCase testCase) + throws Exception { + CelAbstractSyntaxTree ast = compile(testCase.expression); + Program program = PLANNER.plan(ast); + + TypeType result = (TypeType) program.eval(); + + assertThat(result).isEqualTo(testCase.type); + } + @Test public void plan_select_protoMessageField() throws Exception { CelAbstractSyntaxTree ast = compile("msg.single_string"); @@ -1007,6 +1029,23 @@ private enum TypeLiteralTestCase { } } + private enum TypeObjectTestCase { + BOOL("type(true)", SimpleType.BOOL), + INT("type(1)", SimpleType.INT), + DOUBLE("type(1.5)", SimpleType.DOUBLE), + PROTO_MESSAGE_TYPE( + "type(cel.expr.conformance.proto3.TestAllTypes{})", + TYPE_PROVIDER.findType("cel.expr.conformance.proto3.TestAllTypes").get()); + + private final String expression; + private final TypeType type; + + TypeObjectTestCase(String expression, CelType type) { + this.expression = expression; + this.type = TypeType.create(type); + } + } + @SuppressWarnings("Immutable") // Test only private enum PresenceTestCase { PROTO_FIELD_PRESENT( diff --git a/runtime/standard/BUILD.bazel b/runtime/standard/BUILD.bazel index bc6ab9ed3..4ca87e5e0 100644 --- a/runtime/standard/BUILD.bazel +++ b/runtime/standard/BUILD.bazel @@ -406,6 +406,16 @@ cel_android_library( exports = ["//runtime/src/main/java/dev/cel/runtime/standard:uint_android"], ) +java_library( + name = "type", + exports = ["//runtime/src/main/java/dev/cel/runtime/standard:type"], +) + +cel_android_library( + name = "type_android", + exports = ["//runtime/src/main/java/dev/cel/runtime/standard:type_android"], +) + java_library( name = "not_strictly_false", exports = ["//runtime/src/main/java/dev/cel/runtime/standard:not_strictly_false"],