Skip to content

Commit c6a0282

Browse files
CEL Dev Teamcopybara-github
authored andcommitted
Port CEL-Go basic Protobuf constant folding syntax to CEL-Java
PiperOrigin-RevId: 944425297
1 parent 028e171 commit c6a0282

3 files changed

Lines changed: 104 additions & 12 deletions

File tree

optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ java_library(
3131
"//common/navigation:common",
3232
"//common/navigation:mutable_navigation",
3333
"//common/types",
34+
"//common/types:type_providers",
3435
"//extensions:optional_library",
3536
"//optimizer:ast_optimizer",
3637
"//optimizer:mutable_ast",
@@ -40,6 +41,7 @@ java_library(
4041
"//runtime:unknown_attributes",
4142
"@maven//:com_google_errorprone_error_prone_annotations",
4243
"@maven//:com_google_guava_guava",
44+
"@maven//:com_google_protobuf_protobuf_java",
4345
],
4446
)
4547

optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import com.google.common.collect.ImmutableList;
2424
import com.google.common.collect.ImmutableSet;
2525
import com.google.errorprone.annotations.CanIgnoreReturnValue;
26+
import com.google.protobuf.Descriptors.FieldDescriptor;
27+
import com.google.protobuf.Message;
2628
import dev.cel.bundle.Cel;
2729
import dev.cel.common.CelAbstractSyntaxTree;
2830
import dev.cel.common.CelMutableAst;
@@ -42,7 +44,10 @@
4244
import dev.cel.common.navigation.CelNavigableMutableAst;
4345
import dev.cel.common.navigation.CelNavigableMutableExpr;
4446
import dev.cel.common.navigation.TraversalOrder;
47+
import dev.cel.common.types.CelType;
48+
import dev.cel.common.types.CelTypeProvider;
4549
import dev.cel.common.types.SimpleType;
50+
import dev.cel.common.types.StructType;
4651
import dev.cel.extensions.CelOptionalLibrary.Function;
4752
import dev.cel.optimizer.AstMutator;
4853
import dev.cel.optimizer.CelAstOptimizer;
@@ -305,25 +310,28 @@ private Optional<CelMutableAst> maybeFold(
305310
// ex2: optional.ofNonZeroValue(5) -> optional.of(5)
306311
if (result instanceof Optional<?>) {
307312
Optional<?> optResult = ((Optional<?>) result);
308-
return maybeRewriteOptional(optResult, mutableAst, node.expr());
313+
return maybeRewriteOptional(cel.getTypeProvider(), optResult, mutableAst, node.expr());
309314
}
310315

311-
CelMutableExpr adaptedResult = maybeAdaptEvaluatedResult(result).orElse(null);
316+
CelMutableExpr adaptedResult =
317+
maybeAdaptEvaluatedResult(cel.getTypeProvider(), result).orElse(null);
312318
if (adaptedResult == null) {
313319
return Optional.empty();
314320
}
315321

316322
return Optional.of(astMutator.replaceSubtree(mutableAst, adaptedResult, node.id()));
317323
}
318324

