Skip to content

Commit eca997c

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 eca997c

4 files changed

Lines changed: 91 additions & 8 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: 38 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,25 @@ 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, optResult, mutableAst, node.expr());
309314
}
310315

311-
CelMutableExpr adaptedResult = maybeAdaptEvaluatedResult(result).orElse(null);
316+
CelMutableExpr adaptedResult = maybeAdaptEvaluatedResult(cel.getTypeProvider(), result).orElse(null);
312317
if (adaptedResult == null) {
313318
return Optional.empty();
314319
}
315320

316321
return Optional.of(astMutator.replaceSubtree(mutableAst, adaptedResult, node.id()));
317322
}
318323

319-
private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
324+
private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(CelTypeProvider typeProvider, Object result) {
320325
if (CelConstant.isConstantValue(result)) {
321326
return Optional.of(CelMutableExpr.ofConstant(CelConstant.ofObjectValue(result)));
322327
} else if (result instanceof Collection<?>) {
323328
Collection<?> collection = (Collection<?>) result;
324329
List<CelMutableExpr> listElements = new ArrayList<>();
325330
for (Object evaluatedElement : collection) {
326-
CelMutableExpr adaptedExpr = maybeAdaptEvaluatedResult(evaluatedElement).orElse(null);
331+
CelMutableExpr adaptedExpr = maybeAdaptEvaluatedResult(typeProvider, evaluatedElement).orElse(null);
327332
if (adaptedExpr == null) {
328333
return Optional.empty();
329334
}
@@ -335,11 +340,11 @@ private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
335340
Map<?, ?> map = (Map<?, ?>) result;
336341
List<CelMutableMap.Entry> mapEntries = new ArrayList<>();
337342
for (Map.Entry<?, ?> entry : map.entrySet()) {
338-
CelMutableExpr adaptedKey = maybeAdaptEvaluatedResult(entry.getKey()).orElse(null);
343+
CelMutableExpr adaptedKey = maybeAdaptEvaluatedResult(typeProvider, entry.getKey()).orElse(null);
339344
if (adaptedKey == null) {
340345
return Optional.empty();
341346
}
342-
CelMutableExpr adaptedValue = maybeAdaptEvaluatedResult(entry.getValue()).orElse(null);
347+
CelMutableExpr adaptedValue = maybeAdaptEvaluatedResult(typeProvider, entry.getValue()).orElse(null);
343348
if (adaptedValue == null) {
344349
return Optional.empty();
345350
}
@@ -364,14 +369,39 @@ private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
364369
CelMutableExpr.ofConstant(CelConstant.ofValue(timestampStrArg)));
365370

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

369399
// Evaluated result cannot be folded (e.g: unknowns)
370400
return Optional.empty();
371401
}
372402

373403
private Optional<CelMutableAst> maybeRewriteOptional(
374-
Optional<?> optResult, CelMutableAst mutableAst, CelMutableExpr expr) {
404+
Cel cel, Optional<?> optResult, CelMutableAst mutableAst, CelMutableExpr expr) {
375405
Object unwrappedResult = optResult.orElse(null);
376406
if (unwrappedResult == null) {
377407
if (isCallToFunction(expr, Function.OPTIONAL_NONE.getFunction())) {
@@ -387,7 +417,7 @@ private Optional<CelMutableAst> maybeRewriteOptional(
387417
return Optional.empty();
388418
}
389419

390-
CelMutableExpr adaptedResult = maybeAdaptEvaluatedResult(unwrappedResult).orElse(null);
420+
CelMutableExpr adaptedResult = maybeAdaptEvaluatedResult(cel.getTypeProvider(), unwrappedResult).orElse(null);
391421
if (adaptedResult == null) {
392422
// Evaluated result is not an adaptable constant. Leave the optional as is.
393423
return Optional.empty();

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ java_library(
4444
"//:java_truth",
4545
"@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
4646
"@maven//:com_google_guava_guava",
47+
"@maven//:com_google_protobuf_protobuf_java",
4748
],
4849
)
4950

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,27 @@ 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: cel.expr.conformance.proto3.TestAllTypes.NestedMessage{bb: 42}}'}")
207+
@TestParameters(
208+
"{source: 'TestAllTypes{repeated_int32: [1, 2, 3]}', "
209+
+ " expected: 'cel.expr.conformance.proto3.TestAllTypes{repeated_int32: [1, 2, 3]}'}")
210+
@TestParameters(
211+
"{source: 'TestAllTypes{map_int32_int64: {1: 2}}', "
212+
+ " expected: 'cel.expr.conformance.proto3.TestAllTypes{map_int32_int64: {1: 2}}'}")
213+
@TestParameters(
214+
"{source: 'TestAllTypes{single_any: google.protobuf.Any{type_url:"
215+
+ " \"type.googleapis.com/google.protobuf.Int32Value\", value: b\"\\010\\001\"}}', "
216+
+ " expected: 'cel.expr.conformance.proto3.TestAllTypes{single_any:"
217+
+ " google.protobuf.Any{type_url:"
218+
+ " \"type.googleapis.com/google.protobuf.Int32Value\", value: b\"\\010\\001\"}}'}")
198219
@TestParameters("{source: '{\"a\": 1}[\"a\"]', expected: '1'}")
199220
@TestParameters("{source: '{\"a\": {\"b\": 2}}[\"a\"][\"b\"]', expected: '2'}")
200221
@TestParameters("{source: '{\"hello\": \"world\"}.hello == x', expected: '\"world\" == x'}")
@@ -228,6 +249,8 @@ private static Cel setupEnv(CelBuilder celBuilder) {
228249
@TestParameters("{source: 'true == false', expected: 'false'}")
229250
@TestParameters("{source: 'true == true', expected: 'true'}")
230251
@TestParameters("{source: 'false == true', expected: 'false'}")
252+
@TestParameters("{source: 'TestAllTypes{single_int64: 1 + 2 + 3}.single_int64', expected: '6'}")
253+
@TestParameters("{source: 'TestAllTypes{single_int64: 3}.single_int64', expected: '3'}")
231254
@TestParameters("{source: 'false == false', expected: 'true'}")
232255
@TestParameters("{source: '10 == 42', expected: 'false'}")
233256
@TestParameters("{source: '42 == 42', expected: 'true'}")
@@ -449,6 +472,33 @@ public void constantFold_addFoldableFunction_success() throws Exception {
449472
assertThat(CEL_UNPARSER.unparse(optimizedAst)).isEqualTo("true");
450473
}
451474

475+
@Test
476+
public void constantFold_functionReturningUnregisteredMessage_doesNotFold() throws Exception {
477+
Cel customCel =
478+
runtimeFlavor
479+
.builder()
480+
.addVar("x", SimpleType.DYN)
481+
.addFunctionDeclarations(
482+
CelFunctionDecl.newFunctionDeclaration(
483+
"get_unregistered_message",
484+
CelOverloadDecl.newGlobalOverload("get_unregistered_message_overload", SimpleType.ANY)))
485+
.addFunctionBindings(
486+
CelFunctionBinding.from("get_unregistered_message_overload", ImmutableList.of(),
487+
unused -> dev.cel.expr.conformance.proto3.TestAllTypes.getDefaultInstance()))
488+
.build();
489+
ConstantFoldingOptions options =
490+
ConstantFoldingOptions.newBuilder().addFoldableFunctions("get_unregistered_message").build();
491+
CelOptimizer optimizer =
492+
CelOptimizerFactory.standardCelOptimizerBuilder(customCel)
493+
.addAstOptimizers(ConstantFoldingOptimizer.newInstance(options))
494+
.build();
495+
CelAbstractSyntaxTree ast = customCel.compile("get_unregistered_message()").getAst();
496+
497+
CelAbstractSyntaxTree optimizedAst = optimizer.optimize(ast);
498+
499+
assertThat(CEL_UNPARSER.unparse(optimizedAst)).isEqualTo("get_unregistered_message()");
500+
}
501+
452502
@Test
453503
public void constantFold_withExpectedResultTypeSet_success() throws Exception {
454504
Cel cel = runtimeFlavor.builder().setResultType(SimpleType.STRING).build();

0 commit comments

Comments
 (0)