diff --git a/optimizer/src/main/java/dev/cel/optimizer/BUILD.bazel b/optimizer/src/main/java/dev/cel/optimizer/BUILD.bazel index 954deb74..c86b4559 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/BUILD.bazel +++ b/optimizer/src/main/java/dev/cel/optimizer/BUILD.bazel @@ -88,6 +88,7 @@ java_library( "//common/ast", "//common/ast:expr_factory", "//common/navigation", + "//common/types:type_providers", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], diff --git a/optimizer/src/main/java/dev/cel/optimizer/MutableAst.java b/optimizer/src/main/java/dev/cel/optimizer/MutableAst.java index ae2f328e..1e67a4c7 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/MutableAst.java +++ b/optimizer/src/main/java/dev/cel/optimizer/MutableAst.java @@ -21,9 +21,10 @@ import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.collect.HashBasedTable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Table; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelSource; @@ -38,9 +39,13 @@ import dev.cel.common.navigation.CelNavigableAst; import dev.cel.common.navigation.CelNavigableExpr; import dev.cel.common.navigation.CelNavigableExpr.TraversalOrder; +import dev.cel.common.types.CelType; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Optional; +import java.util.stream.Collectors; /** MutableAst contains logic for mutating a {@link CelAbstractSyntaxTree}. */ @Immutable @@ -187,45 +192,112 @@ public CelAbstractSyntaxTree renumberIdsConsecutively(CelAbstractSyntaxTree ast) * *

The expression IDs are not modified when the identifier names are changed. * + *

Mangling occurs only if the iteration variable is referenced within the loop step. + * *

Iteration variables in comprehensions are numbered based on their comprehension nesting - * levels. Examples: + * levels and the iteration variable's type. Examples: * *