319-
private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
325+
private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(
326+
CelTypeProvider typeProvider, Object result) {
320327
if (CelConstant.isConstantValue(result)) {
321328
return Optional.of(CelMutableExpr.ofConstant(CelConstant.ofObjectValue(result)));
322329
} else if (result instanceof Collection<?>) {
323330
Collection<?> collection = (Collection<?>) result;
324331
List<CelMutableExpr> listElements = new ArrayList<>();
325332
for (Object evaluatedElement : collection) {
326-
CelMutableExpr adaptedExpr = maybeAdaptEvaluatedResult(evaluatedElement).orElse(null);
333+
CelMutableExpr adaptedExpr =
334+
maybeAdaptEvaluatedResult(typeProvider, evaluatedElement).orElse(null);
327335
if (adaptedExpr == null) {
328336
return Optional.empty();
329337
}
@@ -335,11 +343,13 @@ private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
335343
Map<?, ?> map = (Map<?, ?>) result;
336344
List<CelMutableMap.Entry> mapEntries = new ArrayList<>();
337345
for (Map.Entry<?, ?> entry : map.entrySet()) {
338-
CelMutableExpr adaptedKey = maybeAdaptEvaluatedResult(entry.getKey()).orElse(null);
346+
CelMutableExpr adaptedKey =
347+
maybeAdaptEvaluatedResult(typeProvider, entry.getKey()).orElse(null);
339348
if (adaptedKey == null) {
340349
return Optional.empty();
341350
}
342-
CelMutableExpr adaptedValue = maybeAdaptEvaluatedResult(entry.getValue()).orElse(null);
351+
CelMutableExpr adaptedValue =
352+
maybeAdaptEvaluatedResult(typeProvider, entry.getValue()).orElse(null);
343353
if (adaptedValue == null) {
344354
return Optional.empty();
345355
}
@@ -364,14 +374,40 @@ private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
364374
CelMutableExpr.ofConstant(CelConstant.ofValue(timestampStrArg)));
365375

366376
return Optional.of(CelMutableExpr.ofCall(timestampCall));
377+
} else if (result instanceof Message) {
378+
Message message = (Message) result;
379+
Optional<CelType> typeOpt =
380+
typeProvider.findType(message.getDescriptorForType().getFullName());
381+
if (!typeOpt.isPresent() || !(typeOpt.get() instanceof StructType)) {
382+
return Optional.empty();
383+
}
384+
StructType structType = (StructType) typeOpt.get();
385+
List<CelMutableStruct.Entry> structEntries = new ArrayList<>();
386+
for (String fieldName : structType.fieldNames()) {
387+
FieldDescriptor fieldDescriptor = message.getDescriptorForType().findFieldByName(fieldName);
388+
if (fieldDescriptor == null) {
389+
continue;
390+
}
391+
if (!message.getAllFields().containsKey(fieldDescriptor)) {
392+
continue;
393+
}
394+
CelMutableExpr adaptedFieldExpr =
395+
maybeAdaptEvaluatedResult(typeProvider, message.getField(fieldDescriptor)).orElse(null);
396+
if (adaptedFieldExpr == null) {
397+
return Optional.empty();
398+
}
399+
structEntries.add(CelMutableStruct.Entry.create(0, fieldName, adaptedFieldExpr));
400+
}
401+
return Optional.of(
402+
CelMutableExpr.ofStruct(CelMutableStruct.create(structType.name(), structEntries)));
367403
}
368404

369405
// Evaluated result cannot be folded (e.g: unknowns)
370406
return Optional.empty();
371407
}
372408

373409
private Optional<CelMutableAst> maybeRewriteOptional(
374-
Optional<?> optResult, CelMutableAst mutableAst, CelMutableExpr expr) {
410+
CelTypeProvider typeProvider, Optional<?> optResult, CelMutableAst mutableAst, CelMutableExpr expr) {
375411
Object unwrappedResult = optResult.orElse(null);
376412
if (unwrappedResult == null) {
377413
if (isCallToFunction(expr, Function.OPTIONAL_NONE.getFunction())) {
@@ -387,7 +423,8 @@ private Optional<CelMutableAst> maybeRewriteOptional(
387423
return Optional.empty();
388424
}
389425

390-
CelMutableExpr adaptedResult = maybeAdaptEvaluatedResult(unwrappedResult).orElse(null);
426+
CelMutableExpr adaptedResult =
427+
maybeAdaptEvaluatedResult(typeProvider, unwrappedResult).orElse(null);
391428
if (adaptedResult == null) {
392429
// Evaluated result is not an adaptable constant. Leave the optional as is.
393430
return Optional.empty();

optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,28 @@ private static Cel setupEnv(CelBuilder celBuilder) {
195195
@TestParameters(
196196
"{source: 'TestAllTypes{single_nested_message: TestAllTypes.NestedMessage{bb:"
197197
+ " 42}}.single_nested_message.bb', expected: '42'}")
198+
@TestParameters(
199+
"{source: 'TestAllTypes{single_int32: 3}', "
200+
+ " expected: 'cel.expr.conformance.proto3.TestAllTypes{single_int32: 3}'}")
201+
@TestParameters(
202+
"{source: 'TestAllTypes{single_float: 1.5}', "
203+
+ " expected: 'cel.expr.conformance.proto3.TestAllTypes{single_float: 1.5}'}")
204+
@TestParameters(
205+
"{source: 'TestAllTypes{single_nested_message: TestAllTypes.NestedMessage{bb: 42}}', "
206+
+ " expected: 'cel.expr.conformance.proto3.TestAllTypes{single_nested_message:"
207+
+ " cel.expr.conformance.proto3.TestAllTypes.NestedMessage{bb: 42}}'}")
208+
@TestParameters(
209+
"{source: 'TestAllTypes{repeated_int32: [1, 2, 3]}', "
210+
+ " expected: 'cel.expr.conformance.proto3.TestAllTypes{repeated_int32: [1, 2, 3]}'}")
211+
@TestParameters(
212+
"{source: 'TestAllTypes{map_int32_int64: {1: 2}}', "
213+
+ " expected: 'cel.expr.conformance.proto3.TestAllTypes{map_int32_int64: {1: 2}}'}")
214+
@TestParameters(
215+
"{source: 'TestAllTypes{single_any: google.protobuf.Any{type_url:"
216+
+ " \"type.googleapis.com/google.protobuf.Int32Value\", value: b\"\\010\\001\"}}', "
217+
+ " expected: 'cel.expr.conformance.proto3.TestAllTypes{single_any:"
218+
+ " google.protobuf.Any{type_url:"
219+
+ " \"type.googleapis.com/google.protobuf.Int32Value\", value: b\"\\010\\001\"}}'}")
198220
@TestParameters("{source: '{\"a\": 1}[\"a\"]', expected: '1'}")
199221
@TestParameters("{source: '{\"a\": {\"b\": 2}}[\"a\"][\"b\"]', expected: '2'}")
200222
@TestParameters("{source: '{\"hello\": \"world\"}.hello == x', expected: '\"world\" == x'}")
@@ -228,6 +250,8 @@ private static Cel setupEnv(CelBuilder celBuilder) {
228250
@TestParameters("{source: 'true == false', expected: 'false'}")
229251
@TestParameters("{source: 'true == true', expected: 'true'}")
230252
@TestParameters("{source: 'false == true', expected: 'false'}")
253+
@TestParameters("{source: 'TestAllTypes{single_int64: 1 + 2 + 3}.single_int64', expected: '6'}")
254+
@TestParameters("{source: 'TestAllTypes{single_int64: 3}.single_int64', expected: '3'}")
231255
@TestParameters("{source: 'false == false', expected: 'true'}")
232256
@TestParameters("{source: '10 == 42', expected: 'false'}")
233257
@TestParameters("{source: '42 == 42', expected: 'true'}")
@@ -449,6 +473,38 @@ public void constantFold_addFoldableFunction_success() throws Exception {
449473
assertThat(CEL_UNPARSER.unparse(optimizedAst)).isEqualTo("true");
450474
}
451475

476+
@Test
477+
public void constantFold_functionReturningUnregisteredMessage_doesNotFold() throws Exception {
478+
Cel customCel =
479+
runtimeFlavor
480+
.builder()
481+
.addVar("x", SimpleType.DYN)
482+
.addFunctionDeclarations(
483+
CelFunctionDecl.newFunctionDeclaration(
484+
"get_unregistered_message",
485+
CelOverloadDecl.newGlobalOverload(
486+
"get_unregistered_message_overload", SimpleType.ANY)))
487+
.addFunctionBindings(
488+
CelFunctionBinding.from(
489+
"get_unregistered_message_overload",
490+
ImmutableList.of(),
491+
unused -> TestAllTypes.getDefaultInstance()))
492+
.build();
493+
ConstantFoldingOptions options =
494+
ConstantFoldingOptions.newBuilder()
495+
.addFoldableFunctions("get_unregistered_message")
496+
.build();
497+
CelOptimizer optimizer =
498+
CelOptimizerFactory.standardCelOptimizerBuilder(customCel)
499+
.addAstOptimizers(ConstantFoldingOptimizer.newInstance(options))
500+
.build();
501+
CelAbstractSyntaxTree ast = customCel.compile("get_unregistered_message()").getAst();
502+
503+
CelAbstractSyntaxTree optimizedAst = optimizer.optimize(ast);
504+
505+
assertThat(CEL_UNPARSER.unparse(optimizedAst)).isEqualTo("get_unregistered_message()");
506+
}
507+
452508
@Test
453509
public void constantFold_withExpectedResultTypeSet_success() throws Exception {
454510
Cel cel = runtimeFlavor.builder().setResultType(SimpleType.STRING).build();
@@ -561,10 +617,7 @@ public void iterationLimitReached_throws() throws Exception {
561617
Cel cel =
562618
runtimeFlavor
563619
.builder()
564-
.setOptions(
565-
CelOptions.current()
566-
.enableHeterogeneousNumericComparisons(true)
567-
.build())
620+
.setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build())
568621
.build();
569622
CelAbstractSyntaxTree ast = cel.compile("1 + 1").getAst();
570623
CelOptimizer optimizer =

0 commit comments

Comments
 (0)