Skip to content

Commit 2b681ba

Browse files
l46kokcopybara-github
authored andcommitted
Evaluate parsed only expressions in PlannerInterpreterTest
PiperOrigin-RevId: 872196586
1 parent bff6518 commit 2b681ba

File tree

8 files changed

+246
-83
lines changed

8 files changed

+246
-83
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ public Object advanceEvaluation(UnknownContext context) throws CelEvaluationExce
182182

183183
static Builder newBuilder() {
184184
return new AutoValue_CelRuntimeImpl.Builder()
185+
.setFunctionBindings(ImmutableMap.of())
185186
.setStandardFunctions(CelStandardFunctions.newBuilder().build())
186187
.setContainer(CelContainer.newBuilder().build())
187188
.setExtensionRegistry(ExtensionRegistry.getEmptyRegistry());
@@ -222,6 +223,8 @@ abstract static class Builder implements CelRuntimeBuilder {
222223

223224
abstract ExtensionRegistry extensionRegistry();
224225

226+
abstract ImmutableMap<String, CelFunctionBinding> functionBindings();
227+
225228
abstract ImmutableSet.Builder<Descriptors.FileDescriptor> fileDescriptorsBuilder();
226229

227230
abstract ImmutableSet.Builder<CelRuntimeLibrary> runtimeLibrariesBuilder();
@@ -442,6 +445,9 @@ public CelRuntime build() {
442445
DescriptorTypeResolver descriptorTypeResolver =
443446
DescriptorTypeResolver.create(combinedTypeProvider);
444447
TypeFunction typeFunction = TypeFunction.create(descriptorTypeResolver);
448+
449+
mutableFunctionBindings.putAll(functionBindings());
450+
445451
for (CelFunctionBinding binding :
446452
typeFunction.newFunctionBindings(options(), runtimeEquality)) {
447453
mutableFunctionBindings.put(binding.getOverloadId(), binding);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ java_library(
128128
"//runtime:interpretable",
129129
"@maven//:com_google_errorprone_error_prone_annotations",
130130
"@maven//:com_google_guava_guava",
131+
"@maven//:org_jspecify_jspecify",
131132
],
132133
)
133134

runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
import dev.cel.common.types.CelType;
2121
import dev.cel.common.types.CelTypeProvider;
2222
import dev.cel.common.types.EnumType;
23+
import dev.cel.common.types.SimpleType;
2324
import dev.cel.common.types.TypeType;
2425
import dev.cel.common.values.CelValue;
2526
import dev.cel.common.values.CelValueConverter;
2627
import dev.cel.runtime.GlobalResolver;
2728
import java.util.NoSuchElementException;
29+
import org.jspecify.annotations.Nullable;
2830

2931
@Immutable
3032
final class NamespacedAttribute implements Attribute {
@@ -34,6 +36,14 @@ final class NamespacedAttribute implements Attribute {
3436
private final CelValueConverter celValueConverter;
3537
private final CelTypeProvider typeProvider;
3638

39+
ImmutableList<Qualifier> qualifiers() {
40+
return qualifiers;
41+
}
42+
43+
ImmutableSet<String> candidateVariableNames() {
44+
return namespacedNames;
45+
}
46+
3747
@Override
3848
public Object resolve(GlobalResolver ctx, ExecutionFrame frame) {
3949
GlobalResolver inputVars = ctx;
@@ -59,41 +69,62 @@ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) {
5969
}
6070
}
6171

62-
CelType type = typeProvider.findType(name).orElse(null);
63-
if (type != null) {
64-
if (qualifiers.isEmpty()) {
65-
// Resolution of a fully qualified type name: foo.bar.baz
66-
return TypeType.create(type);
67-
} else {
68-
// This is potentially a fully qualified reference to an enum value
69-
if (type instanceof EnumType && qualifiers.size() == 1) {
70-
EnumType enumType = (EnumType) type;
71-
String strQualifier = (String) qualifiers.get(0).value();
72-
return enumType
73-
.findNumberByName(strQualifier)
74-
.orElseThrow(
75-
() ->
76-
new NoSuchElementException(
77-
String.format(
78-
"Field %s was not found on enum %s",
79-
enumType.name(), strQualifier)));
80-
}
81-
}
82-
83-
throw new IllegalStateException(
84-
"Unexpected type resolution when there were remaining qualifiers: " + type.name());
72+
// Attempt to resolve the qualify type name if the name is not a variable identifier
73+
value = findIdent(name);
74+
if (value != null) {
75+
return value;
8576
}
8677
}
8778

8879
return MissingAttribute.newMissingAttribute(namespacedNames);
8980
}
9081

91-
ImmutableList<Qualifier> qualifiers() {
92-
return qualifiers;
82+
private @Nullable Object findIdent(String name) {
83+
CelType type = typeProvider.findType(name).orElse(null);
84+
// If the name resolves directly, this is a fully qualified type name
85+
// (ex: 'int' or 'google.protobuf.Timestamp')
86+
if (type != null) {
87+
if (qualifiers.isEmpty()) {
88+
// Resolution of a fully qualified type name: foo.bar.baz
89+
if (type instanceof TypeType) {
90+
// Coalesce all type(foo) "type" into a sentinel runtime type to allow for
91+
// erasure based type comparisons
92+
return TypeType.create(SimpleType.DYN);
93+
}
94+
95+
return TypeType.create(type);
96+
}
97+
98+
throw new IllegalStateException(
99+
"Unexpected type resolution when there were remaining qualifiers: " + type.name());
100+
}
101+
102+
// The name itself could be a fully qualified reference to an enum value
103+
// (e.g: my.enum_type.BAR)
104+
int lastDotIndex = name.lastIndexOf('.');
105+
if (lastDotIndex > 0) {
106+
String enumTypeName = name.substring(0, lastDotIndex);
107+
String enumValueQualifier = name.substring(lastDotIndex + 1);
108+
109+
return typeProvider
110+
.findType(enumTypeName)
111+
.filter(EnumType.class::isInstance)
112+
.map(EnumType.class::cast)
113+
.map(enumType -> getEnumValue(enumType, enumValueQualifier))
114+
.orElse(null);
115+
}
116+
117+
return null;
93118
}
94119

95-
ImmutableSet<String> candidateVariableNames() {
96-
return namespacedNames;
120+
private static Long getEnumValue(EnumType enumType, String field) {
121+
return enumType
122+
.findNumberByName(field)
123+
.map(Integer::longValue)
124+
.orElseThrow(
125+
() ->
126+
new NoSuchElementException(
127+
String.format("Field %s was not found on enum %s", enumType.name(), field)));
97128
}
98129

99130
private GlobalResolver unwrapToNonLocal(GlobalResolver resolver) {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,11 @@ java_library(
131131
"PlannerInterpreterTest.java",
132132
],
133133
deps = [
134+
"//common:cel_ast",
135+
"//common:compiler_common",
136+
"//common:container",
134137
"//common:options",
138+
"//common/types:type_providers",
135139
"//extensions",
136140
"//runtime",
137141
"//runtime:runtime_planner_impl",

runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@
1414

1515
package dev.cel.runtime;
1616

17+
import com.google.testing.junit.testparameterinjector.TestParameter;
1718
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
19+
import dev.cel.common.CelAbstractSyntaxTree;
20+
import dev.cel.common.CelContainer;
1821
import dev.cel.common.CelOptions;
22+
import dev.cel.common.CelValidationException;
23+
import dev.cel.common.types.CelTypeProvider;
1924
import dev.cel.extensions.CelExtensions;
2025
import dev.cel.testing.BaseInterpreterTest;
2126
import org.junit.runner.RunWith;
@@ -24,6 +29,8 @@
2429
@RunWith(TestParameterInjector.class)
2530
public class PlannerInterpreterTest extends BaseInterpreterTest {
2631

32+
@TestParameter boolean isParseOnly;
33+
2734
@Override
2835
protected CelRuntimeBuilder newBaseRuntimeBuilder(CelOptions celOptions) {
2936
return CelRuntimeImpl.newBuilder()
@@ -34,6 +41,36 @@ protected CelRuntimeBuilder newBaseRuntimeBuilder(CelOptions celOptions) {
3441
.addFileTypes(TEST_FILE_DESCRIPTORS);
3542
}
3643

44+
@Override
45+
protected void setContainer(CelContainer container) {
46+
super.setContainer(container);
47+
this.celRuntime = this.celRuntime.toRuntimeBuilder().setContainer(container).build();
48+
}
49+
50+
@Override
51+
protected CelAbstractSyntaxTree prepareTest(CelTypeProvider typeProvider) {
52+
super.prepareCompiler(typeProvider);
53+
54+
CelAbstractSyntaxTree ast;
55+
try {
56+
ast = celCompiler.parse(source, testSourceDescription()).getAst();
57+
} catch (CelValidationException e) {
58+
printTestValidationError(e);
59+
return null;
60+
}
61+
62+
if (isParseOnly) {
63+
return ast;
64+
}
65+
66+
try {
67+
return celCompiler.check(ast).getAst();
68+
} catch (CelValidationException e) {
69+
printTestValidationError(e);
70+
return null;
71+
}
72+
}
73+
3774
@Override
3875
public void unknownField() {
3976
// TODO: Unknown support not implemented yet
@@ -45,4 +82,25 @@ public void unknownResultSet() {
4582
// TODO: Unknown support not implemented yet
4683
skipBaselineVerification();
4784
}
85+
86+
@Override
87+
public void optional() {
88+
if (isParseOnly) {
89+
// TODO: Fix for parsed-only mode.
90+
skipBaselineVerification();
91+
} else {
92+
super.optional();
93+
}
94+
}
95+
96+
@Override
97+
public void optional_errors() {
98+
if (isParseOnly) {
99+
// Parsed-only evaluation contains function name in the
100+
// error message instead of the function overload.
101+
skipBaselineVerification();
102+
} else {
103+
super.optional_errors();
104+
}
105+
}
48106
}

runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,33 @@ public void plan_ident_enum() throws Exception {
277277

278278
Object result = program.eval();
279279

280-
assertThat(result).isEqualTo(1);
280+
assertThat(result).isEqualTo(1L);
281+
}
282+
283+
@Test
284+
public void plan_ident_enumContainer() throws Exception {
285+
CelContainer container = CelContainer.ofName(GlobalEnum.getDescriptor().getFullName());
286+
CelCompiler compiler =
287+
CelCompilerFactory.standardCelCompilerBuilder()
288+
.addMessageTypes(TestAllTypes.getDescriptor())
289+
.setContainer(container)
290+
.build();
291+
CelAbstractSyntaxTree ast = compile(compiler, GlobalEnum.GAR.name());
292+
ProgramPlanner planner =
293+
ProgramPlanner.newPlanner(
294+
TYPE_PROVIDER,
295+
VALUE_PROVIDER,
296+
newDispatcher(),
297+
CEL_VALUE_CONVERTER,
298+
container,
299+
CEL_OPTIONS,
300+
ImmutableSet.of());
301+
302+
Program program = planner.plan(ast);
303+
304+
Object result = program.eval();
305+
306+
assertThat(result).isEqualTo(1L);
281307
}
282308

283309
@Test
@@ -300,6 +326,18 @@ public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) t
300326
assertThat(result).isEqualTo(testCase.type);
301327
}
302328

329+
@Test
330+
public void planIdent_typeLiteral_equality(@TestParameter TypeLiteralTestCase testCase)
331+
throws Exception {
332+
// ex: type(bool) == type, type(TestAllTypes) == type
333+
CelAbstractSyntaxTree ast = compile(String.format("type(%s) == type", testCase.expression));
334+
Program program = PLANNER.plan(ast);
335+
336+
boolean result = (boolean) program.eval();
337+
338+
assertThat(result).isTrue();
339+
}
340+
303341
@Test
304342
public void plan_ident_missingAttribute_throws() throws Exception {
305343
CelAbstractSyntaxTree ast = compile("int_var");

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ java_library(
9393
"//runtime:late_function_binding",
9494
"//runtime:unknown_attributes",
9595
"@cel_spec//proto/cel/expr:checked_java_proto",
96+
"@cel_spec//proto/cel/expr:syntax_java_proto",
9697
"@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
9798
"@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
9899
"@maven//:com_google_errorprone_error_prone_annotations",

0 commit comments

Comments
 (0)