Skip to content

Commit 64dcd15

Browse files
l46kokcopybara-github
authored andcommitted
Add native type representation for runtime type values
PiperOrigin-RevId: 688863503
1 parent da3f8b6 commit 64dcd15

10 files changed

Lines changed: 377 additions & 35 deletions

File tree

common/src/main/java/dev/cel/common/CelOptions.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ public enum ProtoUnsetFieldOptions {
109109

110110
public abstract ProtoUnsetFieldOptions fromProtoUnsetFieldOption();
111111

112+
public abstract boolean adaptRuntimeTypeValueToNativeType();
113+
112114
public abstract boolean enableStringConversion();
113115

114116
public abstract boolean enableStringConcatenation();
@@ -209,6 +211,7 @@ public static Builder newBuilder() {
209211
.comprehensionMaxIterations(-1)
210212
.unwrapWellKnownTypesOnFunctionDispatch(true)
211213
.fromProtoUnsetFieldOption(ProtoUnsetFieldOptions.BIND_DEFAULT)
214+
.adaptRuntimeTypeValueToNativeType(false)
212215
.enableStringConversion(true)
213216
.enableStringConcatenation(true)
214217
.enableListConcatenation(true)
@@ -516,6 +519,12 @@ public abstract static class Builder {
516519
*/
517520
public abstract Builder fromProtoUnsetFieldOption(ProtoUnsetFieldOptions value);
518521

522+
/**
523+
* If enabled, result of the type function call `type(foo)` will be evaluated as a native-type
524+
* equivalent {@code CelRuntimeType} instead of the protobuf type equivalent from {value.proto}.
525+
*/
526+
public abstract Builder adaptRuntimeTypeValueToNativeType(boolean value);
527+
519528
/**
520529
* Enables string() overloads for the runtime. This option exists to maintain parity with
521530
* cel-cpp interpreter options.

extensions/src/test/java/dev/cel/extensions/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ java_library(
2525
"//parser:macro",
2626
"//runtime",
2727
"//runtime:interpreter_util",
28-
"@cel_spec//proto/cel/expr:expr_java_proto",
2928
"@cel_spec//proto/test/v1/proto2:test_all_types_java_proto",
3029
"@cel_spec//proto/test/v1/proto3:test_all_types_java_proto",
3130
"@maven//:com_google_guava_guava",

extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import static com.google.common.truth.Truth.assertThat;
1818
import static org.junit.Assert.assertThrows;
1919

20-
import dev.cel.expr.Value;
2120
import com.google.api.expr.test.v1.proto2.TestAllTypesProto.TestAllTypes;
2221
import com.google.api.expr.test.v1.proto2.TestAllTypesProto.TestAllTypes.NestedMessage;
2322
import com.google.common.collect.ImmutableList;
@@ -45,6 +44,7 @@
4544
import dev.cel.common.types.OptionalType;
4645
import dev.cel.common.types.SimpleType;
4746
import dev.cel.common.types.StructTypeReference;
47+
import dev.cel.common.types.TypeType;
4848
import dev.cel.parser.CelStandardMacro;
4949
import dev.cel.runtime.CelEvaluationException;
5050
import dev.cel.runtime.CelRuntime;
@@ -92,7 +92,11 @@ private enum ConstantTestCases {
9292
private static CelBuilder newCelBuilder() {
9393
return CelFactory.standardCelBuilder()
9494
.setOptions(
95-
CelOptions.current().enableUnsignedLongs(true).enableTimestampEpoch(true).build())
95+
CelOptions.current()
96+
.enableUnsignedLongs(true)
97+
.enableTimestampEpoch(true)
98+
.adaptRuntimeTypeValueToNativeType(true)
99+
.build())
96100
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
97101
.setContainer("google.api.expr.test.v1.proto2")
98102
.addMessageTypes(TestAllTypes.getDescriptor())
@@ -1439,11 +1443,12 @@ public void optionalFlatMapMacro_withNonIdent_throws() {
14391443
@Test
14401444
public void optionalType_typeResolution() throws Exception {
14411445
Cel cel = newCelBuilder().build();
1442-
14431446
CelAbstractSyntaxTree ast = cel.compile("optional_type").getAst();
14441447

1445-
assertThat(cel.createProgram(ast).eval())
1446-
.isEqualTo(Value.newBuilder().setTypeValue("optional_type").build());
1448+
TypeType optionalRuntimeType = (TypeType) cel.createProgram(ast).eval();
1449+
1450+
assertThat(optionalRuntimeType.name()).isEqualTo("type");
1451+
assertThat(optionalRuntimeType.containingTypeName()).isEqualTo("optional_type");
14471452
}
14481453

14491454
@Test

runtime/src/main/java/dev/cel/runtime/BUILD.bazel

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ INTERPRETER_SOURCES = [
4141
"UnknownTrackingInterpretable.java",
4242
]
4343

44+
java_library(
45+
name = "cel_type_resolver",
46+
srcs = ["CelTypeResolver.java"],
47+
deps = [
48+
"//common/types",
49+
"//common/types:type_providers",
50+
"@maven//:com_google_errorprone_error_prone_annotations",
51+
"@maven//:com_google_guava_guava",
52+
"@maven//:com_google_protobuf_protobuf_java",
53+
],
54+
)
55+
4456
java_library(
4557
name = "base",
4658
srcs = BASE_SOURCES,
@@ -80,6 +92,7 @@ java_library(
8092
exports = [":base"],
8193
deps = [
8294
":base",
95+
":cel_type_resolver",
8396
":evaluation_listener",
8497
":runtime_helper",
8598
":unknown_attributes",
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.runtime;
16+
17+
import static com.google.common.base.Preconditions.checkNotNull;
18+
19+
import com.google.common.collect.ImmutableList;
20+
import com.google.common.collect.ImmutableMap;
21+
import com.google.common.primitives.UnsignedLong;
22+
import com.google.errorprone.annotations.Immutable;
23+
import com.google.protobuf.ByteString;
24+
import com.google.protobuf.Duration;
25+
import com.google.protobuf.MessageOrBuilder;
26+
import com.google.protobuf.NullValue;
27+
import com.google.protobuf.Timestamp;
28+
import dev.cel.common.types.CelType;
29+
import dev.cel.common.types.ListType;
30+
import dev.cel.common.types.MapType;
31+
import dev.cel.common.types.OptionalType;
32+
import dev.cel.common.types.SimpleType;
33+
import dev.cel.common.types.StructType;
34+
import dev.cel.common.types.StructTypeReference;
35+
import dev.cel.common.types.TypeType;
36+
import java.util.ArrayList;
37+
import java.util.Collection;
38+
import java.util.HashMap;
39+
import java.util.Map;
40+
import java.util.Optional;
41+
42+
/** {@code CelTypeResolver} resolves {@link CelType} into a runtime type value. */
43+
@Immutable
44+
final class CelTypeResolver {
45+
46+
// Sentinel runtime value representing the special "type" ident. This ensures following to be
47+
// true: type == type(string) && type == type(type("foo"))
48+
private static final TypeType RUNTIME_TYPE_TYPE = TypeType.create(SimpleType.DYN);
49+
50+
private static final ImmutableMap<Class<?>, TypeType> COMMON_TYPES =
51+
ImmutableMap.<Class<?>, TypeType>builder()
52+
.put(Boolean.class, TypeType.create(SimpleType.BOOL))
53+
.put(Double.class, TypeType.create(SimpleType.DOUBLE))
54+
.put(Long.class, TypeType.create(SimpleType.INT))
55+
.put(UnsignedLong.class, TypeType.create(SimpleType.UINT))
56+
.put(String.class, TypeType.create(SimpleType.STRING))
57+
.put(NullValue.class, TypeType.create(SimpleType.NULL_TYPE))
58+
.put(Duration.class, TypeType.create(SimpleType.DURATION))
59+
.put(Timestamp.class, TypeType.create(SimpleType.TIMESTAMP))
60+
.put(ArrayList.class, TypeType.create(ListType.create(SimpleType.DYN)))
61+
.put(ImmutableList.class, TypeType.create(ListType.create(SimpleType.DYN)))
62+
.put(HashMap.class, TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN)))
63+
.put(ImmutableMap.class, TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN)))
64+
.put(Optional.class, TypeType.create(OptionalType.create(SimpleType.DYN)))
65+
.buildOrThrow();
66+
67+
private static final ImmutableMap<Class<?>, TypeType> EXTENDABLE_TYPES =
68+
ImmutableMap.<Class<?>, TypeType>builder()
69+
.put(Collection.class, TypeType.create(ListType.create(SimpleType.DYN)))
70+
.put(ByteString.class, TypeType.create(SimpleType.BYTES))
71+
.put(Map.class, TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN)))
72+
.buildOrThrow();
73+
74+
/** Adapt the type-checked {@link CelType} into a runtime type value {@link TypeType}. */
75+
static TypeType adaptType(CelType type) {
76+
checkNotNull(type);
77+
78+
switch (type.kind()) {
79+
case TYPE:
80+
CelType typeOfType = ((TypeType) type).type();
81+
switch (typeOfType.kind()) {
82+
case STRUCT:
83+
return TypeType.create(adaptStructType((StructType) typeOfType));
84+
case TYPE:
85+
// Unwrap type(type(foo))
86+
return adaptType(typeOfType);
87+
default:
88+
return (TypeType) type;
89+
}
90+
case UNSPECIFIED:
91+
throw new IllegalArgumentException("Unsupported CelType kind: " + type.kind());
92+
default:
93+
return TypeType.create(type);
94+
}
95+
}
96+
97+
/**
98+
* Resolve the CEL type of the {@code obj}, using the {@code checkedTypeValue} as hint for type
99+
* disambiguation.
100+
*/
101+
static TypeType resolveObjectType(Object obj, CelType checkedTypeValue) {
102+
checkNotNull(obj);
103+
checkNotNull(checkedTypeValue);
104+
if (obj instanceof TypeType) {
105+
return RUNTIME_TYPE_TYPE;
106+
}
107+
108+
Class<?> currentClass = obj.getClass();
109+
TypeType runtimeType = COMMON_TYPES.get(currentClass);
110+
if (runtimeType != null) {
111+
return runtimeType;
112+
}
113+
114+
if (obj instanceof MessageOrBuilder) {
115+
MessageOrBuilder msg = (MessageOrBuilder) obj;
116+
// TODO: Replace with CelLiteDescriptor
117+
return TypeType.create(StructTypeReference.create(msg.getDescriptorForType().getFullName()));
118+
}
119+
120+
while (currentClass != null) {
121+
runtimeType = EXTENDABLE_TYPES.get(currentClass);
122+
if (runtimeType != null) {
123+
return runtimeType;
124+
}
125+
126+
// Check interfaces
127+
for (Class<?> interfaceClass : currentClass.getInterfaces()) {
128+
runtimeType = EXTENDABLE_TYPES.get(interfaceClass);
129+
if (runtimeType != null) {
130+
return runtimeType;
131+
}
132+
}
133+
currentClass = currentClass.getSuperclass();
134+
}
135+
136+
return TypeType.create(checkedTypeValue);
137+
}
138+
139+
private static CelType adaptStructType(StructType typeOfType) {
140+
String structName = typeOfType.name();
141+
CelType newTypeOfType;
142+
if (structName.equals(SimpleType.DURATION.name())) {
143+
newTypeOfType = SimpleType.DURATION;
144+
} else if (structName.equals(SimpleType.TIMESTAMP.name())) {
145+
newTypeOfType = SimpleType.TIMESTAMP;
146+
} else {
147+
// Coerces ProtoMessageTypeProvider to be a struct type reference for accurate
148+
// equality tests.
149+
// In the future, we can plumb ProtoMessageTypeProvider through the runtime to retain
150+
// ProtoMessageType here.
151+
newTypeOfType = StructTypeReference.create(typeOfType.name());
152+
}
153+
return newTypeOfType;
154+
}
155+
156+
private CelTypeResolver() {}
157+
}

runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package dev.cel.runtime;
1616

17+
import static com.google.common.base.Preconditions.checkNotNull;
18+
1719
import dev.cel.expr.Value;
1820
import com.google.auto.value.AutoValue;
1921
import com.google.common.base.Joiner;
@@ -53,26 +55,6 @@
5355
/**
5456
* Default implementation of the CEL interpreter.
5557
*
56-
* <p>Use as in:
57-
*
58-
* <pre>
59-
* MessageFactory messageFactory = new LinkedMessageFactory();
60-
* RuntimeTypeProvider typeProvider = new DescriptorMessageProvider(messageFactory);
61-
* Dispatcher dispatcher = DefaultDispatcher.create();
62-
* Interpreter interpreter = new DefaultInterpreter(typeProvider, dispatcher);
63-
* Interpretable interpretable = interpreter.createInterpretable(checkedExpr);
64-
* Object result = interpretable.eval(Activation.of("name", value));
65-
* </pre>
66-
*
67-
* <p>Extensions functions can be added in addition to standard functions to the dispatcher as
68-
* needed.
69-
*
70-
* <p>Note: {MessageFactory} instances may be combined using the {@link
71-
* MessageFactory.CombinedMessageFactory}.
72-
*
73-
* <p>Note: On Android, the {@code DescriptorMessageProvider} is not supported as proto lite does
74-
* not support descriptors. Instead, implement the {@code MessageProvider} interface directly.
75-
*
7658
* <p>CEL Library Internals. Do Not Use.
7759
*/
7860
@ThreadSafe
@@ -117,8 +99,8 @@ static IntermediateResult create(Object value) {
11799
*/
118100
public DefaultInterpreter(
119101
RuntimeTypeProvider typeProvider, Dispatcher dispatcher, CelOptions celOptions) {
120-
this.typeProvider = Preconditions.checkNotNull(typeProvider);
121-
this.dispatcher = Preconditions.checkNotNull(dispatcher);
102+
this.typeProvider = checkNotNull(typeProvider);
103+
this.dispatcher = checkNotNull(dispatcher);
122104
this.celOptions = celOptions;
123105
}
124106

@@ -141,11 +123,11 @@ private static final class DefaultInterpretable
141123
Dispatcher dispatcher,
142124
CelAbstractSyntaxTree ast,
143125
CelOptions celOptions) {
144-
this.typeProvider = Preconditions.checkNotNull(typeProvider);
145-
this.dispatcher = Preconditions.checkNotNull(dispatcher).immutableCopy();
146-
this.ast = Preconditions.checkNotNull(ast);
126+
this.typeProvider = checkNotNull(typeProvider);
127+
this.dispatcher = checkNotNull(dispatcher).immutableCopy();
128+
this.ast = checkNotNull(ast);
147129
this.metadata = new DefaultMetadata(ast);
148-
this.celOptions = Preconditions.checkNotNull(celOptions);
130+
this.celOptions = checkNotNull(celOptions);
149131
}
150132

