diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/SelectOptimizer.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/SelectOptimizer.java
index 3c6097180..56d662237 100644
--- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/SelectOptimizer.java
+++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/SelectOptimizer.java
@@ -58,32 +58,36 @@
import dev.cel.common.types.CelKind;
import dev.cel.common.types.CelTypes;
import dev.cel.common.types.ListType;
+import dev.cel.common.types.MapType;
import dev.cel.common.types.SimpleType;
+import dev.cel.common.types.StructTypeReference;
import dev.cel.common.values.CelByteString;
import dev.cel.optimizer.AstMutator;
import dev.cel.optimizer.CelAstOptimizer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
/**
* Performs field selection optimization on protobuf message select chains.
*
- *
Embeds protobuf field metadata directly into qualification paths ({@code cel.@attribute}) and
- * field presence paths ({@code cel.@hasField}). This accelerates nested field evaluation, enables
- * reflection-free field traversal in resource-constrained runtimes without descriptor tables, and
- * provides resilience against protobuf field renames.
+ *
Embeds protobuf field metadata directly into qualification paths ({@code
+ * cel.@attribute}) and field presence paths ({@code cel.@hasField}). This accelerates nested
+ * field evaluation, enables reflection-free field traversal in resource-constrained runtimes
+ * without descriptor tables, and provides resilience against protobuf field renames.
*
* WARNING: Evaluating optimized ASTs requires explicit runtime support for {@code
- * cel.@attribute} and {@code cel.@hasField}. Ensure that the target evaluation environment (in
- * Java, C++, Go, or other language runtimes) supports these select optimization functions before
- * applying this optimizer. Evaluating an optimized AST in an unsupported runtime will result in an
- * evaluation error due to missing function overloads.
+ * cel.@attribute} and {@code cel.@hasField}. Ensure that the target evaluation environment
+ * (in Java, C++, Go, or other language runtimes) supports these select optimization functions
+ * before applying this optimizer. Evaluating an optimized AST in an unsupported runtime will result
+ * in an evaluation error due to missing function overloads.
*
- * Metadata Tuples: In {@code cel.@attribute}, each step in the qualification path is
- * represented as a metadata tuple:
+ *
Metadata Tuples: In {@code cel.@attribute}, each step in the qualification path
+ * is represented as a metadata tuple:
*
*
* - Scalar fields, repeated fields, maps, and well-known types (timestamp, duration) include
@@ -104,7 +108,7 @@
*
*
* // Selection chains (user message is 3-tuple, leaf scalar is 4-tuple)
- * request.user.age -> cel.@attribute(request,
+ * request.user.age -> cel.@attributeInt(request,
* [[user_num, "user", type_code], [age_num, "age", type_code, default_val]])
*
* // Presence tests (2-tuples)
@@ -124,16 +128,107 @@ public final class SelectOptimizer implements CelAstOptimizer {
*/
private static final long CEL_MAP_TYPE_CODE = 20L;
- private static final String CEL_ATTRIBUTE_FUNCTION_NAME = "cel.@attribute";
+ private static final String CEL_ATTRIBUTE_INT_FUNCTION_NAME = "cel.@attributeInt";
+ private static final String CEL_ATTRIBUTE_UINT_FUNCTION_NAME = "cel.@attributeUint";
+ private static final String CEL_ATTRIBUTE_DOUBLE_FUNCTION_NAME = "cel.@attributeDouble";
+ private static final String CEL_ATTRIBUTE_BOOL_FUNCTION_NAME = "cel.@attributeBool";
+ private static final String CEL_ATTRIBUTE_STRING_FUNCTION_NAME = "cel.@attributeString";
+ private static final String CEL_ATTRIBUTE_BYTES_FUNCTION_NAME = "cel.@attributeBytes";
+ private static final String CEL_ATTRIBUTE_DURATION_FUNCTION_NAME = "cel.@attributeDuration";
+ private static final String CEL_ATTRIBUTE_TIMESTAMP_FUNCTION_NAME = "cel.@attributeTimestamp";
+ private static final String CEL_ATTRIBUTE_LIST_FUNCTION_NAME = "cel.@attributeList";
+ private static final String CEL_ATTRIBUTE_MAP_FUNCTION_NAME = "cel.@attributeMap";
+ private static final String CEL_ATTRIBUTE_MESSAGE_FUNCTION_PREFIX = "cel.@attributeMessage:";
private static final String CEL_HAS_FIELD_FUNCTION_NAME = "cel.@hasField";
@VisibleForTesting
- static final CelFunctionDecl CEL_ATTRIBUTE_FUNCTION_DECL =
+ static final CelFunctionDecl CEL_ATTRIBUTE_INT_FUNCTION_DECL =
CelFunctionDecl.newFunctionDeclaration(
- CEL_ATTRIBUTE_FUNCTION_NAME,
+ CEL_ATTRIBUTE_INT_FUNCTION_NAME,
CelOverloadDecl.newGlobalOverload(
- "cel_attribute_list",
+ "cel_attribute_int_list",
+ SimpleType.INT,
SimpleType.DYN,
+ ListType.create(SimpleType.DYN)));
+
+ private static final CelFunctionDecl CEL_ATTRIBUTE_UINT_FUNCTION_DECL =
+ CelFunctionDecl.newFunctionDeclaration(
+ CEL_ATTRIBUTE_UINT_FUNCTION_NAME,
+ CelOverloadDecl.newGlobalOverload(
+ "cel_attribute_uint_list",
+ SimpleType.UINT,
+ SimpleType.DYN,
+ ListType.create(SimpleType.DYN)));
+
+ private static final CelFunctionDecl CEL_ATTRIBUTE_DOUBLE_FUNCTION_DECL =
+ CelFunctionDecl.newFunctionDeclaration(
+ CEL_ATTRIBUTE_DOUBLE_FUNCTION_NAME,
+ CelOverloadDecl.newGlobalOverload(
+ "cel_attribute_double_list",
+ SimpleType.DOUBLE,
+ SimpleType.DYN,
+ ListType.create(SimpleType.DYN)));
+
+ private static final CelFunctionDecl CEL_ATTRIBUTE_BOOL_FUNCTION_DECL =
+ CelFunctionDecl.newFunctionDeclaration(
+ CEL_ATTRIBUTE_BOOL_FUNCTION_NAME,
+ CelOverloadDecl.newGlobalOverload(
+ "cel_attribute_bool_list",
+ SimpleType.BOOL,
+ SimpleType.DYN,
+ ListType.create(SimpleType.DYN)));
+
+ private static final CelFunctionDecl CEL_ATTRIBUTE_STRING_FUNCTION_DECL =
+ CelFunctionDecl.newFunctionDeclaration(
+ CEL_ATTRIBUTE_STRING_FUNCTION_NAME,
+ CelOverloadDecl.newGlobalOverload(
+ "cel_attribute_string_list",
+ SimpleType.STRING,
+ SimpleType.DYN,
+ ListType.create(SimpleType.DYN)));
+
+ private static final CelFunctionDecl CEL_ATTRIBUTE_BYTES_FUNCTION_DECL =
+ CelFunctionDecl.newFunctionDeclaration(
+ CEL_ATTRIBUTE_BYTES_FUNCTION_NAME,
+ CelOverloadDecl.newGlobalOverload(
+ "cel_attribute_bytes_list",
+ SimpleType.BYTES,
+ SimpleType.DYN,
+ ListType.create(SimpleType.DYN)));
+
+ private static final CelFunctionDecl CEL_ATTRIBUTE_DURATION_FUNCTION_DECL =
+ CelFunctionDecl.newFunctionDeclaration(
+ CEL_ATTRIBUTE_DURATION_FUNCTION_NAME,
+ CelOverloadDecl.newGlobalOverload(
+ "cel_attribute_duration_list",
+ SimpleType.DURATION,
+ SimpleType.DYN,
+ ListType.create(SimpleType.DYN)));
+
+ private static final CelFunctionDecl CEL_ATTRIBUTE_TIMESTAMP_FUNCTION_DECL =
+ CelFunctionDecl.newFunctionDeclaration(
+ CEL_ATTRIBUTE_TIMESTAMP_FUNCTION_NAME,
+ CelOverloadDecl.newGlobalOverload(
+ "cel_attribute_timestamp_list",
+ SimpleType.TIMESTAMP,
+ SimpleType.DYN,
+ ListType.create(SimpleType.DYN)));
+
+ private static final CelFunctionDecl CEL_ATTRIBUTE_LIST_FUNCTION_DECL =
+ CelFunctionDecl.newFunctionDeclaration(
+ CEL_ATTRIBUTE_LIST_FUNCTION_NAME,
+ CelOverloadDecl.newGlobalOverload(
+ "cel_attribute_list_list",
+ ListType.create(SimpleType.DYN),
+ SimpleType.DYN,
+ ListType.create(SimpleType.DYN)));
+
+ private static final CelFunctionDecl CEL_ATTRIBUTE_MAP_FUNCTION_DECL =
+ CelFunctionDecl.newFunctionDeclaration(
+ CEL_ATTRIBUTE_MAP_FUNCTION_NAME,
+ CelOverloadDecl.newGlobalOverload(
+ "cel_attribute_map_list",
+ MapType.create(SimpleType.DYN, SimpleType.DYN),
SimpleType.DYN,
ListType.create(SimpleType.DYN)));
@@ -147,6 +242,20 @@ public final class SelectOptimizer implements CelAstOptimizer {
SimpleType.DYN,
ListType.create(SimpleType.DYN)));
+ private static final ImmutableList STATIC_FUNCTION_DECLS =
+ ImmutableList.of(
+ CEL_ATTRIBUTE_INT_FUNCTION_DECL,
+ CEL_ATTRIBUTE_UINT_FUNCTION_DECL,
+ CEL_ATTRIBUTE_DOUBLE_FUNCTION_DECL,
+ CEL_ATTRIBUTE_BOOL_FUNCTION_DECL,
+ CEL_ATTRIBUTE_STRING_FUNCTION_DECL,
+ CEL_ATTRIBUTE_BYTES_FUNCTION_DECL,
+ CEL_ATTRIBUTE_DURATION_FUNCTION_DECL,
+ CEL_ATTRIBUTE_TIMESTAMP_FUNCTION_DECL,
+ CEL_ATTRIBUTE_LIST_FUNCTION_DECL,
+ CEL_ATTRIBUTE_MAP_FUNCTION_DECL,
+ CEL_HAS_FIELD_FUNCTION_DECL);
+
@VisibleForTesting
static final Extension SELECT_OPTIMIZATION_AST_EXTENSION_TAG =
Extension.create("select_optimization", Version.of(1L, 0L), Component.COMPONENT_RUNTIME);
@@ -168,7 +277,7 @@ public static SelectOptimizer newInstance(Iterable fileDescripto
/** Returns a new select optimizer configured with the provided options and file descriptors. */
public static SelectOptimizer newInstance(
SelectOptimizerOptions options, FileDescriptor... fileDescriptors) {
- return newInstance(options, Arrays.asList(checkNotNull(fileDescriptors)));
+ return newInstance(checkNotNull(options), Arrays.asList(checkNotNull(fileDescriptors)));
}
/** Returns a new select optimizer configured with the provided options and file descriptors. */
@@ -199,28 +308,33 @@ public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel) {
MonotonicIdGenerator idGenerator =
CelExprIdGeneratorFactory.newMonotonicIdGenerator(navAst.getRoot().maxId());
+ LinkedHashMap dynamicMessageFunctionDecls = new LinkedHashMap<>();
int iterationCount = 0;
for (CelNavigableMutableExpr topNode : topOfChainSelects) {
if (++iterationCount > options.iterationLimit()) {
throw new IllegalStateException("Max iteration count reached.");
}
- rewriteSelectChain(astToModify, navAst, topNode, idGenerator);
+ rewriteSelectChain(astToModify, navAst, topNode, idGenerator, dynamicMessageFunctionDecls);
}
astToModify = astMutator.renumberIdsConsecutively(astToModify);
CelAbstractSyntaxTree optimizedAst = tagAstExtension(astToModify.toParsedAst());
- return OptimizationResult.create(
- optimizedAst,
- ImmutableList.of(),
- ImmutableList.of(CEL_ATTRIBUTE_FUNCTION_DECL, CEL_HAS_FIELD_FUNCTION_DECL));
+ ImmutableList.Builder allFunctionDecls =
+ ImmutableList.builderWithExpectedSize(
+ STATIC_FUNCTION_DECLS.size() + dynamicMessageFunctionDecls.size());
+ allFunctionDecls.addAll(STATIC_FUNCTION_DECLS);
+ allFunctionDecls.addAll(dynamicMessageFunctionDecls.values());
+
+ return OptimizationResult.create(optimizedAst, ImmutableList.of(), allFunctionDecls.build());
}
private void rewriteSelectChain(
CelMutableAst astToModify,
CelNavigableMutableAst navAst,
CelNavigableMutableExpr topNode,
- MonotonicIdGenerator idGenerator) {
+ MonotonicIdGenerator idGenerator,
+ Map dynamicMessageFunctionDecls) {
boolean isHasField = topNode.expr().select().testOnly();
astToModify.source().getMacroCalls().remove(topNode.expr().id());
@@ -235,11 +349,11 @@ private void rewriteSelectChain(
CelMutableExpr currentExpr = topNode.expr().select().operand();
while (currentExpr.getKind() == Kind.SELECT) {
CelMutableSelect select = currentExpr.select();
- FieldDescriptor field = getOptimizableFieldForExpr(navAst, select).orElse(null);
- if (field == null) {
+ Optional field = getOptimizableFieldForExpr(navAst, select);
+ if (!field.isPresent()) {
break;
}
- fields.add(field);
+ fields.add(field.get());
currentExpr = select.operand();
}
@@ -295,10 +409,77 @@ private void rewriteSelectChain(
CelMutableExpr qualifiersExpr =
CelMutableExpr.ofList(idGenerator.nextExprId(), CelMutableList.create(qualifierLists));
- String functionName = isHasField ? CEL_HAS_FIELD_FUNCTION_NAME : CEL_ATTRIBUTE_FUNCTION_NAME;
+ String functionName =
+ isHasField ? CEL_HAS_FIELD_FUNCTION_NAME : resolveAttributeFunctionName(topField);
+ if (functionName.startsWith(CEL_ATTRIBUTE_MESSAGE_FUNCTION_PREFIX)) {
+ String messageFullName = topField.getMessageType().getFullName();
+ dynamicMessageFunctionDecls.computeIfAbsent(
+ functionName, fnName -> newAttributeMessageFunctionDeclaration(messageFullName));
+ }
topNode.expr().setCall(CelMutableCall.create(functionName, currentExpr, qualifiersExpr));
}
+ private static String resolveAttributeFunctionName(FieldDescriptor leafField) {
+ if (leafField.isMapField()) {
+ return CEL_ATTRIBUTE_MAP_FUNCTION_NAME;
+ }
+ if (leafField.isRepeated()) {
+ return CEL_ATTRIBUTE_LIST_FUNCTION_NAME;
+ }
+ switch (leafField.getType()) {
+ case INT32:
+ case INT64:
+ case SINT32:
+ case SINT64:
+ case SFIXED32:
+ case SFIXED64:
+ case ENUM:
+ return CEL_ATTRIBUTE_INT_FUNCTION_NAME;
+ case UINT32:
+ case UINT64:
+ case FIXED32:
+ case FIXED64:
+ return CEL_ATTRIBUTE_UINT_FUNCTION_NAME;
+ case DOUBLE:
+ case FLOAT:
+ return CEL_ATTRIBUTE_DOUBLE_FUNCTION_NAME;
+ case BOOL:
+ return CEL_ATTRIBUTE_BOOL_FUNCTION_NAME;
+ case STRING:
+ return CEL_ATTRIBUTE_STRING_FUNCTION_NAME;
+ case BYTES:
+ return CEL_ATTRIBUTE_BYTES_FUNCTION_NAME;
+ case MESSAGE:
+ String messageFullName = leafField.getMessageType().getFullName();
+ if (messageFullName.equals(CelTypes.DURATION_MESSAGE)) {
+ return CEL_ATTRIBUTE_DURATION_FUNCTION_NAME;
+ }
+ if (messageFullName.equals(CelTypes.TIMESTAMP_MESSAGE)) {
+ return CEL_ATTRIBUTE_TIMESTAMP_FUNCTION_NAME;
+ }
+ return getAttributeMessageFunctionName(messageFullName);
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported protobuf field type: " + leafField.getType());
+ }
+ }
+
+ private static String getAttributeMessageFunctionName(String messageFullName) {
+ checkNotNull(messageFullName);
+ return CEL_ATTRIBUTE_MESSAGE_FUNCTION_PREFIX + messageFullName;
+ }
+
+ private static CelFunctionDecl newAttributeMessageFunctionDeclaration(String messageFullName) {
+ checkNotNull(messageFullName);
+ return CelFunctionDecl.newFunctionDeclaration(
+ getAttributeMessageFunctionName(messageFullName),
+ CelOverloadDecl.newGlobalOverload(
+ "cel_attribute_message_" + messageFullName.replace('.', '_'),
+ StructTypeReference.create(messageFullName),
+ SimpleType.DYN,
+ ListType.create(SimpleType.DYN)));
+ }
+
private static long resolveTypeCode(FieldDescriptor field) {
if (field.isMapField()) {
return CEL_MAP_TYPE_CODE;
@@ -414,13 +595,6 @@ private static CelAbstractSyntaxTree tagAstExtension(CelAbstractSyntaxTree ast)
return CelAbstractSyntaxTree.newParsedAst(ast.getExpr(), celSourceBuilder.build());
}
- private SelectOptimizer(
- SelectOptimizerOptions options, Iterable fileDescriptors) {
- this.options = checkNotNull(options);
- this.astMutator = AstMutator.newInstance(options.iterationLimit());
- this.descriptorPool = newDescriptorPool(options, checkNotNull(fileDescriptors));
- }
-
private static CelDescriptorPool newDescriptorPool(
SelectOptimizerOptions options, Iterable fileDescriptors) {
CelDescriptors celDescriptors =
@@ -432,6 +606,13 @@ private static CelDescriptorPool newDescriptorPool(
return CombinedDescriptorPool.create(descriptorPools.build());
}
+ private SelectOptimizer(
+ SelectOptimizerOptions options, Iterable fileDescriptors) {
+ this.options = checkNotNull(options);
+ this.astMutator = AstMutator.newInstance(options.iterationLimit());
+ this.descriptorPool = newDescriptorPool(options, checkNotNull(fileDescriptors));
+ }
+
/** Options configuring the behavior of {@link SelectOptimizer}. */
@AutoValue
public abstract static class SelectOptimizerOptions {
diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel b/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel
index 787012466..1fd34709a 100644
--- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel
+++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel
@@ -26,6 +26,7 @@ java_library(
"//extensions:optional_library",
# "//java/com/google/testing/testsize:annotations",
"//optimizer",
+ "//optimizer:ast_optimizer",
"//optimizer:optimization_exception",
"//optimizer:optimizer_builder",
"//optimizer/optimizers:common_subexpression_elimination",
diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SelectOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SelectOptimizerTest.java
index 7740319fe..0494aa209 100644
--- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SelectOptimizerTest.java
+++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SelectOptimizerTest.java
@@ -17,6 +17,7 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static org.junit.Assert.assertThrows;
@@ -34,15 +35,19 @@
import dev.cel.common.CelFunctionDecl;
import dev.cel.common.CelMutableAst;
import dev.cel.common.CelOptions;
+import dev.cel.common.CelOverloadDecl;
import dev.cel.common.CelProtoAbstractSyntaxTree;
import dev.cel.common.CelValidationException;
+import dev.cel.common.ast.CelReference;
import dev.cel.common.navigation.CelNavigableMutableAst;
+import dev.cel.common.types.ListType;
import dev.cel.common.types.MapType;
import dev.cel.common.types.SimpleType;
import dev.cel.common.types.StructTypeReference;
import dev.cel.expr.conformance.proto2.NestedTestAllTypes;
import dev.cel.expr.conformance.proto2.TestAllTypesProto;
import dev.cel.expr.conformance.proto3.TestAllTypes;
+import dev.cel.optimizer.CelAstOptimizer.OptimizationResult;
import dev.cel.optimizer.CelOptimizer;
import dev.cel.optimizer.CelOptimizerFactory;
import dev.cel.optimizer.optimizers.SelectOptimizer.SelectOptimizerOptions;
@@ -119,27 +124,31 @@ private static Cel setupEnv(CelBuilder celBuilder) {
private enum RewriteTestCase {
// === Selection & Traversal ===
PROTO3_SINGLE_FIELD_SELECT(
- "msg.single_int64", "cel.@attribute(msg, [[2, \"single_int64\", 3, 0]])"),
+ "msg.single_int64", "cel.@attributeInt(msg, [[2, \"single_int64\", 3, 0]])"),
PROTO3_SINGLE_MESSAGE_FIELD_SELECT(
- "msg.single_nested_message", "cel.@attribute(msg, [[21, \"single_nested_message\", 11]])"),
+ "msg.single_nested_message",
+ "cel.@attributeMessage:cel.expr.conformance.proto3.TestAllTypes.NestedMessage(msg, [[21,"
+ + " \"single_nested_message\", 11]])"),
PROTO3_CHAINED_FIELD_SELECT(
"msg.single_nested_message.bb",
- "cel.@attribute(msg, [[21, \"single_nested_message\", 11], [1, \"bb\", 5, 0]])"),
+ "cel.@attributeInt(msg, [[21, \"single_nested_message\", 11], [1, \"bb\", 5, 0]])"),
PROTO2_SINGLE_MESSAGE_FIELD_SELECT(
"proto2_msg.single_nested_message",
- "cel.@attribute(proto2_msg, [[21, \"single_nested_message\", 11]])"),
+ "cel.@attributeMessage:cel.expr.conformance.proto2.TestAllTypes.NestedMessage(proto2_msg,"
+ + " [[21, \"single_nested_message\", 11]])"),
PROTO2_CHAINED_FIELD_SELECT(
"proto2_msg.single_nested_message.bb",
- "cel.@attribute(proto2_msg, [[21, \"single_nested_message\", 11], [1, \"bb\", 5, 0]])"),
+ "cel.@attributeInt(proto2_msg, [[21, \"single_nested_message\", 11], [1, \"bb\", 5, 0]])"),
PROTO2_TRIPLE_CHAINED_FIELD_SELECT(
"nested_msg.child.payload.single_int64",
- "cel.@attribute(nested_msg, "
+ "cel.@attributeInt(nested_msg, "
+ "[[1, \"child\", 11], "
+ "[2, \"payload\", 11], "
+ "[2, \"single_int64\", 3, -64]])"),
PROTO2_CHAINED_MESSAGE_FIELD_SELECT(
"nested_msg.child.payload",
- "cel.@attribute(nested_msg, [[1, \"child\", 11], [2, \"payload\", 11]])"),
+ "cel.@attributeMessage:cel.expr.conformance.proto2.TestAllTypes(nested_msg, [[1,"
+ + " \"child\", 11], [2, \"payload\", 11]])"),
// === Presence Tests: Proto2 (Explicit Presence) vs Proto3 (Implicit/Explicit Presence) ===
// In proto2, scalar fields have explicit presence (has-bit).
@@ -178,102 +187,134 @@ private enum RewriteTestCase {
// === Default Value Divergence: Proto2 Custom Defaults vs Proto3 Zero Defaults ===
// Int32: proto2 has custom default -32, proto3 has 0
PROTO2_CUSTOM_INT32(
- "proto2_msg.single_int32", "cel.@attribute(proto2_msg, [[1, \"single_int32\", 5, -32]])"),
- PROTO3_ZERO_INT32("msg.single_int32", "cel.@attribute(msg, [[1, \"single_int32\", 5, 0]])"),
+ "proto2_msg.single_int32",
+ "cel.@attributeInt(proto2_msg, [[1, \"single_int32\", 5, -32]])"),
+ PROTO3_ZERO_INT32("msg.single_int32", "cel.@attributeInt(msg, [[1, \"single_int32\", 5, 0]])"),
// Int64: proto2 has custom default -64, proto3 has 0
PROTO2_CUSTOM_INT64(
- "proto2_msg.single_int64", "cel.@attribute(proto2_msg, [[2, \"single_int64\", 3, -64]])"),
- PROTO3_ZERO_INT64("msg.single_int64", "cel.@attribute(msg, [[2, \"single_int64\", 3, 0]])"),
+ "proto2_msg.single_int64",
+ "cel.@attributeInt(proto2_msg, [[2, \"single_int64\", 3, -64]])"),
+ PROTO3_ZERO_INT64("msg.single_int64", "cel.@attributeInt(msg, [[2, \"single_int64\", 3, 0]])"),
// Uint32: proto2 has custom default 32, proto3 has 0
PROTO2_CUSTOM_UINT32(
"proto2_msg.single_uint32",
- "cel.@attribute(proto2_msg, [[3, \"single_uint32\", 13, 32u]])"),
+ "cel.@attributeUint(proto2_msg, [[3, \"single_uint32\", 13, 32u]])"),
PROTO3_ZERO_UINT32(
- "msg.single_uint32", "cel.@attribute(msg, [[3, \"single_uint32\", 13, 0u]])"),
+ "msg.single_uint32", "cel.@attributeUint(msg, [[3, \"single_uint32\", 13, 0u]])"),
// Uint64: proto2 has custom default 64, proto3 has 0
PROTO2_CUSTOM_UINT64(
- "proto2_msg.single_uint64", "cel.@attribute(proto2_msg, [[4, \"single_uint64\", 4, 64u]])"),
- PROTO3_ZERO_UINT64("msg.single_uint64", "cel.@attribute(msg, [[4, \"single_uint64\", 4, 0u]])"),
+ "proto2_msg.single_uint64",
+ "cel.@attributeUint(proto2_msg, [[4, \"single_uint64\", 4, 64u]])"),
+ PROTO3_ZERO_UINT64(
+ "msg.single_uint64", "cel.@attributeUint(msg, [[4, \"single_uint64\", 4, 0u]])"),
// String: proto2 has custom default "empty", proto3 has ""
PROTO2_CUSTOM_STRING(
"proto2_msg.single_string",
- "cel.@attribute(proto2_msg, [[14, \"single_string\", 9, \"empty\"]])"),
+ "cel.@attributeString(proto2_msg, [[14, \"single_string\", 9, \"empty\"]])"),
PROTO3_ZERO_STRING(
- "msg.single_string", "cel.@attribute(msg, [[14, \"single_string\", 9, \"\"]])"),
+ "msg.single_string", "cel.@attributeString(msg, [[14, \"single_string\", 9, \"\"]])"),
// Bool: proto2 has custom default true, proto3 has false
PROTO2_CUSTOM_BOOL(
- "proto2_msg.single_bool", "cel.@attribute(proto2_msg, [[13, \"single_bool\", 8, true]])"),
- PROTO3_ZERO_BOOL("msg.single_bool", "cel.@attribute(msg, [[13, \"single_bool\", 8, false]])"),
+ "proto2_msg.single_bool",
+ "cel.@attributeBool(proto2_msg, [[13, \"single_bool\", 8, true]])"),
+ PROTO3_ZERO_BOOL(
+ "msg.single_bool", "cel.@attributeBool(msg, [[13, \"single_bool\", 8, false]])"),
// Float: proto2 has custom default 3.0, proto3 has 0.0
PROTO2_CUSTOM_FLOAT(
- "proto2_msg.single_float", "cel.@attribute(proto2_msg, [[11, \"single_float\", 2, 3.0]])"),
- PROTO3_ZERO_FLOAT("msg.single_float", "cel.@attribute(msg, [[11, \"single_float\", 2, 0.0]])"),
+ "proto2_msg.single_float",
+ "cel.@attributeDouble(proto2_msg, [[11, \"single_float\", 2, 3.0]])"),
+ PROTO3_ZERO_FLOAT(
+ "msg.single_float", "cel.@attributeDouble(msg, [[11, \"single_float\", 2, 0.0]])"),
// Double: proto2 has custom default 6.4, proto3 has 0.0
PROTO2_CUSTOM_DOUBLE(
"proto2_msg.single_double",
- "cel.@attribute(proto2_msg, [[12, \"single_double\", 1, 6.4]])"),
+ "cel.@attributeDouble(proto2_msg, [[12, \"single_double\", 1, 6.4]])"),
PROTO3_ZERO_DOUBLE(
- "msg.single_double", "cel.@attribute(msg, [[12, \"single_double\", 1, 0.0]])"),
+ "msg.single_double", "cel.@attributeDouble(msg, [[12, \"single_double\", 1, 0.0]])"),
// Bytes: proto2 has custom default "none", proto3 has ""
PROTO2_CUSTOM_BYTES(
"proto2_msg.single_bytes",
- "cel.@attribute(proto2_msg, [[15, \"single_bytes\", 12, b\"\\156\\157\\156\\145\"]])"),
+ "cel.@attributeBytes(proto2_msg, [[15, \"single_bytes\", 12, b\"\\156\\157\\156\\145\"]])"),
PROTO3_ZERO_BYTES(
- "msg.single_bytes", "cel.@attribute(msg, [[15, \"single_bytes\", 12, b\"\"]])"),
+ "msg.single_bytes", "cel.@attributeBytes(msg, [[15, \"single_bytes\", 12, b\"\"]])"),
// Enum: proto2 has custom default 1 (BAR), proto3 has 0 (FOO)
PROTO2_CUSTOM_ENUM(
"proto2_msg.single_nested_enum",
- "cel.@attribute(proto2_msg, [[22, \"single_nested_enum\", 14, 1]])"),
+ "cel.@attributeInt(proto2_msg, [[22, \"single_nested_enum\", 14, 1]])"),
PROTO3_ZERO_ENUM(
- "msg.single_nested_enum", "cel.@attribute(msg, [[22, \"single_nested_enum\", 14, 0]])"),
-
- // Fixed / sfixed fields
+ "msg.single_nested_enum", "cel.@attributeInt(msg, [[22, \"single_nested_enum\", 14, 0]])"),
+
+ // Sint / fixed / sfixed fields
+ PROTO3_SINT32("msg.single_sint32", "cel.@attributeInt(msg, [[5, \"single_sint32\", 17, 0]])"),
+ PROTO3_SINT64("msg.single_sint64", "cel.@attributeInt(msg, [[6, \"single_sint64\", 18, 0]])"),
+ PROTO3_FIXED32(
+ "msg.single_fixed32", "cel.@attributeUint(msg, [[7, \"single_fixed32\", 7, 0u]])"),
+ PROTO3_FIXED64(
+ "msg.single_fixed64", "cel.@attributeUint(msg, [[8, \"single_fixed64\", 6, 0u]])"),
PROTO3_SFIXED32(
- "msg.single_sfixed32", "cel.@attribute(msg, [[9, \"single_sfixed32\", 15, 0]])"),
+ "msg.single_sfixed32", "cel.@attributeInt(msg, [[9, \"single_sfixed32\", 15, 0]])"),
PROTO3_SFIXED64(
- "msg.single_sfixed64", "cel.@attribute(msg, [[10, \"single_sfixed64\", 16, 0]])"),
+ "msg.single_sfixed64", "cel.@attributeInt(msg, [[10, \"single_sfixed64\", 16, 0]])"),
+ PROTO2_SINT32(
+ "proto2_msg.single_sint32",
+ "cel.@attributeInt(proto2_msg, [[5, \"single_sint32\", 17, 0]])"),
+ PROTO2_SINT64(
+ "proto2_msg.single_sint64",
+ "cel.@attributeInt(proto2_msg, [[6, \"single_sint64\", 18, 0]])"),
+ PROTO2_FIXED32(
+ "proto2_msg.single_fixed32",
+ "cel.@attributeUint(proto2_msg, [[7, \"single_fixed32\", 7, 0u]])"),
+ PROTO2_FIXED64(
+ "proto2_msg.single_fixed64",
+ "cel.@attributeUint(proto2_msg, [[8, \"single_fixed64\", 6, 0u]])"),
+ PROTO2_SFIXED32(
+ "proto2_msg.single_sfixed32",
+ "cel.@attributeInt(proto2_msg, [[9, \"single_sfixed32\", 15, 0]])"),
+ PROTO2_SFIXED64(
+ "proto2_msg.single_sfixed64",
+ "cel.@attributeInt(proto2_msg, [[10, \"single_sfixed64\", 16, 0]])"),
// Repeated fields: empty list default
PROTO2_REPEATED_PRIMITIVE(
"proto2_msg.repeated_int64",
- "cel.@attribute(proto2_msg, [[32, \"repeated_int64\", 3, []]])"),
+ "cel.@attributeList(proto2_msg, [[32, \"repeated_int64\", 3, []]])"),
PROTO3_REPEATED_PRIMITIVE(
- "msg.repeated_int64", "cel.@attribute(msg, [[32, \"repeated_int64\", 3, []]])"),
+ "msg.repeated_int64", "cel.@attributeList(msg, [[32, \"repeated_int64\", 3, []]])"),
PROTO3_REPEATED_MESSAGE(
"msg.repeated_nested_message",
- "cel.@attribute(msg, [[51, \"repeated_nested_message\", 11, []]])"),
+ "cel.@attributeList(msg, [[51, \"repeated_nested_message\", 11, []]])"),
// Well-known types
PROTO3_TIMESTAMP(
"msg.single_timestamp",
- "cel.@attribute(msg, [[102, \"single_timestamp\", 11, timestamp(0)]])"),
+ "cel.@attributeTimestamp(msg, [[102, \"single_timestamp\", 11, timestamp(0)]])"),
PROTO3_DURATION(
"msg.single_duration",
- "cel.@attribute(msg, [[101, \"single_duration\", 11, duration(\"0s\")]])"),
+ "cel.@attributeDuration(msg, [[101, \"single_duration\", 11, duration(\"0s\")]])"),
// Map selects
MAP_FIELD_INDEXING(
"msg.map_int64_message[1].bb",
- "cel.@attribute("
- + "cel.@attribute(msg, [[95, \"map_int64_message\", 20, {}]])[1], "
+ "cel.@attributeInt("
+ + "cel.@attributeMap(msg, [[95, \"map_int64_message\", 20, {}]])[1], "
+ "[[1, \"bb\", 5, 0]])"),
MAP_FIELD_SELECT_CHAIN_STOPS_AT_MAP_BOUNDARY(
"map_var_msg.key.single_nested_message.bb",
- "cel.@attribute(map_var_msg.key, "
+ "cel.@attributeInt(map_var_msg.key, "
+ "[[21, \"single_nested_message\", 11], "
+ "[1, \"bb\", 5, 0]])"),
MAP_FIELD_SELECT_STOPS_AT_MAP_BOUNDARY(
"map_var_msg.key.single_int64",
- "cel.@attribute(map_var_msg.key, [[2, \"single_int64\", 3, 0]])"),
+ "cel.@attributeInt(map_var_msg.key, [[2, \"single_int64\", 3, 0]])"),
MAP_FIELD_HAS_STOPS_AT_MAP_BOUNDARY(
"has(map_var_msg.key.single_nested_message)",
"cel.@hasField(map_var_msg.key, [[21, \"single_nested_message\"]])"),
@@ -282,18 +323,18 @@ private enum RewriteTestCase {
"cel.@hasField(map_var_msg.key, [[21, \"single_nested_message\"], [1, \"bb\"]])"),
PROTO_MAP_FIELD_SELECT_STOPS_AT_MAP_BOUNDARY(
"msg.map_string_message.key.bb",
- "cel.@attribute("
- + "cel.@attribute(msg, [[227, \"map_string_message\", 20, {}]]).key, "
+ "cel.@attributeInt("
+ + "cel.@attributeMap(msg, [[227, \"map_string_message\", 20, {}]]).key, "
+ "[[1, \"bb\", 5, 0]])"),
PROTO_MAP_FIELD_HAS_STOPS_AT_MAP_BOUNDARY(
"has(msg.map_string_message.key.bb)",
"cel.@hasField("
- + "cel.@attribute(msg, [[227, \"map_string_message\", 20, {}]]).key, "
+ + "cel.@attributeMap(msg, [[227, \"map_string_message\", 20, {}]]).key, "
+ "[[1, \"bb\"]])"),
MIXED_BOOLEAN_EXPRESSION(
"msg.single_int64 > 0 && has(msg.single_nested_message)",
- "cel.@attribute(msg, [[2, \"single_int64\", 3, 0]]) > 0 "
+ "cel.@attributeInt(msg, [[2, \"single_int64\", 3, 0]]) > 0 "
+ "&& cel.@hasField(msg, [[21, \"single_nested_message\"]])");
private final String expression;
@@ -376,7 +417,7 @@ public void optimize_withFileDescriptors_success() throws Exception {
CelAbstractSyntaxTree optimizedAst = optimizer.optimize(ast);
assertThat(CEL_UNPARSER.unparse(optimizedAst))
- .isEqualTo("cel.@attribute(msg, [[2, \"single_int64\", 3, 0]])");
+ .isEqualTo("cel.@attributeInt(msg, [[2, \"single_int64\", 3, 0]])");
}
@Test
@@ -394,7 +435,7 @@ public void optimize_withFileDescriptorsIterable_success() throws Exception {
CelAbstractSyntaxTree optimizedAst = optimizer.optimize(ast);
assertThat(CEL_UNPARSER.unparse(optimizedAst))
- .isEqualTo("cel.@attribute(msg, [[2, \"single_int64\", 3, 0]])");
+ .isEqualTo("cel.@attributeInt(msg, [[2, \"single_int64\", 3, 0]])");
}
@Test
@@ -409,7 +450,7 @@ public void newInstance_withOptionsAndFileDescriptors_preservesAddedDescriptors(
CelAbstractSyntaxTree optimizedAst = optimizer.optimize(ast, cel).optimizedAst();
assertThat(CEL_UNPARSER.unparse(optimizedAst))
- .isEqualTo("cel.@attribute(proto2_msg, [[2, \"single_int64\", 3, -64]])");
+ .isEqualTo("cel.@attributeInt(proto2_msg, [[2, \"single_int64\", 3, -64]])");
}
@Test
@@ -456,10 +497,10 @@ public void optimizeAndEvaluate_withAttributeFunctionBinding_evaluatesSuccessful
throws Exception {
Cel celWithBinding =
cel.toCelBuilder()
- .addFunctionDeclarations(SelectOptimizer.CEL_ATTRIBUTE_FUNCTION_DECL)
+ .addFunctionDeclarations(SelectOptimizer.CEL_ATTRIBUTE_INT_FUNCTION_DECL)
.addFunctionBindings(
CelFunctionBinding.from(
- "cel_attribute_list", Object.class, List.class, (target, path) -> 42L))
+ "cel_attribute_int_list", Object.class, List.class, (target, path) -> 42L))
.build();
CelOptimizer optimizer =
CelOptimizerFactory.standardCelOptimizerBuilder(celWithBinding)
@@ -484,10 +525,10 @@ public void optimizeAndEvaluate_withChainedMessageSelect_unpacksTuplesSuccessful
throws Exception {
Cel celWithBinding =
cel.toCelBuilder()
- .addFunctionDeclarations(SelectOptimizer.CEL_ATTRIBUTE_FUNCTION_DECL)
+ .addFunctionDeclarations(SelectOptimizer.CEL_ATTRIBUTE_INT_FUNCTION_DECL)
.addFunctionBindings(
CelFunctionBinding.from(
- "cel_attribute_list", Object.class, List.class, (target, path) -> path))
+ "cel_attribute_int_list", Object.class, List.class, (target, path) -> path))
.build();
CelOptimizer optimizer =
CelOptimizerFactory.standardCelOptimizerBuilder(celWithBinding)
@@ -542,10 +583,10 @@ public void optimizeAndEvaluate_withHasFieldFunctionBinding_evaluatesSuccessfull
public void optimizeAndEvaluate_withSelectOnMapValue_evaluatesSuccessfully() throws Exception {
Cel celWithBinding =
cel.toCelBuilder()
- .addFunctionDeclarations(SelectOptimizer.CEL_ATTRIBUTE_FUNCTION_DECL)
+ .addFunctionDeclarations(SelectOptimizer.CEL_ATTRIBUTE_INT_FUNCTION_DECL)
.addFunctionBindings(
CelFunctionBinding.from(
- "cel_attribute_list", Object.class, List.class, (target, path) -> 42L))
+ "cel_attribute_int_list", Object.class, List.class, (target, path) -> 42L))
.build();
CelOptimizer optimizer =
CelOptimizerFactory.standardCelOptimizerBuilder(celWithBinding)
@@ -605,10 +646,10 @@ public void optimizeAndEvaluate_withHasOnMapValue_evaluatesSuccessfully() throws
public void optimizeAndEvaluate_withMissingMapKey_throwsEvaluationException() throws Exception {
Cel celWithBinding =
cel.toCelBuilder()
- .addFunctionDeclarations(SelectOptimizer.CEL_ATTRIBUTE_FUNCTION_DECL)
+ .addFunctionDeclarations(SelectOptimizer.CEL_ATTRIBUTE_INT_FUNCTION_DECL)
.addFunctionBindings(
CelFunctionBinding.from(
- "cel_attribute_list", Object.class, List.class, (target, path) -> 42L))
+ "cel_attribute_int_list", Object.class, List.class, (target, path) -> 42L))
.build();
CelOptimizer optimizer =
CelOptimizerFactory.standardCelOptimizerBuilder(celWithBinding)
@@ -744,7 +785,7 @@ public void newInstance_fileDescriptorsVarargs_defaultOptions_success() throws E
CelAbstractSyntaxTree optimizedAst = optimizer.optimize(ast, cel).optimizedAst();
assertThat(CEL_UNPARSER.unparse(optimizedAst))
- .isEqualTo("cel.@attribute(proto2_msg, [[2, \"single_int64\", 3, -64]])");
+ .isEqualTo("cel.@attributeInt(proto2_msg, [[2, \"single_int64\", 3, -64]])");
}
@Test
@@ -756,7 +797,7 @@ public void newInstance_fileDescriptorsIterable_defaultOptions_success() throws
CelAbstractSyntaxTree optimizedAst = optimizer.optimize(ast, cel).optimizedAst();
assertThat(CEL_UNPARSER.unparse(optimizedAst))
- .isEqualTo("cel.@attribute(proto2_msg, [[2, \"single_int64\", 3, -64]])");
+ .isEqualTo("cel.@attributeInt(proto2_msg, [[2, \"single_int64\", 3, -64]])");
}
@Test
@@ -774,7 +815,7 @@ public void newInstance_fileDescriptorsIterable_defaultOptions_success() throws
CelAbstractSyntaxTree proto3Optimized = optimizer.optimize(proto3Ast, cel).optimizedAst();
assertThat(CEL_UNPARSER.unparse(proto2Optimized))
- .isEqualTo("cel.@attribute(proto2_msg, [[2, \"single_int64\", 3, -64]])");
+ .isEqualTo("cel.@attributeInt(proto2_msg, [[2, \"single_int64\", 3, -64]])");
assertThat(CEL_UNPARSER.unparse(proto3Optimized)).isEqualTo("msg.single_int64");
}
@@ -794,19 +835,19 @@ public void newInstance_fileDescriptorsIterable_defaultOptions_success() throws
CelAbstractSyntaxTree proto3Optimized = optimizer.optimize(proto3Ast, cel).optimizedAst();
assertThat(CEL_UNPARSER.unparse(proto2Optimized))
- .isEqualTo("cel.@attribute(proto2_msg, [[2, \"single_int64\", 3, -64]])");
+ .isEqualTo("cel.@attributeInt(proto2_msg, [[2, \"single_int64\", 3, -64]])");
assertThat(CEL_UNPARSER.unparse(proto3Optimized)).isEqualTo("msg.single_int64");
}
private enum CompilerRejectionTestCase {
- ATTRIBUTE_AT_SIGN(
- SelectOptimizer.CEL_ATTRIBUTE_FUNCTION_DECL,
- "cel.@attribute(msg, [])",
+ ATTRIBUTE_INT_AT_SIGN(
+ SelectOptimizer.CEL_ATTRIBUTE_INT_FUNCTION_DECL,
+ "cel.@attributeInt(msg, [])",
"token recognition error at: '@'"),
- ATTRIBUTE_OVERLOAD(
- SelectOptimizer.CEL_ATTRIBUTE_FUNCTION_DECL,
- "cel_attribute_list(msg, [])",
- "undeclared reference to 'cel_attribute_list'"),
+ ATTRIBUTE_INT_OVERLOAD(
+ SelectOptimizer.CEL_ATTRIBUTE_INT_FUNCTION_DECL,
+ "cel_attribute_int_list(msg, [])",
+ "undeclared reference to 'cel_attribute_int_list'"),
HAS_FIELD_AT_SIGN(
SelectOptimizer.CEL_HAS_FIELD_FUNCTION_DECL,
"cel.@hasField(msg, [])",
@@ -840,6 +881,220 @@ public void compile_sourceWithInternalFunctionCall_failsCompilation(
assertThat(e).hasMessageThat().contains(testCase.expectedErrorMessage);
}
+ @Test
+ public void optimize_binaryAddition_resolvesSingleOverload() throws Exception {
+ CelAbstractSyntaxTree ast = cel.compile("msg.single_int64 + msg.single_sfixed64").getAst();
+
+ CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast);
+
+ assertThat(CEL_UNPARSER.unparse(optimizedAst))
+ .isEqualTo(
+ "cel.@attributeInt(msg, [[2, \"single_int64\", 3, 0]]) + "
+ + "cel.@attributeInt(msg, [[10, \"single_sfixed64\", 16, 0]])");
+ assertThat(optimizedAst.isChecked()).isTrue();
+ }
+
+ @Test
+ public void optimize_stringConcatenation_resolvesSingleOverload() throws Exception {
+ CelAbstractSyntaxTree ast = cel.compile("msg.single_string + 'suffix'").getAst();
+
+ CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast);
+
+ assertThat(CEL_UNPARSER.unparse(optimizedAst))
+ .isEqualTo("cel.@attributeString(msg, [[14, \"single_string\", 9, \"\"]]) + \"suffix\"");
+ assertThat(optimizedAst.isChecked()).isTrue();
+ }
+
+ @Test
+ public void optimize_chainedSelectInBinaryExpression_resolvesSingleOverload() throws Exception {
+ CelAbstractSyntaxTree ast = cel.compile("msg.single_nested_message.bb + 1").getAst();
+
+ CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast);
+
+ assertThat(CEL_UNPARSER.unparse(optimizedAst))
+ .isEqualTo(
+ "cel.@attributeInt(msg, [[21, \"single_nested_message\", 11], [1, \"bb\", 5, 0]])"
+ + " + 1");
+ assertThat(optimizedAst.isChecked()).isTrue();
+ }
+
+ private enum OverloadResolutionTestCase {
+ INT_ADDITION("msg.single_int64 + msg.single_sfixed64", "add_int64"),
+ UINT_ADDITION("msg.single_uint64 + msg.single_fixed64", "add_uint64"),
+ DOUBLE_ADDITION("msg.single_double + msg.single_float", "add_double"),
+ STRING_ADDITION("msg.single_string + 'suffix'", "add_string"),
+ BYTES_ADDITION("msg.single_bytes + b'suffix'", "add_bytes"),
+ TIMESTAMP_DURATION_ADDITION(
+ "msg.single_timestamp + msg.single_duration", "add_timestamp_duration"),
+ LOGICAL_AND("msg.single_bool && true", "logical_and");
+
+ private final String expression;
+ private final String expectedOverloadId;
+
+ OverloadResolutionTestCase(String expression, String expectedOverloadId) {
+ this.expression = expression;
+ this.expectedOverloadId = expectedOverloadId;
+ }
+ }
+
+ @Test
+ public void optimize_binaryOperation_hasUnambiguousOverloadReference(
+ @TestParameter OverloadResolutionTestCase testCase) throws Exception {
+ CelAbstractSyntaxTree ast = cel.compile(testCase.expression).getAst();
+
+ CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast);
+
+ // Root node is the binary call.
+ assertThat(
+ optimizedAst.getReference(optimizedAst.getExpr().id()).map(CelReference::overloadIds))
+ .hasValue(ImmutableList.of(testCase.expectedOverloadId));
+ }
+
+ @Test
+ public void optimize_binaryOperation_referenceMapContainsOnlySingularOverload(
+ @TestParameter OverloadResolutionTestCase testCase) throws Exception {
+ CelAbstractSyntaxTree ast = cel.compile(testCase.expression).getAst();
+
+ CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast);
+
+ // Verify all call references in the AST have exactly one resolved overload, never a list of
+ // candidates.
+ ImmutableList callReferences =
+ optimizedAst.getReferenceMap().values().stream()
+ .filter(ref -> !ref.overloadIds().isEmpty())
+ .collect(toImmutableList());
+ assertThat(callReferences).isNotEmpty();
+ for (CelReference ref : callReferences) {
+ assertWithMessage("Call reference %s must have exactly 1 resolved overload", ref)
+ .that(ref.overloadIds())
+ .hasSize(1);
+ }
+ }
+
+ @Test
+ public void optimize_pipelineWithDownstreamOptimizer_generatesSingularOverload()
+ throws Exception {
+ CelOptimizer chainedOptimizer =
+ CelOptimizerFactory.standardCelOptimizerBuilder(cel)
+ .addAstOptimizers(
+ SelectOptimizer.newInstance(TestAllTypes.getDescriptor().getFile()),
+ ConstantFoldingOptimizer.getInstance())
+ .build();
+ CelAbstractSyntaxTree ast = cel.compile("msg.single_int64 + (1 + 2)").getAst();
+
+ CelAbstractSyntaxTree optimizedAst = chainedOptimizer.optimize(ast);
+
+ assertThat(CEL_UNPARSER.unparse(optimizedAst))
+ .isEqualTo("cel.@attributeInt(msg, [[2, \"single_int64\", 3, 0]]) + 3");
+ assertThat(
+ optimizedAst.getReference(optimizedAst.getExpr().id()).map(CelReference::overloadIds))
+ .hasValue(ImmutableList.of("add_int64"));
+ }
+
+ @Test
+ public void optimize_messageEquality_typeChecksSuccessfully() throws Exception {
+ CelAbstractSyntaxTree ast =
+ cel.compile("msg.single_nested_message == msg.single_nested_message").getAst();
+
+ CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast);
+
+ assertThat(CEL_UNPARSER.unparse(optimizedAst))
+ .isEqualTo(
+ "cel.@attributeMessage:cel.expr.conformance.proto3.TestAllTypes.NestedMessage(msg,"
+ + " [[21, \"single_nested_message\", 11]]) == "
+ + "cel.@attributeMessage:cel.expr.conformance.proto3.TestAllTypes.NestedMessage(msg,"
+ + " [[21, \"single_nested_message\", 11]])");
+ assertThat(optimizedAst.isChecked()).isTrue();
+ }
+
+ @Test
+ public void optimize_messagePassedToCustomFunction_typeChecksSuccessfully() throws Exception {
+ Cel celWithFn =
+ cel.toCelBuilder()
+ .addFunctionDeclarations(
+ CelFunctionDecl.newFunctionDeclaration(
+ "takeNested",
+ CelOverloadDecl.newGlobalOverload(
+ "take_nested",
+ SimpleType.BOOL,
+ StructTypeReference.create(
+ TestAllTypes.NestedMessage.getDescriptor().getFullName()))))
+ .build();
+ CelOptimizer optimizer =
+ CelOptimizerFactory.standardCelOptimizerBuilder(celWithFn)
+ .addAstOptimizers(SelectOptimizer.newInstance(TestAllTypes.getDescriptor().getFile()))
+ .build();
+ CelAbstractSyntaxTree ast = celWithFn.compile("takeNested(msg.single_nested_message)").getAst();
+
+ CelAbstractSyntaxTree optimizedAst = optimizer.optimize(ast);
+
+ assertThat(CEL_UNPARSER.unparse(optimizedAst))
+ .isEqualTo(
+ "takeNested(cel.@attributeMessage:cel.expr.conformance.proto3.TestAllTypes.NestedMessage(msg,"
+ + " [[21, \"single_nested_message\", 11]]))");
+ assertThat(optimizedAst.isChecked()).isTrue();
+ }
+
+ @Test
+ public void optimize_overloadedMessageFunction_resolvesSingleOverload() throws Exception {
+ Cel celWithFn =
+ cel.toCelBuilder()
+ .addFunctionDeclarations(
+ CelFunctionDecl.newFunctionDeclaration(
+ "processMsg",
+ CelOverloadDecl.newGlobalOverload(
+ "process_nested",
+ SimpleType.BOOL,
+ StructTypeReference.create(
+ TestAllTypes.NestedMessage.getDescriptor().getFullName())),
+ CelOverloadDecl.newGlobalOverload(
+ "process_all_types",
+ SimpleType.BOOL,
+ StructTypeReference.create(TestAllTypes.getDescriptor().getFullName()))))
+ .build();
+ CelOptimizer optimizer =
+ CelOptimizerFactory.standardCelOptimizerBuilder(celWithFn)
+ .addAstOptimizers(SelectOptimizer.newInstance(TestAllTypes.getDescriptor().getFile()))
+ .build();
+ CelAbstractSyntaxTree ast = celWithFn.compile("processMsg(msg.single_nested_message)").getAst();
+
+ CelAbstractSyntaxTree optimizedAst = optimizer.optimize(ast);
+
+ assertThat(
+ optimizedAst.getReference(optimizedAst.getExpr().id()).map(CelReference::overloadIds))
+ .hasValue(ImmutableList.of("process_nested"));
+ assertThat(optimizedAst.getResultType()).isEqualTo(SimpleType.BOOL);
+ }
+
+ @Test
+ public void optimize_newFunctionDecls_containsDynamicMessageFunctionDeclAndDeduplicates()
+ throws Exception {
+ SelectOptimizer optimizer = SelectOptimizer.newInstance(TestAllTypes.getDescriptor().getFile());
+ CelAbstractSyntaxTree ast =
+ cel.compile("msg.single_nested_message == msg.single_nested_message").getAst();
+
+ OptimizationResult result = optimizer.optimize(ast, cel);
+
+ String expectedFunctionName =
+ "cel.@attributeMessage:" + TestAllTypes.NestedMessage.getDescriptor().getFullName();
+ ImmutableList dynamicDecls =
+ result.newFunctionDecls().stream()
+ .filter(decl -> decl.name().equals(expectedFunctionName))
+ .collect(toImmutableList());
+ CelFunctionDecl expectedDecl =
+ CelFunctionDecl.newFunctionDeclaration(
+ expectedFunctionName,
+ CelOverloadDecl.newGlobalOverload(
+ "cel_attribute_message_"
+ + TestAllTypes.NestedMessage.getDescriptor().getFullName().replace('.', '_'),
+ StructTypeReference.create(
+ TestAllTypes.NestedMessage.getDescriptor().getFullName()),
+ SimpleType.DYN,
+ ListType.create(SimpleType.DYN)));
+
+ assertThat(dynamicDecls).containsExactly(expectedDecl);
+ }
+
@Test
public void optimize_toParsedExpr_matchesExpectedSerializedProto() throws Exception {
CelAbstractSyntaxTree ast = cel.compile("msg.single_nested_message.bb").getAst();
@@ -848,7 +1103,7 @@ public void optimize_toParsedExpr_matchesExpectedSerializedProto() throws Except
"expr {\n"
+ " id: 1\n"
+ " call_expr {\n"
- + " function: \"cel.@attribute\"\n"
+ + " function: \"cel.@attributeInt\"\n"
+ " args {\n"
+ " id: 2\n"
+ " ident_expr {\n"