Skip to content

Commit

Permalink
Decouple CelTypeResolver from full protobuf implementation
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 725790812
  • Loading branch information
l46kok authored and copybara-github committed Feb 14, 2025
1 parent a99f209 commit f16e29e
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 33 deletions.
6 changes: 6 additions & 0 deletions runtime/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ java_library(
exports = ["//runtime/src/main/java/dev/cel/runtime:proto_message_runtime_equality"],
)

java_library(
name = "type_resolver",
visibility = ["//visibility:public"],
exports = ["//runtime/src/main/java/dev/cel/runtime:type_resolver"],
)

java_library(
name = "unknown_attributes",
exports = ["//runtime/src/main/java/dev/cel/runtime:unknown_attributes"],
Expand Down
26 changes: 21 additions & 5 deletions runtime/src/main/java/dev/cel/runtime/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,25 @@ INTERPRETER_SOURCES = [
]

java_library(
name = "cel_type_resolver",
srcs = ["CelTypeResolver.java"],
name = "type_resolver",
srcs = ["TypeResolver.java"],
tags = [
],
deps = [
"//common/types",
"//common/types:type_providers",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_protobuf_protobuf_java",
],
)

java_library(
name = "descriptor_type_resolver",
srcs = ["DescriptorTypeResolver.java"],
visibility = ["//visibility:private"],
deps = [
":type_resolver",
"//common/types",
"//common/types:type_providers",
"@maven//:com_google_errorprone_error_prone_annotations",
Expand All @@ -56,8 +72,7 @@ java_library(
deps = [
":evaluation_exception",
":metadata",
"//:auto_value",
"//common",
"//common:cel_ast",
"//common/annotations",
"@maven//:com_google_code_findbugs_annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
Expand All @@ -75,12 +90,12 @@ java_library(
exports = [":base"],
deps = [
":base",
":cel_type_resolver",
":evaluation_exception",
":evaluation_exception_builder",
":evaluation_listener",
":metadata",
":runtime_helpers",
":type_resolver",
":unknown_attributes",
"//:auto_value",
"//common",
Expand Down Expand Up @@ -243,6 +258,7 @@ java_library(
tags = [
],
deps = [
":descriptor_type_resolver",
":evaluation_exception",
":evaluation_listener",
":interpreter",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,11 @@ public CelRuntimeLegacyImpl build() {
}

return new CelRuntimeLegacyImpl(
new DefaultInterpreter(runtimeTypeProvider, dispatcher.immutableCopy(), options),
new DefaultInterpreter(
DescriptorTypeResolver.create(),
runtimeTypeProvider,
dispatcher.immutableCopy(),
options),
options,
this);
}
Expand Down
20 changes: 14 additions & 6 deletions runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
@Internal
public final class DefaultInterpreter implements Interpreter {

private final TypeResolver typeResolver;
private final RuntimeTypeProvider typeProvider;
private final Dispatcher dispatcher;
private final CelOptions celOptions;
Expand Down Expand Up @@ -98,31 +99,38 @@ static IntermediateResult create(Object value) {
* @param celOptions the configurable flags for adjusting evaluation behavior.
*/
public DefaultInterpreter(
RuntimeTypeProvider typeProvider, Dispatcher dispatcher, CelOptions celOptions) {
TypeResolver typeResolver,
RuntimeTypeProvider typeProvider,
Dispatcher dispatcher,
CelOptions celOptions) {
this.typeResolver = checkNotNull(typeResolver);
this.typeProvider = checkNotNull(typeProvider);
this.dispatcher = checkNotNull(dispatcher);
this.celOptions = celOptions;
this.celOptions = checkNotNull(celOptions);
}

@Override
public Interpretable createInterpretable(CelAbstractSyntaxTree ast) {
return new DefaultInterpretable(typeProvider, dispatcher, ast, celOptions);
return new DefaultInterpretable(typeResolver, typeProvider, dispatcher, ast, celOptions);
}

@Immutable
private static final class DefaultInterpretable
implements Interpretable, UnknownTrackingInterpretable {
private final TypeResolver typeResolver;
private final RuntimeTypeProvider typeProvider;
private final Dispatcher.ImmutableCopy dispatcher;
private final Metadata metadata;
private final CelAbstractSyntaxTree ast;
private final CelOptions celOptions;

DefaultInterpretable(
TypeResolver typeResolver,
RuntimeTypeProvider typeProvider,
Dispatcher dispatcher,
CelAbstractSyntaxTree ast,
CelOptions celOptions) {
this.typeResolver = checkNotNull(typeResolver);
this.typeProvider = checkNotNull(typeProvider);
this.dispatcher = checkNotNull(dispatcher).immutableCopy();
this.ast = checkNotNull(ast);
Expand Down Expand Up @@ -263,7 +271,7 @@ private IntermediateResult resolveIdent(ExecutionFrame frame, CelExpr expr, Stri
// Check whether the type exists in the type check map as a 'type'.
Optional<CelType> checkedType = ast.getType(expr.id());
if (checkedType.isPresent() && checkedType.get().kind() == CelKind.TYPE) {
TypeType typeValue = CelTypeResolver.adaptType(checkedType.get());
TypeType typeValue = typeResolver.adaptType(checkedType.get());
return IntermediateResult.create(typeValue);
}

Expand Down Expand Up @@ -643,9 +651,9 @@ private IntermediateResult evalType(ExecutionFrame frame, CelCall callExpr)
.setMetadata(metadata, typeExprArg.id())
.build());

CelType checkedTypeValue = CelTypeResolver.adaptType(checkedType);
CelType checkedTypeValue = typeResolver.adaptType(checkedType);
return IntermediateResult.create(
CelTypeResolver.resolveObjectType(argResult.value(), checkedTypeValue));
typeResolver.resolveObjectType(argResult.value(), checkedTypeValue));
}

private IntermediateResult evalOptionalOr(ExecutionFrame frame, CelCall callExpr)
Expand Down
55 changes: 55 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/DescriptorTypeResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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 static com.google.common.base.Preconditions.checkNotNull;

import com.google.errorprone.annotations.Immutable;
import com.google.protobuf.MessageOrBuilder;
import dev.cel.common.types.CelType;
import dev.cel.common.types.StructTypeReference;
import dev.cel.common.types.TypeType;
import java.util.Optional;

/**
* {@code DescriptorTypeResolver} extends {@link TypeResolver} and additionally resolves incoming
* protobuf message types using descriptors.
*/
@Immutable
final class DescriptorTypeResolver extends TypeResolver {

static DescriptorTypeResolver create() {
return new DescriptorTypeResolver();
}

@Override
TypeType resolveObjectType(Object obj, CelType typeCheckedType) {
checkNotNull(obj);

Optional<TypeType> wellKnownTypeType = resolveWellKnownObjectType(obj);
if (wellKnownTypeType.isPresent()) {
return wellKnownTypeType.get();
}

if (obj instanceof MessageOrBuilder) {
MessageOrBuilder msg = (MessageOrBuilder) obj;
return TypeType.create(StructTypeReference.create(msg.getDescriptorForType().getFullName()));
}

return super.resolveObjectType(obj, typeCheckedType);
}

private DescriptorTypeResolver() {}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2024 Google LLC
// 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.
Expand All @@ -16,13 +16,14 @@

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.UnsignedLong;
import com.google.errorprone.annotations.Immutable;
import com.google.protobuf.ByteString;
import com.google.protobuf.Duration;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.MessageLiteOrBuilder;
import com.google.protobuf.NullValue;
import com.google.protobuf.Timestamp;
import dev.cel.common.types.CelType;
Expand All @@ -40,15 +41,19 @@
import java.util.Optional;

/**
* {@code CelTypeResolver} 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.)
* {@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.)
*/
@Immutable
final class CelTypeResolver {
class TypeResolver {

static TypeResolver create() {
return new TypeResolver();
}

// Sentinel runtime value representing the special "type" ident. This ensures following to be
// true: type == type(string) && type == type(type("foo"))
private static final TypeType RUNTIME_TYPE_TYPE = TypeType.create(SimpleType.DYN);
@VisibleForTesting static final TypeType RUNTIME_TYPE_TYPE = TypeType.create(SimpleType.DYN);

private static final ImmutableMap<Class<?>, TypeType> COMMON_TYPES =
ImmutableMap.<Class<?>, TypeType>builder()
Expand All @@ -61,9 +66,7 @@ final class CelTypeResolver {
.put(Duration.class, TypeType.create(SimpleType.DURATION))
.put(Timestamp.class, TypeType.create(SimpleType.TIMESTAMP))
.put(ArrayList.class, TypeType.create(ListType.create(SimpleType.DYN)))
.put(ImmutableList.class, TypeType.create(ListType.create(SimpleType.DYN)))
.put(HashMap.class, TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN)))
.put(ImmutableMap.class, TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN)))
.put(Optional.class, TypeType.create(OptionalType.create(SimpleType.DYN)))
.buildOrThrow();

Expand All @@ -75,7 +78,7 @@ final class CelTypeResolver {
.buildOrThrow();

/** Adapt the type-checked {@link CelType} into a runtime type value {@link TypeType}. */
static TypeType adaptType(CelType typeCheckedType) {
TypeType adaptType(CelType typeCheckedType) {
checkNotNull(typeCheckedType);

switch (typeCheckedType.kind()) {
Expand All @@ -94,25 +97,44 @@ static TypeType adaptType(CelType typeCheckedType) {
}
}

/** Resolve the CEL type of the {@code obj}. */
static TypeType resolveObjectType(Object obj, CelType typeCheckedType) {
checkNotNull(obj);
Optional<TypeType> resolveWellKnownObjectType(Object obj) {
if (obj instanceof TypeType) {
return RUNTIME_TYPE_TYPE;
return Optional.of(RUNTIME_TYPE_TYPE);
}

Class<?> currentClass = obj.getClass();
TypeType runtimeType = COMMON_TYPES.get(currentClass);
if (runtimeType != null) {
return runtimeType;
TypeType commonType = COMMON_TYPES.get(currentClass);
if (commonType != null) {
return Optional.of(commonType);
}

// Guava's Immutable classes use package-private implementations, such that they require an
// explicit check.
if (ImmutableList.class.isAssignableFrom(currentClass)) {
return Optional.of(TypeType.create(ListType.create(SimpleType.DYN)));
} else if (ImmutableMap.class.isAssignableFrom(currentClass)) {
return Optional.of(TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN)));
}

return Optional.empty();
}

/** Resolve the CEL type of the {@code obj}. */
TypeType resolveObjectType(Object obj, CelType typeCheckedType) {
checkNotNull(obj);
Optional<TypeType> wellKnownTypeType = resolveWellKnownObjectType(obj);
if (wellKnownTypeType.isPresent()) {
return wellKnownTypeType.get();
}

if (obj instanceof MessageOrBuilder) {
MessageOrBuilder msg = (MessageOrBuilder) obj;
if (obj instanceof MessageLiteOrBuilder) {
// TODO: Replace with CelLiteDescriptor
return TypeType.create(StructTypeReference.create(msg.getDescriptorForType().getFullName()));
throw new UnsupportedOperationException("Not implemented yet");
}

Class<?> currentClass = obj.getClass();
TypeType runtimeType;

// Handle types that the client may have extended.
while (currentClass != null) {
runtimeType = EXTENDABLE_TYPES.get(currentClass);
Expand Down Expand Up @@ -151,5 +173,5 @@ private static CelType adaptStructType(StructType typeOfType) {
return newTypeOfType;
}

private CelTypeResolver() {}
TypeResolver() {}
}
1 change: 1 addition & 0 deletions runtime/src/test/java/dev/cel/runtime/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ java_library(
"//runtime:proto_message_runtime_helpers",
"//runtime:runtime_equality",
"//runtime:runtime_helpers",
"//runtime:type_resolver",
"//runtime:unknown_attributes",
"//runtime:unknown_options",
"@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import org.junit.runner.RunWith;

@RunWith(TestParameterInjector.class)
public class CelTypeResolverTest {
public class DescriptorTypeResolverTest {

private static final ProtoMessageTypeProvider PROTO_MESSAGE_TYPE_PROVIDER =
new ProtoMessageTypeProvider(ImmutableList.of(TestAllTypes.getDescriptor()));
Expand Down
Loading

0 comments on commit f16e29e

Please sign in to comment.