* * @param ast AST to mutate * @param newIdentPrefix Prefix to use for new identifier names. For example, providing @c will - * produce @c0, @c1, @c2... as new names. + * produce @c0:0, @c0:1, @c1:0, @c2:0... as new names. */ public MangledComprehensionAst mangleComprehensionIdentifierNames( CelAbstractSyntaxTree ast, String newIdentPrefix) { - int iterCount; CelNavigableAst newNavigableAst = CelNavigableAst.fromAst(ast); - ImmutableSet.Builder mangledComprehensionIdents = ImmutableSet.builder(); - for (iterCount = 0; iterCount < iterationLimit; iterCount++) { + LinkedHashMap comprehensionsToMangle = + newNavigableAst + .getRoot() + // This is important - mangling needs to happen bottom-up to avoid stepping over + // shadowed variables that are not part of the comprehension being mangled. + .allNodes(TraversalOrder.POST_ORDER) + .filter(node -> node.getKind().equals(Kind.COMPREHENSION)) + .filter(node -> !node.expr().comprehension().iterVar().startsWith(newIdentPrefix)) + .filter( + node -> { + // Ensure the iter_var is actually referenced in the loop_step. If it's not, we + // can skip mangling. + String iterVar = node.expr().comprehension().iterVar(); + return CelNavigableExpr.fromExpr(node.expr().comprehension().loopStep()) + .allNodes() + .anyMatch( + subNode -> subNode.expr().identOrDefault().name().contains(iterVar)); + }) + .collect( + Collectors.toMap( + k -> k, + v -> { + String iterVar = v.expr().comprehension().iterVar(); + long iterVarId = + CelNavigableExpr.fromExpr(v.expr().comprehension().loopStep()) + .allNodes() + .filter( + loopStepNode -> + loopStepNode.expr().identOrDefault().name().equals(iterVar)) + .map(CelNavigableExpr::id) + .findAny() + .orElseThrow( + () -> { + throw new NoSuchElementException( + "Expected iteration variable to exist in expr id: " + + v.id()); + }); + + return ast.getType(iterVarId) + .orElseThrow( + () -> + new NoSuchElementException( + "Checked type not present for: " + iterVarId)); + }, + (x, y) -> { + throw new IllegalStateException("Unexpected CelNavigableExpr collision"); + }, + LinkedHashMap::new)); + int iterCount = 0; + + // The map that we'll eventually return to the caller. + HashMap mangledIdentNamesToType = new HashMap<>(); + // Intermediary table used for the purposes of generating a unique mangled variable name. + Table comprehensionLevelToType = HashBasedTable.create(); + for (Entry comprehensionEntry : comprehensionsToMangle.entrySet()) { + iterCount++; + // Refetch the comprehension node as mutating the AST could have renumbered its IDs. CelNavigableExpr comprehensionNode = newNavigableAst .getRoot() - // This is important - mangling needs to happen bottom-up to avoid stepping over - // shadowed variables that are not part of the comprehension being mangled. .allNodes(TraversalOrder.POST_ORDER) .filter(node -> node.getKind().equals(Kind.COMPREHENSION)) .filter(node -> !node.expr().comprehension().iterVar().startsWith(newIdentPrefix)) .findAny() - .orElse(null); - if (comprehensionNode == null) { - break; - } + .orElseThrow( + () -> new NoSuchElementException("Failed to refetch mutated comprehension")); + CelType comprehensionEntryType = comprehensionEntry.getValue(); CelExpr.Builder comprehensionExpr = comprehensionNode.expr().toBuilder(); String iterVar = comprehensionExpr.comprehension().iterVar(); int comprehensionNestingLevel = countComprehensionNestingLevel(comprehensionNode); - String mangledVarName = newIdentPrefix + comprehensionNestingLevel; - mangledComprehensionIdents.add(mangledVarName); + String mangledVarName; + if (comprehensionLevelToType.contains(comprehensionNestingLevel, comprehensionEntryType)) { + mangledVarName = + comprehensionLevelToType.get(comprehensionNestingLevel, comprehensionEntryType); + } else { + // First time encountering the pair of . Generate a unique + // mangled variable name for this. + int uniqueTypeIdx = comprehensionLevelToType.row(comprehensionNestingLevel).size(); + mangledVarName = newIdentPrefix + comprehensionNestingLevel + ":" + uniqueTypeIdx; + comprehensionLevelToType.put( + comprehensionNestingLevel, comprehensionEntryType, mangledVarName); + } + mangledIdentNamesToType.put(mangledVarName, comprehensionEntryType); CelExpr.Builder mutatedComprehensionExpr = mangleIdentsInComprehensionExpr( @@ -254,7 +326,8 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( throw new IllegalStateException("Max iteration count reached."); } - return MangledComprehensionAst.of(newNavigableAst.getAst(), mangledComprehensionIdents.build()); + return MangledComprehensionAst.of( + newNavigableAst.getAst(), ImmutableMap.copyOf(mangledIdentNamesToType)); } /** @@ -588,11 +661,11 @@ public abstract static class MangledComprehensionAst { /** AST after the iteration variables have been mangled. */ public abstract CelAbstractSyntaxTree ast(); - /** Set of identifiers with the iteration variable mangled. */ - public abstract ImmutableSet mangledComprehensionIdents(); + /** Map containing the mangled identifier names to their types. */ + public abstract ImmutableMap mangledComprehensionIdents(); private static MangledComprehensionAst of( - CelAbstractSyntaxTree ast, ImmutableSet mangledComprehensionIdents) { + CelAbstractSyntaxTree ast, ImmutableMap mangledComprehensionIdents) { return new AutoValue_MutableAst_MangledComprehensionAst(ast, mangledComprehensionIdents); } } diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java index 28b5e02a..4933b90d 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java @@ -130,8 +130,6 @@ private CelAbstractSyntaxTree optimizeUsingCelBlock( navigableAst.getAst(), MANGLED_COMPREHENSION_IDENTIFIER_PREFIX); CelAbstractSyntaxTree astToModify = mangledComprehensionAst.ast(); CelSource sourceToModify = astToModify.getSource(); - ImmutableSet mangledIdentDecls = - newMangledIdentDecls(celBuilder, mangledComprehensionAst); int blockIdentifierIndex = 0; int iterCount; @@ -192,7 +190,11 @@ private CelAbstractSyntaxTree optimizeUsingCelBlock( // Add all mangled comprehension identifiers to the environment, so that the subexpressions can // retain context to them. - celBuilder.addVarDeclarations(mangledIdentDecls); + mangledComprehensionAst + .mangledComprehensionIdents() + .forEach( + (identName, type) -> + celBuilder.addVarDeclarations(CelVarDecl.newVarDeclaration(identName, type))); // Type-check all sub-expressions then add them as block identifiers to the CEL environment addBlockIdentsToEnv(celBuilder, subexpressions); @@ -260,41 +262,6 @@ private static void addBlockIdentsToEnv(CelBuilder celBuilder, List sub } } - private static ImmutableSet newMangledIdentDecls( - CelBuilder celBuilder, MangledComprehensionAst mangledComprehensionAst) { - if (mangledComprehensionAst.mangledComprehensionIdents().isEmpty()) { - return ImmutableSet.of(); - } - CelAbstractSyntaxTree ast = mangledComprehensionAst.ast(); - try { - ast = celBuilder.build().check(ast).getAst(); - } catch (CelValidationException e) { - throw new IllegalStateException("Failed to type-check mangled AST.", e); - } - - ImmutableSet.Builder mangledVarDecls = ImmutableSet.builder(); - for (String ident : mangledComprehensionAst.mangledComprehensionIdents()) { - CelExpr mangledIdentExpr = - CelNavigableAst.fromAst(ast) - .getRoot() - .allNodes() - .filter(node -> node.getKind().equals(Kind.IDENT)) - .map(CelNavigableExpr::expr) - .filter(expr -> expr.ident().name().equals(ident)) - .findAny() - .orElse(null); - if (mangledIdentExpr == null) { - break; - } - - CelType mangledIdentType = - ast.getType(mangledIdentExpr.id()).orElseThrow(() -> new NoSuchElementException("?")); - mangledVarDecls.add(CelVarDecl.newVarDeclaration(ident, mangledIdentType)); - } - - return mangledVarDecls.build(); - } - private CelAbstractSyntaxTree optimizeUsingCelBind(CelNavigableAst navigableAst) { CelAbstractSyntaxTree astToModify = mutableAst diff --git a/optimizer/src/test/java/dev/cel/optimizer/MutableAstTest.java b/optimizer/src/test/java/dev/cel/optimizer/MutableAstTest.java index 9c32520b..557302e4 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/MutableAstTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/MutableAstTest.java @@ -687,7 +687,7 @@ public void mangleComprehensionVariable_singleMacro() throws Exception { assertThat(mangledAst.getExpr().toString()) .isEqualTo( "COMPREHENSION [13] {\n" - + " iter_var: @c0\n" + + " iter_var: @c0:0\n" + " iter_range: {\n" + " CREATE_LIST [1] {\n" + " elements: {\n" @@ -722,7 +722,7 @@ public void mangleComprehensionVariable_singleMacro() throws Exception { + " name: __result__\n" + " }\n" + " IDENT [5] {\n" - + " name: @c0\n" + + " name: @c0:0\n" + " }\n" + " }\n" + " }\n" @@ -733,7 +733,7 @@ public void mangleComprehensionVariable_singleMacro() throws Exception { + " }\n" + " }\n" + "}"); - assertThat(CEL_UNPARSER.unparse(mangledAst)).isEqualTo("[false].exists(@c0, @c0)"); + assertThat(CEL_UNPARSER.unparse(mangledAst)).isEqualTo("[false].exists(@c0:0, @c0:0)"); assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval()).isEqualTo(false); assertConsistentMacroCalls(ast); } @@ -748,7 +748,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw assertThat(mangledAst.getExpr().toString()) .isEqualTo( "COMPREHENSION [27] {\n" - + " iter_var: @c0\n" + + " iter_var: @c0:0\n" + " iter_range: {\n" + " CREATE_LIST [1] {\n" + " elements: {\n" @@ -785,12 +785,12 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " name: __result__\n" + " }\n" + " COMPREHENSION [19] {\n" - + " iter_var: @c1\n" + + " iter_var: @c1:0\n" + " iter_range: {\n" + " CREATE_LIST [5] {\n" + " elements: {\n" + " IDENT [6] {\n" - + " name: @c0\n" + + " name: @c0:0\n" + " }\n" + " }\n" + " }\n" @@ -825,7 +825,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " function: _==_\n" + " args: {\n" + " IDENT [9] {\n" - + " name: @c1\n" + + " name: @c1:0\n" + " }\n" + " CONSTANT [11] { value: 1 }\n" + " }\n" @@ -850,7 +850,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + "}"); assertThat(CEL_UNPARSER.unparse(mangledAst)) - .isEqualTo("[x].exists(@c0, [@c0].exists(@c1, @c1 == 1))"); + .isEqualTo("[x].exists(@c0:0, [@c0:0].exists(@c1:0, @c1:0 == 1))"); assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval(ImmutableMap.of("x", 1))) .isEqualTo(true); assertConsistentMacroCalls(ast); 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 c907ef1f..e3861f73 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel @@ -7,7 +7,7 @@ java_library( testonly = 1, srcs = glob(["*.java"]), deps = [ - "//:java_truth", + # "//java/com/google/testing/testsize:annotations", "//bundle:cel", "//common", "//common:compiler_common", @@ -28,9 +28,10 @@ java_library( "//parser:operator", "//parser:unparser", "//runtime", - "@maven//:com_google_guava_guava", - "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:junit_junit", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "//:java_truth", + "@maven//:com_google_guava_guava", ], ) @@ -38,6 +39,7 @@ junit4_test_suites( name = "test_suites", sizes = [ "small", + "medium", ], src_dir = "src/test/java", deps = [":tests"], diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java index cf7cdd6d..4b99c325 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java @@ -23,6 +23,7 @@ import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; +// import com.google.testing.testsize.MediumTest; import dev.cel.bundle.Cel; import dev.cel.bundle.CelBuilder; import dev.cel.bundle.CelFactory; @@ -65,6 +66,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +// @MediumTest @RunWith(TestParameterInjector.class) public class SubexpressionOptimizerTest { @@ -478,15 +480,22 @@ private enum CseTestCase { // the same. "size([[1].exists(i, i > 0)]) + size([[1].exists(j, j > 0)]) + " + "size([[2].exists(k, k > 1)]) + size([[2].exists(l, l > 1)]) == 4", - "cel.bind(@r1, size([[2].exists(@c0, @c0 > 1)]), " - + "cel.bind(@r0, size([[1].exists(@c0, @c0 > 0)]), @r0 + @r0) + @r1 + @r1) == 4", - "cel.@block([size([[1].exists(@c0, @c0 > 0)]), size([[2].exists(@c0, @c0 > 1)])], @index0 +" - + " @index0 + @index1 + @index1 == 4)"), + "cel.bind(@r1, size([[2].exists(@c0:0, @c0:0 > 1)]), " + + "cel.bind(@r0, size([[1].exists(@c0:0, @c0:0 > 0)]), @r0 + @r0) + @r1 + @r1) == 4", + "cel.@block([size([[1].exists(@c0:0, @c0:0 > 0)]), size([[2].exists(@c0:0, @c0:0 > 1)])]," + + " @index0 + @index0 + @index1 + @index1 == 4)"), + MULTIPLE_MACROS_2( + "[[1].exists(i, i > 0)] + [[1].exists(j, j > 0)] + [['a'].exists(k, k == 'a')] +" + + " [['a'].exists(l, l == 'a')] == [true, true, true, true]", + "cel.bind(@r1, [[\"a\"].exists(@c0:1, @c0:1 == \"a\")], cel.bind(@r0, [[1].exists(@c0:0," + + " @c0:0 > 0)], @r0 + @r0) + @r1 + @r1) == [true, true, true, true]", + "cel.@block([[[1].exists(@c0:0, @c0:0 > 0)], [[\"a\"].exists(@c0:1, @c0:1 == \"a\")]]," + + " @index0 + @index0 + @index1 + @index1 == [true, true, true, true])"), NESTED_MACROS( "[1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]]", - "cel.bind(@r0, [1, 2, 3], @r0.map(@c0, @r0.map(@c1, @c1 + 1))) == " + "cel.bind(@r0, [1, 2, 3], @r0.map(@c0:0, @r0.map(@c1:0, @c1:0 + 1))) == " + "cel.bind(@r1, [2, 3, 4], [@r1, @r1, @r1])", - "cel.@block([[1, 2, 3], [2, 3, 4]], @index0.map(@c0, @index0.map(@c1, @c1 + 1)) ==" + "cel.@block([[1, 2, 3], [2, 3, 4]], @index0.map(@c0:0, @index0.map(@c1:0, @c1:0 + 1)) ==" + " [@index1, @index1, @index1])"), INCLUSION_LIST( "1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3]", @@ -500,17 +509,17 @@ private enum CseTestCase { "cel.@block([{true: false}], 2 in {\"a\": 1, 2: @index0, 3: @index0})"), MACRO_SHADOWED_VARIABLE( "[x - 1 > 3 ? x - 1 : 5].exists(x, x - 1 > 3) || x - 1 > 3", - "cel.bind(@r0, x - 1, cel.bind(@r1, @r0 > 3, [@r1 ? @r0 : 5].exists(@c0, @c0 - 1 > 3) ||" - + " @r1))", - "cel.@block([x - 1, @index0 > 3], [@index1 ? @index0 : 5].exists(@c0, @c0 - 1 > 3) ||" + "cel.bind(@r0, x - 1, cel.bind(@r1, @r0 > 3, [@r1 ? @r0 : 5].exists(@c0:0, @c0:0 - 1 > 3)" + + " || @r1))", + "cel.@block([x - 1, @index0 > 3], [@index1 ? @index0 : 5].exists(@c0:0, @c0:0 - 1 > 3) ||" + " @index1)"), MACRO_SHADOWED_VARIABLE_2( "size([\"foo\", \"bar\"].map(x, [x + x, x + x]).map(x, [x + x, x + x])) == 2", - "size([\"foo\", \"bar\"].map(@c1, cel.bind(@r0, @c1 + @c1, [@r0, @r0]))" - + ".map(@c0, cel.bind(@r1, @c0 + @c0, [@r1, @r1]))) == 2", - "cel.@block([@c1 + @c1, @c0 + @c0], " - + "size([\"foo\", \"bar\"].map(@c1, [@index0, @index0])" - + ".map(@c0, [@index1, @index1])) == 2)"), + "size([\"foo\", \"bar\"].map(@c1:0, cel.bind(@r0, @c1:0 + @c1:0, [@r0, @r0]))" + + ".map(@c0:0, cel.bind(@r1, @c0:0 + @c0:0, [@r1, @r1]))) == 2", + "cel.@block([@c1:0 + @c1:0, @c0:0 + @c0:0], " + + "size([\"foo\", \"bar\"].map(@c1:0, [@index0, @index0])" + + ".map(@c0:0, [@index1, @index1])) == 2)"), PRESENCE_TEST( "has({'a': true}.a) && {'a':true}['a']", "cel.bind(@r0, {\"a\": true}, has(@r0.a) && @r0[\"a\"])", @@ -747,10 +756,11 @@ public void celBlock_nestedComprehension_iterVarReferencedAcrossComprehensions() assertThat(CEL.createProgram(optimizedAst).eval()).isEqualTo(true); assertThat(CEL_UNPARSER.unparse(optimizedAst)) .isEqualTo( - "cel.@block([@c0 + @c0, [\"bar\"], @c0 + @c1, @index2 + @c2], [\"foo\"].map(@c0," - + " [@index1, [@index0, @index0]] + @index1.map(@c1, [@index2, [\"baz\"].map(@c2," - + " [@index3, @index2, @index3])])) == [[@index1, [\"foofoo\", \"foofoo\"]," - + " [\"foobar\", [[\"foobarbaz\", \"foobar\", \"foobarbaz\"]]]]])"); + "cel.@block([@c0:0 + @c0:0, [\"bar\"], @c0:0 + @c1:0, @index2 + @c2:0]," + + " [\"foo\"].map(@c0:0, [@index1, [@index0, @index0]] + @index1.map(@c1:0," + + " [@index2, [\"baz\"].map(@c2:0, [@index3, @index2, @index3])])) == [[@index1," + + " [\"foofoo\", \"foofoo\"], [\"foobar\", [[\"foobarbaz\", \"foobar\"," + + " \"foobarbaz\"]]]]])"); } @Test @@ -1014,9 +1024,9 @@ public void cse_withCelBind_largeNestedMacro() throws Exception { assertThat(CEL_UNPARSER.unparse(optimizedAst)) .isEqualTo( - "cel.bind(@r0, [1, 2, 3], cel.bind(@r1, size(@r0.map(@c0, @r0.map(@c1, @r0.map(@c2, " - + "@r0.map(@c3, @r0.map(@c4, @r0.map(@c5, @r0.map(@c6, @r0.map(@c7, @r0))))))))), " - + "@r1 + @r1 + @r1 + @r1 + @r1 + @r1 + @r1 + @r1 + @r1))"); + "cel.bind(@r0, [1, 2, 3], cel.bind(@r1, size(@r0.map(i, @r0.map(i, @r0.map(i," + + " @r0.map(i, @r0.map(i, @r0.map(i, @r0.map(i, @r0.map(i, @r0))))))))), @r1 + @r1" + + " + @r1 + @r1 + @r1 + @r1 + @r1 + @r1 + @r1))"); assertThat(CEL.createProgram(optimizedAst).eval()).isEqualTo(27); } @@ -1050,10 +1060,10 @@ public void cse_withCelBlock_largeNestedMacro() throws Exception { assertThat(CEL_UNPARSER.unparse(optimizedAst)) .isEqualTo( - "cel.@block([[1, 2, 3], size(@index0.map(@c0, @index0.map(@c1, @index0.map(@c2," - + " @index0.map(@c3, @index0.map(@c4, @index0.map(@c5, @index0.map(@c6," - + " @index0.map(@c7, @index0)))))))))], @index1 + @index1 + @index1 + @index1 +" - + " @index1 + @index1 + @index1 + @index1 + @index1)"); + "cel.@block([[1, 2, 3], size(@index0.map(i, @index0.map(i, @index0.map(i," + + " @index0.map(i, @index0.map(i, @index0.map(i, @index0.map(i, @index0.map(i," + + " @index0)))))))))], @index1 + @index1 + @index1 + @index1 + @index1 + @index1 +" + + " @index1 + @index1 + @index1)"); assertThat(CEL.createProgram(optimizedAst).eval()).isEqualTo(27); }