Skip to content

Commit 735056b

Browse files
CEL Dev Teamcopybara-github
authored andcommitted
Port CEL-Go comprehension loop variable shadowing to CEL-Java
PiperOrigin-RevId: 944969366
1 parent 028e171 commit 735056b

4 files changed

Lines changed: 146 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: 78 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;
@@ -276,6 +281,38 @@ private static boolean areChildrenArgConstant(CelNavigableMutableExpr expr) {
276281
return false;
277282
}
278283

284+
/**
285+
* Checks whether the provided identifier node is a loop variable bound by a surrounding
286+
* comprehension. Identifiers existing strictly within loop initialization bounds (e.g. iterRange)
287+
* are not considered loop variables.
288+
*/
289+
private static boolean isComprehensionBoundLoopVariable(CelNavigableMutableExpr identNode) {
290+
String identName = identNode.expr().ident().name();
291+
CelNavigableMutableExpr child = identNode;
292+
CelNavigableMutableExpr parent = identNode.parent().orElse(null);
293+
294+
while (parent != null) {
295+
if (parent.getKind().equals(Kind.COMPREHENSION)) {
296+
CelMutableComprehension comprehension = parent.expr().comprehension();
297+
long childId = child.expr().id();
298+
299+
boolean isInitializationNode =
300+
childId == comprehension.iterRange().id() || childId == comprehension.accuInit().id();
301+
302+
if (!isInitializationNode) {
303+
if (identName.equals(comprehension.accuVar())
304+
|| identName.equals(comprehension.iterVar())
305+
|| identName.equals(comprehension.iterVar2())) {
306+
return true;
307+
}
308+
}
309+
}
310+
child = parent;
311+
parent = parent.parent().orElse(null);
312+
}
313+
return false;
314+
}
315+
279316
private static boolean isNestedComprehension(CelNavigableMutableExpr expr) {
280317
Optional<CelNavigableMutableExpr> maybeParent = expr.parent();
281318
while (maybeParent.isPresent()) {
@@ -305,25 +342,28 @@ private Optional<CelMutableAst> maybeFold(
305342
// ex2: optional.ofNonZeroValue(5) -> optional.of(5)
306343
if (result instanceof Optional<?>) {
307344
Optional<?> optResult = ((Optional<?>) result);
308-
return maybeRewriteOptional(optResult, mutableAst, node.expr());
345+
return maybeRewriteOptional(cel, optResult, mutableAst, node.expr());
309346
}
310347

311-
CelMutableExpr adaptedResult = maybeAdaptEvaluatedResult(result).orElse(null);
348+
CelMutableExpr adaptedResult =
349+
maybeAdaptEvaluatedResult(cel.getTypeProvider(), result).orElse(null);
312350
if (adaptedResult == null) {
313351
return Optional.empty();
314352
}
315353

316354
return Optional.of(astMutator.replaceSubtree(mutableAst, adaptedResult, node.id()));
317355
}
318356

319-
private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
357+
private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(
358+
CelTypeProvider typeProvider, Object result) {
320359
if (CelConstant.isConstantValue(result)) {
321360
return Optional.of(CelMutableExpr.ofConstant(CelConstant.ofObjectValue(result)));
322361
} else if (result instanceof Collection<?>) {
323362
Collection<?> collection = (Collection<?>) result;
324363
List<CelMutableExpr> listElements = new ArrayList<>();
325364
for (Object evaluatedElement : collection) {
326-
CelMutableExpr adaptedExpr = maybeAdaptEvaluatedResult(evaluatedElement).orElse(null);
365+
CelMutableExpr adaptedExpr =
366+
maybeAdaptEvaluatedResult(typeProvider, evaluatedElement).orElse(null);
327367
if (adaptedExpr == null) {
328368
return Optional.empty();
329369
}
@@ -335,11 +375,13 @@ private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
335375
Map<?, ?> map = (Map<?, ?>) result;
336376
List<CelMutableMap.Entry> mapEntries = new ArrayList<>();
337377
for (Map.Entry<?, ?> entry : map.entrySet()) {
338-
CelMutableExpr adaptedKey = maybeAdaptEvaluatedResult(entry.getKey()).orElse(null);
378+
CelMutableExpr adaptedKey =
379+
maybeAdaptEvaluatedResult(typeProvider, entry.getKey()).orElse(null);
339380
if (adaptedKey == null) {
340381
return Optional.empty();
341382
}
342-
CelMutableExpr adaptedValue = maybeAdaptEvaluatedResult(entry.getValue()).orElse(null);
383+
CelMutableExpr adaptedValue =
384+
maybeAdaptEvaluatedResult(typeProvider, entry.getValue()).orElse(null);
343385
if (adaptedValue == null) {
344386
return Optional.empty();
345387
}
@@ -364,14 +406,40 @@ private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
364406
CelMutableExpr.ofConstant(CelConstant.ofValue(timestampStrArg)));
365407

366408
return Optional.of(CelMutableExpr.ofCall(timestampCall));
409+
} else if (result instanceof Message) {
410+
Message message = (Message) result;
411+
Optional<CelType> typeOpt =
412+
typeProvider.findType(message.getDescriptorForType().getFullName());
413+
if (!typeOpt.isPresent() || !(typeOpt.get() instanceof StructType)) {
414+
return Optional.empty();
415+
}
416+
StructType structType = (StructType) typeOpt.get();
417+
List<CelMutableStruct.Entry> structEntries = new ArrayList<>();
418+
for (String fieldName : structType.fieldNames()) {
419+
FieldDescriptor fieldDescriptor = message.getDescriptorForType().findFieldByName(fieldName);
420+
if (fieldDescriptor == null) {
421+
continue;
422+
}
423+
if (!message.getAllFields().containsKey(fieldDescriptor)) {
424+
continue;
425+
}
426+
CelMutableExpr adaptedFieldExpr =
427+
maybeAdaptEvaluatedResult(typeProvider, message.getField(fieldDescriptor)).orElse(null);
428+
if (adaptedFieldExpr == null) {
429+
return Optional.empty();
430+
}
431+
structEntries.add(CelMutableStruct.Entry.create(0, fieldName, adaptedFieldExpr));
432+
}
433+
return Optional.of(
434+
CelMutableExpr.ofStruct(CelMutableStruct.create(structType.name(), structEntries)));
367435
}
368436

369437
// Evaluated result cannot be folded (e.g: unknowns)
370438
return Optional.empty();
371439
}
372440

373441
private Optional<CelMutableAst> maybeRewriteOptional(
374-
Optional<?> optResult, CelMutableAst mutableAst, CelMutableExpr expr) {
442+
Cel cel, Optional<?> optResult, CelMutableAst mutableAst, CelMutableExpr expr) {
375443
Object unwrappedResult = optResult.orElse(null);
376444
if (unwrappedResult == null) {
377445
if (isCallToFunction(expr, Function.OPTIONAL_NONE.getFunction())) {
@@ -387,7 +455,8 @@ private Optional<CelMutableAst> maybeRewriteOptional(
387455
return Optional.empty();
388456
}
389457

390-
CelMutableExpr adaptedResult = maybeAdaptEvaluatedResult(unwrappedResult).orElse(null);
458+
CelMutableExpr adaptedResult =
459+
maybeAdaptEvaluatedResult(cel.getTypeProvider(), unwrappedResult).orElse(null);
391460
if (adaptedResult == null) {
392461
// Evaluated result is not an adaptable constant. Leave the optional as is.
393462
return Optional.empty();
@@ -733,6 +802,7 @@ private static Object evaluateExpr(Cel cel, CelNavigableMutableExpr navigableMut
733802
navigableMutableExpr
734803
.allNodes()
735804
.filter(node -> node.getKind().equals(Kind.IDENT))
805+
.filter(node -> !isComprehensionBoundLoopVariable(node))
736806
.map(node -> node.expr().ident().name())
737807
.map(CelAttributePattern::fromQualifiedIdentifier)
738808
.collect(toImmutableList());

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: 65 additions & 0 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,17 @@ 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: '[1, 2, 3].map(item, item * 2)', expected: '[2, 4, 6]'}")
254+
@TestParameters("{source: '[1, 2, 3].filter(item, item > 1)', expected: '[2, 3]'}")
255+
@TestParameters("{source: '[1, 2, 3].exists(item, item > 1)', expected: 'true'}")
256+
@TestParameters("{source: '[1, 2, 3].all(item, item > 1)', expected: 'false'}")
257+
@TestParameters(
258+
"{source: '[1, 2, x].map(item, item * 2)', expected: '[1, 2, x].map(item, item * 2)'}")
259+
@TestParameters(
260+
"{source: '[(x - 1 > 3) ? (x - 1) : 5].exists(x, x - 1 > 3)', expected: '[(x - 1 > 3) ? (x -"
261+
+ " 1) : 5].exists(x, x - 1 > 3)'}")
262+
@TestParameters("{source: 'TestAllTypes{single_int64: 1 + 2 + 3}.single_int64', expected: '6'}")
263+
@TestParameters("{source: 'TestAllTypes{single_int64: 3}.single_int64', expected: '3'}")
231264
@TestParameters("{source: 'false == false', expected: 'true'}")
232265
@TestParameters("{source: '10 == 42', expected: 'false'}")
233266
@TestParameters("{source: '42 == 42', expected: 'true'}")
@@ -449,6 +482,38 @@ public void constantFold_addFoldableFunction_success() throws Exception {
449482
assertThat(CEL_UNPARSER.unparse(optimizedAst)).isEqualTo("true");
450483
}
451484

485+
@Test
486+
public void constantFold_functionReturningUnregisteredMessage_doesNotFold() throws Exception {
487+
Cel customCel =
488+
runtimeFlavor
489+
.builder()
490+
.addVar("x", SimpleType.DYN)
491+
.addFunctionDeclarations(
492+
CelFunctionDecl.newFunctionDeclaration(
493+
"get_unregistered_message",
494+
CelOverloadDecl.newGlobalOverload(
495+
"get_unregistered_message_overload", SimpleType.ANY)))
496+
.addFunctionBindings(
497+
CelFunctionBinding.from(
498+
"get_unregistered_message_overload",
499+
ImmutableList.of(),
500+
unused -> dev.cel.expr.conformance.proto3.TestAllTypes.getDefaultInstance()))
501+
.build();
502+
ConstantFoldingOptions options =
503+
ConstantFoldingOptions.newBuilder()
504+
.addFoldableFunctions("get_unregistered_message")
505+
.build();
506+
CelOptimizer optimizer =
507+
CelOptimizerFactory.standardCelOptimizerBuilder(customCel)
508+
.addAstOptimizers(ConstantFoldingOptimizer.newInstance(options))
509+
.build();
510+
CelAbstractSyntaxTree ast = customCel.compile("get_unregistered_message()").getAst();
511+
512+
CelAbstractSyntaxTree optimizedAst = optimizer.optimize(ast);
513+
514+
assertThat(CEL_UNPARSER.unparse(optimizedAst)).isEqualTo("get_unregistered_message()");
515+
}
516+
452517
@Test
453518
public void constantFold_withExpectedResultTypeSet_success() throws Exception {
454519
Cel cel = runtimeFlavor.builder().setResultType(SimpleType.STRING).build();

0 commit comments

Comments
 (0)