Skip to content

Commit 00943d9

Browse files
l46kokcopybara-github
authored andcommitted
Expose CelType as a native representation for runtime type value.
PiperOrigin-RevId: 688863503
1 parent dc78309 commit 00943d9

11 files changed

Lines changed: 395 additions & 35 deletions

File tree

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

Lines changed: 11 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,14 @@ 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 CelType} instead of the protobuf type equivalent from {value.proto}.
525+
*
526+
* <p>This is a temporary flag for migration purposes, and will be removed in the near future.
527+
*/
528+
public abstract Builder adaptRuntimeTypeValueToNativeType(boolean value);
529+
519530
/**
520531
* Enables string() overloads for the runtime. This option exists to maintain parity with
521532
* cel-cpp interpreter options.

conformance/src/test/java/dev/cel/conformance/ConformanceTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import dev.cel.common.types.ListType;
4545
import dev.cel.common.types.MapType;
4646
import dev.cel.common.types.SimpleType;
47+
import dev.cel.common.types.TypeType;
4748
import dev.cel.compiler.CelCompilerFactory;
4849
import dev.cel.extensions.CelExtensions;
4950
import dev.cel.extensions.CelOptionalLibrary;
@@ -88,6 +89,7 @@ private static TypeRegistry newDefaultTypeRegistry() {
8889
CelOptions.current()
8990
.enableTimestampEpoch(true)
9091
.enableUnsignedLongs(true)
92+
.adaptRuntimeTypeValueToNativeType(true)
9193
.enableHeterogeneousNumericComparisons(true)
9294
.enableProtoDifferencerEquality(true)
9395
.enableOptionalSyntax(true)
@@ -332,6 +334,10 @@ private static Value toValue(Object object, CelType type) throws Exception {
332334
if (object instanceof Message) {
333335
return Value.newBuilder().setObjectValue(Any.pack((Message) object)).build();
334336
}
337+
if (object instanceof TypeType) {
338+
return Value.newBuilder().setTypeValue(((TypeType) object).containingTypeName()).build();
339+
}
340+
335341
throw new IllegalArgumentException(
336342
String.format("Unexpected result type: %s", object.getClass()));
337343
}

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/cel/expr/conformance/proto3:test_all_types_java_proto",
3029
"@cel_spec//proto/test/v1/proto2: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.common.collect.ImmutableList;
2221
import com.google.common.collect.ImmutableMap;
2322
import com.google.common.primitives.UnsignedLong;
@@ -43,6 +42,7 @@
4342
import dev.cel.common.types.OptionalType;
4443
import dev.cel.common.types.SimpleType;
4544
import dev.cel.common.types.StructTypeReference;
45+
import dev.cel.common.types.TypeType;
4646
import dev.cel.expr.conformance.proto3.TestAllTypes;
4747
import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage;
4848
import dev.cel.parser.CelStandardMacro;
@@ -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("cel.expr.conformance.proto3")
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: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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+
/**
43+
* {@code CelTypeResolver} resolves incoming {@link CelType} into {@link TypeType}., either as part
44+
* of a type call (type('foo'), type(1), etc.) or as a type literal (type, int, string, etc.)
45+
*/
46+
@Immutable
47+
final class CelTypeResolver {
48+
49+
// Sentinel runtime value representing the special "type" ident. This ensures following to be
50+
// true: type == type(string) && type == type(type("foo"))
51+
private static final TypeType RUNTIME_TYPE_TYPE = TypeType.create(SimpleType.DYN);
52+
53+
private static final ImmutableMap<Class<?>, TypeType> COMMON_TYPES =
54+
ImmutableMap.<Class<?>, TypeType>builder()
55+
.put(Boolean.class, TypeType.create(SimpleType.BOOL))
56+
.put(Double.class, TypeType.create(SimpleType.DOUBLE))
57+
.put(Long.class, TypeType.create(SimpleType.INT))
58+
.put(UnsignedLong.class, TypeType.create(SimpleType.UINT))
59+
.put(String.class, TypeType.create(SimpleType.STRING))
60+
.put(NullValue.class, TypeType.create(SimpleType.NULL_TYPE))
61+
.put(Duration.class, TypeType.create(SimpleType.DURATION))
62+
.put(Timestamp.class, TypeType.create(SimpleType.TIMESTAMP))
63+
.put(ArrayList.class, TypeType.create(ListType.create(SimpleType.DYN)))
64+
.put(ImmutableList.class, TypeType.create(ListType.create(SimpleType.DYN)))
65+
.put(HashMap.class, TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN)))
66+
.put(ImmutableMap.class, TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN)))
67+
.put(Optional.class, TypeType.create(OptionalType.create(SimpleType.DYN)))
68+
.buildOrThrow();
69+
70+
private static final ImmutableMap<Class<?>, TypeType> EXTENDABLE_TYPES =
71+
ImmutableMap.<Class<?>, TypeType>builder()
72+
.put(Collection.class, TypeType.create(ListType.create(SimpleType.DYN)))
73+
.put(ByteString.class, TypeType.create(SimpleType.BYTES))
74+
.put(Map.class, TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN)))
75+
.buildOrThrow();
76+
77+
/** Adapt the type-checked {@link CelType} into a runtime type value {@link TypeType}. */
78+
static TypeType adaptType(CelType typeCheckedType) {
79+
checkNotNull(typeCheckedType);
80+
81+
switch (typeCheckedType.kind()) {
82+
case TYPE:
83+
CelType typeOfType = ((TypeType) typeCheckedType).type();
84+
switch (typeOfType.kind()) {
85+
case STRUCT:
86+
return TypeType.create(adaptStructType((StructType) typeOfType));
87+
default:
88+
return (TypeType) typeCheckedType;
89+
}
90+
case UNSPECIFIED:
91+
throw new IllegalArgumentException("Unsupported CelType kind: " + typeCheckedType.kind());
92+
default:
93+
return TypeType.create(typeCheckedType);
94+
}
95+
}
96+
97+
/** Resolve the CEL type of the {@code obj}. */
98+
static TypeType resolveObjectType(Object obj, CelType typeCheckedType) {
99+
checkNotNull(obj);
100+
if (obj instanceof TypeType) {
101+
return RUNTIME_TYPE_TYPE;
102+
}
103+
104+
Class<?> currentClass = obj.getClass();
105+
TypeType runtimeType = COMMON_TYPES.get(currentClass);
106+
if (runtimeType != null) {
107+
return runtimeType;
108+
}
109+
110+
if (obj instanceof MessageOrBuilder) {
111+
MessageOrBuilder msg = (MessageOrBuilder) obj;
112+
// TODO: Replace with CelLiteDescriptor
113+
return TypeType.create(StructTypeReference.create(msg.getDescriptorForType().getFullName()));
114+
}
115+
116+
// Handle types that the client may have extended.
117+
while (currentClass != null) {
118+
runtimeType = EXTENDABLE_TYPES.get(currentClass);
119+
if (runtimeType != null) {
120+
return runtimeType;
121+
}
122+
123+
// Check interfaces
124+
for (Class<?> interfaceClass : currentClass.getInterfaces()) {
125+
runtimeType = EXTENDABLE_TYPES.get(interfaceClass);
126+
if (runtimeType != null) {
127+
return runtimeType;
128+
}
129+
}
130+
currentClass = currentClass.getSuperclass();
131+
}
132+
133+
// This is an opaque type, or something CEL doesn't know about.
134+
return (TypeType) typeCheckedType;
135+
}
136+
137+
private static CelType adaptStructType(StructType typeOfType) {
138+
String structName = typeOfType.name();
139+
CelType newTypeOfType;
140+
if (structName.equals(SimpleType.DURATION.name())) {
141+
newTypeOfType = SimpleType.DURATION;
142+
} else if (structName.equals(SimpleType.TIMESTAMP.name())) {
143+
newTypeOfType = SimpleType.TIMESTAMP;
144+
} else {
145+
// Coerces ProtoMessageTypeProvider to be a struct type reference for accurate
146+
// equality tests.
147+
// In the future, we can plumb ProtoMessageTypeProvider through the runtime to retain
148+
// ProtoMessageType here.
149+
newTypeOfType = StructTypeReference.create(typeOfType.name());
150+
}
151+
return newTypeOfType;
152+
}
153+
154+
private CelTypeResolver() {}
155+
}

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

Lines changed: 20 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,14 @@ 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+
CelType checkedTypeValue = CelTypeResolver.adaptType(checkedType);
649+
typeValue = CelTypeResolver.resolveObjectType(argResult.value(), checkedTypeValue);
650+
} else {
651+
Value checkedTypeValue = typeProvider.adaptType(checkedType);
652+
typeValue = typeProvider.resolveObjectType(argResult.value(), checkedTypeValue);
653+
}
663654
return IntermediateResult.create(typeValue);
664655
}
665656

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
/**

0 commit comments

Comments
 (0)