Skip to content

Commit 0affe67

Browse files
l46kokcopybara-github
authored andcommitted
Fix parsed-only evaluation for enum identifiers with containers
PiperOrigin-RevId: 872631218
1 parent bff6518 commit 0affe67

2 files changed

Lines changed: 80 additions & 28 deletions

File tree

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

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import dev.cel.common.values.CelValueConverter;
2626
import dev.cel.runtime.GlobalResolver;
2727
import java.util.NoSuchElementException;
28+
import org.jspecify.annotations.Nullable;
2829

2930
@Immutable
3031
final class NamespacedAttribute implements Attribute {
@@ -34,6 +35,14 @@ final class NamespacedAttribute implements Attribute {
3435
private final CelValueConverter celValueConverter;
3536
private final CelTypeProvider typeProvider;
3637

38+
ImmutableList<Qualifier> qualifiers() {
39+
return qualifiers;
40+
}
41+
42+
ImmutableSet<String> candidateVariableNames() {
43+
return namespacedNames;
44+
}
45+
3746
@Override
3847
public Object resolve(GlobalResolver ctx, ExecutionFrame frame) {
3948
GlobalResolver inputVars = ctx;
@@ -59,41 +68,58 @@ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) {
5968
}
6069
}
6170

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());
71+
// Attempt to resolve the qualify type name if the name is not a variable identifier
72+
value = findIdent(name);
73+
if (value != null) {
74+
return value;
8575
}
8676
}
8777

8878
return MissingAttribute.newMissingAttribute(namespacedNames);
8979
}
9080

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

95-
ImmutableSet<String> candidateVariableNames() {
96-
return namespacedNames;
115+
private static Long getEnumValue(EnumType enumType, String field) {
116+
return enumType
117+
.findNumberByName(field)
118+
.map(Integer::longValue)
119+
.orElseThrow(
120+
() ->
121+
new NoSuchElementException(
122+
String.format("Field %s was not found on enum %s", enumType.name(), field)));
97123
}
98124

99125
private GlobalResolver unwrapToNonLocal(GlobalResolver resolver) {

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

Lines changed: 27 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

0 commit comments

Comments
 (0)