Skip to content

Commit f253306

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

3 files changed

Lines changed: 62 additions & 0 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ java_library(
4040
"//runtime:unknown_attributes",
4141
"@maven//:com_google_errorprone_error_prone_annotations",
4242
"@maven//:com_google_guava_guava",
43+
"@maven//:com_google_protobuf_protobuf_java",
4344
],
4445
)
4546

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

Lines changed: 50 additions & 0 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;
@@ -276,6 +278,38 @@ private static boolean areChildrenArgConstant(CelNavigableMutableExpr expr) {
276278
return false;
277279
}
278280

281+
/**
282+
* Checks whether the provided identifier node is a loop variable bound by a surrounding
283+
* comprehension. Identifiers existing strictly within loop initialization bounds (e.g. iterRange)
284+
* are not considered loop variables.
285+
*/
286+
private static boolean isComprehensionBoundLoopVariable(CelNavigableMutableExpr identNode) {
287+
String identName = identNode.expr().ident().name();
288+
CelNavigableMutableExpr child = identNode;
289+
CelNavigableMutableExpr parent = identNode.parent().orElse(null);
290+
291+
while (parent != null) {
292+
if (parent.getKind().equals(Kind.COMPREHENSION)) {
293+
CelMutableComprehension comprehension = parent.expr().comprehension();
294+
long childId = child.expr().id();
295+
296+
boolean isInitializationNode =
297+
childId == comprehension.iterRange().id() || childId == comprehension.accuInit().id();
298+
299+
if (!isInitializationNode) {
300+
if (identName.equals(comprehension.accuVar())
301+
|| identName.equals(comprehension.iterVar())
302+
|| identName.equals(comprehension.iterVar2())) {
303+
return true;
304+
}
305+
}
306+
}
307+
child = parent;
308+
parent = parent.parent().orElse(null);
309+
}
310+
return false;
311+
}
312+
279313
private static boolean isNestedComprehension(CelNavigableMutableExpr expr) {
280314
Optional<CelNavigableMutableExpr> maybeParent = expr.parent();
281315
while (maybeParent.isPresent()) {
@@ -364,6 +398,21 @@ private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
364398
CelMutableExpr.ofConstant(CelConstant.ofValue(timestampStrArg)));
365399

366400
return Optional.of(CelMutableExpr.ofCall(timestampCall));
401+
} else if (result instanceof Message) {
402+
Message message = (Message) result;
403+
List<CelMutableStruct.Entry> structEntries = new ArrayList<>();
404+
for (Map.Entry<FieldDescriptor, Object> entry : message.getAllFields().entrySet()) {
405+
CelMutableExpr adaptedFieldExpr = maybeAdaptEvaluatedResult(entry.getValue()).orElse(null);
406+
if (adaptedFieldExpr == null) {
407+
return Optional.empty();
408+
}
409+
structEntries.add(
410+
CelMutableStruct.Entry.create(0, entry.getKey().getName(), adaptedFieldExpr));
411+
}
412+
return Optional.of(
413+
CelMutableExpr.ofStruct(
414+
CelMutableStruct.create(
415+
message.getDescriptorForType().getFullName(), structEntries)));
367416
}
368417

369418
// Evaluated result cannot be folded (e.g: unknowns)
@@ -733,6 +782,7 @@ private static Object evaluateExpr(Cel cel, CelNavigableMutableExpr navigableMut
733782
navigableMutableExpr
734783
.allNodes()
735784
.filter(node -> node.getKind().equals(Kind.IDENT))
785+
.filter(node -> !isComprehensionBoundLoopVariable(node))
736786
.map(node -> node.expr().ident().name())
737787
.map(CelAttributePattern::fromQualifiedIdentifier)
738788
.collect(toImmutableList());

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,17 @@ private static Cel setupEnv(CelBuilder celBuilder) {
228228
@TestParameters("{source: 'true == false', expected: 'false'}")
229229
@TestParameters("{source: 'true == true', expected: 'true'}")
230230
@TestParameters("{source: 'false == true', expected: 'false'}")
231+
@TestParameters("{source: '[1, 2, 3].map(item, item * 2)', expected: '[2, 4, 6]'}")
232+
@TestParameters("{source: '[1, 2, 3].filter(item, item > 1)', expected: '[2, 3]'}")
233+
@TestParameters("{source: '[1, 2, 3].exists(item, item > 1)', expected: 'true'}")
234+
@TestParameters("{source: '[1, 2, 3].all(item, item > 1)', expected: 'false'}")
235+
@TestParameters(
236+
"{source: '[1, 2, x].map(item, item * 2)', expected: '[1, 2, x].map(item, item * 2)'}")
237+
@TestParameters(
238+
"{source: '[(x - 1 > 3) ? (x - 1) : 5].exists(x, x - 1 > 3)', expected: '[(x - 1 > 3) ? (x -"
239+
+ " 1) : 5].exists(x, x - 1 > 3)'}")
240+
@TestParameters("{source: 'TestAllTypes{single_int64: 1 + 2 + 3}.single_int64', expected: '6'}")
241+
@TestParameters("{source: 'TestAllTypes{single_int64: 3}.single_int64', expected: '3'}")
231242
@TestParameters("{source: 'false == false', expected: 'true'}")
232243
@TestParameters("{source: '10 == 42', expected: 'false'}")
233244
@TestParameters("{source: '42 == 42', expected: 'true'}")

0 commit comments

Comments
 (0)