Skip to content

Commit 53d00cf

Browse files
l46kokcopybara-github
authored andcommitted
Fix parsed-only evaluation for type equality
PiperOrigin-RevId: 872633409
1 parent bff6518 commit 53d00cf

File tree

2 files changed

+97
-28
lines changed

2 files changed

+97
-28
lines changed

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/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");

0 commit comments

Comments
 (0)