151133
@Override
@@ -278,7 +260,10 @@ private IntermediateResult resolveIdent(ExecutionFrame frame, CelExpr expr, Stri
278260
// Check whether the type exists in the type check map as a 'type'.
279261
Optional<CelType> checkedType = ast.getType(expr.id());
280262
if (checkedType.isPresent() && checkedType.get().kind() == CelKind.TYPE) {
281-
Object typeValue = typeProvider.adaptType(checkedType.get());
263+
Object typeValue =
264+
celOptions.adaptRuntimeTypeValueToNativeType()
265+
? CelTypeResolver.adaptType(checkedType.get())
266+
: typeProvider.adaptType(checkedType.get());
282267
return IntermediateResult.create(typeValue);
283268
}
284269

@@ -658,8 +643,15 @@ private IntermediateResult evalType(ExecutionFrame frame, CelCall callExpr)
658643
.setLocation(metadata, typeExprArg.id())
659644
.build());
660645

661-
Value checkedTypeValue = typeProvider.adaptType(checkedType);
662-
Object typeValue = typeProvider.resolveObjectType(argResult.value(), checkedTypeValue);
646+
Object typeValue;
647+
if (celOptions.adaptRuntimeTypeValueToNativeType()) {
648+
// typeValue = checkedType;
649+
CelType checkedTypeValue = CelTypeResolver.adaptType(checkedType);
650+
typeValue = CelTypeResolver.resolveObjectType(argResult.value(), checkedTypeValue);
651+
} else {
652+
Value checkedTypeValue = typeProvider.adaptType(checkedType);
653+
typeValue = typeProvider.resolveObjectType(argResult.value(), checkedTypeValue);
654+
}
663655
return IntermediateResult.create(typeValue);
664656
}
665657

runtime/src/main/java/dev/cel/runtime/StandardTypeResolver.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,12 @@
4242
* by the CEL standard environment.
4343
*
4444
* <p>CEL Library Internals. Do Not Use.
45+
*
46+
* @deprecated Use {@code CelTypeResolver} instead.
4547
*/
4648
@Immutable
4749
@Internal
50+
@Deprecated
4851
public final class StandardTypeResolver implements TypeResolver {
4952

5053
/**

runtime/src/test/java/dev/cel/runtime/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ java_library(
3131
"//common/internal:well_known_proto",
3232
"//common/types",
3333
"//common/types:cel_v1alpha1_types",
34+
"//common/types:message_type_provider",
3435
"//compiler",
3536
"//compiler:compiler_builder",
37+
"//extensions:optional_library",
3638
"//parser:macro",
3739
"//parser:unparser",
3840
"//policy:config",

0 commit comments

Comments
 (0)