From 3ae7fdbb455ca5de04d3fd9ad4dc210ca5d6da06 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 19 Feb 2026 17:04:58 -0800 Subject: [PATCH] Fix parsed-only evaluation for enum identifiers with containers PiperOrigin-RevId: 872631218 --- .../java/dev/cel/runtime/planner/BUILD.bazel | 1 + .../runtime/planner/NamespacedAttribute.java | 78 ++++++++++++------- .../runtime/planner/ProgramPlannerTest.java | 28 ++++++- 3 files changed, 79 insertions(+), 28 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index 9ede866ab..0a7ebbfb3 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -128,6 +128,7 @@ java_library( "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", + "@maven//:org_jspecify_jspecify", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java index 12b56049a..5d9bf75fa 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java @@ -25,6 +25,7 @@ import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.GlobalResolver; import java.util.NoSuchElementException; +import org.jspecify.annotations.Nullable; @Immutable final class NamespacedAttribute implements Attribute { @@ -34,6 +35,14 @@ final class NamespacedAttribute implements Attribute { private final CelValueConverter celValueConverter; private final CelTypeProvider typeProvider; + ImmutableList qualifiers() { + return qualifiers; + } + + ImmutableSet candidateVariableNames() { + return namespacedNames; + } + @Override public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { GlobalResolver inputVars = ctx; @@ -59,41 +68,56 @@ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { } } - CelType type = typeProvider.findType(name).orElse(null); - if (type != null) { - if (qualifiers.isEmpty()) { - // Resolution of a fully qualified type name: foo.bar.baz - return TypeType.create(type); - } else { - // This is potentially a fully qualified reference to an enum value - if (type instanceof EnumType && qualifiers.size() == 1) { - EnumType enumType = (EnumType) type; - String strQualifier = (String) qualifiers.get(0).value(); - return enumType - .findNumberByName(strQualifier) - .orElseThrow( - () -> - new NoSuchElementException( - String.format( - "Field %s was not found on enum %s", - enumType.name(), strQualifier))); - } - } - - throw new IllegalStateException( - "Unexpected type resolution when there were remaining qualifiers: " + type.name()); + // Attempt to resolve the qualify type name if the name is not a variable identifier + value = findIdent(name); + if (value != null) { + return value; } } return MissingAttribute.newMissingAttribute(namespacedNames); } - ImmutableList qualifiers() { - return qualifiers; + private @Nullable Object findIdent(String name) { + CelType type = typeProvider.findType(name).orElse(null); + // If the name resolves directly, this is a fully qualified type name + // (ex: 'int' or 'google.protobuf.Timestamp') + if (type != null) { + if (qualifiers.isEmpty()) { + // Resolution of a fully qualified type name: foo.bar.baz + return TypeType.create(type); + } + + throw new IllegalStateException( + "Unexpected type resolution when there were remaining qualifiers: " + type.name()); + } + + // The name itself could be a fully qualified reference to an enum value + // (e.g: my.enum_type.BAR) + int lastDotIndex = name.lastIndexOf('.'); + if (lastDotIndex > 0) { + String enumTypeName = name.substring(0, lastDotIndex); + String enumValueQualifier = name.substring(lastDotIndex + 1); + + return typeProvider + .findType(enumTypeName) + .filter(EnumType.class::isInstance) + .map(EnumType.class::cast) + .map(enumType -> getEnumValue(enumType, enumValueQualifier)) + .orElse(null); + } + + return null; } - ImmutableSet candidateVariableNames() { - return namespacedNames; + private static Long getEnumValue(EnumType enumType, String field) { + return enumType + .findNumberByName(field) + .map(Integer::longValue) + .orElseThrow( + () -> + new NoSuchElementException( + String.format("Field %s was not found on enum %s", enumType.name(), field))); } private GlobalResolver unwrapToNonLocal(GlobalResolver resolver) { diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index d1141777f..27c56a5cd 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -277,7 +277,33 @@ public void plan_ident_enum() throws Exception { Object result = program.eval(); - assertThat(result).isEqualTo(1); + assertThat(result).isEqualTo(1L); + } + + @Test + public void plan_ident_enumContainer() throws Exception { + CelContainer container = CelContainer.ofName(GlobalEnum.getDescriptor().getFullName()); + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addMessageTypes(TestAllTypes.getDescriptor()) + .setContainer(container) + .build(); + CelAbstractSyntaxTree ast = compile(compiler, GlobalEnum.GAR.name()); + ProgramPlanner planner = + ProgramPlanner.newPlanner( + TYPE_PROVIDER, + VALUE_PROVIDER, + newDispatcher(), + CEL_VALUE_CONVERTER, + container, + CEL_OPTIONS, + ImmutableSet.of()); + + Program program = planner.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo(1L); } @Test