From bcb02b4cb805faff9f87ec1df36a65f9f5c6fc0a Mon Sep 17 00:00:00 2001 From: Muhammad Askri Date: Thu, 4 Sep 2025 16:15:18 -0700 Subject: [PATCH 01/27] Renaming the CelComprehensionsExtensions docs to align more closely with lib implementation PiperOrigin-RevId: 803215288 --- extensions/src/main/java/dev/cel/extensions/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/src/main/java/dev/cel/extensions/README.md b/extensions/src/main/java/dev/cel/extensions/README.md index a5b65168c..10c5217e8 100644 --- a/extensions/src/main/java/dev/cel/extensions/README.md +++ b/extensions/src/main/java/dev/cel/extensions/README.md @@ -912,7 +912,8 @@ regex.extractAll('id:123, id:456', 'assa') == [] regex.extractAll('testuser@testdomain', '(.*)@([^.]*)') \\ Runtime Error multiple capture group ``` -## TwoVarComprehensions + +## Comprehensions TwoVarComprehensions introduces support for two-variable comprehensions. From 4a1ed0ffa52565dd90e715bb92bb7c61730cacd3 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 8 Sep 2025 13:22:50 -0700 Subject: [PATCH 02/27] Remove capability to serially increment mangled variable indices PiperOrigin-RevId: 804561557 --- .../java/dev/cel/optimizer/AstMutator.java | 37 +++---------- .../optimizers/SubexpressionOptimizer.java | 3 +- .../dev/cel/optimizer/AstMutatorTest.java | 53 +++++++++---------- 3 files changed, 35 insertions(+), 58 deletions(-) diff --git a/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java b/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java index 00c5c2e3a..9506a2f33 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java +++ b/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java @@ -203,14 +203,9 @@ public CelMutableAst renumberIdsConsecutively(CelMutableAst mutableAst) { * @param newIterVarPrefix Prefix to use for new iteration variable identifier name. For example, * providing @c will produce @c0:0, @c0:1, @c1:0, @c2:0... as new names. * @param newAccuVarPrefix Prefix to use for new accumulation variable identifier name. - * @param incrementSerially If true, indices for the mangled variables are incremented serially - * per occurrence regardless of their nesting level or its types. */ public MangledComprehensionAst mangleComprehensionIdentifierNames( - CelMutableAst ast, - String newIterVarPrefix, - String newAccuVarPrefix, - boolean incrementSerially) { + CelMutableAst ast, String newIterVarPrefix, String newAccuVarPrefix) { CelNavigableMutableAst navigableMutableAst = CelNavigableMutableAst.fromAst(ast); Predicate comprehensionIdentifierPredicate = x -> true; comprehensionIdentifierPredicate = @@ -301,29 +296,13 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( MangledComprehensionType comprehensionEntryType = comprehensionEntry.getValue(); CelMutableExpr comprehensionExpr = comprehensionNode.expr(); - MangledComprehensionName mangledComprehensionName; - if (incrementSerially) { - // In case of applying CSE via cascaded cel.binds, not only is mangling based on level/types - // meaningless (because all comprehensions are nested anyways, thus all indices would be - // uinque), - // it can lead to an erroneous result due to extracting a common subexpr with accu_var at - // the wrong scope. - // Example: "[1].exists(k, k > 1) && [2].exists(l, l > 1). The loop step for both branches - // are identical, but shouldn't be extracted. - String mangledIterVarName = newIterVarPrefix + ":" + iterCount; - String mangledResultName = newAccuVarPrefix + ":" + iterCount; - mangledComprehensionName = - MangledComprehensionName.of(mangledIterVarName, mangledResultName); - mangledIdentNamesToType.put(mangledComprehensionName, comprehensionEntry.getValue()); - } else { - mangledComprehensionName = - getMangledComprehensionName( - newIterVarPrefix, - newAccuVarPrefix, - comprehensionNode, - comprehensionLevelToType, - comprehensionEntryType); - } + MangledComprehensionName mangledComprehensionName = + getMangledComprehensionName( + newIterVarPrefix, + newAccuVarPrefix, + comprehensionNode, + comprehensionLevelToType, + comprehensionEntryType); mangledIdentNamesToType.put(mangledComprehensionName, comprehensionEntryType); String iterVar = comprehensionExpr.comprehension().iterVar(); 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 a39ff813a..9003e82d9 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java @@ -136,8 +136,7 @@ private OptimizationResult optimizeUsingCelBlock(CelAbstractSyntaxTree ast, Cel astMutator.mangleComprehensionIdentifierNames( astToModify, MANGLED_COMPREHENSION_ITER_VAR_PREFIX, - MANGLED_COMPREHENSION_ACCU_VAR_PREFIX, - /* incrementSerially= */ false); + MANGLED_COMPREHENSION_ACCU_VAR_PREFIX); astToModify = mangledComprehensionAst.mutableAst(); CelMutableSource sourceToModify = astToModify.source(); diff --git a/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java b/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java index 37641fac3..639315d0b 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java @@ -666,14 +666,14 @@ public void mangleComprehensionVariable_singleMacro() throws Exception { CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac", true) + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac") .mutableAst() .toParsedAst(); assertThat(mangledAst.getExpr().toString()) .isEqualTo( "COMPREHENSION [13] {\n" - + " iter_var: @it:0\n" + + " iter_var: @it:0:0\n" + " iter_range: {\n" + " LIST [1] {\n" + " elements: {\n" @@ -681,7 +681,7 @@ public void mangleComprehensionVariable_singleMacro() throws Exception { + " }\n" + " }\n" + " }\n" - + " accu_var: @ac:0\n" + + " accu_var: @ac:0:0\n" + " accu_init: {\n" + " CONSTANT [6] { value: false }\n" + " }\n" @@ -693,7 +693,7 @@ public void mangleComprehensionVariable_singleMacro() throws Exception { + " function: !_\n" + " args: {\n" + " IDENT [7] {\n" - + " name: @ac:0\n" + + " name: @ac:0:0\n" + " }\n" + " }\n" + " }\n" @@ -705,21 +705,21 @@ public void mangleComprehensionVariable_singleMacro() throws Exception { + " function: _||_\n" + " args: {\n" + " IDENT [10] {\n" - + " name: @ac:0\n" + + " name: @ac:0:0\n" + " }\n" + " IDENT [5] {\n" - + " name: @it:0\n" + + " name: @it:0:0\n" + " }\n" + " }\n" + " }\n" + " }\n" + " result: {\n" + " IDENT [12] {\n" - + " name: @ac:0\n" + + " name: @ac:0:0\n" + " }\n" + " }\n" + "}"); - assertThat(CEL_UNPARSER.unparse(mangledAst)).isEqualTo("[false].exists(@it:0, @it:0)"); + assertThat(CEL_UNPARSER.unparse(mangledAst)).isEqualTo("[false].exists(@it:0:0, @it:0:0)"); assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval()).isEqualTo(false); assertConsistentMacroCalls(ast); } @@ -734,7 +734,7 @@ public void mangleComprehensionVariable_adjacentMacros_sameIterVarTypes() throws CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac", false) + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac") .mutableAst() .toParsedAst(); @@ -756,7 +756,7 @@ public void mangleComprehensionVariable_adjacentMacros_differentIterVarTypes() t CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac", false) + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac") .mutableAst() .toParsedAst(); @@ -780,7 +780,7 @@ public void mangleComprehensionVariable_macroSourceDisabled_macroCallMapIsEmpty( CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac", true) + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac") .mutableAst() .toParsedAst(); @@ -793,14 +793,14 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac", true) + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac") .mutableAst() .toParsedAst(); assertThat(mangledAst.getExpr().toString()) .isEqualTo( "COMPREHENSION [27] {\n" - + " iter_var: @it:1\n" + + " iter_var: @it:0:0\n" + " iter_range: {\n" + " LIST [1] {\n" + " elements: {\n" @@ -810,7 +810,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " }\n" + " }\n" + " }\n" - + " accu_var: @ac:1\n" + + " accu_var: @ac:0:0\n" + " accu_init: {\n" + " CONSTANT [20] { value: false }\n" + " }\n" @@ -822,7 +822,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " function: !_\n" + " args: {\n" + " IDENT [21] {\n" - + " name: @ac:1\n" + + " name: @ac:0:0\n" + " }\n" + " }\n" + " }\n" @@ -834,20 +834,20 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " function: _||_\n" + " args: {\n" + " IDENT [24] {\n" - + " name: @ac:1\n" + + " name: @ac:0:0\n" + " }\n" + " COMPREHENSION [19] {\n" - + " iter_var: @it:0\n" + + " iter_var: @it:1:0\n" + " iter_range: {\n" + " LIST [5] {\n" + " elements: {\n" + " IDENT [6] {\n" - + " name: @it:1\n" + + " name: @it:0:0\n" + " }\n" + " }\n" + " }\n" + " }\n" - + " accu_var: @ac:0\n" + + " accu_var: @ac:1:0\n" + " accu_init: {\n" + " CONSTANT [12] { value: false }\n" + " }\n" @@ -859,7 +859,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " function: !_\n" + " args: {\n" + " IDENT [13] {\n" - + " name: @ac:0\n" + + " name: @ac:1:0\n" + " }\n" + " }\n" + " }\n" @@ -871,13 +871,13 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " function: _||_\n" + " args: {\n" + " IDENT [16] {\n" - + " name: @ac:0\n" + + " name: @ac:1:0\n" + " }\n" + " CALL [10] {\n" + " function: _==_\n" + " args: {\n" + " IDENT [9] {\n" - + " name: @it:0\n" + + " name: @it:1:0\n" + " }\n" + " CONSTANT [11] { value: 1 }\n" + " }\n" @@ -887,7 +887,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " }\n" + " result: {\n" + " IDENT [18] {\n" - + " name: @ac:0\n" + + " name: @ac:1:0\n" + " }\n" + " }\n" + " }\n" @@ -896,13 +896,12 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " }\n" + " result: {\n" + " IDENT [26] {\n" - + " name: @ac:1\n" + + " name: @ac:0:0\n" + " }\n" + " }\n" + "}"); - assertThat(CEL_UNPARSER.unparse(mangledAst)) - .isEqualTo("[x].exists(@it:1, [@it:1].exists(@it:0, @it:0 == 1))"); + .isEqualTo("[x].exists(@it:0:0, [@it:0:0].exists(@it:1:0, @it:1:0 == 1))"); assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval(ImmutableMap.of("x", 1))) .isEqualTo(true); assertConsistentMacroCalls(ast); @@ -914,7 +913,7 @@ public void mangleComprehensionVariable_hasMacro_noOp() throws Exception { CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac", true) + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac") .mutableAst() .toParsedAst(); From 0bb2f7295029ba4068a0bc90f416383159c10450 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 9 Sep 2025 12:01:30 -0700 Subject: [PATCH 03/27] Enhance CSE to handle two variable comprehensions PiperOrigin-RevId: 805007450 --- .../dev/cel/extensions/CelExtensions.java | 4 +- .../dev/cel/extensions/CelExtensionsTest.java | 3 +- .../java/dev/cel/optimizer/AstMutator.java | 103 +- .../optimizers/SubexpressionOptimizer.java | 28 +- .../dev/cel/optimizer/AstMutatorTest.java | 108 +- .../ConstantFoldingOptimizerTest.java | 9 +- .../SubexpressionOptimizerBaselineTest.java | 49 +- ...old_before_subexpression_unparsed.baseline | 120 ++ ...ion_ast_block_common_subexpr_only.baseline | 1340 +++++++++++- ...ssion_ast_block_recursion_depth_1.baseline | 1797 ++++++++++++++-- ...ssion_ast_block_recursion_depth_2.baseline | 1795 ++++++++++++++-- ...ssion_ast_block_recursion_depth_3.baseline | 1800 +++++++++++++++-- ...ssion_ast_block_recursion_depth_4.baseline | 1345 +++++++++++- ...ssion_ast_block_recursion_depth_5.baseline | 1408 ++++++++++++- ...ssion_ast_block_recursion_depth_6.baseline | 1385 ++++++++++++- ...ssion_ast_block_recursion_depth_7.baseline | 1352 ++++++++++++- ...ssion_ast_block_recursion_depth_8.baseline | 1347 +++++++++++- ...ssion_ast_block_recursion_depth_9.baseline | 1347 +++++++++++- .../resources/subexpression_unparsed.baseline | 120 ++ 19 files changed, 14647 insertions(+), 813 deletions(-) diff --git a/extensions/src/main/java/dev/cel/extensions/CelExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelExtensions.java index 5d70f8dfc..2d14ed118 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelExtensions.java @@ -338,7 +338,9 @@ public static ImmutableSet getAllFunctionNames() { stream(CelListsExtensions.Function.values()) .map(CelListsExtensions.Function::getFunction), stream(CelRegexExtensions.Function.values()) - .map(CelRegexExtensions.Function::getFunction)) + .map(CelRegexExtensions.Function::getFunction), + stream(CelComprehensionsExtensions.Function.values()) + .map(CelComprehensionsExtensions.Function::getFunction)) .collect(toImmutableSet()); } diff --git a/extensions/src/test/java/dev/cel/extensions/CelExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelExtensionsTest.java index db126f6ee..61922f70f 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelExtensionsTest.java @@ -187,6 +187,7 @@ public void getAllFunctionNames() { "lists.@sortByAssociatedKeys", "regex.replace", "regex.extract", - "regex.extractAll"); + "regex.extractAll", + "cel.@mapInsert"); } } diff --git a/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java b/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java index 9506a2f33..10ea36e48 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java +++ b/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java @@ -60,7 +60,7 @@ public final class AstMutator { private final long iterationLimit; /** - * Returns a new instance of a AST mutator with the iteration limit set. + * Returns a new instance of an AST mutator with the iteration limit set. * *

Mutation is performed by walking the existing AST until the expression node to replace is * found, then the new subtree is walked to complete the mutation. Visiting of each node @@ -205,15 +205,20 @@ public CelMutableAst renumberIdsConsecutively(CelMutableAst mutableAst) { * @param newAccuVarPrefix Prefix to use for new accumulation variable identifier name. */ public MangledComprehensionAst mangleComprehensionIdentifierNames( - CelMutableAst ast, String newIterVarPrefix, String newAccuVarPrefix) { + CelMutableAst ast, + String newIterVarPrefix, + String newIterVar2Prefix, + String newAccuVarPrefix) { CelNavigableMutableAst navigableMutableAst = CelNavigableMutableAst.fromAst(ast); Predicate comprehensionIdentifierPredicate = x -> true; comprehensionIdentifierPredicate = comprehensionIdentifierPredicate .and(node -> node.getKind().equals(Kind.COMPREHENSION)) - .and(node -> !node.expr().comprehension().iterVar().startsWith(newIterVarPrefix)) - .and(node -> !node.expr().comprehension().accuVar().startsWith(newAccuVarPrefix)); - + .and(node -> !node.expr().comprehension().iterVar().startsWith(newIterVarPrefix + ":")) + .and(node -> !node.expr().comprehension().accuVar().startsWith(newAccuVarPrefix + ":")) + .and( + node -> + !node.expr().comprehension().iterVar2().startsWith(newIterVar2Prefix + ":")); LinkedHashMap comprehensionsToMangle = navigableMutableAst .getRoot() @@ -226,13 +231,17 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( // Ensure the iter_var or the comprehension result is actually referenced in the // loop_step. If it's not, we can skip mangling. String iterVar = node.expr().comprehension().iterVar(); + String iterVar2 = node.expr().comprehension().iterVar2(); String result = node.expr().comprehension().result().ident().name(); return CelNavigableMutableExpr.fromExpr(node.expr().comprehension().loopStep()) .allNodes() .filter(subNode -> subNode.getKind().equals(Kind.IDENT)) .map(subNode -> subNode.expr().ident()) .anyMatch( - ident -> ident.name().contains(iterVar) || ident.name().contains(result)); + ident -> + ident.name().contains(iterVar) + || ident.name().contains(iterVar2) + || ident.name().contains(result)); }) .collect( Collectors.toMap( @@ -240,6 +249,7 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( v -> { CelMutableComprehension comprehension = v.expr().comprehension(); String iterVar = comprehension.iterVar(); + String iterVar2 = comprehension.iterVar2(); // Identifiers to mangle could be the iteration variable, comprehension // result or both, but at least one has to exist. // As an example, [1,2].map(i, 3) would result in optional.empty for iteration @@ -253,6 +263,16 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( && loopStepNode.expr().ident().name().equals(iterVar)) .map(CelNavigableMutableExpr::id) .findAny(); + Optional iterVar2Id = + CelNavigableMutableExpr.fromExpr(comprehension.loopStep()) + .allNodes() + .filter( + loopStepNode -> + !iterVar2.isEmpty() + && loopStepNode.getKind().equals(Kind.IDENT) + && loopStepNode.expr().ident().name().equals(iterVar2)) + .map(CelNavigableMutableExpr::id) + .findAny(); Optional iterVarType = iterVarId.map( id -> @@ -264,6 +284,17 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( "Checked type not present for iteration" + " variable: " + iterVarId))); + Optional iterVar2Type = + iterVar2Id.map( + id -> + navigableMutableAst + .getType(id) + .orElseThrow( + () -> + new NoSuchElementException( + "Checked type not present for iteration" + + " variable: " + + iterVar2Id))); CelType resultType = navigableMutableAst .getType(comprehension.result().id()) @@ -273,7 +304,7 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( "Result type was not present for the comprehension ID: " + comprehension.result().id())); - return MangledComprehensionType.of(iterVarType, resultType); + return MangledComprehensionType.of(iterVarType, iterVar2Type, resultType); }, (x, y) -> { throw new IllegalStateException( @@ -299,6 +330,7 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( MangledComprehensionName mangledComprehensionName = getMangledComprehensionName( newIterVarPrefix, + newIterVar2Prefix, newAccuVarPrefix, comprehensionNode, comprehensionLevelToType, @@ -306,12 +338,14 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( mangledIdentNamesToType.put(mangledComprehensionName, comprehensionEntryType); String iterVar = comprehensionExpr.comprehension().iterVar(); + String iterVar2 = comprehensionExpr.comprehension().iterVar2(); String accuVar = comprehensionExpr.comprehension().accuVar(); mutatedComprehensionExpr = mangleIdentsInComprehensionExpr( mutatedComprehensionExpr, comprehensionExpr, iterVar, + iterVar2, accuVar, mangledComprehensionName); // Repeat the mangling process for the macro source. @@ -320,6 +354,7 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( newSource, mutatedComprehensionExpr, iterVar, + iterVar2, mangledComprehensionName, comprehensionExpr.id()); iterCount++; @@ -339,6 +374,7 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( private static MangledComprehensionName getMangledComprehensionName( String newIterVarPrefix, + String newIterVar2Prefix, String newResultPrefix, CelNavigableMutableExpr comprehensionNode, Table comprehensionLevelToType, @@ -356,7 +392,11 @@ private static MangledComprehensionName getMangledComprehensionName( newIterVarPrefix + ":" + comprehensionNestingLevel + ":" + uniqueTypeIdx; String mangledResultName = newResultPrefix + ":" + comprehensionNestingLevel + ":" + uniqueTypeIdx; - mangledComprehensionName = MangledComprehensionName.of(mangledIterVarName, mangledResultName); + String mangledIterVar2Name = + newIterVar2Prefix + ":" + comprehensionNestingLevel + ":" + uniqueTypeIdx; + + mangledComprehensionName = + MangledComprehensionName.of(mangledIterVarName, mangledIterVar2Name, mangledResultName); comprehensionLevelToType.put( comprehensionNestingLevel, comprehensionEntryType, mangledComprehensionName); } @@ -509,6 +549,7 @@ private CelMutableExpr mangleIdentsInComprehensionExpr( CelMutableExpr root, CelMutableExpr comprehensionExpr, String originalIterVar, + String originalIterVar2, String originalAccuVar, MangledComprehensionName mangledComprehensionName) { CelMutableComprehension comprehension = comprehensionExpr.comprehension(); @@ -517,11 +558,18 @@ private CelMutableExpr mangleIdentsInComprehensionExpr( replaceIdentName(comprehensionExpr, originalAccuVar, mangledComprehensionName.resultName()); comprehension.setIterVar(mangledComprehensionName.iterVarName()); + // Most standard macros set accu_var as __result__, but not all (ex: cel.bind). if (comprehension.accuVar().equals(originalAccuVar)) { comprehension.setAccuVar(mangledComprehensionName.resultName()); } + if (!originalIterVar2.isEmpty()) { + comprehension.setIterVar2(mangledComprehensionName.iterVar2Name()); + replaceIdentName( + comprehension.loopStep(), originalIterVar2, mangledComprehensionName.iterVar2Name()); + } + return mutateExpr(NO_OP_ID_GENERATOR, root, comprehensionExpr, comprehensionExpr.id()); } @@ -560,6 +608,7 @@ private CelMutableSource mangleIdentsInMacroSource( CelMutableSource sourceBuilder, CelMutableExpr mutatedComprehensionExpr, String originalIterVar, + String originalIterVar2, MangledComprehensionName mangledComprehensionName, long originalComprehensionId) { if (!sourceBuilder.getMacroCalls().containsKey(originalComprehensionId)) { @@ -583,7 +632,6 @@ private CelMutableSource mangleIdentsInMacroSource( // macro call expression. CelMutableExpr identToMangle = macroExpr.call().args().get(0); if (identToMangle.ident().name().equals(originalIterVar)) { - // if (identToMangle.identOrDefault().name().equals(originalIterVar)) { macroExpr = mutateExpr( NO_OP_ID_GENERATOR, @@ -591,6 +639,18 @@ private CelMutableSource mangleIdentsInMacroSource( CelMutableExpr.ofIdent(mangledComprehensionName.iterVarName()), identToMangle.id()); } + if (!originalIterVar2.isEmpty()) { + // Similarly by convention, iter_var2 is always the second argument of the macro call. + identToMangle = macroExpr.call().args().get(1); + if (identToMangle.ident().name().equals(originalIterVar2)) { + macroExpr = + mutateExpr( + NO_OP_ID_GENERATOR, + macroExpr, + CelMutableExpr.ofIdent(mangledComprehensionName.iterVar2Name()), + identToMangle.id()); + } + } newSource.addMacroCalls(originalComprehensionId, macroExpr); @@ -794,7 +854,7 @@ private static void unwrapListArgumentsInMacroCallExpr( newMacroCall.addArgs( existingMacroCall.args().get(0)); // iter_var is first argument of the call by convention - CelMutableList extraneousList = null; + CelMutableList extraneousList; if (loopStepArgs.size() == 2) { extraneousList = loopStepArgs.get(1).list(); } else { @@ -874,14 +934,22 @@ private static MangledComprehensionAst of( @AutoValue public abstract static class MangledComprehensionType { - /** Type of iter_var */ + /** + * Type of iter_var. Empty if iter_var is not referenced in the expression anywhere (ex: "i" in + * "[1].exists(i, true)" + */ public abstract Optional iterVarType(); + /** Type of iter_var2. */ + public abstract Optional iterVar2Type(); + /** Type of comprehension result */ public abstract CelType resultType(); - private static MangledComprehensionType of(Optional iterVarType, CelType resultType) { - return new AutoValue_AstMutator_MangledComprehensionType(iterVarType, resultType); + private static MangledComprehensionType of( + Optional iterVarType, Optional iterVarType2, CelType resultType) { + return new AutoValue_AstMutator_MangledComprehensionType( + iterVarType, iterVarType2, resultType); } } @@ -895,11 +963,16 @@ public abstract static class MangledComprehensionName { /** Mangled name for iter_var */ public abstract String iterVarName(); + /** Mangled name for iter_var2 */ + public abstract String iterVar2Name(); + /** Mangled name for comprehension result */ public abstract String resultName(); - private static MangledComprehensionName of(String iterVarName, String resultName) { - return new AutoValue_AstMutator_MangledComprehensionName(iterVarName, resultName); + private static MangledComprehensionName of( + String iterVarName, String iterVar2Name, String resultName) { + return new AutoValue_AstMutator_MangledComprehensionName( + iterVarName, iterVar2Name, resultName); } } } 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 9003e82d9..eceb0bbe1 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java @@ -91,6 +91,7 @@ public class SubexpressionOptimizer implements CelAstOptimizer { new SubexpressionOptimizer(SubexpressionOptimizerOptions.newBuilder().build()); private static final String BIND_IDENTIFIER_PREFIX = "@r"; private static final String MANGLED_COMPREHENSION_ITER_VAR_PREFIX = "@it"; + private static final String MANGLED_COMPREHENSION_ITER_VAR2_PREFIX = "@it2"; private static final String MANGLED_COMPREHENSION_ACCU_VAR_PREFIX = "@ac"; private static final String CEL_BLOCK_FUNCTION = "cel.@block"; private static final String BLOCK_INDEX_PREFIX = "@index"; @@ -136,6 +137,7 @@ private OptimizationResult optimizeUsingCelBlock(CelAbstractSyntaxTree ast, Cel astMutator.mangleComprehensionIdentifierNames( astToModify, MANGLED_COMPREHENSION_ITER_VAR_PREFIX, + MANGLED_COMPREHENSION_ITER_VAR2_PREFIX, MANGLED_COMPREHENSION_ACCU_VAR_PREFIX); astToModify = mangledComprehensionAst.mutableAst(); CelMutableSource sourceToModify = astToModify.source(); @@ -196,6 +198,12 @@ private OptimizationResult optimizeUsingCelBlock(CelAbstractSyntaxTree ast, Cel iterVarType -> newVarDecls.add( CelVarDecl.newVarDeclaration(name.iterVarName(), iterVarType))); + type.iterVar2Type() + .ifPresent( + iterVar2Type -> + newVarDecls.add( + CelVarDecl.newVarDeclaration(name.iterVar2Name(), iterVar2Type))); + newVarDecls.add(CelVarDecl.newVarDeclaration(name.resultName(), type.resultType())); }); @@ -445,16 +453,16 @@ private boolean containsComprehensionIdentInSubexpr(CelNavigableMutableExpr navE navExpr .allNodes() .filter( - node -> - node.getKind().equals(Kind.IDENT) - && (node.expr() - .ident() - .name() - .startsWith(MANGLED_COMPREHENSION_ITER_VAR_PREFIX) - || node.expr() - .ident() - .name() - .startsWith(MANGLED_COMPREHENSION_ACCU_VAR_PREFIX))) + node -> { + if (!node.getKind().equals(Kind.IDENT)) { + return false; + } + + String identName = node.expr().ident().name(); + return identName.startsWith(MANGLED_COMPREHENSION_ITER_VAR_PREFIX) + || identName.startsWith(MANGLED_COMPREHENSION_ITER_VAR2_PREFIX) + || identName.startsWith(MANGLED_COMPREHENSION_ACCU_VAR_PREFIX); + }) .collect(toImmutableList()); if (comprehensionIdents.isEmpty()) { diff --git a/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java b/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java index 639315d0b..31b5da6fa 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java @@ -60,8 +60,9 @@ public class AstMutatorTest { .setStandardMacros(CelStandardMacro.STANDARD_MACROS) .setOptions(CelOptions.current().populateMacroCalls(true).build()) .addMessageTypes(TestAllTypes.getDescriptor()) - .addCompilerLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.bindings()) - .addRuntimeLibraries(CelOptionalLibrary.INSTANCE) + .addCompilerLibraries( + CelOptionalLibrary.INSTANCE, CelExtensions.bindings(), CelExtensions.comprehensions()) + .addRuntimeLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.comprehensions()) .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) .addVar("x", SimpleType.INT) @@ -666,7 +667,7 @@ public void mangleComprehensionVariable_singleMacro() throws Exception { CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac") + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") .mutableAst() .toParsedAst(); @@ -724,6 +725,72 @@ public void mangleComprehensionVariable_singleMacro() throws Exception { assertConsistentMacroCalls(ast); } + @Test + public void mangleComprehensionVariable_withTwoIterVars_singleMacro() throws Exception { + CelAbstractSyntaxTree ast = CEL.compile("[false].exists(i, v, v)").getAst(); + + CelAbstractSyntaxTree mangledAst = + AST_MUTATOR + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") + .mutableAst() + .toParsedAst(); + + assertThat(mangledAst.getExpr().toString()) + .isEqualTo( + "COMPREHENSION [14] {\n" + + " iter_var: @it:0:0\n" + + " iter_var2: @it2:0:0\n" + + " iter_range: {\n" + + " LIST [1] {\n" + + " elements: {\n" + + " CONSTANT [2] { value: false }\n" + + " }\n" + + " }\n" + + " }\n" + + " accu_var: @ac:0:0\n" + + " accu_init: {\n" + + " CONSTANT [7] { value: false }\n" + + " }\n" + + " loop_condition: {\n" + + " CALL [10] {\n" + + " function: @not_strictly_false\n" + + " args: {\n" + + " CALL [9] {\n" + + " function: !_\n" + + " args: {\n" + + " IDENT [8] {\n" + + " name: @ac:0:0\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " loop_step: {\n" + + " CALL [12] {\n" + + " function: _||_\n" + + " args: {\n" + + " IDENT [11] {\n" + + " name: @ac:0:0\n" + + " }\n" + + " IDENT [6] {\n" + + " name: @it2:0:0\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " result: {\n" + + " IDENT [13] {\n" + + " name: @ac:0:0\n" + + " }\n" + + " }\n" + + "}"); + assertThat(CEL_UNPARSER.unparse(mangledAst)) + .isEqualTo("[false].exists(@it:0:0, @it2:0:0, @it2:0:0)"); + assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval()).isEqualTo(false); + assertConsistentMacroCalls(ast); + } + @Test public void mangleComprehensionVariable_adjacentMacros_sameIterVarTypes() throws Exception { CelAbstractSyntaxTree ast = @@ -734,7 +801,7 @@ public void mangleComprehensionVariable_adjacentMacros_sameIterVarTypes() throws CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac") + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") .mutableAst() .toParsedAst(); @@ -746,6 +813,31 @@ public void mangleComprehensionVariable_adjacentMacros_sameIterVarTypes() throws assertConsistentMacroCalls(ast); } + @Test + public void mangleComprehensionVariable_withTwoIterVars_adjacentMacros_sameIterVarTypes() + throws Exception { + CelAbstractSyntaxTree ast = + CEL.compile( + // LHS & RHS are same expressions, just different var names + "[1, 2].transformMap(i, v, [1,2].transformMap(i, v, i)) == " + + "[1, 2].transformMap(x, y, [1,2].transformMap(x, y, x))") + .getAst(); + + CelAbstractSyntaxTree mangledAst = + AST_MUTATOR + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") + .mutableAst() + .toParsedAst(); + + assertThat(CEL_UNPARSER.unparse(mangledAst)) + .isEqualTo( + "[1, 2].transformMap(@it:0:0, @it2:0:0, [1, 2].transformMap(@it:1:0, @it2:1:0," + + " @it:1:0)) == [1, 2].transformMap(@it:0:0, @it2:0:0, [1," + + " 2].transformMap(@it:1:0, @it2:1:0, @it:1:0))"); + assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval()).isEqualTo(true); + assertConsistentMacroCalls(ast); + } + @Test public void mangleComprehensionVariable_adjacentMacros_differentIterVarTypes() throws Exception { CelAbstractSyntaxTree ast = @@ -756,7 +848,7 @@ public void mangleComprehensionVariable_adjacentMacros_differentIterVarTypes() t CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac") + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") .mutableAst() .toParsedAst(); @@ -780,7 +872,7 @@ public void mangleComprehensionVariable_macroSourceDisabled_macroCallMapIsEmpty( CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac") + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") .mutableAst() .toParsedAst(); @@ -793,7 +885,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac") + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") .mutableAst() .toParsedAst(); @@ -913,7 +1005,7 @@ public void mangleComprehensionVariable_hasMacro_noOp() throws Exception { CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac") + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") .mutableAst() .toParsedAst(); diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java index d5d204834..66c5d86d5 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java @@ -213,6 +213,10 @@ public void constantFold_success(String source, String expected) throws Exceptio @TestParameters( "{source: '[1, 2, 3].map(i, [1, 2, 3].map(j, i * j).filter(k, k % 2 == x))', " + "expected: '[1, 2, 3].map(i, [1, 2, 3].map(j, i * j).filter(k, k % 2 == x))'}") + @TestParameters("{source: '[1, 2, 3, 4].all(i, v, i < v)', expected: 'true'}") + @TestParameters( + "{source: '[1 + 1, 2 + 2, 3 + 3].all(i, v, i < 5 && v < x)', " + + "expected: '[2, 4, 6].all(i, v, i < 5 && v < x)'}") @TestParameters( "{source: '[{}, {\"a\": 1}, {\"b\": 2}].filter(m, has(m.a))', expected: '[{\"a\": 1}]'}") @TestParameters( @@ -241,8 +245,9 @@ public void constantFold_macros_macroCallMetadataPopulated(String source, String .addMessageTypes(TestAllTypes.getDescriptor()) .setStandardMacros(CelStandardMacro.STANDARD_MACROS) .setOptions(CelOptions.current().populateMacroCalls(true).build()) - .addCompilerLibraries(CelExtensions.bindings(), CelOptionalLibrary.INSTANCE) - .addRuntimeLibraries(CelOptionalLibrary.INSTANCE) + .addCompilerLibraries( + CelExtensions.bindings(), CelExtensions.optional(), CelExtensions.comprehensions()) + .addRuntimeLibraries(CelExtensions.optional(), CelExtensions.comprehensions()) .build(); CelOptimizer celOptimizer = CelOptimizerFactory.standardCelOptimizerBuilder(cel) diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java index 0b8a1e3f3..93f7778ec 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java @@ -35,7 +35,6 @@ import dev.cel.expr.conformance.proto3.NestedTestAllTypes; import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.extensions.CelExtensions; -import dev.cel.extensions.CelOptionalLibrary; import dev.cel.optimizer.CelOptimizer; import dev.cel.optimizer.CelOptimizerFactory; import dev.cel.optimizer.optimizers.SubexpressionOptimizer.SubexpressionOptimizerOptions; @@ -97,15 +96,13 @@ public void allOptimizers_producesSameEvaluationResult( throws Exception { skipBaselineVerification(); CelAbstractSyntaxTree ast = CEL.compile(cseTestCase.source).getAst(); - Object expectedEvalResult = - CEL.createProgram(ast) - .eval(ImmutableMap.of("msg", TEST_ALL_TYPES_INPUT, "x", 5L, "opt_x", Optional.of(5L))); + ImmutableMap inputMap = + ImmutableMap.of("msg", TEST_ALL_TYPES_INPUT, "x", 5L, "y", 6L, "opt_x", Optional.of(5L)); + Object expectedEvalResult = CEL.createProgram(ast).eval(inputMap); CelAbstractSyntaxTree optimizedAst = cseTestOptimizer.cseOptimizer.optimize(ast); - Object optimizedEvalResult = - CEL.createProgram(optimizedAst) - .eval(ImmutableMap.of("msg", TEST_ALL_TYPES_INPUT, "x", 5L, "opt_x", Optional.of(5L))); + Object optimizedEvalResult = CEL.createProgram(optimizedAst).eval(inputMap); assertThat(optimizedEvalResult).isEqualTo(expectedEvalResult); } @@ -119,7 +116,13 @@ public void subexpression_unparsed() throws Exception { boolean resultPrinted = false; for (CseTestOptimizer cseTestOptimizer : CseTestOptimizer.values()) { String optimizerName = cseTestOptimizer.name(); - CelAbstractSyntaxTree optimizedAst = cseTestOptimizer.cseOptimizer.optimize(ast); + CelAbstractSyntaxTree optimizedAst; + try { + optimizedAst = cseTestOptimizer.cseOptimizer.optimize(ast); + } catch (Exception e) { + testOutput().printf("[%s]: Optimization Error: %s", optimizerName, e); + continue; + } if (!resultPrinted) { Object optimizedEvalResult = CEL.createProgram(optimizedAst) @@ -264,8 +267,9 @@ private static CelBuilder newCelBuilder() { .setStandardMacros(CelStandardMacro.STANDARD_MACROS) .setOptions( CelOptions.current().enableTimestampEpoch(true).populateMacroCalls(true).build()) - .addCompilerLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.bindings()) - .addRuntimeLibraries(CelOptionalLibrary.INSTANCE) + .addCompilerLibraries( + CelExtensions.optional(), CelExtensions.bindings(), CelExtensions.comprehensions()) + .addRuntimeLibraries(CelExtensions.optional(), CelExtensions.comprehensions()) .addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( "pure_custom_func", @@ -279,6 +283,7 @@ private static CelBuilder newCelBuilder() { CelFunctionBinding.from("non_pure_custom_func_overload", Long.class, val -> val), CelFunctionBinding.from("pure_custom_func_overload", Long.class, val -> val)) .addVar("x", SimpleType.DYN) + .addVar("y", SimpleType.DYN) .addVar("opt_x", OptionalType.create(SimpleType.DYN)) .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); } @@ -399,16 +404,40 @@ private enum CseTestCase { MULTIPLE_MACROS_3( "[1].exists(i, i > 0) && [1].exists(j, j > 0) && [1].exists(k, k > 1) && [2].exists(l, l >" + " 1)"), + MULTIPLE_MACROS_COMP_V2_1( + // Note that all of these have different iteration variables, but they are still logically + // the same. + "size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + " + + "size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) " + + " == 4"), + MULTIPLE_MACROS_COMP_V2_2( + "[1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && " + + "[1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0)"), NESTED_MACROS("[1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]]"), NESTED_MACROS_2("[1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]]"), + NESTED_MACROS_COMP_V2_1( + "[1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == " + + "[[2, 4, 6], [2, 4, 6], [2, 4, 6]]"), + NESTED_MACROS_COMP_V2_2( + "[1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]]"), ADJACENT_NESTED_MACROS( "[1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [1,2,3].map(j, [1, 2, 3].map(j, j + 1))"), + ADJACENT_NESTED_MACROS_COMP_V2( + "[1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) ==" + + " [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1))"), INCLUSION_LIST("1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3]"), INCLUSION_MAP("2 in {'a': 1, 2: {true: false}, 3: {true: false}}"), MACRO_ITER_VAR_NOT_REFERENCED( "[1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]]"), + MACRO_ITER_VAR2_NOT_REFERENCED( + "[1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == " + + "[[[3, 4], [3, 4]], [[3, 4], [3, 4]]]"), MACRO_SHADOWED_VARIABLE("[x - 1 > 3 ? x - 1 : 5].exists(x, x - 1 > 3) || x - 1 > 3"), MACRO_SHADOWED_VARIABLE_2("[\"foo\", \"bar\"].map(x, [x + x, x + x]).map(x, [x + x, x + x])"), + MACRO_SHADOWED_VARIABLE_COMP_V2_1( + "[x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3"), + MACRO_SHADOWED_VARIABLE_COMP_V2_2( + "[\"foo\", \"bar\"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y])"), PRESENCE_TEST("has({'a': true}.a) && {'a':true}['a']"), PRESENCE_TEST_2("has({'a': true}.a) && has({'a': true}.a)"), PRESENCE_TEST_WITH_TERNARY( diff --git a/optimizer/src/test/resources/constfold_before_subexpression_unparsed.baseline b/optimizer/src/test/resources/constfold_before_subexpression_unparsed.baseline index 0d3f25bcf..cf6fc3e5f 100644 --- a/optimizer/src/test/resources/constfold_before_subexpression_unparsed.baseline +++ b/optimizer/src/test/resources/constfold_before_subexpression_unparsed.baseline @@ -298,6 +298,36 @@ Result: false [BLOCK_RECURSION_DEPTH_8]: false [BLOCK_RECURSION_DEPTH_9]: false +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +Result: true +[BLOCK_COMMON_SUBEXPR_ONLY]: true +[BLOCK_RECURSION_DEPTH_1]: true +[BLOCK_RECURSION_DEPTH_2]: true +[BLOCK_RECURSION_DEPTH_3]: true +[BLOCK_RECURSION_DEPTH_4]: true +[BLOCK_RECURSION_DEPTH_5]: true +[BLOCK_RECURSION_DEPTH_6]: true +[BLOCK_RECURSION_DEPTH_7]: true +[BLOCK_RECURSION_DEPTH_8]: true +[BLOCK_RECURSION_DEPTH_9]: true + +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +Result: false +[BLOCK_COMMON_SUBEXPR_ONLY]: false +[BLOCK_RECURSION_DEPTH_1]: false +[BLOCK_RECURSION_DEPTH_2]: false +[BLOCK_RECURSION_DEPTH_3]: false +[BLOCK_RECURSION_DEPTH_4]: false +[BLOCK_RECURSION_DEPTH_5]: false +[BLOCK_RECURSION_DEPTH_6]: false +[BLOCK_RECURSION_DEPTH_7]: false +[BLOCK_RECURSION_DEPTH_8]: false +[BLOCK_RECURSION_DEPTH_9]: false + Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]] =====> @@ -328,6 +358,36 @@ Result: true [BLOCK_RECURSION_DEPTH_8]: true [BLOCK_RECURSION_DEPTH_9]: true +Test case: NESTED_MACROS_COMP_V2_1 +Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] +=====> +Result: true +[BLOCK_COMMON_SUBEXPR_ONLY]: true +[BLOCK_RECURSION_DEPTH_1]: true +[BLOCK_RECURSION_DEPTH_2]: true +[BLOCK_RECURSION_DEPTH_3]: true +[BLOCK_RECURSION_DEPTH_4]: true +[BLOCK_RECURSION_DEPTH_5]: true +[BLOCK_RECURSION_DEPTH_6]: true +[BLOCK_RECURSION_DEPTH_7]: true +[BLOCK_RECURSION_DEPTH_8]: true +[BLOCK_RECURSION_DEPTH_9]: true + +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] +=====> +Result: true +[BLOCK_COMMON_SUBEXPR_ONLY]: true +[BLOCK_RECURSION_DEPTH_1]: true +[BLOCK_RECURSION_DEPTH_2]: true +[BLOCK_RECURSION_DEPTH_3]: true +[BLOCK_RECURSION_DEPTH_4]: true +[BLOCK_RECURSION_DEPTH_5]: true +[BLOCK_RECURSION_DEPTH_6]: true +[BLOCK_RECURSION_DEPTH_7]: true +[BLOCK_RECURSION_DEPTH_8]: true +[BLOCK_RECURSION_DEPTH_9]: true + Test case: ADJACENT_NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [1,2,3].map(j, [1, 2, 3].map(j, j + 1)) =====> @@ -343,6 +403,21 @@ Result: true [BLOCK_RECURSION_DEPTH_8]: true [BLOCK_RECURSION_DEPTH_9]: true +Test case: ADJACENT_NESTED_MACROS_COMP_V2 +Source: [1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) == [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1)) +=====> +Result: true +[BLOCK_COMMON_SUBEXPR_ONLY]: true +[BLOCK_RECURSION_DEPTH_1]: true +[BLOCK_RECURSION_DEPTH_2]: true +[BLOCK_RECURSION_DEPTH_3]: true +[BLOCK_RECURSION_DEPTH_4]: true +[BLOCK_RECURSION_DEPTH_5]: true +[BLOCK_RECURSION_DEPTH_6]: true +[BLOCK_RECURSION_DEPTH_7]: true +[BLOCK_RECURSION_DEPTH_8]: true +[BLOCK_RECURSION_DEPTH_9]: true + Test case: INCLUSION_LIST Source: 1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3] =====> @@ -388,6 +463,21 @@ Result: true [BLOCK_RECURSION_DEPTH_8]: true [BLOCK_RECURSION_DEPTH_9]: true +Test case: MACRO_ITER_VAR2_NOT_REFERENCED +Source: [1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +=====> +Result: true +[BLOCK_COMMON_SUBEXPR_ONLY]: true +[BLOCK_RECURSION_DEPTH_1]: true +[BLOCK_RECURSION_DEPTH_2]: true +[BLOCK_RECURSION_DEPTH_3]: true +[BLOCK_RECURSION_DEPTH_4]: true +[BLOCK_RECURSION_DEPTH_5]: true +[BLOCK_RECURSION_DEPTH_6]: true +[BLOCK_RECURSION_DEPTH_7]: true +[BLOCK_RECURSION_DEPTH_8]: true +[BLOCK_RECURSION_DEPTH_9]: true + Test case: MACRO_SHADOWED_VARIABLE Source: [x - 1 > 3 ? x - 1 : 5].exists(x, x - 1 > 3) || x - 1 > 3 =====> @@ -418,6 +508,36 @@ Result: [[[foofoo, foofoo, foofoo, foofoo], [foofoo, foofoo, foofoo, foofoo]], [ [BLOCK_RECURSION_DEPTH_8]: cel.@block([["foofoo", "foofoo", "foofoo", "foofoo"], ["barbar", "barbar", "barbar", "barbar"]], [[@index0, @index0], [@index1, @index1]]) [BLOCK_RECURSION_DEPTH_9]: cel.@block([["foofoo", "foofoo", "foofoo", "foofoo"], ["barbar", "barbar", "barbar", "barbar"]], [[@index0, @index0], [@index1, @index1]]) +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +Result: CelUnknownSet{attributes=[], unknownExprIds=[6]} +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([x - y - 1, @index0 > 3], [@index1 ? @index0 : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index1) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([x - y, @index0 - 1, @index1 > 3, @index2 ? @index1 : 5, [@index3]], @index4.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index2) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([x - y - 1, @index0 > 3, [@index1 ? @index0 : 5]], @index2.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index1) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([x - y - 1 > 3, @index0 ? (x - y - 1) : 5, [@index1]], @index2.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([x - y - 1 > 3, [@index0 ? (x - y - 1) : 5]], @index1.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) + +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +Result: {0=[0, [0, foofoo, 0, foofoo]], 1=[2, [2, barbar, 2, barbar]]} +[BLOCK_COMMON_SUBEXPR_ONLY]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[0, "foofoo", 0, "foofoo"], [0, @index0], [2, "barbar", 2, "barbar"], [2, @index2]], {0: @index1, 1: @index3}) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[0, [0, "foofoo", 0, "foofoo"]], [2, [2, "barbar", 2, "barbar"]]], {0: @index0, 1: @index1}) +[BLOCK_RECURSION_DEPTH_3]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_4]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_5]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_6]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_7]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_8]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_9]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} + Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> diff --git a/optimizer/src/test/resources/subexpression_ast_block_common_subexpr_only.baseline b/optimizer/src/test/resources/subexpression_ast_block_common_subexpr_only.baseline index 43782477c..c5a603bbd 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_common_subexpr_only.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_common_subexpr_only.baseline @@ -1647,6 +1647,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + CALL [20] { + function: size + args: { + LIST [21] { + elements: { + IDENT [22] { + name: @index0 + } + } + } + } + } + COMPREHENSION [23] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [24] { + elements: { + CONSTANT [25] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [26] { value: false } + } + loop_condition: { + CALL [27] { + function: @not_strictly_false + args: { + CALL [28] { + function: !_ + args: { + IDENT [29] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [30] { + function: _||_ + args: { + IDENT [31] { + name: @ac:0:0 + } + CALL [32] { + function: _&&_ + args: { + CALL [33] { + function: _>_ + args: { + IDENT [34] { + name: @it:0:0 + } + CONSTANT [35] { value: 1 } + } + } + CALL [36] { + function: _>=_ + args: { + IDENT [37] { + name: @it2:0:0 + } + CONSTANT [38] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [39] { + name: @ac:0:0 + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index2 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index1 + } + IDENT [48] { + name: @index1 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]] =====> @@ -1732,7 +2174,269 @@ CALL [1] { IDENT [27] { name: @it:1:0 } - CONSTANT [28] { value: 1 } + CONSTANT [28] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [30] { + name: @ac:0:0 + } + } + } + LIST [31] { + elements: { + IDENT [32] { + name: @index1 + } + IDENT [33] { + name: @index1 + } + IDENT [34] { + name: @index1 + } + } + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [31] { + function: _==_ + args: { + COMPREHENSION [30] { + iter_var: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: 1 } + CONSTANT [3] { value: 2 } + } + } + } + accu_var: @result + accu_init: { + LIST [24] { + elements: { + } + } + } + loop_condition: { + CONSTANT [25] { value: true } + } + loop_step: { + CALL [28] { + function: _+_ + args: { + IDENT [26] { + name: @result + } + LIST [27] { + elements: { + COMPREHENSION [23] { + iter_var: x + iter_range: { + LIST [6] { + elements: { + CONSTANT [7] { value: 1 } + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 3 } + } + } + } + accu_var: @result + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [21] { + function: _?_:_ + args: { + CALL [13] { + function: _==_ + args: { + IDENT [12] { + name: x + } + IDENT [14] { + name: y + } + } + } + CALL [19] { + function: _+_ + args: { + IDENT [17] { + name: @result + } + LIST [18] { + elements: { + IDENT [11] { + name: x + } + } + } + } + } + IDENT [20] { + name: @result + } + } + } + } + result: { + IDENT [22] { + name: @result + } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @result + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 2 } + } + } + } + } + } +} +Test case: NESTED_MACROS_COMP_V2_1 +Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } + } + } + } + } + CALL [11] { + function: _==_ + args: { + COMPREHENSION [12] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [13] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [14] { + elements: { + } + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [16] { + function: _+_ + args: { + IDENT [17] { + name: @ac:0:0 + } + LIST [18] { + elements: { + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [20] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [21] { + elements: { + } + } + } + loop_condition: { + CONSTANT [22] { value: true } + } + loop_step: { + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @ac:1:0 + } + LIST [25] { + elements: { + CALL [26] { + function: _+_ + args: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @it:1:0 + } + IDENT [29] { + name: @it2:1:0 + } + } + } + CONSTANT [30] { value: 1 } } } } @@ -1741,7 +2445,7 @@ CALL [1] { } } result: { - IDENT [29] { + IDENT [31] { name: @ac:1:0 } } @@ -1752,20 +2456,20 @@ CALL [1] { } } result: { - IDENT [30] { + IDENT [32] { name: @ac:0:0 } } } - LIST [31] { + LIST [33] { elements: { - IDENT [32] { + IDENT [34] { name: @index1 } - IDENT [33] { + IDENT [35] { name: @index1 } - IDENT [34] { + IDENT [36] { name: @index1 } } @@ -1774,14 +2478,15 @@ CALL [1] { } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> -CALL [31] { +CALL [36] { function: _==_ args: { - COMPREHENSION [30] { - iter_var: y + COMPREHENSION [35] { + iter_var: i + iter_var2: y iter_range: { LIST [1] { elements: { @@ -1792,82 +2497,98 @@ CALL [31] { } accu_var: @result accu_init: { - LIST [24] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [25] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [28] { + CALL [33] { function: _+_ args: { - IDENT [26] { + IDENT [31] { name: @result } - LIST [27] { + LIST [32] { elements: { - COMPREHENSION [23] { + COMPREHENSION [28] { iter_var: x iter_range: { - LIST [6] { + LIST [7] { elements: { - CONSTANT [7] { value: 1 } - CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } + CONSTANT [8] { value: 1 } + CONSTANT [9] { value: 2 } + CONSTANT [10] { value: 3 } } } } accu_var: @result accu_init: { - LIST [15] { + LIST [20] { elements: { } } } loop_condition: { - CONSTANT [16] { value: true } + CONSTANT [21] { value: true } } loop_step: { - CALL [21] { + CALL [26] { function: _?_:_ args: { - CALL [13] { - function: _==_ + CALL [16] { + function: _&&_ args: { - IDENT [12] { - name: x + CALL [14] { + function: _==_ + args: { + IDENT [13] { + name: x + } + IDENT [15] { + name: y + } + } } - IDENT [14] { - name: y + CALL [18] { + function: _<_ + args: { + IDENT [17] { + name: i + } + IDENT [19] { + name: y + } + } } } } - CALL [19] { + CALL [24] { function: _+_ args: { - IDENT [17] { + IDENT [22] { name: @result } - LIST [18] { + LIST [23] { elements: { - IDENT [11] { + IDENT [12] { name: x } } } } } - IDENT [20] { + IDENT [25] { name: @result } } } } result: { - IDENT [22] { + IDENT [27] { name: @result } } @@ -1878,21 +2599,21 @@ CALL [31] { } } result: { - IDENT [29] { + IDENT [34] { name: @result } } } - LIST [32] { + LIST [37] { elements: { - LIST [33] { + LIST [38] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [39] { value: 1 } } } - LIST [35] { + LIST [40] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [41] { value: 2 } } } } @@ -2012,6 +2733,125 @@ CALL [1] { } } } +Test case: ADJACENT_NESTED_MACROS_COMP_V2 +Source: [1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) == [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + COMPREHENSION [7] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [8] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [9] { + + } + } + loop_condition: { + CONSTANT [10] { value: true } + } + loop_step: { + CALL [11] { + function: cel.@mapInsert + args: { + IDENT [12] { + name: @ac:0:0 + } + IDENT [13] { + name: @it:0:0 + } + COMPREHENSION [14] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [15] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [16] { + + } + } + loop_condition: { + CONSTANT [17] { value: true } + } + loop_step: { + CALL [18] { + function: cel.@mapInsert + args: { + IDENT [19] { + name: @ac:1:0 + } + IDENT [20] { + name: @it:1:0 + } + CALL [21] { + function: _+_ + args: { + CALL [22] { + function: _+_ + args: { + IDENT [23] { + name: @it:1:0 + } + IDENT [24] { + name: @it2:1:0 + } + } + } + CONSTANT [25] { value: 1 } + } + } + } + } + } + result: { + IDENT [26] { + name: @ac:1:0 + } + } + } + } + } + } + result: { + IDENT [27] { + name: @ac:0:0 + } + } + } + } + } + CALL [28] { + function: _==_ + args: { + IDENT [29] { + name: @index1 + } + IDENT [30] { + name: @index1 + } + } + } + } +} Test case: INCLUSION_LIST Source: 1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3] =====> @@ -2117,24 +2957,150 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 - } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +Test case: MACRO_ITER_VAR_NOT_REFERENCED +Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + } + } + LIST [6] { + elements: { + CONSTANT [7] { value: 3 } + CONSTANT [8] { value: 4 } + } + } + LIST [9] { + elements: { + IDENT [10] { + name: @index1 + } + IDENT [11] { + name: @index1 + } + } + } + } + } + CALL [12] { + function: _==_ + args: { + COMPREHENSION [13] { + iter_var: @it:0:0 + iter_range: { + IDENT [14] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @ac:0:0 + } + LIST [19] { + elements: { + COMPREHENSION [20] { + iter_var: @it:1:0 + iter_range: { + IDENT [21] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [22] { + elements: { + } + } + } + loop_condition: { + CONSTANT [23] { value: true } + } + loop_step: { + CALL [24] { + function: _+_ + args: { + IDENT [25] { + name: @ac:1:0 + } + LIST [26] { + elements: { + IDENT [27] { + name: @index1 + } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:0:0 } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + } + LIST [30] { + elements: { + IDENT [31] { + name: @index2 } - value: { - IDENT [18] { - name: @index0 - } + IDENT [32] { + name: @index2 } } } @@ -2142,8 +3108,8 @@ CALL [1] { } } } -Test case: MACRO_ITER_VAR_NOT_REFERENCED -Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +Test case: MACRO_ITER_VAR2_NOT_REFERENCED +Source: [1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] =====> CALL [1] { function: cel.@block @@ -2179,6 +3145,7 @@ CALL [1] { args: { COMPREHENSION [13] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [14] { name: @index0 @@ -2205,6 +3172,7 @@ CALL [1] { elements: { COMPREHENSION [20] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [21] { name: @index0 @@ -2505,6 +3473,258 @@ COMPREHENSION [35] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _-_ + args: { + CALL [4] { + function: _-_ + args: { + IDENT [5] { + name: x + } + IDENT [6] { + name: y + } + } + } + CONSTANT [7] { value: 1 } + } + } + CALL [8] { + function: _>_ + args: { + IDENT [9] { + name: @index0 + } + CONSTANT [10] { value: 3 } + } + } + } + } + CALL [11] { + function: _||_ + args: { + COMPREHENSION [12] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [13] { + elements: { + CALL [14] { + function: _?_:_ + args: { + IDENT [15] { + name: @index1 + } + IDENT [16] { + name: @index0 + } + CONSTANT [17] { value: 5 } + } + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [18] { value: false } + } + loop_condition: { + CALL [19] { + function: @not_strictly_false + args: { + CALL [20] { + function: !_ + args: { + IDENT [21] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [22] { + function: _||_ + args: { + IDENT [23] { + name: @ac:0:0 + } + CALL [24] { + function: _>_ + args: { + CALL [25] { + function: _-_ + args: { + CALL [26] { + function: _-_ + args: { + IDENT [27] { + name: @it:0:0 + } + IDENT [28] { + name: @it2:0:0 + } + } + } + CONSTANT [29] { value: 1 } + } + } + CONSTANT [30] { value: 3 } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:0:0 + } + } + } + IDENT [32] { + name: @index1 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +COMPREHENSION [35] { + iter_var: x + iter_var2: y + iter_range: { + COMPREHENSION [19] { + iter_var: x + iter_var2: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: "foo" } + CONSTANT [3] { value: "bar" } + } + } + } + accu_var: @result + accu_init: { + MAP [14] { + + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [17] { + function: cel.@mapInsert + args: { + IDENT [16] { + name: @result + } + IDENT [5] { + name: x + } + LIST [7] { + elements: { + CALL [9] { + function: _+_ + args: { + IDENT [8] { + name: x + } + IDENT [10] { + name: x + } + } + } + CALL [12] { + function: _+_ + args: { + IDENT [11] { + name: y + } + IDENT [13] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @result + } + } + } + } + accu_var: @result + accu_init: { + MAP [30] { + + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [33] { + function: cel.@mapInsert + args: { + IDENT [32] { + name: @result + } + IDENT [21] { + name: x + } + LIST [23] { + elements: { + CALL [25] { + function: _+_ + args: { + IDENT [24] { + name: x + } + IDENT [26] { + name: x + } + } + } + CALL [28] { + function: _+_ + args: { + IDENT [27] { + name: y + } + IDENT [29] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @result + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_1.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_1.baseline index 5bce4d49a..e00dd4bdc 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_1.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_1.baseline @@ -2220,6 +2220,650 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + } + } + LIST [5] { + elements: { + CONSTANT [6] { value: 2 } + } + } + } + } + CALL [7] { + function: _==_ + args: { + CALL [8] { + function: _+_ + args: { + CALL [9] { + function: _+_ + args: { + CALL [10] { + function: _+_ + args: { + CALL [11] { + function: size + args: { + LIST [12] { + elements: { + COMPREHENSION [13] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [14] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [15] { value: false } + } + loop_condition: { + CALL [16] { + function: @not_strictly_false + args: { + CALL [17] { + function: !_ + args: { + IDENT [18] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [19] { + function: _||_ + args: { + IDENT [20] { + name: @ac:0:0 + } + CALL [21] { + function: _&&_ + args: { + CALL [22] { + function: _>_ + args: { + IDENT [23] { + name: @it:0:0 + } + CONSTANT [24] { value: 0 } + } + } + CALL [25] { + function: _>=_ + args: { + IDENT [26] { + name: @it2:0:0 + } + CONSTANT [27] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:0:0 + } + } + } + } + } + } + } + CALL [29] { + function: size + args: { + LIST [30] { + elements: { + COMPREHENSION [31] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [32] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [33] { value: false } + } + loop_condition: { + CALL [34] { + function: @not_strictly_false + args: { + CALL [35] { + function: !_ + args: { + IDENT [36] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [37] { + function: _||_ + args: { + IDENT [38] { + name: @ac:0:0 + } + CALL [39] { + function: _&&_ + args: { + CALL [40] { + function: _>_ + args: { + IDENT [41] { + name: @it:0:0 + } + CONSTANT [42] { value: 0 } + } + } + CALL [43] { + function: _>=_ + args: { + IDENT [44] { + name: @it2:0:0 + } + CONSTANT [45] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [46] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CALL [47] { + function: size + args: { + LIST [48] { + elements: { + COMPREHENSION [49] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [50] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [51] { value: false } + } + loop_condition: { + CALL [52] { + function: @not_strictly_false + args: { + CALL [53] { + function: !_ + args: { + IDENT [54] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [55] { + function: _||_ + args: { + IDENT [56] { + name: @ac:0:0 + } + CALL [57] { + function: _&&_ + args: { + CALL [58] { + function: _>_ + args: { + IDENT [59] { + name: @it:0:0 + } + CONSTANT [60] { value: 1 } + } + } + CALL [61] { + function: _>=_ + args: { + IDENT [62] { + name: @it2:0:0 + } + CONSTANT [63] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [64] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CALL [65] { + function: size + args: { + LIST [66] { + elements: { + COMPREHENSION [67] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [68] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [69] { value: false } + } + loop_condition: { + CALL [70] { + function: @not_strictly_false + args: { + CALL [71] { + function: !_ + args: { + IDENT [72] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [73] { + function: _||_ + args: { + IDENT [74] { + name: @ac:0:0 + } + CALL [75] { + function: _&&_ + args: { + CALL [76] { + function: _>_ + args: { + IDENT [77] { + name: @it:0:0 + } + CONSTANT [78] { value: 1 } + } + } + CALL [79] { + function: _>=_ + args: { + IDENT [80] { + name: @it2:0:0 + } + CONSTANT [81] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [82] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CONSTANT [83] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + } + } + LIST [5] { + elements: { + CONSTANT [6] { value: 2 } + } + } + } + } + CALL [7] { + function: _&&_ + args: { + CALL [8] { + function: _&&_ + args: { + COMPREHENSION [9] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [10] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [11] { value: false } + } + loop_condition: { + CALL [12] { + function: @not_strictly_false + args: { + CALL [13] { + function: !_ + args: { + IDENT [14] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [15] { + function: _||_ + args: { + IDENT [16] { + name: @ac:0:0 + } + CALL [17] { + function: _&&_ + args: { + CALL [18] { + function: _>_ + args: { + IDENT [19] { + name: @it:0:0 + } + CONSTANT [20] { value: 0 } + } + } + CALL [21] { + function: _>_ + args: { + IDENT [22] { + name: @it2:0:0 + } + CONSTANT [23] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [24] { + name: @ac:0:0 + } + } + } + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [26] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [27] { value: false } + } + loop_condition: { + CALL [28] { + function: @not_strictly_false + args: { + CALL [29] { + function: !_ + args: { + IDENT [30] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [31] { + function: _||_ + args: { + IDENT [32] { + name: @ac:0:0 + } + CALL [33] { + function: _&&_ + args: { + CALL [34] { + function: _>_ + args: { + IDENT [35] { + name: @it:0:0 + } + CONSTANT [36] { value: 0 } + } + } + CALL [37] { + function: _>_ + args: { + IDENT [38] { + name: @it2:0:0 + } + CONSTANT [39] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [40] { + name: @ac:0:0 + } + } + } + } + } + CALL [41] { + function: _&&_ + args: { + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [43] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [44] { value: false } + } + loop_condition: { + CALL [45] { + function: @not_strictly_false + args: { + CALL [46] { + function: !_ + args: { + IDENT [47] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [48] { + function: _||_ + args: { + IDENT [49] { + name: @ac:0:0 + } + CALL [50] { + function: _&&_ + args: { + CALL [51] { + function: _>_ + args: { + IDENT [52] { + name: @it:0:0 + } + CONSTANT [53] { value: 1 } + } + } + CALL [54] { + function: _>_ + args: { + IDENT [55] { + name: @it2:0:0 + } + CONSTANT [56] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [57] { + name: @ac:0:0 + } + } + } + COMPREHENSION [58] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [59] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [60] { value: false } + } + loop_condition: { + CALL [61] { + function: @not_strictly_false + args: { + CALL [62] { + function: !_ + args: { + IDENT [63] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [64] { + function: _||_ + args: { + IDENT [65] { + name: @ac:0:0 + } + CALL [66] { + function: _&&_ + args: { + CALL [67] { + function: _>_ + args: { + IDENT [68] { + name: @it:0:0 + } + CONSTANT [69] { value: 1 } + } + } + CALL [70] { + function: _>_ + args: { + IDENT [71] { + name: @it2:0:0 + } + CONSTANT [72] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [73] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]] =====> @@ -2238,8 +2882,287 @@ CALL [1] { LIST [7] { elements: { CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } - CONSTANT [10] { value: 4 } + CONSTANT [9] { value: 3 } + CONSTANT [10] { value: 4 } + } + } + LIST [11] { + elements: { + IDENT [12] { + name: @index1 + } + IDENT [13] { + name: @index1 + } + IDENT [14] { + name: @index1 + } + } + } + } + } + CALL [15] { + function: _==_ + args: { + COMPREHENSION [16] { + iter_var: @it:0:0 + iter_range: { + IDENT [17] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [18] { + elements: { + } + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: _+_ + args: { + IDENT [21] { + name: @ac:0:0 + } + LIST [22] { + elements: { + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:1:0 + } + LIST [29] { + elements: { + CALL [30] { + function: _+_ + args: { + IDENT [31] { + name: @it:1:0 + } + CONSTANT [32] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [33] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index2 + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + } + } + LIST [6] { + elements: { + CONSTANT [7] { value: 1 } + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 3 } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + } + } + LIST [12] { + elements: { + CONSTANT [13] { value: 2 } + } + } + LIST [14] { + elements: { + IDENT [15] { + name: @index2 + } + IDENT [16] { + name: @index3 + } + } + } + } + } + CALL [17] { + function: _==_ + args: { + COMPREHENSION [18] { + iter_var: @it:0:0 + iter_range: { + IDENT [19] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [20] { + elements: { + } + } + } + loop_condition: { + CONSTANT [21] { value: true } + } + loop_step: { + CALL [22] { + function: _+_ + args: { + IDENT [23] { + name: @ac:0:0 + } + LIST [24] { + elements: { + COMPREHENSION [25] { + iter_var: @it:1:0 + iter_range: { + IDENT [26] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [27] { + elements: { + } + } + } + loop_condition: { + CONSTANT [28] { value: true } + } + loop_step: { + CALL [29] { + function: _?_:_ + args: { + CALL [30] { + function: _==_ + args: { + IDENT [31] { + name: @it:1:0 + } + IDENT [32] { + name: @it:0:0 + } + } + } + CALL [33] { + function: _+_ + args: { + IDENT [34] { + name: @ac:1:0 + } + LIST [35] { + elements: { + IDENT [36] { + name: @it:1:0 + } + } + } + } + } + IDENT [37] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [38] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [39] { + name: @ac:0:0 + } + } + } + IDENT [40] { + name: @index4 + } + } + } + } +} +Test case: NESTED_MACROS_COMP_V2_1 +Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } } } LIST [11] { @@ -2262,6 +3185,7 @@ CALL [1] { args: { COMPREHENSION [16] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [17] { name: @index0 @@ -2288,6 +3212,7 @@ CALL [1] { elements: { COMPREHENSION [23] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [24] { name: @index0 @@ -2315,10 +3240,18 @@ CALL [1] { CALL [30] { function: _+_ args: { - IDENT [31] { - name: @it:1:0 + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it:1:0 + } + IDENT [33] { + name: @it2:1:0 + } + } } - CONSTANT [32] { value: 1 } + CONSTANT [34] { value: 1 } } } } @@ -2327,7 +3260,7 @@ CALL [1] { } } result: { - IDENT [33] { + IDENT [35] { name: @ac:1:0 } } @@ -2338,20 +3271,20 @@ CALL [1] { } } result: { - IDENT [34] { + IDENT [36] { name: @ac:0:0 } } } - IDENT [35] { + IDENT [37] { name: @index2 } } } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> CALL [1] { function: cel.@block @@ -2398,6 +3331,7 @@ CALL [1] { args: { COMPREHENSION [18] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [19] { name: @index0 @@ -2444,39 +3378,55 @@ CALL [1] { function: _?_:_ args: { CALL [30] { - function: _==_ + function: _&&_ args: { - IDENT [31] { - name: @it:1:0 + CALL [31] { + function: _==_ + args: { + IDENT [32] { + name: @it:1:0 + } + IDENT [33] { + name: @it2:0:0 + } + } } - IDENT [32] { - name: @it:0:0 + CALL [34] { + function: _<_ + args: { + IDENT [35] { + name: @it:0:0 + } + IDENT [36] { + name: @it2:0:0 + } + } } } } - CALL [33] { + CALL [37] { function: _+_ args: { - IDENT [34] { + IDENT [38] { name: @ac:1:0 } - LIST [35] { + LIST [39] { elements: { - IDENT [36] { + IDENT [40] { name: @it:1:0 } } } } } - IDENT [37] { + IDENT [41] { name: @ac:1:0 } } } } result: { - IDENT [38] { + IDENT [42] { name: @ac:1:0 } } @@ -2487,12 +3437,12 @@ CALL [1] { } } result: { - IDENT [39] { + IDENT [43] { name: @ac:0:0 } } } - IDENT [40] { + IDENT [44] { name: @index4 } } @@ -2551,45 +3501,241 @@ CALL [1] { IDENT [16] { name: @index0 } - } - accu_var: @ac:1:0 - accu_init: { - LIST [17] { - elements: { - } + } + accu_var: @ac:1:0 + accu_init: { + LIST [17] { + elements: { + } + } + } + loop_condition: { + CONSTANT [18] { value: true } + } + loop_step: { + CALL [19] { + function: _+_ + args: { + IDENT [20] { + name: @ac:1:0 + } + LIST [21] { + elements: { + CALL [22] { + function: _+_ + args: { + IDENT [23] { + name: @it:1:0 + } + CONSTANT [24] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [25] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [26] { + name: @ac:0:0 + } + } + } + COMPREHENSION [27] { + iter_var: @it:0:0 + iter_range: { + IDENT [28] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [29] { + elements: { + } + } + } + loop_condition: { + CONSTANT [30] { value: true } + } + loop_step: { + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @ac:0:0 + } + LIST [33] { + elements: { + COMPREHENSION [34] { + iter_var: @it:1:0 + iter_range: { + IDENT [35] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [36] { + elements: { + } + } + } + loop_condition: { + CONSTANT [37] { value: true } + } + loop_step: { + CALL [38] { + function: _+_ + args: { + IDENT [39] { + name: @ac:1:0 + } + LIST [40] { + elements: { + CALL [41] { + function: _+_ + args: { + IDENT [42] { + name: @it:1:0 + } + CONSTANT [43] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [44] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [45] { + name: @ac:0:0 + } + } + } + } + } + } +} +Test case: ADJACENT_NESTED_MACROS_COMP_V2 +Source: [1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) == [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + } + } + CALL [7] { + function: _==_ + args: { + COMPREHENSION [8] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [9] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [10] { + + } + } + loop_condition: { + CONSTANT [11] { value: true } + } + loop_step: { + CALL [12] { + function: cel.@mapInsert + args: { + IDENT [13] { + name: @ac:0:0 + } + IDENT [14] { + name: @it:0:0 + } + COMPREHENSION [15] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [16] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [17] { + + } + } + loop_condition: { + CONSTANT [18] { value: true } + } + loop_step: { + CALL [19] { + function: cel.@mapInsert + args: { + IDENT [20] { + name: @ac:1:0 + } + IDENT [21] { + name: @it:1:0 } - } - loop_condition: { - CONSTANT [18] { value: true } - } - loop_step: { - CALL [19] { + CALL [22] { function: _+_ args: { - IDENT [20] { - name: @ac:1:0 - } - LIST [21] { - elements: { - CALL [22] { - function: _+_ - args: { - IDENT [23] { - name: @it:1:0 - } - CONSTANT [24] { value: 1 } - } + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @it:1:0 + } + IDENT [25] { + name: @it2:1:0 } } } + CONSTANT [26] { value: 1 } } } } - result: { - IDENT [25] { - name: @ac:1:0 - } - } + } + } + result: { + IDENT [27] { + name: @ac:1:0 } } } @@ -2597,82 +3743,88 @@ CALL [1] { } } result: { - IDENT [26] { + IDENT [28] { name: @ac:0:0 } } } - COMPREHENSION [27] { + COMPREHENSION [29] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - IDENT [28] { + IDENT [30] { name: @index0 } } accu_var: @ac:0:0 accu_init: { - LIST [29] { - elements: { - } + MAP [31] { + } } loop_condition: { - CONSTANT [30] { value: true } + CONSTANT [32] { value: true } } loop_step: { - CALL [31] { - function: _+_ + CALL [33] { + function: cel.@mapInsert args: { - IDENT [32] { + IDENT [34] { name: @ac:0:0 } - LIST [33] { - elements: { - COMPREHENSION [34] { - iter_var: @it:1:0 - iter_range: { - IDENT [35] { - name: @index0 + IDENT [35] { + name: @it:0:0 + } + COMPREHENSION [36] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [37] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [38] { + + } + } + loop_condition: { + CONSTANT [39] { value: true } + } + loop_step: { + CALL [40] { + function: cel.@mapInsert + args: { + IDENT [41] { + name: @ac:1:0 } - } - accu_var: @ac:1:0 - accu_init: { - LIST [36] { - elements: { - } + IDENT [42] { + name: @it:1:0 } - } - loop_condition: { - CONSTANT [37] { value: true } - } - loop_step: { - CALL [38] { + CALL [43] { function: _+_ args: { - IDENT [39] { - name: @ac:1:0 - } - LIST [40] { - elements: { - CALL [41] { - function: _+_ - args: { - IDENT [42] { - name: @it:1:0 - } - CONSTANT [43] { value: 1 } - } + CALL [44] { + function: _+_ + args: { + IDENT [45] { + name: @it:1:0 + } + IDENT [46] { + name: @it2:1:0 } } } + CONSTANT [47] { value: 1 } } } } - result: { - IDENT [44] { - name: @ac:1:0 - } - } + } + } + result: { + IDENT [48] { + name: @ac:1:0 } } } @@ -2680,7 +3832,7 @@ CALL [1] { } } result: { - IDENT [45] { + IDENT [49] { name: @ac:0:0 } } @@ -2813,32 +3965,164 @@ CALL [1] { } } } - MAP_ENTRY [14] { - key: { - CONSTANT [15] { value: 3 } - } - value: { - IDENT [16] { - name: @index0 - } + MAP_ENTRY [14] { + key: { + CONSTANT [15] { value: 3 } + } + value: { + IDENT [16] { + name: @index0 + } + } + } + } + } + } + CALL [17] { + function: @in + args: { + CONSTANT [18] { value: 2 } + IDENT [19] { + name: @index1 + } + } + } + } +} +Test case: MACRO_ITER_VAR_NOT_REFERENCED +Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + } + } + LIST [6] { + elements: { + CONSTANT [7] { value: 3 } + CONSTANT [8] { value: 4 } + } + } + LIST [9] { + elements: { + IDENT [10] { + name: @index1 + } + IDENT [11] { + name: @index1 + } + } + } + LIST [12] { + elements: { + IDENT [13] { + name: @index1 + } + } + } + LIST [14] { + elements: { + IDENT [15] { + name: @index2 + } + IDENT [16] { + name: @index2 + } + } + } + } + } + CALL [17] { + function: _==_ + args: { + COMPREHENSION [18] { + iter_var: @it:0:0 + iter_range: { + IDENT [19] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [20] { + elements: { + } + } + } + loop_condition: { + CONSTANT [21] { value: true } + } + loop_step: { + CALL [22] { + function: _+_ + args: { + IDENT [23] { + name: @ac:0:0 + } + LIST [24] { + elements: { + COMPREHENSION [25] { + iter_var: @it:1:0 + iter_range: { + IDENT [26] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [27] { + elements: { + } + } + } + loop_condition: { + CONSTANT [28] { value: true } + } + loop_step: { + CALL [29] { + function: _+_ + args: { + IDENT [30] { + name: @ac:1:0 + } + IDENT [31] { + name: @index3 + } + } + } + } + result: { + IDENT [32] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [33] { + name: @ac:0:0 } } } - } - } - CALL [17] { - function: @in - args: { - CONSTANT [18] { value: 2 } - IDENT [19] { - name: @index1 + IDENT [34] { + name: @index4 } } } } } -Test case: MACRO_ITER_VAR_NOT_REFERENCED -Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +Test case: MACRO_ITER_VAR2_NOT_REFERENCED +Source: [1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] =====> CALL [1] { function: cel.@block @@ -2891,6 +4175,7 @@ CALL [1] { args: { COMPREHENSION [18] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [19] { name: @index0 @@ -2917,6 +4202,7 @@ CALL [1] { elements: { COMPREHENSION [25] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [26] { name: @index0 @@ -3224,6 +4510,279 @@ CALL [1] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _-_ + args: { + IDENT [4] { + name: x + } + IDENT [5] { + name: y + } + } + } + CALL [6] { + function: _-_ + args: { + IDENT [7] { + name: @index0 + } + CONSTANT [8] { value: 1 } + } + } + CALL [9] { + function: _>_ + args: { + IDENT [10] { + name: @index1 + } + CONSTANT [11] { value: 3 } + } + } + CALL [12] { + function: _?_:_ + args: { + IDENT [13] { + name: @index2 + } + IDENT [14] { + name: @index1 + } + CONSTANT [15] { value: 5 } + } + } + LIST [16] { + elements: { + IDENT [17] { + name: @index3 + } + } + } + } + } + CALL [18] { + function: _||_ + args: { + COMPREHENSION [19] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [20] { + name: @index4 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [21] { value: false } + } + loop_condition: { + CALL [22] { + function: @not_strictly_false + args: { + CALL [23] { + function: !_ + args: { + IDENT [24] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [25] { + function: _||_ + args: { + IDENT [26] { + name: @ac:0:0 + } + CALL [27] { + function: _>_ + args: { + CALL [28] { + function: _-_ + args: { + CALL [29] { + function: _-_ + args: { + IDENT [30] { + name: @it:0:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CONSTANT [32] { value: 1 } + } + } + CONSTANT [33] { value: 3 } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index2 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: "foo" } + CONSTANT [5] { value: "bar" } + } + } + } + } + COMPREHENSION [6] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + COMPREHENSION [7] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [8] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [9] { + + } + } + loop_condition: { + CONSTANT [10] { value: true } + } + loop_step: { + CALL [11] { + function: cel.@mapInsert + args: { + IDENT [12] { + name: @ac:1:0 + } + IDENT [13] { + name: @it:1:0 + } + LIST [14] { + elements: { + CALL [15] { + function: _+_ + args: { + IDENT [16] { + name: @it:1:0 + } + IDENT [17] { + name: @it:1:0 + } + } + } + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @it2:1:0 + } + IDENT [20] { + name: @it2:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [21] { + name: @ac:1:0 + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [22] { + + } + } + loop_condition: { + CONSTANT [23] { value: true } + } + loop_step: { + CALL [24] { + function: cel.@mapInsert + args: { + IDENT [25] { + name: @ac:0:0 + } + IDENT [26] { + name: @it:0:0 + } + LIST [27] { + elements: { + CALL [28] { + function: _+_ + args: { + IDENT [29] { + name: @it:0:0 + } + IDENT [30] { + name: @it:0:0 + } + } + } + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it2:0:0 + } + IDENT [33] { + name: @it2:0:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_2.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_2.baseline index 285b6183c..9d22f2e7c 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_2.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_2.baseline @@ -2041,6 +2041,650 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + } + } + LIST [5] { + elements: { + CONSTANT [6] { value: 2 } + } + } + } + } + CALL [7] { + function: _==_ + args: { + CALL [8] { + function: _+_ + args: { + CALL [9] { + function: _+_ + args: { + CALL [10] { + function: _+_ + args: { + CALL [11] { + function: size + args: { + LIST [12] { + elements: { + COMPREHENSION [13] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [14] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [15] { value: false } + } + loop_condition: { + CALL [16] { + function: @not_strictly_false + args: { + CALL [17] { + function: !_ + args: { + IDENT [18] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [19] { + function: _||_ + args: { + IDENT [20] { + name: @ac:0:0 + } + CALL [21] { + function: _&&_ + args: { + CALL [22] { + function: _>_ + args: { + IDENT [23] { + name: @it:0:0 + } + CONSTANT [24] { value: 0 } + } + } + CALL [25] { + function: _>=_ + args: { + IDENT [26] { + name: @it2:0:0 + } + CONSTANT [27] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:0:0 + } + } + } + } + } + } + } + CALL [29] { + function: size + args: { + LIST [30] { + elements: { + COMPREHENSION [31] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [32] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [33] { value: false } + } + loop_condition: { + CALL [34] { + function: @not_strictly_false + args: { + CALL [35] { + function: !_ + args: { + IDENT [36] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [37] { + function: _||_ + args: { + IDENT [38] { + name: @ac:0:0 + } + CALL [39] { + function: _&&_ + args: { + CALL [40] { + function: _>_ + args: { + IDENT [41] { + name: @it:0:0 + } + CONSTANT [42] { value: 0 } + } + } + CALL [43] { + function: _>=_ + args: { + IDENT [44] { + name: @it2:0:0 + } + CONSTANT [45] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [46] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CALL [47] { + function: size + args: { + LIST [48] { + elements: { + COMPREHENSION [49] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [50] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [51] { value: false } + } + loop_condition: { + CALL [52] { + function: @not_strictly_false + args: { + CALL [53] { + function: !_ + args: { + IDENT [54] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [55] { + function: _||_ + args: { + IDENT [56] { + name: @ac:0:0 + } + CALL [57] { + function: _&&_ + args: { + CALL [58] { + function: _>_ + args: { + IDENT [59] { + name: @it:0:0 + } + CONSTANT [60] { value: 1 } + } + } + CALL [61] { + function: _>=_ + args: { + IDENT [62] { + name: @it2:0:0 + } + CONSTANT [63] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [64] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CALL [65] { + function: size + args: { + LIST [66] { + elements: { + COMPREHENSION [67] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [68] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [69] { value: false } + } + loop_condition: { + CALL [70] { + function: @not_strictly_false + args: { + CALL [71] { + function: !_ + args: { + IDENT [72] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [73] { + function: _||_ + args: { + IDENT [74] { + name: @ac:0:0 + } + CALL [75] { + function: _&&_ + args: { + CALL [76] { + function: _>_ + args: { + IDENT [77] { + name: @it:0:0 + } + CONSTANT [78] { value: 1 } + } + } + CALL [79] { + function: _>=_ + args: { + IDENT [80] { + name: @it2:0:0 + } + CONSTANT [81] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [82] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CONSTANT [83] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + } + } + LIST [5] { + elements: { + CONSTANT [6] { value: 2 } + } + } + } + } + CALL [7] { + function: _&&_ + args: { + CALL [8] { + function: _&&_ + args: { + COMPREHENSION [9] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [10] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [11] { value: false } + } + loop_condition: { + CALL [12] { + function: @not_strictly_false + args: { + CALL [13] { + function: !_ + args: { + IDENT [14] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [15] { + function: _||_ + args: { + IDENT [16] { + name: @ac:0:0 + } + CALL [17] { + function: _&&_ + args: { + CALL [18] { + function: _>_ + args: { + IDENT [19] { + name: @it:0:0 + } + CONSTANT [20] { value: 0 } + } + } + CALL [21] { + function: _>_ + args: { + IDENT [22] { + name: @it2:0:0 + } + CONSTANT [23] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [24] { + name: @ac:0:0 + } + } + } + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [26] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [27] { value: false } + } + loop_condition: { + CALL [28] { + function: @not_strictly_false + args: { + CALL [29] { + function: !_ + args: { + IDENT [30] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [31] { + function: _||_ + args: { + IDENT [32] { + name: @ac:0:0 + } + CALL [33] { + function: _&&_ + args: { + CALL [34] { + function: _>_ + args: { + IDENT [35] { + name: @it:0:0 + } + CONSTANT [36] { value: 0 } + } + } + CALL [37] { + function: _>_ + args: { + IDENT [38] { + name: @it2:0:0 + } + CONSTANT [39] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [40] { + name: @ac:0:0 + } + } + } + } + } + CALL [41] { + function: _&&_ + args: { + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [43] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [44] { value: false } + } + loop_condition: { + CALL [45] { + function: @not_strictly_false + args: { + CALL [46] { + function: !_ + args: { + IDENT [47] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [48] { + function: _||_ + args: { + IDENT [49] { + name: @ac:0:0 + } + CALL [50] { + function: _&&_ + args: { + CALL [51] { + function: _>_ + args: { + IDENT [52] { + name: @it:0:0 + } + CONSTANT [53] { value: 1 } + } + } + CALL [54] { + function: _>_ + args: { + IDENT [55] { + name: @it2:0:0 + } + CONSTANT [56] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [57] { + name: @ac:0:0 + } + } + } + COMPREHENSION [58] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [59] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [60] { value: false } + } + loop_condition: { + CALL [61] { + function: @not_strictly_false + args: { + CALL [62] { + function: !_ + args: { + IDENT [63] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [64] { + function: _||_ + args: { + IDENT [65] { + name: @ac:0:0 + } + CALL [66] { + function: _&&_ + args: { + CALL [67] { + function: _>_ + args: { + IDENT [68] { + name: @it:0:0 + } + CONSTANT [69] { value: 1 } + } + } + CALL [70] { + function: _>_ + args: { + IDENT [71] { + name: @it2:0:0 + } + CONSTANT [72] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [73] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]] =====> @@ -2059,8 +2703,281 @@ CALL [1] { LIST [7] { elements: { CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } - CONSTANT [10] { value: 4 } + CONSTANT [9] { value: 3 } + CONSTANT [10] { value: 4 } + } + } + LIST [11] { + elements: { + IDENT [12] { + name: @index1 + } + IDENT [13] { + name: @index1 + } + IDENT [14] { + name: @index1 + } + } + } + } + } + CALL [15] { + function: _==_ + args: { + COMPREHENSION [16] { + iter_var: @it:0:0 + iter_range: { + IDENT [17] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [18] { + elements: { + } + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: _+_ + args: { + IDENT [21] { + name: @ac:0:0 + } + LIST [22] { + elements: { + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:1:0 + } + LIST [29] { + elements: { + CALL [30] { + function: _+_ + args: { + IDENT [31] { + name: @it:1:0 + } + CONSTANT [32] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [33] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index2 + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + LIST [6] { + elements: { + CONSTANT [7] { value: 2 } + } + } + } + } + LIST [8] { + elements: { + CONSTANT [9] { value: 1 } + CONSTANT [10] { value: 2 } + } + } + LIST [11] { + elements: { + CONSTANT [12] { value: 1 } + CONSTANT [13] { value: 2 } + CONSTANT [14] { value: 3 } + } + } + } + } + CALL [15] { + function: _==_ + args: { + COMPREHENSION [16] { + iter_var: @it:0:0 + iter_range: { + IDENT [17] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [18] { + elements: { + } + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: _+_ + args: { + IDENT [21] { + name: @ac:0:0 + } + LIST [22] { + elements: { + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index2 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _?_:_ + args: { + CALL [28] { + function: _==_ + args: { + IDENT [29] { + name: @it:1:0 + } + IDENT [30] { + name: @it:0:0 + } + } + } + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @ac:1:0 + } + LIST [33] { + elements: { + IDENT [34] { + name: @it:1:0 + } + } + } + } + } + IDENT [35] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [36] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [37] { + name: @ac:0:0 + } + } + } + IDENT [38] { + name: @index0 + } + } + } + } +} +Test case: NESTED_MACROS_COMP_V2_1 +Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } } } LIST [11] { @@ -2083,6 +3000,7 @@ CALL [1] { args: { COMPREHENSION [16] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [17] { name: @index0 @@ -2109,6 +3027,7 @@ CALL [1] { elements: { COMPREHENSION [23] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [24] { name: @index0 @@ -2136,10 +3055,18 @@ CALL [1] { CALL [30] { function: _+_ args: { - IDENT [31] { - name: @it:1:0 + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it:1:0 + } + IDENT [33] { + name: @it2:1:0 + } + } } - CONSTANT [32] { value: 1 } + CONSTANT [34] { value: 1 } } } } @@ -2148,7 +3075,7 @@ CALL [1] { } } result: { - IDENT [33] { + IDENT [35] { name: @ac:1:0 } } @@ -2159,20 +3086,20 @@ CALL [1] { } } result: { - IDENT [34] { + IDENT [36] { name: @ac:0:0 } } } - IDENT [35] { + IDENT [37] { name: @index2 } } } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> CALL [1] { function: cel.@block @@ -2213,6 +3140,7 @@ CALL [1] { args: { COMPREHENSION [16] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [17] { name: @index1 @@ -2259,39 +3187,55 @@ CALL [1] { function: _?_:_ args: { CALL [28] { - function: _==_ + function: _&&_ args: { - IDENT [29] { - name: @it:1:0 + CALL [29] { + function: _==_ + args: { + IDENT [30] { + name: @it:1:0 + } + IDENT [31] { + name: @it2:0:0 + } + } } - IDENT [30] { - name: @it:0:0 + CALL [32] { + function: _<_ + args: { + IDENT [33] { + name: @it:0:0 + } + IDENT [34] { + name: @it2:0:0 + } + } } } } - CALL [31] { + CALL [35] { function: _+_ args: { - IDENT [32] { + IDENT [36] { name: @ac:1:0 } - LIST [33] { + LIST [37] { elements: { - IDENT [34] { + IDENT [38] { name: @it:1:0 } } } } } - IDENT [35] { + IDENT [39] { name: @ac:1:0 } } } } result: { - IDENT [36] { + IDENT [40] { name: @ac:1:0 } } @@ -2302,12 +3246,12 @@ CALL [1] { } } result: { - IDENT [37] { + IDENT [41] { name: @ac:0:0 } } } - IDENT [38] { + IDENT [42] { name: @index0 } } @@ -2353,58 +3297,254 @@ CALL [1] { } loop_step: { CALL [12] { - function: _+_ + function: _+_ + args: { + IDENT [13] { + name: @ac:0:0 + } + LIST [14] { + elements: { + COMPREHENSION [15] { + iter_var: @it:1:0 + iter_range: { + IDENT [16] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [17] { + elements: { + } + } + } + loop_condition: { + CONSTANT [18] { value: true } + } + loop_step: { + CALL [19] { + function: _+_ + args: { + IDENT [20] { + name: @ac:1:0 + } + LIST [21] { + elements: { + CALL [22] { + function: _+_ + args: { + IDENT [23] { + name: @it:1:0 + } + CONSTANT [24] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [25] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [26] { + name: @ac:0:0 + } + } + } + COMPREHENSION [27] { + iter_var: @it:0:0 + iter_range: { + IDENT [28] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [29] { + elements: { + } + } + } + loop_condition: { + CONSTANT [30] { value: true } + } + loop_step: { + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @ac:0:0 + } + LIST [33] { + elements: { + COMPREHENSION [34] { + iter_var: @it:1:0 + iter_range: { + IDENT [35] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [36] { + elements: { + } + } + } + loop_condition: { + CONSTANT [37] { value: true } + } + loop_step: { + CALL [38] { + function: _+_ + args: { + IDENT [39] { + name: @ac:1:0 + } + LIST [40] { + elements: { + CALL [41] { + function: _+_ + args: { + IDENT [42] { + name: @it:1:0 + } + CONSTANT [43] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [44] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [45] { + name: @ac:0:0 + } + } + } + } + } + } +} +Test case: ADJACENT_NESTED_MACROS_COMP_V2 +Source: [1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) == [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + } + } + CALL [7] { + function: _==_ + args: { + COMPREHENSION [8] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [9] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [10] { + + } + } + loop_condition: { + CONSTANT [11] { value: true } + } + loop_step: { + CALL [12] { + function: cel.@mapInsert args: { IDENT [13] { name: @ac:0:0 } - LIST [14] { - elements: { - COMPREHENSION [15] { - iter_var: @it:1:0 - iter_range: { - IDENT [16] { - name: @index0 + IDENT [14] { + name: @it:0:0 + } + COMPREHENSION [15] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [16] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [17] { + + } + } + loop_condition: { + CONSTANT [18] { value: true } + } + loop_step: { + CALL [19] { + function: cel.@mapInsert + args: { + IDENT [20] { + name: @ac:1:0 } - } - accu_var: @ac:1:0 - accu_init: { - LIST [17] { - elements: { - } + IDENT [21] { + name: @it:1:0 } - } - loop_condition: { - CONSTANT [18] { value: true } - } - loop_step: { - CALL [19] { + CALL [22] { function: _+_ args: { - IDENT [20] { - name: @ac:1:0 - } - LIST [21] { - elements: { - CALL [22] { - function: _+_ - args: { - IDENT [23] { - name: @it:1:0 - } - CONSTANT [24] { value: 1 } - } + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @it:1:0 + } + IDENT [25] { + name: @it2:1:0 } } } + CONSTANT [26] { value: 1 } } } } - result: { - IDENT [25] { - name: @ac:1:0 - } - } + } + } + result: { + IDENT [27] { + name: @ac:1:0 } } } @@ -2412,82 +3552,88 @@ CALL [1] { } } result: { - IDENT [26] { + IDENT [28] { name: @ac:0:0 } } } - COMPREHENSION [27] { + COMPREHENSION [29] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - IDENT [28] { + IDENT [30] { name: @index0 } } accu_var: @ac:0:0 accu_init: { - LIST [29] { - elements: { - } + MAP [31] { + } } loop_condition: { - CONSTANT [30] { value: true } + CONSTANT [32] { value: true } } loop_step: { - CALL [31] { - function: _+_ + CALL [33] { + function: cel.@mapInsert args: { - IDENT [32] { + IDENT [34] { name: @ac:0:0 } - LIST [33] { - elements: { - COMPREHENSION [34] { - iter_var: @it:1:0 - iter_range: { - IDENT [35] { - name: @index0 + IDENT [35] { + name: @it:0:0 + } + COMPREHENSION [36] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [37] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [38] { + + } + } + loop_condition: { + CONSTANT [39] { value: true } + } + loop_step: { + CALL [40] { + function: cel.@mapInsert + args: { + IDENT [41] { + name: @ac:1:0 } - } - accu_var: @ac:1:0 - accu_init: { - LIST [36] { - elements: { - } + IDENT [42] { + name: @it:1:0 } - } - loop_condition: { - CONSTANT [37] { value: true } - } - loop_step: { - CALL [38] { + CALL [43] { function: _+_ args: { - IDENT [39] { - name: @ac:1:0 - } - LIST [40] { - elements: { - CALL [41] { - function: _+_ - args: { - IDENT [42] { - name: @it:1:0 - } - CONSTANT [43] { value: 1 } - } + CALL [44] { + function: _+_ + args: { + IDENT [45] { + name: @it:1:0 + } + IDENT [46] { + name: @it2:1:0 } } } + CONSTANT [47] { value: 1 } } } } - result: { - IDENT [44] { - name: @ac:1:0 - } - } + } + } + result: { + IDENT [48] { + name: @ac:1:0 } } } @@ -2495,7 +3641,7 @@ CALL [1] { } } result: { - IDENT [45] { + IDENT [49] { name: @ac:0:0 } } @@ -2623,20 +3769,158 @@ CALL [1] { key: { CONSTANT [14] { value: 2 } } - value: { - IDENT [15] { - name: @index0 + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +Test case: MACRO_ITER_VAR_NOT_REFERENCED +Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + LIST [13] { + elements: { + LIST [14] { + elements: { + CONSTANT [15] { value: 3 } + CONSTANT [16] { value: 4 } + } + } + } + } + COMPREHENSION [17] { + iter_var: @it:1:0 + iter_range: { + IDENT [18] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [19] { + elements: { + } + } + } + loop_condition: { + CONSTANT [20] { value: true } + } + loop_step: { + CALL [21] { + function: _+_ + args: { + IDENT [22] { + name: @ac:1:0 + } + IDENT [23] { + name: @index2 + } + } + } + } + result: { + IDENT [24] { + name: @ac:1:0 + } + } + } + LIST [25] { + elements: { + IDENT [26] { + name: @index3 + } + } + } + } + } + CALL [27] { + function: _==_ + args: { + COMPREHENSION [28] { + iter_var: @it:0:0 + iter_range: { + IDENT [29] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [30] { + elements: { + } + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [32] { + function: _+_ + args: { + IDENT [33] { + name: @ac:0:0 + } + IDENT [34] { + name: @index4 + } } } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + result: { + IDENT [35] { + name: @ac:0:0 } - value: { - IDENT [18] { - name: @index0 - } + } + } + LIST [36] { + elements: { + IDENT [37] { + name: @index0 + } + IDENT [38] { + name: @index0 } } } @@ -2644,8 +3928,8 @@ CALL [1] { } } } -Test case: MACRO_ITER_VAR_NOT_REFERENCED -Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +Test case: MACRO_ITER_VAR2_NOT_REFERENCED +Source: [1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] =====> CALL [1] { function: cel.@block @@ -2686,6 +3970,7 @@ CALL [1] { } COMPREHENSION [17] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [18] { name: @index1 @@ -2734,6 +4019,7 @@ CALL [1] { args: { COMPREHENSION [28] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [29] { name: @index1 @@ -3040,6 +4326,273 @@ CALL [1] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _-_ + args: { + CALL [4] { + function: _-_ + args: { + IDENT [5] { + name: x + } + IDENT [6] { + name: y + } + } + } + CONSTANT [7] { value: 1 } + } + } + CALL [8] { + function: _>_ + args: { + IDENT [9] { + name: @index0 + } + CONSTANT [10] { value: 3 } + } + } + LIST [11] { + elements: { + CALL [12] { + function: _?_:_ + args: { + IDENT [13] { + name: @index1 + } + IDENT [14] { + name: @index0 + } + CONSTANT [15] { value: 5 } + } + } + } + } + } + } + CALL [16] { + function: _||_ + args: { + COMPREHENSION [17] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [18] { + name: @index2 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [19] { value: false } + } + loop_condition: { + CALL [20] { + function: @not_strictly_false + args: { + CALL [21] { + function: !_ + args: { + IDENT [22] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [23] { + function: _||_ + args: { + IDENT [24] { + name: @ac:0:0 + } + CALL [25] { + function: _>_ + args: { + CALL [26] { + function: _-_ + args: { + CALL [27] { + function: _-_ + args: { + IDENT [28] { + name: @it:0:0 + } + IDENT [29] { + name: @it2:0:0 + } + } + } + CONSTANT [30] { value: 1 } + } + } + CONSTANT [31] { value: 3 } + } + } + } + } + } + result: { + IDENT [32] { + name: @ac:0:0 + } + } + } + IDENT [33] { + name: @index1 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: "foo" } + CONSTANT [5] { value: "bar" } + } + } + } + } + COMPREHENSION [6] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + COMPREHENSION [7] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [8] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [9] { + + } + } + loop_condition: { + CONSTANT [10] { value: true } + } + loop_step: { + CALL [11] { + function: cel.@mapInsert + args: { + IDENT [12] { + name: @ac:1:0 + } + IDENT [13] { + name: @it:1:0 + } + LIST [14] { + elements: { + CALL [15] { + function: _+_ + args: { + IDENT [16] { + name: @it:1:0 + } + IDENT [17] { + name: @it:1:0 + } + } + } + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @it2:1:0 + } + IDENT [20] { + name: @it2:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [21] { + name: @ac:1:0 + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [22] { + + } + } + loop_condition: { + CONSTANT [23] { value: true } + } + loop_step: { + CALL [24] { + function: cel.@mapInsert + args: { + IDENT [25] { + name: @ac:0:0 + } + IDENT [26] { + name: @it:0:0 + } + LIST [27] { + elements: { + CALL [28] { + function: _+_ + args: { + IDENT [29] { + name: @it:0:0 + } + IDENT [30] { + name: @it:0:0 + } + } + } + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it2:0:0 + } + IDENT [33] { + name: @it2:0:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_3.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_3.baseline index d9211978c..f91f528f5 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_3.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_3.baseline @@ -1736,6 +1736,650 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + } + } + LIST [5] { + elements: { + CONSTANT [6] { value: 2 } + } + } + } + } + CALL [7] { + function: _==_ + args: { + CALL [8] { + function: _+_ + args: { + CALL [9] { + function: _+_ + args: { + CALL [10] { + function: _+_ + args: { + CALL [11] { + function: size + args: { + LIST [12] { + elements: { + COMPREHENSION [13] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [14] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [15] { value: false } + } + loop_condition: { + CALL [16] { + function: @not_strictly_false + args: { + CALL [17] { + function: !_ + args: { + IDENT [18] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [19] { + function: _||_ + args: { + IDENT [20] { + name: @ac:0:0 + } + CALL [21] { + function: _&&_ + args: { + CALL [22] { + function: _>_ + args: { + IDENT [23] { + name: @it:0:0 + } + CONSTANT [24] { value: 0 } + } + } + CALL [25] { + function: _>=_ + args: { + IDENT [26] { + name: @it2:0:0 + } + CONSTANT [27] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:0:0 + } + } + } + } + } + } + } + CALL [29] { + function: size + args: { + LIST [30] { + elements: { + COMPREHENSION [31] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [32] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [33] { value: false } + } + loop_condition: { + CALL [34] { + function: @not_strictly_false + args: { + CALL [35] { + function: !_ + args: { + IDENT [36] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [37] { + function: _||_ + args: { + IDENT [38] { + name: @ac:0:0 + } + CALL [39] { + function: _&&_ + args: { + CALL [40] { + function: _>_ + args: { + IDENT [41] { + name: @it:0:0 + } + CONSTANT [42] { value: 0 } + } + } + CALL [43] { + function: _>=_ + args: { + IDENT [44] { + name: @it2:0:0 + } + CONSTANT [45] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [46] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CALL [47] { + function: size + args: { + LIST [48] { + elements: { + COMPREHENSION [49] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [50] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [51] { value: false } + } + loop_condition: { + CALL [52] { + function: @not_strictly_false + args: { + CALL [53] { + function: !_ + args: { + IDENT [54] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [55] { + function: _||_ + args: { + IDENT [56] { + name: @ac:0:0 + } + CALL [57] { + function: _&&_ + args: { + CALL [58] { + function: _>_ + args: { + IDENT [59] { + name: @it:0:0 + } + CONSTANT [60] { value: 1 } + } + } + CALL [61] { + function: _>=_ + args: { + IDENT [62] { + name: @it2:0:0 + } + CONSTANT [63] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [64] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CALL [65] { + function: size + args: { + LIST [66] { + elements: { + COMPREHENSION [67] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [68] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [69] { value: false } + } + loop_condition: { + CALL [70] { + function: @not_strictly_false + args: { + CALL [71] { + function: !_ + args: { + IDENT [72] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [73] { + function: _||_ + args: { + IDENT [74] { + name: @ac:0:0 + } + CALL [75] { + function: _&&_ + args: { + CALL [76] { + function: _>_ + args: { + IDENT [77] { + name: @it:0:0 + } + CONSTANT [78] { value: 1 } + } + } + CALL [79] { + function: _>=_ + args: { + IDENT [80] { + name: @it2:0:0 + } + CONSTANT [81] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [82] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CONSTANT [83] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + } + } + LIST [5] { + elements: { + CONSTANT [6] { value: 2 } + } + } + } + } + CALL [7] { + function: _&&_ + args: { + CALL [8] { + function: _&&_ + args: { + COMPREHENSION [9] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [10] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [11] { value: false } + } + loop_condition: { + CALL [12] { + function: @not_strictly_false + args: { + CALL [13] { + function: !_ + args: { + IDENT [14] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [15] { + function: _||_ + args: { + IDENT [16] { + name: @ac:0:0 + } + CALL [17] { + function: _&&_ + args: { + CALL [18] { + function: _>_ + args: { + IDENT [19] { + name: @it:0:0 + } + CONSTANT [20] { value: 0 } + } + } + CALL [21] { + function: _>_ + args: { + IDENT [22] { + name: @it2:0:0 + } + CONSTANT [23] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [24] { + name: @ac:0:0 + } + } + } + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [26] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [27] { value: false } + } + loop_condition: { + CALL [28] { + function: @not_strictly_false + args: { + CALL [29] { + function: !_ + args: { + IDENT [30] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [31] { + function: _||_ + args: { + IDENT [32] { + name: @ac:0:0 + } + CALL [33] { + function: _&&_ + args: { + CALL [34] { + function: _>_ + args: { + IDENT [35] { + name: @it:0:0 + } + CONSTANT [36] { value: 0 } + } + } + CALL [37] { + function: _>_ + args: { + IDENT [38] { + name: @it2:0:0 + } + CONSTANT [39] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [40] { + name: @ac:0:0 + } + } + } + } + } + CALL [41] { + function: _&&_ + args: { + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [43] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [44] { value: false } + } + loop_condition: { + CALL [45] { + function: @not_strictly_false + args: { + CALL [46] { + function: !_ + args: { + IDENT [47] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [48] { + function: _||_ + args: { + IDENT [49] { + name: @ac:0:0 + } + CALL [50] { + function: _&&_ + args: { + CALL [51] { + function: _>_ + args: { + IDENT [52] { + name: @it:0:0 + } + CONSTANT [53] { value: 1 } + } + } + CALL [54] { + function: _>_ + args: { + IDENT [55] { + name: @it2:0:0 + } + CONSTANT [56] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [57] { + name: @ac:0:0 + } + } + } + COMPREHENSION [58] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [59] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [60] { value: false } + } + loop_condition: { + CALL [61] { + function: @not_strictly_false + args: { + CALL [62] { + function: !_ + args: { + IDENT [63] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [64] { + function: _||_ + args: { + IDENT [65] { + name: @ac:0:0 + } + CALL [66] { + function: _&&_ + args: { + CALL [67] { + function: _>_ + args: { + IDENT [68] { + name: @it:0:0 + } + CONSTANT [69] { value: 1 } + } + } + CALL [70] { + function: _>_ + args: { + IDENT [71] { + name: @it2:0:0 + } + CONSTANT [72] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [73] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]] =====> @@ -1754,8 +2398,281 @@ CALL [1] { LIST [7] { elements: { CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } - CONSTANT [10] { value: 4 } + CONSTANT [9] { value: 3 } + CONSTANT [10] { value: 4 } + } + } + LIST [11] { + elements: { + IDENT [12] { + name: @index1 + } + IDENT [13] { + name: @index1 + } + IDENT [14] { + name: @index1 + } + } + } + } + } + CALL [15] { + function: _==_ + args: { + COMPREHENSION [16] { + iter_var: @it:0:0 + iter_range: { + IDENT [17] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [18] { + elements: { + } + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: _+_ + args: { + IDENT [21] { + name: @ac:0:0 + } + LIST [22] { + elements: { + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:1:0 + } + LIST [29] { + elements: { + CALL [30] { + function: _+_ + args: { + IDENT [31] { + name: @it:1:0 + } + CONSTANT [32] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [33] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index2 + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + LIST [6] { + elements: { + CONSTANT [7] { value: 2 } + } + } + } + } + LIST [8] { + elements: { + CONSTANT [9] { value: 1 } + CONSTANT [10] { value: 2 } + } + } + LIST [11] { + elements: { + CONSTANT [12] { value: 1 } + CONSTANT [13] { value: 2 } + CONSTANT [14] { value: 3 } + } + } + } + } + CALL [15] { + function: _==_ + args: { + COMPREHENSION [16] { + iter_var: @it:0:0 + iter_range: { + IDENT [17] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [18] { + elements: { + } + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: _+_ + args: { + IDENT [21] { + name: @ac:0:0 + } + LIST [22] { + elements: { + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index2 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _?_:_ + args: { + CALL [28] { + function: _==_ + args: { + IDENT [29] { + name: @it:1:0 + } + IDENT [30] { + name: @it:0:0 + } + } + } + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @ac:1:0 + } + LIST [33] { + elements: { + IDENT [34] { + name: @it:1:0 + } + } + } + } + } + IDENT [35] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [36] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [37] { + name: @ac:0:0 + } + } + } + IDENT [38] { + name: @index0 + } + } + } + } +} +Test case: NESTED_MACROS_COMP_V2_1 +Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } } } LIST [11] { @@ -1778,6 +2695,7 @@ CALL [1] { args: { COMPREHENSION [16] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [17] { name: @index0 @@ -1804,6 +2722,7 @@ CALL [1] { elements: { COMPREHENSION [23] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [24] { name: @index0 @@ -1831,10 +2750,18 @@ CALL [1] { CALL [30] { function: _+_ args: { - IDENT [31] { - name: @it:1:0 + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it:1:0 + } + IDENT [33] { + name: @it2:1:0 + } + } } - CONSTANT [32] { value: 1 } + CONSTANT [34] { value: 1 } } } } @@ -1843,7 +2770,7 @@ CALL [1] { } } result: { - IDENT [33] { + IDENT [35] { name: @ac:1:0 } } @@ -1854,20 +2781,20 @@ CALL [1] { } } result: { - IDENT [34] { + IDENT [36] { name: @ac:0:0 } } } - IDENT [35] { + IDENT [37] { name: @index2 } } } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> CALL [1] { function: cel.@block @@ -1908,6 +2835,7 @@ CALL [1] { args: { COMPREHENSION [16] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [17] { name: @index1 @@ -1954,39 +2882,55 @@ CALL [1] { function: _?_:_ args: { CALL [28] { - function: _==_ + function: _&&_ args: { - IDENT [29] { - name: @it:1:0 + CALL [29] { + function: _==_ + args: { + IDENT [30] { + name: @it:1:0 + } + IDENT [31] { + name: @it2:0:0 + } + } } - IDENT [30] { - name: @it:0:0 + CALL [32] { + function: _<_ + args: { + IDENT [33] { + name: @it:0:0 + } + IDENT [34] { + name: @it2:0:0 + } + } } } } - CALL [31] { + CALL [35] { function: _+_ args: { - IDENT [32] { + IDENT [36] { name: @ac:1:0 } - LIST [33] { + LIST [37] { elements: { - IDENT [34] { + IDENT [38] { name: @it:1:0 } } } } } - IDENT [35] { + IDENT [39] { name: @ac:1:0 } } } } result: { - IDENT [36] { + IDENT [40] { name: @ac:1:0 } } @@ -1997,12 +2941,12 @@ CALL [1] { } } result: { - IDENT [37] { + IDENT [41] { name: @ac:0:0 } } } - IDENT [38] { + IDENT [42] { name: @index0 } } @@ -2061,45 +3005,241 @@ CALL [1] { IDENT [16] { name: @index0 } - } - accu_var: @ac:1:0 - accu_init: { - LIST [17] { - elements: { - } + } + accu_var: @ac:1:0 + accu_init: { + LIST [17] { + elements: { + } + } + } + loop_condition: { + CONSTANT [18] { value: true } + } + loop_step: { + CALL [19] { + function: _+_ + args: { + IDENT [20] { + name: @ac:1:0 + } + LIST [21] { + elements: { + CALL [22] { + function: _+_ + args: { + IDENT [23] { + name: @it:1:0 + } + CONSTANT [24] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [25] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [26] { + name: @ac:0:0 + } + } + } + COMPREHENSION [27] { + iter_var: @it:0:0 + iter_range: { + IDENT [28] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [29] { + elements: { + } + } + } + loop_condition: { + CONSTANT [30] { value: true } + } + loop_step: { + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @ac:0:0 + } + LIST [33] { + elements: { + COMPREHENSION [34] { + iter_var: @it:1:0 + iter_range: { + IDENT [35] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [36] { + elements: { + } + } + } + loop_condition: { + CONSTANT [37] { value: true } + } + loop_step: { + CALL [38] { + function: _+_ + args: { + IDENT [39] { + name: @ac:1:0 + } + LIST [40] { + elements: { + CALL [41] { + function: _+_ + args: { + IDENT [42] { + name: @it:1:0 + } + CONSTANT [43] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [44] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [45] { + name: @ac:0:0 + } + } + } + } + } + } +} +Test case: ADJACENT_NESTED_MACROS_COMP_V2 +Source: [1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) == [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + } + } + CALL [7] { + function: _==_ + args: { + COMPREHENSION [8] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [9] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [10] { + + } + } + loop_condition: { + CONSTANT [11] { value: true } + } + loop_step: { + CALL [12] { + function: cel.@mapInsert + args: { + IDENT [13] { + name: @ac:0:0 + } + IDENT [14] { + name: @it:0:0 + } + COMPREHENSION [15] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [16] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [17] { + + } + } + loop_condition: { + CONSTANT [18] { value: true } + } + loop_step: { + CALL [19] { + function: cel.@mapInsert + args: { + IDENT [20] { + name: @ac:1:0 + } + IDENT [21] { + name: @it:1:0 } - } - loop_condition: { - CONSTANT [18] { value: true } - } - loop_step: { - CALL [19] { + CALL [22] { function: _+_ args: { - IDENT [20] { - name: @ac:1:0 - } - LIST [21] { - elements: { - CALL [22] { - function: _+_ - args: { - IDENT [23] { - name: @it:1:0 - } - CONSTANT [24] { value: 1 } - } + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @it:1:0 + } + IDENT [25] { + name: @it2:1:0 } } } + CONSTANT [26] { value: 1 } } } } - result: { - IDENT [25] { - name: @ac:1:0 - } - } + } + } + result: { + IDENT [27] { + name: @ac:1:0 } } } @@ -2107,82 +3247,88 @@ CALL [1] { } } result: { - IDENT [26] { + IDENT [28] { name: @ac:0:0 } } } - COMPREHENSION [27] { + COMPREHENSION [29] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - IDENT [28] { + IDENT [30] { name: @index0 } } accu_var: @ac:0:0 accu_init: { - LIST [29] { - elements: { - } + MAP [31] { + } } loop_condition: { - CONSTANT [30] { value: true } + CONSTANT [32] { value: true } } loop_step: { - CALL [31] { - function: _+_ + CALL [33] { + function: cel.@mapInsert args: { - IDENT [32] { + IDENT [34] { name: @ac:0:0 } - LIST [33] { - elements: { - COMPREHENSION [34] { - iter_var: @it:1:0 - iter_range: { - IDENT [35] { - name: @index0 + IDENT [35] { + name: @it:0:0 + } + COMPREHENSION [36] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [37] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [38] { + + } + } + loop_condition: { + CONSTANT [39] { value: true } + } + loop_step: { + CALL [40] { + function: cel.@mapInsert + args: { + IDENT [41] { + name: @ac:1:0 } - } - accu_var: @ac:1:0 - accu_init: { - LIST [36] { - elements: { - } + IDENT [42] { + name: @it:1:0 } - } - loop_condition: { - CONSTANT [37] { value: true } - } - loop_step: { - CALL [38] { + CALL [43] { function: _+_ args: { - IDENT [39] { - name: @ac:1:0 - } - LIST [40] { - elements: { - CALL [41] { - function: _+_ - args: { - IDENT [42] { - name: @it:1:0 - } - CONSTANT [43] { value: 1 } - } + CALL [44] { + function: _+_ + args: { + IDENT [45] { + name: @it:1:0 + } + IDENT [46] { + name: @it2:1:0 } } } + CONSTANT [47] { value: 1 } } } } - result: { - IDENT [44] { - name: @ac:1:0 - } - } + } + } + result: { + IDENT [48] { + name: @ac:1:0 } } } @@ -2190,7 +3336,7 @@ CALL [1] { } } result: { - IDENT [45] { + IDENT [49] { name: @ac:0:0 } } @@ -2311,24 +3457,159 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 - } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +Test case: MACRO_ITER_VAR_NOT_REFERENCED +Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + LIST [13] { + elements: { + LIST [14] { + elements: { + CONSTANT [15] { value: 3 } + CONSTANT [16] { value: 4 } + } + } + } + } + COMPREHENSION [17] { + iter_var: @it:1:0 + iter_range: { + IDENT [18] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [19] { + elements: { + } + } + } + loop_condition: { + CONSTANT [20] { value: true } + } + loop_step: { + CALL [21] { + function: _+_ + args: { + IDENT [22] { + name: @ac:1:0 + } + IDENT [23] { + name: @index2 + } + } + } + } + result: { + IDENT [24] { + name: @ac:1:0 + } + } + } + } + } + CALL [25] { + function: _==_ + args: { + COMPREHENSION [26] { + iter_var: @it:0:0 + iter_range: { + IDENT [27] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [28] { + elements: { + } + } + } + loop_condition: { + CONSTANT [29] { value: true } + } + loop_step: { + CALL [30] { + function: _+_ + args: { + IDENT [31] { + name: @ac:0:0 + } + LIST [32] { + elements: { + IDENT [33] { + name: @index3 + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + } + LIST [35] { + elements: { + IDENT [36] { + name: @index0 } - value: { - IDENT [18] { - name: @index0 - } + IDENT [37] { + name: @index0 } } } @@ -2336,8 +3617,8 @@ CALL [1] { } } } -Test case: MACRO_ITER_VAR_NOT_REFERENCED -Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +Test case: MACRO_ITER_VAR2_NOT_REFERENCED +Source: [1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] =====> CALL [1] { function: cel.@block @@ -2378,6 +3659,7 @@ CALL [1] { } COMPREHENSION [17] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [18] { name: @index1 @@ -2419,6 +3701,7 @@ CALL [1] { args: { COMPREHENSION [26] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [27] { name: @index1 @@ -2726,6 +4009,287 @@ CALL [1] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + CALL [10] { + function: _?_:_ + args: { + IDENT [11] { + name: @index0 + } + CALL [12] { + function: _-_ + args: { + CALL [13] { + function: _-_ + args: { + IDENT [14] { + name: x + } + IDENT [15] { + name: y + } + } + } + CONSTANT [16] { value: 1 } + } + } + CONSTANT [17] { value: 5 } + } + } + LIST [18] { + elements: { + IDENT [19] { + name: @index1 + } + } + } + } + } + CALL [20] { + function: _||_ + args: { + COMPREHENSION [21] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [22] { + name: @index2 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _>_ + args: { + CALL [30] { + function: _-_ + args: { + CALL [31] { + function: _-_ + args: { + IDENT [32] { + name: @it:0:0 + } + IDENT [33] { + name: @it2:0:0 + } + } + } + CONSTANT [34] { value: 1 } + } + } + CONSTANT [35] { value: 3 } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + IDENT [37] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: "foo" } + CONSTANT [5] { value: "bar" } + } + } + } + } + COMPREHENSION [6] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + COMPREHENSION [7] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [8] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [9] { + + } + } + loop_condition: { + CONSTANT [10] { value: true } + } + loop_step: { + CALL [11] { + function: cel.@mapInsert + args: { + IDENT [12] { + name: @ac:1:0 + } + IDENT [13] { + name: @it:1:0 + } + LIST [14] { + elements: { + CALL [15] { + function: _+_ + args: { + IDENT [16] { + name: @it:1:0 + } + IDENT [17] { + name: @it:1:0 + } + } + } + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @it2:1:0 + } + IDENT [20] { + name: @it2:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [21] { + name: @ac:1:0 + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [22] { + + } + } + loop_condition: { + CONSTANT [23] { value: true } + } + loop_step: { + CALL [24] { + function: cel.@mapInsert + args: { + IDENT [25] { + name: @ac:0:0 + } + IDENT [26] { + name: @it:0:0 + } + LIST [27] { + elements: { + CALL [28] { + function: _+_ + args: { + IDENT [29] { + name: @it:0:0 + } + IDENT [30] { + name: @it:0:0 + } + } + } + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it2:0:0 + } + IDENT [33] { + name: @it2:0:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline index cf40f26ee..8d221f3de 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline @@ -1700,6 +1700,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [21] { + elements: { + CONSTANT [22] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _&&_ + args: { + CALL [30] { + function: _>_ + args: { + IDENT [31] { + name: @it:0:0 + } + CONSTANT [32] { value: 1 } + } + } + CALL [33] { + function: _>=_ + args: { + IDENT [34] { + name: @it2:0:0 + } + CONSTANT [35] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + CALL [37] { + function: size + args: { + LIST [38] { + elements: { + IDENT [39] { + name: @index0 + } + } + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index1 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index2 + } + IDENT [48] { + name: @index2 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]] =====> @@ -1882,60 +2324,354 @@ CALL [1] { } LIST [16] { elements: { - IDENT [17] { + IDENT [17] { + name: @it:1:0 + } + } + } + } + } + IDENT [18] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [19] { + name: @ac:1:0 + } + } + } + } + } + CALL [20] { + function: _==_ + args: { + COMPREHENSION [21] { + iter_var: @it:0:0 + iter_range: { + LIST [22] { + elements: { + CONSTANT [23] { value: 1 } + CONSTANT [24] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:0:0 + } + LIST [29] { + elements: { + IDENT [30] { + name: @index0 + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:0:0 + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 2 } + } + } + } + } + } + } + } +} +Test case: NESTED_MACROS_COMP_V2_1 +Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } + } + } + LIST [11] { + elements: { + IDENT [12] { + name: @index1 + } + IDENT [13] { + name: @index1 + } + IDENT [14] { + name: @index1 + } + } + } + } + } + CALL [15] { + function: _==_ + args: { + COMPREHENSION [16] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [17] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [18] { + elements: { + } + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: _+_ + args: { + IDENT [21] { + name: @ac:0:0 + } + LIST [22] { + elements: { + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [24] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:1:0 + } + LIST [29] { + elements: { + CALL [30] { + function: _+_ + args: { + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it:1:0 + } + IDENT [33] { + name: @it2:1:0 + } + } + } + CONSTANT [34] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [35] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + IDENT [37] { + name: @index2 + } + } + } + } +} +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:1:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [8] { + elements: { + } + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: _?_:_ + args: { + CALL [11] { + function: _&&_ + args: { + CALL [12] { + function: _==_ + args: { + IDENT [13] { + name: @it:1:0 + } + IDENT [14] { + name: @it2:0:0 + } + } + } + CALL [15] { + function: _<_ + args: { + IDENT [16] { + name: @it:0:0 + } + IDENT [17] { + name: @it2:0:0 + } + } + } + } + } + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @ac:1:0 + } + LIST [20] { + elements: { + IDENT [21] { name: @it:1:0 } } } } } - IDENT [18] { + IDENT [22] { name: @ac:1:0 } } } } result: { - IDENT [19] { + IDENT [23] { name: @ac:1:0 } } } } } - CALL [20] { + CALL [24] { function: _==_ args: { - COMPREHENSION [21] { + COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - LIST [22] { + LIST [26] { elements: { - CONSTANT [23] { value: 1 } - CONSTANT [24] { value: 2 } + CONSTANT [27] { value: 1 } + CONSTANT [28] { value: 2 } } } } accu_var: @ac:0:0 accu_init: { - LIST [25] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [26] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [27] { + CALL [31] { function: _+_ args: { - IDENT [28] { + IDENT [32] { name: @ac:0:0 } - LIST [29] { + LIST [33] { elements: { - IDENT [30] { + IDENT [34] { name: @index0 } } @@ -1944,21 +2680,21 @@ CALL [1] { } } result: { - IDENT [31] { + IDENT [35] { name: @ac:0:0 } } } - LIST [32] { + LIST [36] { elements: { - LIST [33] { + LIST [37] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [38] { value: 1 } } } - LIST [35] { + LIST [39] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [40] { value: 2 } } } } @@ -2084,6 +2820,129 @@ CALL [1] { } } } +Test case: ADJACENT_NESTED_MACROS_COMP_V2 +Source: [1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) == [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [8] { + + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: cel.@mapInsert + args: { + IDENT [11] { + name: @ac:1:0 + } + IDENT [12] { + name: @it:1:0 + } + CALL [13] { + function: _+_ + args: { + CALL [14] { + function: _+_ + args: { + IDENT [15] { + name: @it:1:0 + } + IDENT [16] { + name: @it2:1:0 + } + } + } + CONSTANT [17] { value: 1 } + } + } + } + } + } + result: { + IDENT [18] { + name: @ac:1:0 + } + } + } + COMPREHENSION [19] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [20] { + elements: { + CONSTANT [21] { value: 1 } + CONSTANT [22] { value: 2 } + CONSTANT [23] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [24] { + + } + } + loop_condition: { + CONSTANT [25] { value: true } + } + loop_step: { + CALL [26] { + function: cel.@mapInsert + args: { + IDENT [27] { + name: @ac:0:0 + } + IDENT [28] { + name: @it:0:0 + } + IDENT [29] { + name: @index0 + } + } + } + } + result: { + IDENT [30] { + name: @ac:0:0 + } + } + } + } + } + CALL [31] { + function: _==_ + args: { + IDENT [32] { + name: @index1 + } + IDENT [33] { + name: @index1 + } + } + } + } +} Test case: INCLUSION_LIST Source: 1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3] =====> @@ -2193,24 +3052,156 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 - } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +Test case: MACRO_ITER_VAR_NOT_REFERENCED +Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_range: { + IDENT [14] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @ac:1:0 + } + LIST [19] { + elements: { + LIST [20] { + elements: { + CONSTANT [21] { value: 3 } + CONSTANT [22] { value: 4 } + } + } + } + } + } + } + } + result: { + IDENT [23] { + name: @ac:1:0 + } + } + } + } + } + CALL [24] { + function: _==_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_range: { + IDENT [26] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [27] { + elements: { + } + } + } + loop_condition: { + CONSTANT [28] { value: true } + } + loop_step: { + CALL [29] { + function: _+_ + args: { + IDENT [30] { + name: @ac:0:0 + } + LIST [31] { + elements: { + IDENT [32] { + name: @index2 + } + } + } + } + } + } + result: { + IDENT [33] { + name: @ac:0:0 } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + } + LIST [34] { + elements: { + IDENT [35] { + name: @index0 } - value: { - IDENT [18] { - name: @index0 - } + IDENT [36] { + name: @index0 } } } @@ -2218,8 +3209,8 @@ CALL [1] { } } } -Test case: MACRO_ITER_VAR_NOT_REFERENCED -Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +Test case: MACRO_ITER_VAR2_NOT_REFERENCED +Source: [1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] =====> CALL [1] { function: cel.@block @@ -2250,6 +3241,7 @@ CALL [1] { } COMPREHENSION [13] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [14] { name: @index1 @@ -2298,6 +3290,7 @@ CALL [1] { args: { COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [26] { name: @index1 @@ -2602,6 +3595,284 @@ CALL [1] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + LIST [10] { + elements: { + CALL [11] { + function: _?_:_ + args: { + IDENT [12] { + name: @index0 + } + CALL [13] { + function: _-_ + args: { + CALL [14] { + function: _-_ + args: { + IDENT [15] { + name: x + } + IDENT [16] { + name: y + } + } + } + CONSTANT [17] { value: 1 } + } + } + CONSTANT [18] { value: 5 } + } + } + } + } + } + } + CALL [19] { + function: _||_ + args: { + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [21] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [22] { value: false } + } + loop_condition: { + CALL [23] { + function: @not_strictly_false + args: { + CALL [24] { + function: !_ + args: { + IDENT [25] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [26] { + function: _||_ + args: { + IDENT [27] { + name: @ac:0:0 + } + CALL [28] { + function: _>_ + args: { + CALL [29] { + function: _-_ + args: { + CALL [30] { + function: _-_ + args: { + IDENT [31] { + name: @it:0:0 + } + IDENT [32] { + name: @it2:0:0 + } + } + } + CONSTANT [33] { value: 1 } + } + } + CONSTANT [34] { value: 3 } + } + } + } + } + } + result: { + IDENT [35] { + name: @ac:0:0 + } + } + } + IDENT [36] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: "foo" } + CONSTANT [6] { value: "bar" } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [7] { + + } + } + loop_condition: { + CONSTANT [8] { value: true } + } + loop_step: { + CALL [9] { + function: cel.@mapInsert + args: { + IDENT [10] { + name: @ac:1:0 + } + IDENT [11] { + name: @it:1:0 + } + LIST [12] { + elements: { + CALL [13] { + function: _+_ + args: { + IDENT [14] { + name: @it:1:0 + } + IDENT [15] { + name: @it:1:0 + } + } + } + CALL [16] { + function: _+_ + args: { + IDENT [17] { + name: @it2:1:0 + } + IDENT [18] { + name: @it2:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:1:0 + } + } + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [21] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [22] { + + } + } + loop_condition: { + CONSTANT [23] { value: true } + } + loop_step: { + CALL [24] { + function: cel.@mapInsert + args: { + IDENT [25] { + name: @ac:0:0 + } + IDENT [26] { + name: @it:0:0 + } + LIST [27] { + elements: { + CALL [28] { + function: _+_ + args: { + IDENT [29] { + name: @it:0:0 + } + IDENT [30] { + name: @it:0:0 + } + } + } + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it2:0:0 + } + IDENT [33] { + name: @it2:0:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline index 0eadf7a83..b80e2883f 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline @@ -1682,6 +1682,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [21] { + elements: { + CONSTANT [22] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _&&_ + args: { + CALL [30] { + function: _>_ + args: { + IDENT [31] { + name: @it:0:0 + } + CONSTANT [32] { value: 1 } + } + } + CALL [33] { + function: _>=_ + args: { + IDENT [34] { + name: @it2:0:0 + } + CONSTANT [35] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + CALL [37] { + function: size + args: { + LIST [38] { + elements: { + IDENT [39] { + name: @index0 + } + } + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index1 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index2 + } + IDENT [48] { + name: @index2 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]] =====> @@ -1700,12 +2142,280 @@ CALL [1] { LIST [7] { elements: { CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } - CONSTANT [10] { value: 4 } + CONSTANT [9] { value: 3 } + CONSTANT [10] { value: 4 } + } + } + COMPREHENSION [11] { + iter_var: @it:1:0 + iter_range: { + IDENT [12] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [13] { + elements: { + } + } + } + loop_condition: { + CONSTANT [14] { value: true } + } + loop_step: { + CALL [15] { + function: _+_ + args: { + IDENT [16] { + name: @ac:1:0 + } + LIST [17] { + elements: { + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @it:1:0 + } + CONSTANT [20] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [21] { + name: @ac:1:0 + } + } + } + } + } + CALL [22] { + function: _==_ + args: { + COMPREHENSION [23] { + iter_var: @it:0:0 + iter_range: { + IDENT [24] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:0:0 + } + LIST [29] { + elements: { + IDENT [30] { + name: @index2 + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:0:0 + } + } + } + LIST [32] { + elements: { + IDENT [33] { + name: @index1 + } + IDENT [34] { + name: @index1 + } + IDENT [35] { + name: @index1 + } + } + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:1:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [8] { + elements: { + } + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: _?_:_ + args: { + CALL [11] { + function: _==_ + args: { + IDENT [12] { + name: @it:1:0 + } + IDENT [13] { + name: @it:0:0 + } + } + } + CALL [14] { + function: _+_ + args: { + IDENT [15] { + name: @ac:1:0 + } + LIST [16] { + elements: { + IDENT [17] { + name: @it:1:0 + } + } + } + } + } + IDENT [18] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [19] { + name: @ac:1:0 + } + } + } + } + } + CALL [20] { + function: _==_ + args: { + COMPREHENSION [21] { + iter_var: @it:0:0 + iter_range: { + LIST [22] { + elements: { + CONSTANT [23] { value: 1 } + CONSTANT [24] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:0:0 + } + LIST [29] { + elements: { + IDENT [30] { + name: @index0 + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:0:0 + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 2 } + } + } + } + } + } + } + } +} +Test case: NESTED_MACROS_COMP_V2_1 +Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } } } COMPREHENSION [11] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [12] { name: @index0 @@ -1733,10 +2443,18 @@ CALL [1] { CALL [18] { function: _+_ args: { - IDENT [19] { - name: @it:1:0 + CALL [19] { + function: _+_ + args: { + IDENT [20] { + name: @it:1:0 + } + IDENT [21] { + name: @it2:1:0 + } + } } - CONSTANT [20] { value: 1 } + CONSTANT [22] { value: 1 } } } } @@ -1745,43 +2463,44 @@ CALL [1] { } } result: { - IDENT [21] { + IDENT [23] { name: @ac:1:0 } } } } } - CALL [22] { + CALL [24] { function: _==_ args: { - COMPREHENSION [23] { + COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - IDENT [24] { + IDENT [26] { name: @index0 } } accu_var: @ac:0:0 accu_init: { - LIST [25] { + LIST [27] { elements: { } } } loop_condition: { - CONSTANT [26] { value: true } + CONSTANT [28] { value: true } } loop_step: { - CALL [27] { + CALL [29] { function: _+_ args: { - IDENT [28] { + IDENT [30] { name: @ac:0:0 } - LIST [29] { + LIST [31] { elements: { - IDENT [30] { + IDENT [32] { name: @index2 } } @@ -1790,20 +2509,20 @@ CALL [1] { } } result: { - IDENT [31] { + IDENT [33] { name: @ac:0:0 } } } - LIST [32] { + LIST [34] { elements: { - IDENT [33] { + IDENT [35] { name: @index1 } - IDENT [34] { + IDENT [36] { name: @index1 } - IDENT [35] { + IDENT [37] { name: @index1 } } @@ -1812,8 +2531,8 @@ CALL [1] { } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> CALL [1] { function: cel.@block @@ -1846,78 +2565,95 @@ CALL [1] { function: _?_:_ args: { CALL [11] { - function: _==_ - args: { - IDENT [12] { - name: @it:1:0 + function: _&&_ + args: { + CALL [12] { + function: _==_ + args: { + IDENT [13] { + name: @it:1:0 + } + IDENT [14] { + name: @it2:0:0 + } + } } - IDENT [13] { - name: @it:0:0 + CALL [15] { + function: _<_ + args: { + IDENT [16] { + name: @it:0:0 + } + IDENT [17] { + name: @it2:0:0 + } + } } } } - CALL [14] { + CALL [18] { function: _+_ args: { - IDENT [15] { + IDENT [19] { name: @ac:1:0 } - LIST [16] { + LIST [20] { elements: { - IDENT [17] { + IDENT [21] { name: @it:1:0 } } } } } - IDENT [18] { + IDENT [22] { name: @ac:1:0 } } } } result: { - IDENT [19] { + IDENT [23] { name: @ac:1:0 } } } } } - CALL [20] { + CALL [24] { function: _==_ args: { - COMPREHENSION [21] { + COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - LIST [22] { + LIST [26] { elements: { - CONSTANT [23] { value: 1 } - CONSTANT [24] { value: 2 } + CONSTANT [27] { value: 1 } + CONSTANT [28] { value: 2 } } } } accu_var: @ac:0:0 accu_init: { - LIST [25] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [26] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [27] { + CALL [31] { function: _+_ args: { - IDENT [28] { + IDENT [32] { name: @ac:0:0 } - LIST [29] { + LIST [33] { elements: { - IDENT [30] { + IDENT [34] { name: @index0 } } @@ -1926,21 +2662,21 @@ CALL [1] { } } result: { - IDENT [31] { + IDENT [35] { name: @ac:0:0 } } } - LIST [32] { + LIST [36] { elements: { - LIST [33] { + LIST [37] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [38] { value: 1 } } } - LIST [35] { + LIST [39] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [40] { value: 2 } } } } @@ -2066,6 +2802,129 @@ CALL [1] { } } } +Test case: ADJACENT_NESTED_MACROS_COMP_V2 +Source: [1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) == [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [8] { + + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: cel.@mapInsert + args: { + IDENT [11] { + name: @ac:1:0 + } + IDENT [12] { + name: @it:1:0 + } + CALL [13] { + function: _+_ + args: { + CALL [14] { + function: _+_ + args: { + IDENT [15] { + name: @it:1:0 + } + IDENT [16] { + name: @it2:1:0 + } + } + } + CONSTANT [17] { value: 1 } + } + } + } + } + } + result: { + IDENT [18] { + name: @ac:1:0 + } + } + } + COMPREHENSION [19] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [20] { + elements: { + CONSTANT [21] { value: 1 } + CONSTANT [22] { value: 2 } + CONSTANT [23] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [24] { + + } + } + loop_condition: { + CONSTANT [25] { value: true } + } + loop_step: { + CALL [26] { + function: cel.@mapInsert + args: { + IDENT [27] { + name: @ac:0:0 + } + IDENT [28] { + name: @it:0:0 + } + IDENT [29] { + name: @index0 + } + } + } + } + result: { + IDENT [30] { + name: @ac:0:0 + } + } + } + } + } + CALL [31] { + function: _==_ + args: { + IDENT [32] { + name: @index1 + } + IDENT [33] { + name: @index1 + } + } + } + } +} Test case: INCLUSION_LIST Source: 1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3] =====> @@ -2162,37 +3021,169 @@ CALL [1] { } } } - CALL [7] { - function: @in + CALL [7] { + function: @in + args: { + CONSTANT [8] { value: 2 } + MAP [9] { + MAP_ENTRY [10] { + key: { + CONSTANT [11] { value: "a" } + } + value: { + CONSTANT [12] { value: 1 } + } + } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +Test case: MACRO_ITER_VAR_NOT_REFERENCED +Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_range: { + IDENT [14] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @ac:1:0 + } + LIST [19] { + elements: { + LIST [20] { + elements: { + CONSTANT [21] { value: 3 } + CONSTANT [22] { value: 4 } + } + } + } + } + } + } + } + result: { + IDENT [23] { + name: @ac:1:0 + } + } + } + } + } + CALL [24] { + function: _==_ args: { - CONSTANT [8] { value: 2 } - MAP [9] { - MAP_ENTRY [10] { - key: { - CONSTANT [11] { value: "a" } - } - value: { - CONSTANT [12] { value: 1 } + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_range: { + IDENT [26] { + name: @index1 } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } + accu_var: @ac:0:0 + accu_init: { + LIST [27] { + elements: { + } } - value: { - IDENT [15] { - name: @index0 + } + loop_condition: { + CONSTANT [28] { value: true } + } + loop_step: { + CALL [29] { + function: _+_ + args: { + IDENT [30] { + name: @ac:0:0 + } + LIST [31] { + elements: { + IDENT [32] { + name: @index2 + } + } + } } } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + result: { + IDENT [33] { + name: @ac:0:0 } - value: { - IDENT [18] { - name: @index0 - } + } + } + LIST [34] { + elements: { + IDENT [35] { + name: @index0 + } + IDENT [36] { + name: @index0 } } } @@ -2200,8 +3191,8 @@ CALL [1] { } } } -Test case: MACRO_ITER_VAR_NOT_REFERENCED -Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +Test case: MACRO_ITER_VAR2_NOT_REFERENCED +Source: [1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] =====> CALL [1] { function: cel.@block @@ -2232,6 +3223,7 @@ CALL [1] { } COMPREHENSION [13] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [14] { name: @index1 @@ -2280,6 +3272,7 @@ CALL [1] { args: { COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [26] { name: @index1 @@ -2584,6 +3577,269 @@ CALL [1] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + } + } + CALL [10] { + function: _||_ + args: { + COMPREHENSION [11] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [12] { + elements: { + CALL [13] { + function: _?_:_ + args: { + IDENT [14] { + name: @index0 + } + CALL [15] { + function: _-_ + args: { + CALL [16] { + function: _-_ + args: { + IDENT [17] { + name: x + } + IDENT [18] { + name: y + } + } + } + CONSTANT [19] { value: 1 } + } + } + CONSTANT [20] { value: 5 } + } + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [21] { value: false } + } + loop_condition: { + CALL [22] { + function: @not_strictly_false + args: { + CALL [23] { + function: !_ + args: { + IDENT [24] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [25] { + function: _||_ + args: { + IDENT [26] { + name: @ac:0:0 + } + CALL [27] { + function: _>_ + args: { + CALL [28] { + function: _-_ + args: { + CALL [29] { + function: _-_ + args: { + IDENT [30] { + name: @it:0:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CONSTANT [32] { value: 1 } + } + } + CONSTANT [33] { value: 3 } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +COMPREHENSION [35] { + iter_var: x + iter_var2: y + iter_range: { + COMPREHENSION [19] { + iter_var: x + iter_var2: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: "foo" } + CONSTANT [3] { value: "bar" } + } + } + } + accu_var: @result + accu_init: { + MAP [14] { + + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [17] { + function: cel.@mapInsert + args: { + IDENT [16] { + name: @result + } + IDENT [5] { + name: x + } + LIST [7] { + elements: { + CALL [9] { + function: _+_ + args: { + IDENT [8] { + name: x + } + IDENT [10] { + name: x + } + } + } + CALL [12] { + function: _+_ + args: { + IDENT [11] { + name: y + } + IDENT [13] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @result + } + } + } + } + accu_var: @result + accu_init: { + MAP [30] { + + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [33] { + function: cel.@mapInsert + args: { + IDENT [32] { + name: @result + } + IDENT [21] { + name: x + } + LIST [23] { + elements: { + CALL [25] { + function: _+_ + args: { + IDENT [24] { + name: x + } + IDENT [26] { + name: x + } + } + } + CALL [28] { + function: _+_ + args: { + IDENT [27] { + name: y + } + IDENT [29] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @result + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline index 7eadf9498..c8424586d 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline @@ -1676,6 +1676,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [21] { + elements: { + CONSTANT [22] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _&&_ + args: { + CALL [30] { + function: _>_ + args: { + IDENT [31] { + name: @it:0:0 + } + CONSTANT [32] { value: 1 } + } + } + CALL [33] { + function: _>=_ + args: { + IDENT [34] { + name: @it2:0:0 + } + CONSTANT [35] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + CALL [37] { + function: size + args: { + LIST [38] { + elements: { + IDENT [39] { + name: @index0 + } + } + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index1 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index2 + } + IDENT [48] { + name: @index2 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]] =====> @@ -1694,12 +2136,280 @@ CALL [1] { LIST [7] { elements: { CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } - CONSTANT [10] { value: 4 } + CONSTANT [9] { value: 3 } + CONSTANT [10] { value: 4 } + } + } + COMPREHENSION [11] { + iter_var: @it:1:0 + iter_range: { + IDENT [12] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [13] { + elements: { + } + } + } + loop_condition: { + CONSTANT [14] { value: true } + } + loop_step: { + CALL [15] { + function: _+_ + args: { + IDENT [16] { + name: @ac:1:0 + } + LIST [17] { + elements: { + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @it:1:0 + } + CONSTANT [20] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [21] { + name: @ac:1:0 + } + } + } + } + } + CALL [22] { + function: _==_ + args: { + COMPREHENSION [23] { + iter_var: @it:0:0 + iter_range: { + IDENT [24] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:0:0 + } + LIST [29] { + elements: { + IDENT [30] { + name: @index2 + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:0:0 + } + } + } + LIST [32] { + elements: { + IDENT [33] { + name: @index1 + } + IDENT [34] { + name: @index1 + } + IDENT [35] { + name: @index1 + } + } + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:1:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [8] { + elements: { + } + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: _?_:_ + args: { + CALL [11] { + function: _==_ + args: { + IDENT [12] { + name: @it:1:0 + } + IDENT [13] { + name: @it:0:0 + } + } + } + CALL [14] { + function: _+_ + args: { + IDENT [15] { + name: @ac:1:0 + } + LIST [16] { + elements: { + IDENT [17] { + name: @it:1:0 + } + } + } + } + } + IDENT [18] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [19] { + name: @ac:1:0 + } + } + } + } + } + CALL [20] { + function: _==_ + args: { + COMPREHENSION [21] { + iter_var: @it:0:0 + iter_range: { + LIST [22] { + elements: { + CONSTANT [23] { value: 1 } + CONSTANT [24] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:0:0 + } + LIST [29] { + elements: { + IDENT [30] { + name: @index0 + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:0:0 + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 2 } + } + } + } + } + } + } + } +} +Test case: NESTED_MACROS_COMP_V2_1 +Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } } } COMPREHENSION [11] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [12] { name: @index0 @@ -1727,10 +2437,18 @@ CALL [1] { CALL [18] { function: _+_ args: { - IDENT [19] { - name: @it:1:0 + CALL [19] { + function: _+_ + args: { + IDENT [20] { + name: @it:1:0 + } + IDENT [21] { + name: @it2:1:0 + } + } } - CONSTANT [20] { value: 1 } + CONSTANT [22] { value: 1 } } } } @@ -1739,43 +2457,44 @@ CALL [1] { } } result: { - IDENT [21] { + IDENT [23] { name: @ac:1:0 } } } } } - CALL [22] { + CALL [24] { function: _==_ args: { - COMPREHENSION [23] { + COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - IDENT [24] { + IDENT [26] { name: @index0 } } accu_var: @ac:0:0 accu_init: { - LIST [25] { + LIST [27] { elements: { } } } loop_condition: { - CONSTANT [26] { value: true } + CONSTANT [28] { value: true } } loop_step: { - CALL [27] { + CALL [29] { function: _+_ args: { - IDENT [28] { + IDENT [30] { name: @ac:0:0 } - LIST [29] { + LIST [31] { elements: { - IDENT [30] { + IDENT [32] { name: @index2 } } @@ -1784,20 +2503,20 @@ CALL [1] { } } result: { - IDENT [31] { + IDENT [33] { name: @ac:0:0 } } } - LIST [32] { + LIST [34] { elements: { - IDENT [33] { + IDENT [35] { name: @index1 } - IDENT [34] { + IDENT [36] { name: @index1 } - IDENT [35] { + IDENT [37] { name: @index1 } } @@ -1806,8 +2525,8 @@ CALL [1] { } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> CALL [1] { function: cel.@block @@ -1840,78 +2559,95 @@ CALL [1] { function: _?_:_ args: { CALL [11] { - function: _==_ + function: _&&_ args: { - IDENT [12] { - name: @it:1:0 + CALL [12] { + function: _==_ + args: { + IDENT [13] { + name: @it:1:0 + } + IDENT [14] { + name: @it2:0:0 + } + } } - IDENT [13] { - name: @it:0:0 + CALL [15] { + function: _<_ + args: { + IDENT [16] { + name: @it:0:0 + } + IDENT [17] { + name: @it2:0:0 + } + } } } } - CALL [14] { + CALL [18] { function: _+_ args: { - IDENT [15] { + IDENT [19] { name: @ac:1:0 } - LIST [16] { + LIST [20] { elements: { - IDENT [17] { + IDENT [21] { name: @it:1:0 } } } } } - IDENT [18] { + IDENT [22] { name: @ac:1:0 } } } } result: { - IDENT [19] { + IDENT [23] { name: @ac:1:0 } } } } } - CALL [20] { + CALL [24] { function: _==_ args: { - COMPREHENSION [21] { + COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - LIST [22] { + LIST [26] { elements: { - CONSTANT [23] { value: 1 } - CONSTANT [24] { value: 2 } + CONSTANT [27] { value: 1 } + CONSTANT [28] { value: 2 } } } } accu_var: @ac:0:0 accu_init: { - LIST [25] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [26] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [27] { + CALL [31] { function: _+_ args: { - IDENT [28] { + IDENT [32] { name: @ac:0:0 } - LIST [29] { + LIST [33] { elements: { - IDENT [30] { + IDENT [34] { name: @index0 } } @@ -1920,21 +2656,21 @@ CALL [1] { } } result: { - IDENT [31] { + IDENT [35] { name: @ac:0:0 } } } - LIST [32] { + LIST [36] { elements: { - LIST [33] { + LIST [37] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [38] { value: 1 } } } - LIST [35] { + LIST [39] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [40] { value: 2 } } } } @@ -2060,6 +2796,126 @@ CALL [1] { } } } +Test case: ADJACENT_NESTED_MACROS_COMP_V2 +Source: [1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) == [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [8] { + + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: cel.@mapInsert + args: { + IDENT [11] { + name: @ac:0:0 + } + IDENT [12] { + name: @it:0:0 + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [14] { + elements: { + CONSTANT [15] { value: 1 } + CONSTANT [16] { value: 2 } + CONSTANT [17] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [18] { + + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: cel.@mapInsert + args: { + IDENT [21] { + name: @ac:1:0 + } + IDENT [22] { + name: @it:1:0 + } + CALL [23] { + function: _+_ + args: { + CALL [24] { + function: _+_ + args: { + IDENT [25] { + name: @it:1:0 + } + IDENT [26] { + name: @it2:1:0 + } + } + } + CONSTANT [27] { value: 1 } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:1:0 + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:0:0 + } + } + } + } + } + CALL [30] { + function: _==_ + args: { + IDENT [31] { + name: @index0 + } + IDENT [32] { + name: @index0 + } + } + } + } +} Test case: INCLUSION_LIST Source: 1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3] =====> @@ -2169,24 +3025,156 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +Test case: MACRO_ITER_VAR_NOT_REFERENCED +Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_range: { + IDENT [14] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @ac:1:0 + } + LIST [19] { + elements: { + LIST [20] { + elements: { + CONSTANT [21] { value: 3 } + CONSTANT [22] { value: 4 } + } + } + } + } + } + } + } + result: { + IDENT [23] { + name: @ac:1:0 + } + } + } + } + } + CALL [24] { + function: _==_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_range: { + IDENT [26] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [27] { + elements: { + } + } + } + loop_condition: { + CONSTANT [28] { value: true } + } + loop_step: { + CALL [29] { + function: _+_ + args: { + IDENT [30] { + name: @ac:0:0 + } + LIST [31] { + elements: { + IDENT [32] { + name: @index2 + } + } + } } } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + result: { + IDENT [33] { + name: @ac:0:0 } - value: { - IDENT [18] { - name: @index0 - } + } + } + LIST [34] { + elements: { + IDENT [35] { + name: @index0 + } + IDENT [36] { + name: @index0 } } } @@ -2194,8 +3182,8 @@ CALL [1] { } } } -Test case: MACRO_ITER_VAR_NOT_REFERENCED -Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +Test case: MACRO_ITER_VAR2_NOT_REFERENCED +Source: [1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] =====> CALL [1] { function: cel.@block @@ -2226,6 +3214,7 @@ CALL [1] { } COMPREHENSION [13] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [14] { name: @index1 @@ -2274,6 +3263,7 @@ CALL [1] { args: { COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [26] { name: @index1 @@ -2566,6 +3556,269 @@ COMPREHENSION [35] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + } + } + CALL [10] { + function: _||_ + args: { + COMPREHENSION [11] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [12] { + elements: { + CALL [13] { + function: _?_:_ + args: { + IDENT [14] { + name: @index0 + } + CALL [15] { + function: _-_ + args: { + CALL [16] { + function: _-_ + args: { + IDENT [17] { + name: x + } + IDENT [18] { + name: y + } + } + } + CONSTANT [19] { value: 1 } + } + } + CONSTANT [20] { value: 5 } + } + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [21] { value: false } + } + loop_condition: { + CALL [22] { + function: @not_strictly_false + args: { + CALL [23] { + function: !_ + args: { + IDENT [24] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [25] { + function: _||_ + args: { + IDENT [26] { + name: @ac:0:0 + } + CALL [27] { + function: _>_ + args: { + CALL [28] { + function: _-_ + args: { + CALL [29] { + function: _-_ + args: { + IDENT [30] { + name: @it:0:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CONSTANT [32] { value: 1 } + } + } + CONSTANT [33] { value: 3 } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +COMPREHENSION [35] { + iter_var: x + iter_var2: y + iter_range: { + COMPREHENSION [19] { + iter_var: x + iter_var2: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: "foo" } + CONSTANT [3] { value: "bar" } + } + } + } + accu_var: @result + accu_init: { + MAP [14] { + + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [17] { + function: cel.@mapInsert + args: { + IDENT [16] { + name: @result + } + IDENT [5] { + name: x + } + LIST [7] { + elements: { + CALL [9] { + function: _+_ + args: { + IDENT [8] { + name: x + } + IDENT [10] { + name: x + } + } + } + CALL [12] { + function: _+_ + args: { + IDENT [11] { + name: y + } + IDENT [13] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @result + } + } + } + } + accu_var: @result + accu_init: { + MAP [30] { + + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [33] { + function: cel.@mapInsert + args: { + IDENT [32] { + name: @result + } + IDENT [21] { + name: x + } + LIST [23] { + elements: { + CALL [25] { + function: _+_ + args: { + IDENT [24] { + name: x + } + IDENT [26] { + name: x + } + } + } + CALL [28] { + function: _+_ + args: { + IDENT [27] { + name: y + } + IDENT [29] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @result + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_7.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_7.baseline index 8bba9777e..bab77a361 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_7.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_7.baseline @@ -1673,6 +1673,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [21] { + elements: { + CONSTANT [22] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _&&_ + args: { + CALL [30] { + function: _>_ + args: { + IDENT [31] { + name: @it:0:0 + } + CONSTANT [32] { value: 1 } + } + } + CALL [33] { + function: _>=_ + args: { + IDENT [34] { + name: @it2:0:0 + } + CONSTANT [35] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + CALL [37] { + function: size + args: { + LIST [38] { + elements: { + IDENT [39] { + name: @index0 + } + } + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index1 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index2 + } + IDENT [48] { + name: @index2 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]] =====> @@ -1783,15 +2225,280 @@ CALL [1] { } } } - LIST [31] { + LIST [31] { + elements: { + IDENT [32] { + name: @index1 + } + IDENT [33] { + name: @index1 + } + IDENT [34] { + name: @index1 + } + } + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [31] { + function: _==_ + args: { + COMPREHENSION [30] { + iter_var: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: 1 } + CONSTANT [3] { value: 2 } + } + } + } + accu_var: @result + accu_init: { + LIST [24] { + elements: { + } + } + } + loop_condition: { + CONSTANT [25] { value: true } + } + loop_step: { + CALL [28] { + function: _+_ + args: { + IDENT [26] { + name: @result + } + LIST [27] { + elements: { + COMPREHENSION [23] { + iter_var: x + iter_range: { + LIST [6] { + elements: { + CONSTANT [7] { value: 1 } + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 3 } + } + } + } + accu_var: @result + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [21] { + function: _?_:_ + args: { + CALL [13] { + function: _==_ + args: { + IDENT [12] { + name: x + } + IDENT [14] { + name: y + } + } + } + CALL [19] { + function: _+_ + args: { + IDENT [17] { + name: @result + } + LIST [18] { + elements: { + IDENT [11] { + name: x + } + } + } + } + } + IDENT [20] { + name: @result + } + } + } + } + result: { + IDENT [22] { + name: @result + } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @result + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 2 } + } + } + } + } + } +} +Test case: NESTED_MACROS_COMP_V2_1 +Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } + } + } + COMPREHENSION [11] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [12] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [13] { + elements: { + } + } + } + loop_condition: { + CONSTANT [14] { value: true } + } + loop_step: { + CALL [15] { + function: _+_ + args: { + IDENT [16] { + name: @ac:1:0 + } + LIST [17] { + elements: { + CALL [18] { + function: _+_ + args: { + CALL [19] { + function: _+_ + args: { + IDENT [20] { + name: @it:1:0 + } + IDENT [21] { + name: @it2:1:0 + } + } + } + CONSTANT [22] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [23] { + name: @ac:1:0 + } + } + } + } + } + CALL [24] { + function: _==_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [26] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [27] { + elements: { + } + } + } + loop_condition: { + CONSTANT [28] { value: true } + } + loop_step: { + CALL [29] { + function: _+_ + args: { + IDENT [30] { + name: @ac:0:0 + } + LIST [31] { + elements: { + IDENT [32] { + name: @index2 + } + } + } + } + } + } + result: { + IDENT [33] { + name: @ac:0:0 + } + } + } + LIST [34] { elements: { - IDENT [32] { + IDENT [35] { name: @index1 } - IDENT [33] { + IDENT [36] { name: @index1 } - IDENT [34] { + IDENT [37] { name: @index1 } } @@ -1800,14 +2507,15 @@ CALL [1] { } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> -CALL [31] { +CALL [36] { function: _==_ args: { - COMPREHENSION [30] { - iter_var: y + COMPREHENSION [35] { + iter_var: i + iter_var2: y iter_range: { LIST [1] { elements: { @@ -1818,82 +2526,98 @@ CALL [31] { } accu_var: @result accu_init: { - LIST [24] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [25] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [28] { + CALL [33] { function: _+_ args: { - IDENT [26] { + IDENT [31] { name: @result } - LIST [27] { + LIST [32] { elements: { - COMPREHENSION [23] { + COMPREHENSION [28] { iter_var: x iter_range: { - LIST [6] { + LIST [7] { elements: { - CONSTANT [7] { value: 1 } - CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } + CONSTANT [8] { value: 1 } + CONSTANT [9] { value: 2 } + CONSTANT [10] { value: 3 } } } } accu_var: @result accu_init: { - LIST [15] { + LIST [20] { elements: { } } } loop_condition: { - CONSTANT [16] { value: true } + CONSTANT [21] { value: true } } loop_step: { - CALL [21] { + CALL [26] { function: _?_:_ args: { - CALL [13] { - function: _==_ + CALL [16] { + function: _&&_ args: { - IDENT [12] { - name: x + CALL [14] { + function: _==_ + args: { + IDENT [13] { + name: x + } + IDENT [15] { + name: y + } + } } - IDENT [14] { - name: y + CALL [18] { + function: _<_ + args: { + IDENT [17] { + name: i + } + IDENT [19] { + name: y + } + } } } } - CALL [19] { + CALL [24] { function: _+_ args: { - IDENT [17] { + IDENT [22] { name: @result } - LIST [18] { + LIST [23] { elements: { - IDENT [11] { + IDENT [12] { name: x } } } } } - IDENT [20] { + IDENT [25] { name: @result } } } } result: { - IDENT [22] { + IDENT [27] { name: @result } } @@ -1904,21 +2628,21 @@ CALL [31] { } } result: { - IDENT [29] { + IDENT [34] { name: @result } } } - LIST [32] { + LIST [37] { elements: { - LIST [33] { + LIST [38] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [39] { value: 1 } } } - LIST [35] { + LIST [40] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [41] { value: 2 } } } } @@ -2039,6 +2763,126 @@ CALL [1] { } } } +Test case: ADJACENT_NESTED_MACROS_COMP_V2 +Source: [1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) == [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [8] { + + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: cel.@mapInsert + args: { + IDENT [11] { + name: @ac:0:0 + } + IDENT [12] { + name: @it:0:0 + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [14] { + elements: { + CONSTANT [15] { value: 1 } + CONSTANT [16] { value: 2 } + CONSTANT [17] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [18] { + + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: cel.@mapInsert + args: { + IDENT [21] { + name: @ac:1:0 + } + IDENT [22] { + name: @it:1:0 + } + CALL [23] { + function: _+_ + args: { + CALL [24] { + function: _+_ + args: { + IDENT [25] { + name: @it:1:0 + } + IDENT [26] { + name: @it2:1:0 + } + } + } + CONSTANT [27] { value: 1 } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:1:0 + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:0:0 + } + } + } + } + } + CALL [30] { + function: _==_ + args: { + IDENT [31] { + name: @index0 + } + IDENT [32] { + name: @index0 + } + } + } + } +} Test case: INCLUSION_LIST Source: 1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3] =====> @@ -2148,24 +2992,153 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 - } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +Test case: MACRO_ITER_VAR_NOT_REFERENCED +Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + } + } + CALL [13] { + function: _==_ + args: { + COMPREHENSION [14] { + iter_var: @it:0:0 + iter_range: { + IDENT [15] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [16] { + elements: { + } + } + } + loop_condition: { + CONSTANT [17] { value: true } + } + loop_step: { + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @ac:0:0 + } + LIST [20] { + elements: { + COMPREHENSION [21] { + iter_var: @it:1:0 + iter_range: { + IDENT [22] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [23] { + elements: { + } + } + } + loop_condition: { + CONSTANT [24] { value: true } + } + loop_step: { + CALL [25] { + function: _+_ + args: { + IDENT [26] { + name: @ac:1:0 + } + LIST [27] { + elements: { + LIST [28] { + elements: { + CONSTANT [29] { value: 3 } + CONSTANT [30] { value: 4 } + } + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [32] { + name: @ac:0:0 } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + } + LIST [33] { + elements: { + IDENT [34] { + name: @index0 } - value: { - IDENT [18] { - name: @index0 - } + IDENT [35] { + name: @index0 } } } @@ -2173,8 +3146,8 @@ CALL [1] { } } } -Test case: MACRO_ITER_VAR_NOT_REFERENCED -Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +Test case: MACRO_ITER_VAR2_NOT_REFERENCED +Source: [1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] =====> CALL [1] { function: cel.@block @@ -2210,6 +3183,7 @@ CALL [1] { args: { COMPREHENSION [14] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [15] { name: @index1 @@ -2236,6 +3210,7 @@ CALL [1] { elements: { COMPREHENSION [21] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [22] { name: @index1 @@ -2542,6 +3517,269 @@ COMPREHENSION [35] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + } + } + CALL [10] { + function: _||_ + args: { + COMPREHENSION [11] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [12] { + elements: { + CALL [13] { + function: _?_:_ + args: { + IDENT [14] { + name: @index0 + } + CALL [15] { + function: _-_ + args: { + CALL [16] { + function: _-_ + args: { + IDENT [17] { + name: x + } + IDENT [18] { + name: y + } + } + } + CONSTANT [19] { value: 1 } + } + } + CONSTANT [20] { value: 5 } + } + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [21] { value: false } + } + loop_condition: { + CALL [22] { + function: @not_strictly_false + args: { + CALL [23] { + function: !_ + args: { + IDENT [24] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [25] { + function: _||_ + args: { + IDENT [26] { + name: @ac:0:0 + } + CALL [27] { + function: _>_ + args: { + CALL [28] { + function: _-_ + args: { + CALL [29] { + function: _-_ + args: { + IDENT [30] { + name: @it:0:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CONSTANT [32] { value: 1 } + } + } + CONSTANT [33] { value: 3 } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +COMPREHENSION [35] { + iter_var: x + iter_var2: y + iter_range: { + COMPREHENSION [19] { + iter_var: x + iter_var2: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: "foo" } + CONSTANT [3] { value: "bar" } + } + } + } + accu_var: @result + accu_init: { + MAP [14] { + + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [17] { + function: cel.@mapInsert + args: { + IDENT [16] { + name: @result + } + IDENT [5] { + name: x + } + LIST [7] { + elements: { + CALL [9] { + function: _+_ + args: { + IDENT [8] { + name: x + } + IDENT [10] { + name: x + } + } + } + CALL [12] { + function: _+_ + args: { + IDENT [11] { + name: y + } + IDENT [13] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @result + } + } + } + } + accu_var: @result + accu_init: { + MAP [30] { + + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [33] { + function: cel.@mapInsert + args: { + IDENT [32] { + name: @result + } + IDENT [21] { + name: x + } + LIST [23] { + elements: { + CALL [25] { + function: _+_ + args: { + IDENT [24] { + name: x + } + IDENT [26] { + name: x + } + } + } + CALL [28] { + function: _+_ + args: { + IDENT [27] { + name: y + } + IDENT [29] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @result + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_8.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_8.baseline index 2bee1cefa..1ce112b6c 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_8.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_8.baseline @@ -1673,6 +1673,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [21] { + elements: { + CONSTANT [22] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _&&_ + args: { + CALL [30] { + function: _>_ + args: { + IDENT [31] { + name: @it:0:0 + } + CONSTANT [32] { value: 1 } + } + } + CALL [33] { + function: _>=_ + args: { + IDENT [34] { + name: @it2:0:0 + } + CONSTANT [35] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + CALL [37] { + function: size + args: { + LIST [38] { + elements: { + IDENT [39] { + name: @index0 + } + } + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index1 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index2 + } + IDENT [48] { + name: @index2 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]] =====> @@ -1785,13 +2227,275 @@ CALL [1] { } LIST [31] { elements: { - IDENT [32] { + IDENT [32] { + name: @index1 + } + IDENT [33] { + name: @index1 + } + IDENT [34] { + name: @index1 + } + } + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [31] { + function: _==_ + args: { + COMPREHENSION [30] { + iter_var: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: 1 } + CONSTANT [3] { value: 2 } + } + } + } + accu_var: @result + accu_init: { + LIST [24] { + elements: { + } + } + } + loop_condition: { + CONSTANT [25] { value: true } + } + loop_step: { + CALL [28] { + function: _+_ + args: { + IDENT [26] { + name: @result + } + LIST [27] { + elements: { + COMPREHENSION [23] { + iter_var: x + iter_range: { + LIST [6] { + elements: { + CONSTANT [7] { value: 1 } + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 3 } + } + } + } + accu_var: @result + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [21] { + function: _?_:_ + args: { + CALL [13] { + function: _==_ + args: { + IDENT [12] { + name: x + } + IDENT [14] { + name: y + } + } + } + CALL [19] { + function: _+_ + args: { + IDENT [17] { + name: @result + } + LIST [18] { + elements: { + IDENT [11] { + name: x + } + } + } + } + } + IDENT [20] { + name: @result + } + } + } + } + result: { + IDENT [22] { + name: @result + } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @result + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 2 } + } + } + } + } + } +} +Test case: NESTED_MACROS_COMP_V2_1 +Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } + } + } + } + } + CALL [11] { + function: _==_ + args: { + COMPREHENSION [12] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [13] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [14] { + elements: { + } + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [16] { + function: _+_ + args: { + IDENT [17] { + name: @ac:0:0 + } + LIST [18] { + elements: { + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [20] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [21] { + elements: { + } + } + } + loop_condition: { + CONSTANT [22] { value: true } + } + loop_step: { + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @ac:1:0 + } + LIST [25] { + elements: { + CALL [26] { + function: _+_ + args: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @it:1:0 + } + IDENT [29] { + name: @it2:1:0 + } + } + } + CONSTANT [30] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [32] { + name: @ac:0:0 + } + } + } + LIST [33] { + elements: { + IDENT [34] { name: @index1 } - IDENT [33] { + IDENT [35] { name: @index1 } - IDENT [34] { + IDENT [36] { name: @index1 } } @@ -1800,14 +2504,15 @@ CALL [1] { } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> -CALL [31] { +CALL [36] { function: _==_ args: { - COMPREHENSION [30] { - iter_var: y + COMPREHENSION [35] { + iter_var: i + iter_var2: y iter_range: { LIST [1] { elements: { @@ -1818,82 +2523,98 @@ CALL [31] { } accu_var: @result accu_init: { - LIST [24] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [25] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [28] { + CALL [33] { function: _+_ args: { - IDENT [26] { + IDENT [31] { name: @result } - LIST [27] { + LIST [32] { elements: { - COMPREHENSION [23] { + COMPREHENSION [28] { iter_var: x iter_range: { - LIST [6] { + LIST [7] { elements: { - CONSTANT [7] { value: 1 } - CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } + CONSTANT [8] { value: 1 } + CONSTANT [9] { value: 2 } + CONSTANT [10] { value: 3 } } } } accu_var: @result accu_init: { - LIST [15] { + LIST [20] { elements: { } } } loop_condition: { - CONSTANT [16] { value: true } + CONSTANT [21] { value: true } } loop_step: { - CALL [21] { + CALL [26] { function: _?_:_ args: { - CALL [13] { - function: _==_ + CALL [16] { + function: _&&_ args: { - IDENT [12] { - name: x + CALL [14] { + function: _==_ + args: { + IDENT [13] { + name: x + } + IDENT [15] { + name: y + } + } } - IDENT [14] { - name: y + CALL [18] { + function: _<_ + args: { + IDENT [17] { + name: i + } + IDENT [19] { + name: y + } + } } } } - CALL [19] { + CALL [24] { function: _+_ args: { - IDENT [17] { + IDENT [22] { name: @result } - LIST [18] { + LIST [23] { elements: { - IDENT [11] { + IDENT [12] { name: x } } } } } - IDENT [20] { + IDENT [25] { name: @result } } } } result: { - IDENT [22] { + IDENT [27] { name: @result } } @@ -1904,21 +2625,21 @@ CALL [31] { } } result: { - IDENT [29] { + IDENT [34] { name: @result } } } - LIST [32] { + LIST [37] { elements: { - LIST [33] { + LIST [38] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [39] { value: 1 } } } - LIST [35] { + LIST [40] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [41] { value: 2 } } } } @@ -2039,6 +2760,126 @@ CALL [1] { } } } +Test case: ADJACENT_NESTED_MACROS_COMP_V2 +Source: [1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) == [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [8] { + + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: cel.@mapInsert + args: { + IDENT [11] { + name: @ac:0:0 + } + IDENT [12] { + name: @it:0:0 + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [14] { + elements: { + CONSTANT [15] { value: 1 } + CONSTANT [16] { value: 2 } + CONSTANT [17] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [18] { + + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: cel.@mapInsert + args: { + IDENT [21] { + name: @ac:1:0 + } + IDENT [22] { + name: @it:1:0 + } + CALL [23] { + function: _+_ + args: { + CALL [24] { + function: _+_ + args: { + IDENT [25] { + name: @it:1:0 + } + IDENT [26] { + name: @it2:1:0 + } + } + } + CONSTANT [27] { value: 1 } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:1:0 + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:0:0 + } + } + } + } + } + CALL [30] { + function: _==_ + args: { + IDENT [31] { + name: @index0 + } + IDENT [32] { + name: @index0 + } + } + } + } +} Test case: INCLUSION_LIST Source: 1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3] =====> @@ -2148,24 +2989,153 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 - } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +Test case: MACRO_ITER_VAR_NOT_REFERENCED +Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + } + } + CALL [13] { + function: _==_ + args: { + COMPREHENSION [14] { + iter_var: @it:0:0 + iter_range: { + IDENT [15] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [16] { + elements: { + } + } + } + loop_condition: { + CONSTANT [17] { value: true } + } + loop_step: { + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @ac:0:0 + } + LIST [20] { + elements: { + COMPREHENSION [21] { + iter_var: @it:1:0 + iter_range: { + IDENT [22] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [23] { + elements: { + } + } + } + loop_condition: { + CONSTANT [24] { value: true } + } + loop_step: { + CALL [25] { + function: _+_ + args: { + IDENT [26] { + name: @ac:1:0 + } + LIST [27] { + elements: { + LIST [28] { + elements: { + CONSTANT [29] { value: 3 } + CONSTANT [30] { value: 4 } + } + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [32] { + name: @ac:0:0 } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + } + LIST [33] { + elements: { + IDENT [34] { + name: @index0 } - value: { - IDENT [18] { - name: @index0 - } + IDENT [35] { + name: @index0 } } } @@ -2173,8 +3143,8 @@ CALL [1] { } } } -Test case: MACRO_ITER_VAR_NOT_REFERENCED -Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +Test case: MACRO_ITER_VAR2_NOT_REFERENCED +Source: [1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] =====> CALL [1] { function: cel.@block @@ -2210,6 +3180,7 @@ CALL [1] { args: { COMPREHENSION [14] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [15] { name: @index1 @@ -2236,6 +3207,7 @@ CALL [1] { elements: { COMPREHENSION [21] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [22] { name: @index1 @@ -2542,6 +3514,269 @@ COMPREHENSION [35] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + } + } + CALL [10] { + function: _||_ + args: { + COMPREHENSION [11] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [12] { + elements: { + CALL [13] { + function: _?_:_ + args: { + IDENT [14] { + name: @index0 + } + CALL [15] { + function: _-_ + args: { + CALL [16] { + function: _-_ + args: { + IDENT [17] { + name: x + } + IDENT [18] { + name: y + } + } + } + CONSTANT [19] { value: 1 } + } + } + CONSTANT [20] { value: 5 } + } + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [21] { value: false } + } + loop_condition: { + CALL [22] { + function: @not_strictly_false + args: { + CALL [23] { + function: !_ + args: { + IDENT [24] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [25] { + function: _||_ + args: { + IDENT [26] { + name: @ac:0:0 + } + CALL [27] { + function: _>_ + args: { + CALL [28] { + function: _-_ + args: { + CALL [29] { + function: _-_ + args: { + IDENT [30] { + name: @it:0:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CONSTANT [32] { value: 1 } + } + } + CONSTANT [33] { value: 3 } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +COMPREHENSION [35] { + iter_var: x + iter_var2: y + iter_range: { + COMPREHENSION [19] { + iter_var: x + iter_var2: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: "foo" } + CONSTANT [3] { value: "bar" } + } + } + } + accu_var: @result + accu_init: { + MAP [14] { + + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [17] { + function: cel.@mapInsert + args: { + IDENT [16] { + name: @result + } + IDENT [5] { + name: x + } + LIST [7] { + elements: { + CALL [9] { + function: _+_ + args: { + IDENT [8] { + name: x + } + IDENT [10] { + name: x + } + } + } + CALL [12] { + function: _+_ + args: { + IDENT [11] { + name: y + } + IDENT [13] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @result + } + } + } + } + accu_var: @result + accu_init: { + MAP [30] { + + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [33] { + function: cel.@mapInsert + args: { + IDENT [32] { + name: @result + } + IDENT [21] { + name: x + } + LIST [23] { + elements: { + CALL [25] { + function: _+_ + args: { + IDENT [24] { + name: x + } + IDENT [26] { + name: x + } + } + } + CALL [28] { + function: _+_ + args: { + IDENT [27] { + name: y + } + IDENT [29] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @result + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_9.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_9.baseline index 740b3e04f..aa13c7633 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_9.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_9.baseline @@ -1661,6 +1661,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [21] { + elements: { + CONSTANT [22] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _&&_ + args: { + CALL [30] { + function: _>_ + args: { + IDENT [31] { + name: @it:0:0 + } + CONSTANT [32] { value: 1 } + } + } + CALL [33] { + function: _>=_ + args: { + IDENT [34] { + name: @it2:0:0 + } + CONSTANT [35] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + CALL [37] { + function: size + args: { + LIST [38] { + elements: { + IDENT [39] { + name: @index0 + } + } + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index1 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index2 + } + IDENT [48] { + name: @index2 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]] =====> @@ -1773,13 +2215,275 @@ CALL [1] { } LIST [31] { elements: { - IDENT [32] { + IDENT [32] { + name: @index1 + } + IDENT [33] { + name: @index1 + } + IDENT [34] { + name: @index1 + } + } + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [31] { + function: _==_ + args: { + COMPREHENSION [30] { + iter_var: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: 1 } + CONSTANT [3] { value: 2 } + } + } + } + accu_var: @result + accu_init: { + LIST [24] { + elements: { + } + } + } + loop_condition: { + CONSTANT [25] { value: true } + } + loop_step: { + CALL [28] { + function: _+_ + args: { + IDENT [26] { + name: @result + } + LIST [27] { + elements: { + COMPREHENSION [23] { + iter_var: x + iter_range: { + LIST [6] { + elements: { + CONSTANT [7] { value: 1 } + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 3 } + } + } + } + accu_var: @result + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [21] { + function: _?_:_ + args: { + CALL [13] { + function: _==_ + args: { + IDENT [12] { + name: x + } + IDENT [14] { + name: y + } + } + } + CALL [19] { + function: _+_ + args: { + IDENT [17] { + name: @result + } + LIST [18] { + elements: { + IDENT [11] { + name: x + } + } + } + } + } + IDENT [20] { + name: @result + } + } + } + } + result: { + IDENT [22] { + name: @result + } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @result + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 2 } + } + } + } + } + } +} +Test case: NESTED_MACROS_COMP_V2_1 +Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } + } + } + } + } + CALL [11] { + function: _==_ + args: { + COMPREHENSION [12] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [13] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [14] { + elements: { + } + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [16] { + function: _+_ + args: { + IDENT [17] { + name: @ac:0:0 + } + LIST [18] { + elements: { + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [20] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [21] { + elements: { + } + } + } + loop_condition: { + CONSTANT [22] { value: true } + } + loop_step: { + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @ac:1:0 + } + LIST [25] { + elements: { + CALL [26] { + function: _+_ + args: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @it:1:0 + } + IDENT [29] { + name: @it2:1:0 + } + } + } + CONSTANT [30] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [32] { + name: @ac:0:0 + } + } + } + LIST [33] { + elements: { + IDENT [34] { name: @index1 } - IDENT [33] { + IDENT [35] { name: @index1 } - IDENT [34] { + IDENT [36] { name: @index1 } } @@ -1788,14 +2492,15 @@ CALL [1] { } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> -CALL [31] { +CALL [36] { function: _==_ args: { - COMPREHENSION [30] { - iter_var: y + COMPREHENSION [35] { + iter_var: i + iter_var2: y iter_range: { LIST [1] { elements: { @@ -1806,82 +2511,98 @@ CALL [31] { } accu_var: @result accu_init: { - LIST [24] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [25] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [28] { + CALL [33] { function: _+_ args: { - IDENT [26] { + IDENT [31] { name: @result } - LIST [27] { + LIST [32] { elements: { - COMPREHENSION [23] { + COMPREHENSION [28] { iter_var: x iter_range: { - LIST [6] { + LIST [7] { elements: { - CONSTANT [7] { value: 1 } - CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } + CONSTANT [8] { value: 1 } + CONSTANT [9] { value: 2 } + CONSTANT [10] { value: 3 } } } } accu_var: @result accu_init: { - LIST [15] { + LIST [20] { elements: { } } } loop_condition: { - CONSTANT [16] { value: true } + CONSTANT [21] { value: true } } loop_step: { - CALL [21] { + CALL [26] { function: _?_:_ args: { - CALL [13] { - function: _==_ + CALL [16] { + function: _&&_ args: { - IDENT [12] { - name: x + CALL [14] { + function: _==_ + args: { + IDENT [13] { + name: x + } + IDENT [15] { + name: y + } + } } - IDENT [14] { - name: y + CALL [18] { + function: _<_ + args: { + IDENT [17] { + name: i + } + IDENT [19] { + name: y + } + } } } } - CALL [19] { + CALL [24] { function: _+_ args: { - IDENT [17] { + IDENT [22] { name: @result } - LIST [18] { + LIST [23] { elements: { - IDENT [11] { + IDENT [12] { name: x } } } } } - IDENT [20] { + IDENT [25] { name: @result } } } } result: { - IDENT [22] { + IDENT [27] { name: @result } } @@ -1892,21 +2613,21 @@ CALL [31] { } } result: { - IDENT [29] { + IDENT [34] { name: @result } } } - LIST [32] { + LIST [37] { elements: { - LIST [33] { + LIST [38] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [39] { value: 1 } } } - LIST [35] { + LIST [40] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [41] { value: 2 } } } } @@ -2027,6 +2748,126 @@ CALL [1] { } } } +Test case: ADJACENT_NESTED_MACROS_COMP_V2 +Source: [1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) == [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [8] { + + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: cel.@mapInsert + args: { + IDENT [11] { + name: @ac:0:0 + } + IDENT [12] { + name: @it:0:0 + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [14] { + elements: { + CONSTANT [15] { value: 1 } + CONSTANT [16] { value: 2 } + CONSTANT [17] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [18] { + + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: cel.@mapInsert + args: { + IDENT [21] { + name: @ac:1:0 + } + IDENT [22] { + name: @it:1:0 + } + CALL [23] { + function: _+_ + args: { + CALL [24] { + function: _+_ + args: { + IDENT [25] { + name: @it:1:0 + } + IDENT [26] { + name: @it2:1:0 + } + } + } + CONSTANT [27] { value: 1 } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:1:0 + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:0:0 + } + } + } + } + } + CALL [30] { + function: _==_ + args: { + IDENT [31] { + name: @index0 + } + IDENT [32] { + name: @index0 + } + } + } + } +} Test case: INCLUSION_LIST Source: 1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3] =====> @@ -2136,24 +2977,153 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 - } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +Test case: MACRO_ITER_VAR_NOT_REFERENCED +Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + } + } + CALL [13] { + function: _==_ + args: { + COMPREHENSION [14] { + iter_var: @it:0:0 + iter_range: { + IDENT [15] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [16] { + elements: { + } + } + } + loop_condition: { + CONSTANT [17] { value: true } + } + loop_step: { + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @ac:0:0 + } + LIST [20] { + elements: { + COMPREHENSION [21] { + iter_var: @it:1:0 + iter_range: { + IDENT [22] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [23] { + elements: { + } + } + } + loop_condition: { + CONSTANT [24] { value: true } + } + loop_step: { + CALL [25] { + function: _+_ + args: { + IDENT [26] { + name: @ac:1:0 + } + LIST [27] { + elements: { + LIST [28] { + elements: { + CONSTANT [29] { value: 3 } + CONSTANT [30] { value: 4 } + } + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [32] { + name: @ac:0:0 } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + } + LIST [33] { + elements: { + IDENT [34] { + name: @index0 } - value: { - IDENT [18] { - name: @index0 - } + IDENT [35] { + name: @index0 } } } @@ -2161,8 +3131,8 @@ CALL [1] { } } } -Test case: MACRO_ITER_VAR_NOT_REFERENCED -Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +Test case: MACRO_ITER_VAR2_NOT_REFERENCED +Source: [1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] =====> CALL [1] { function: cel.@block @@ -2198,6 +3168,7 @@ CALL [1] { args: { COMPREHENSION [14] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [15] { name: @index1 @@ -2224,6 +3195,7 @@ CALL [1] { elements: { COMPREHENSION [21] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [22] { name: @index1 @@ -2530,6 +3502,269 @@ COMPREHENSION [35] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + } + } + CALL [10] { + function: _||_ + args: { + COMPREHENSION [11] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [12] { + elements: { + CALL [13] { + function: _?_:_ + args: { + IDENT [14] { + name: @index0 + } + CALL [15] { + function: _-_ + args: { + CALL [16] { + function: _-_ + args: { + IDENT [17] { + name: x + } + IDENT [18] { + name: y + } + } + } + CONSTANT [19] { value: 1 } + } + } + CONSTANT [20] { value: 5 } + } + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [21] { value: false } + } + loop_condition: { + CALL [22] { + function: @not_strictly_false + args: { + CALL [23] { + function: !_ + args: { + IDENT [24] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [25] { + function: _||_ + args: { + IDENT [26] { + name: @ac:0:0 + } + CALL [27] { + function: _>_ + args: { + CALL [28] { + function: _-_ + args: { + CALL [29] { + function: _-_ + args: { + IDENT [30] { + name: @it:0:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CONSTANT [32] { value: 1 } + } + } + CONSTANT [33] { value: 3 } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +COMPREHENSION [35] { + iter_var: x + iter_var2: y + iter_range: { + COMPREHENSION [19] { + iter_var: x + iter_var2: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: "foo" } + CONSTANT [3] { value: "bar" } + } + } + } + accu_var: @result + accu_init: { + MAP [14] { + + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [17] { + function: cel.@mapInsert + args: { + IDENT [16] { + name: @result + } + IDENT [5] { + name: x + } + LIST [7] { + elements: { + CALL [9] { + function: _+_ + args: { + IDENT [8] { + name: x + } + IDENT [10] { + name: x + } + } + } + CALL [12] { + function: _+_ + args: { + IDENT [11] { + name: y + } + IDENT [13] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @result + } + } + } + } + accu_var: @result + accu_init: { + MAP [30] { + + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [33] { + function: cel.@mapInsert + args: { + IDENT [32] { + name: @result + } + IDENT [21] { + name: x + } + LIST [23] { + elements: { + CALL [25] { + function: _+_ + args: { + IDENT [24] { + name: x + } + IDENT [26] { + name: x + } + } + } + CALL [28] { + function: _+_ + args: { + IDENT [27] { + name: y + } + IDENT [29] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @result + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> diff --git a/optimizer/src/test/resources/subexpression_unparsed.baseline b/optimizer/src/test/resources/subexpression_unparsed.baseline index f2ec443d3..0ad89f2fc 100644 --- a/optimizer/src/test/resources/subexpression_unparsed.baseline +++ b/optimizer/src/test/resources/subexpression_unparsed.baseline @@ -298,6 +298,36 @@ Result: false [BLOCK_RECURSION_DEPTH_8]: cel.@block([[1].exists(@it:0:0, @it:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it:0:0 > 1) && [2].exists(@it:0:0, @it:0:0 > 1)) [BLOCK_RECURSION_DEPTH_9]: cel.@block([[1].exists(@it:0:0, @it:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it:0:0 > 1) && [2].exists(@it:0:0, @it:0:0 > 1)) +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +Result: true +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), size([@index0]), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index2])], @index1 + @index1 + @index3 + @index3 == 4) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1], [2]], size([@index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0)]) + size([@index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0)]) + size([@index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0)]) + size([@index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0)]) == 4) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1], [2]], size([@index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0)]) + size([@index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0)]) + size([@index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0)]) + size([@index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0)]) == 4) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1], [2]], size([@index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0)]) + size([@index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0)]) + size([@index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0)]) + size([@index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0)]) == 4) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index0]), size([@index1])], @index2 + @index2 + @index3 + @index3 == 4) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index0]), size([@index1])], @index2 + @index2 + @index3 + @index3 == 4) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index0]), size([@index1])], @index2 + @index2 + @index3 + @index3 == 4) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index0]), size([@index1])], @index2 + @index2 + @index3 + @index3 == 4) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index0]), size([@index1])], @index2 + @index2 + @index3 + @index3 == 4) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index0]), size([@index1])], @index2 + @index2 + @index3 + @index3 == 4) + +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +Result: false +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1], [2]], @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0) && @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0) && @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && @index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1], [2]], @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0) && @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0) && @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && @index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1], [2]], @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0) && @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0) && @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && @index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) + Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]] =====> @@ -328,6 +358,36 @@ Result: true [BLOCK_RECURSION_DEPTH_8]: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] [BLOCK_RECURSION_DEPTH_9]: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_1 +Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] +=====> +Result: true +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2, 3], [2, 4, 6]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2, 3], [2, 4, 6], [@index1, @index1, @index1]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index2) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1, 2, 3], [2, 4, 6], [@index1, @index1, @index1]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index2) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1, 2, 3], [2, 4, 6], [@index1, @index1, @index1]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index2) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3], [2, 4, 6], [@index1, @index1, @index1]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index2) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3], [2, 4, 6], @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)], @index0.transformList(@it:0:0, @it2:0:0, @index2) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3], [2, 4, 6], @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)], @index0.transformList(@it:0:0, @it2:0:0, @index2) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1, 2, 3], [2, 4, 6], @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)], @index0.transformList(@it:0:0, @it2:0:0, @index2) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1, 2, 3], [2, 4, 6]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1, 2, 3], [2, 4, 6]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == [@index1, @index1, @index1]) + +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] +=====> +Result: true +[BLOCK_COMMON_SUBEXPR_ONLY]: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2], [1, 2, 3], [1], [2], [@index2, @index3]], @index0.transformList(@it:0:0, @it2:0:0, @index1.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index4) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:0:0, @it2:0:0, @index2.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index0) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:0:0, @it2:0:0, @index2.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index0) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3].filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)], [1, 2].transformList(@it:0:0, @it2:0:0, @index0) == [[1], [2]]) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3].filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)], [1, 2].transformList(@it:0:0, @it2:0:0, @index0) == [[1], [2]]) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3].filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)], [1, 2].transformList(@it:0:0, @it2:0:0, @index0) == [[1], [2]]) +[BLOCK_RECURSION_DEPTH_7]: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] +[BLOCK_RECURSION_DEPTH_8]: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] +[BLOCK_RECURSION_DEPTH_9]: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] + Test case: ADJACENT_NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [1,2,3].map(j, [1, 2, 3].map(j, j + 1)) =====> @@ -343,6 +403,21 @@ Result: true [BLOCK_RECURSION_DEPTH_8]: cel.@block([[1, 2, 3].map(@it:0:0, [1, 2, 3].map(@it:1:0, @it:1:0 + 1))], @index0 == @index0) [BLOCK_RECURSION_DEPTH_9]: cel.@block([[1, 2, 3].map(@it:0:0, [1, 2, 3].map(@it:1:0, @it:1:0 + 1))], @index0 == @index0) +Test case: ADJACENT_NESTED_MACROS_COMP_V2 +Source: [1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) == [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1)) +=====> +Result: true +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2, 3], @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))], @index1 == @index1) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2, 3]], @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1, 2, 3]], @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1, 2, 3]], @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3].transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1), [1, 2, 3].transformMap(@it:0:0, @it2:0:0, @index0)], @index1 == @index1) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3].transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1), [1, 2, 3].transformMap(@it:0:0, @it2:0:0, @index0)], @index1 == @index1) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3].transformMap(@it:0:0, @it2:0:0, [1, 2, 3].transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))], @index0 == @index0) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1, 2, 3].transformMap(@it:0:0, @it2:0:0, [1, 2, 3].transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))], @index0 == @index0) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1, 2, 3].transformMap(@it:0:0, @it2:0:0, [1, 2, 3].transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))], @index0 == @index0) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1, 2, 3].transformMap(@it:0:0, @it2:0:0, [1, 2, 3].transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))], @index0 == @index0) + Test case: INCLUSION_LIST Source: 1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3] =====> @@ -388,6 +463,21 @@ Result: true [BLOCK_RECURSION_DEPTH_8]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.map(@it:0:0, @index1.map(@it:1:0, [3, 4])) == [@index0, @index0]) [BLOCK_RECURSION_DEPTH_9]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.map(@it:0:0, @index1.map(@it:1:0, [3, 4])) == [@index0, @index0]) +Test case: MACRO_ITER_VAR2_NOT_REFERENCED +Source: [1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] +=====> +Result: true +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2], [3, 4], [@index1, @index1]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @index1)) == [@index2, @index2]) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2], [3, 4], [@index1, @index1], [@index1], [@index2, @index2]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @index1)) == @index4) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[[3, 4], [3, 4]], [1, 2], [[3, 4]], @index1.transformList(@it:1:0, @it2:1:0, [3, 4]), [@index3]], @index1.transformList(@it:0:0, @it2:0:0, @index3) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[[3, 4], [3, 4]], [1, 2], [[3, 4]], @index1.transformList(@it:1:0, @it2:1:0, [3, 4])], @index1.transformList(@it:0:0, @it2:0:0, @index3) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.transformList(@it:1:0, @it2:1:0, [3, 4])], @index1.transformList(@it:0:0, @it2:0:0, @index2) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.transformList(@it:1:0, @it2:1:0, [3, 4])], @index1.transformList(@it:0:0, @it2:0:0, @index2) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.transformList(@it:1:0, @it2:1:0, [3, 4])], @index1.transformList(@it:0:0, @it2:0:0, @index2) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.transformList(@it:0:0, @it2:0:0, @index1.transformList(@it:1:0, @it2:1:0, [3, 4])) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.transformList(@it:0:0, @it2:0:0, @index1.transformList(@it:1:0, @it2:1:0, [3, 4])) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.transformList(@it:0:0, @it2:0:0, @index1.transformList(@it:1:0, @it2:1:0, [3, 4])) == [@index0, @index0]) + Test case: MACRO_SHADOWED_VARIABLE Source: [x - 1 > 3 ? x - 1 : 5].exists(x, x - 1 > 3) || x - 1 > 3 =====> @@ -418,6 +508,36 @@ Result: [[[foofoo, foofoo, foofoo, foofoo], [foofoo, foofoo, foofoo, foofoo]], [ [BLOCK_RECURSION_DEPTH_8]: ["foo", "bar"].map(x, [x + x, x + x]).map(x, [x + x, x + x]) [BLOCK_RECURSION_DEPTH_9]: ["foo", "bar"].map(x, [x + x, x + x]).map(x, [x + x, x + x]) +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +Result: CelUnknownSet{attributes=[], unknownExprIds=[6]} +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([x - y - 1, @index0 > 3], [@index1 ? @index0 : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index1) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([x - y, @index0 - 1, @index1 > 3, @index2 ? @index1 : 5, [@index3]], @index4.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index2) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([x - y - 1, @index0 > 3, [@index1 ? @index0 : 5]], @index2.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index1) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([x - y - 1 > 3, @index0 ? (x - y - 1) : 5, [@index1]], @index2.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([x - y - 1 > 3, [@index0 ? (x - y - 1) : 5]], @index1.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) + +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +Result: {0=[0, [0, foofoo, 0, foofoo]], 1=[2, [2, barbar, 2, barbar]]} +[BLOCK_COMMON_SUBEXPR_ONLY]: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([["foo", "bar"]], @index0.transformMap(@it:1:0, @it2:1:0, [@it:1:0 + @it:1:0, @it2:1:0 + @it2:1:0]).transformMap(@it:0:0, @it2:0:0, [@it:0:0 + @it:0:0, @it2:0:0 + @it2:0:0])) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([["foo", "bar"]], @index0.transformMap(@it:1:0, @it2:1:0, [@it:1:0 + @it:1:0, @it2:1:0 + @it2:1:0]).transformMap(@it:0:0, @it2:0:0, [@it:0:0 + @it:0:0, @it2:0:0 + @it2:0:0])) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([["foo", "bar"]], @index0.transformMap(@it:1:0, @it2:1:0, [@it:1:0 + @it:1:0, @it2:1:0 + @it2:1:0]).transformMap(@it:0:0, @it2:0:0, [@it:0:0 + @it:0:0, @it2:0:0 + @it2:0:0])) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([["foo", "bar"].transformMap(@it:1:0, @it2:1:0, [@it:1:0 + @it:1:0, @it2:1:0 + @it2:1:0])], @index0.transformMap(@it:0:0, @it2:0:0, [@it:0:0 + @it:0:0, @it2:0:0 + @it2:0:0])) +[BLOCK_RECURSION_DEPTH_5]: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +[BLOCK_RECURSION_DEPTH_6]: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +[BLOCK_RECURSION_DEPTH_7]: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +[BLOCK_RECURSION_DEPTH_8]: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +[BLOCK_RECURSION_DEPTH_9]: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) + Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> From 00de410da81fcf90cb6ca95d958a5c844072a36c Mon Sep 17 00:00:00 2001 From: CEL Dev Team Date: Mon, 15 Sep 2025 00:30:43 -0700 Subject: [PATCH 04/27] Add an overload for `CelMutableExpr.ofComprehension`. The new overload allows creating a `CelMutableExpr` from a `CelMutableComprehension` without specifying an expression ID, defaulting the ID to 0. PiperOrigin-RevId: 807117259 --- .../dev/cel/common/ast/CelMutableExpr.java | 4 +++ .../cel/common/ast/CelMutableExprTest.java | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java b/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java index d3940405e..e0fabc400 100644 --- a/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java +++ b/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java @@ -1034,6 +1034,10 @@ public static CelMutableExpr ofMap(long id, CelMutableMap mutableMap) { return new CelMutableExpr(id, mutableMap); } + public static CelMutableExpr ofComprehension(CelMutableComprehension mutableComprehension) { + return ofComprehension(0, mutableComprehension); + } + public static CelMutableExpr ofComprehension( long id, CelMutableComprehension mutableComprehension) { return new CelMutableExpr(id, mutableComprehension); diff --git a/common/src/test/java/dev/cel/common/ast/CelMutableExprTest.java b/common/src/test/java/dev/cel/common/ast/CelMutableExprTest.java index 2307b6219..a44c30bb1 100644 --- a/common/src/test/java/dev/cel/common/ast/CelMutableExprTest.java +++ b/common/src/test/java/dev/cel/common/ast/CelMutableExprTest.java @@ -600,6 +600,35 @@ public void mutableMap_deepCopy() { .containsExactlyElementsIn(deepCopiedExpr.map().entries()); } + @Test + public void ofComprehension() { + CelMutableExpr mutableExpr = + CelMutableExpr.ofComprehension( + CelMutableComprehension.create( + "iterVar", + CelMutableExpr.ofList( + CelMutableList.create(CelMutableExpr.ofConstant(CelConstant.ofValue(true)))), + "accuVar", + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofIdent("__result__"))); + + assertThat(mutableExpr.id()).isEqualTo(0L); + assertThat(mutableExpr.comprehension()) + .isEqualTo( + CelMutableComprehension.create( + "iterVar", + "", + CelMutableExpr.ofList( + CelMutableList.create(CelMutableExpr.ofConstant(CelConstant.ofValue(true)))), + "accuVar", + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofIdent("__result__"))); + } + @Test public void ofComprehension_withId() { CelMutableExpr mutableExpr = From 45d550bdb386f82770430baa0d8569f29b4d4244 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 15 Sep 2025 17:59:03 -0700 Subject: [PATCH 05/27] Internal Changes PiperOrigin-RevId: 807457876 --- .../test/java/dev/cel/conformance/ConformanceTestRunner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java b/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java index 50fb0e086..89598ed3e 100644 --- a/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java +++ b/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java @@ -108,7 +108,7 @@ protected List getChildren() { @Override protected Description describeChild(ConformanceTest child) { return Description.createTestDescription( - ConformanceTest.class, child.getName(), ConformanceTest.class.getAnnotations()); + ConformanceTests.class, child.getName(), ConformanceTest.class.getAnnotations()); } @Override From 2f87ed9436e15261ec43e5d82c582ce17f34cce1 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 16 Sep 2025 11:22:30 -0700 Subject: [PATCH 06/27] Override environment's expected result type during constant folding PiperOrigin-RevId: 807773409 --- .../java/dev/cel/optimizer/optimizers/BUILD.bazel | 1 + .../optimizers/ConstantFoldingOptimizer.java | 6 +++++- .../optimizers/ConstantFoldingOptimizerTest.java | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel b/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel index 7728c0ac8..7674870a8 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel @@ -26,6 +26,7 @@ java_library( "//common/ast", "//common/ast:mutable_expr", "//common/navigation:mutable_navigation", + "//common/types", "//extensions:optional_library", "//optimizer:ast_optimizer", "//optimizer:mutable_ast", diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java index e46ee2e98..fd52f4138 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java @@ -38,6 +38,7 @@ import dev.cel.common.ast.CelMutableExprConverter; import dev.cel.common.navigation.CelNavigableMutableAst; import dev.cel.common.navigation.CelNavigableMutableExpr; +import dev.cel.common.types.SimpleType; import dev.cel.extensions.CelOptionalLibrary.Function; import dev.cel.optimizer.AstMutator; import dev.cel.optimizer.CelAstOptimizer; @@ -88,6 +89,9 @@ private static CelMutableExpr newOptionalNoneExpr() { @Override public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel) throws CelOptimizationException { + // Override the environment's expected type to generally allow all subtrees to be folded. + Cel optimizerEnv = cel.toCelBuilder().setResultType(SimpleType.DYN).build(); + CelMutableAst mutableAst = CelMutableAst.fromCelAst(ast); int iterCount = 0; boolean continueFolding = true; @@ -112,7 +116,7 @@ public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel) mutatedResult = maybePruneBranches(mutableAst, foldableExpr.expr()); if (!mutatedResult.isPresent()) { // Evaluate the call then fold - mutatedResult = maybeFold(cel, mutableAst, foldableExpr); + mutatedResult = maybeFold(optimizerEnv, mutableAst, foldableExpr); } if (!mutatedResult.isPresent()) { diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java index 66c5d86d5..ea1e77edb 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java @@ -347,6 +347,20 @@ public void constantFold_addFoldableFunction_success() throws Exception { assertThat(CEL_UNPARSER.unparse(optimizedAst)).isEqualTo("true"); } + @Test + public void constantFold_withExpectedResultTypeSet_success() throws Exception { + Cel cel = CelFactory.standardCelBuilder().setResultType(SimpleType.STRING).build(); + CelOptimizer optimizer = + CelOptimizerFactory.standardCelOptimizerBuilder(cel) + .addAstOptimizers(ConstantFoldingOptimizer.getInstance()) + .build(); + CelAbstractSyntaxTree ast = cel.compile("string(!true)").getAst(); + + CelAbstractSyntaxTree optimizedAst = optimizer.optimize(ast); + + assertThat(CEL_UNPARSER.unparse(optimizedAst)).isEqualTo("\"false\""); + } + @Test public void constantFold_withMacroCallPopulated_comprehensionsAreReplacedWithNotSet() throws Exception { From da0f0ae8e958a4609523472c28d68abee8e16f32 Mon Sep 17 00:00:00 2001 From: Chinmay Madeshi Date: Tue, 16 Sep 2025 20:28:33 -0700 Subject: [PATCH 07/27] Setup of the coverage index. PiperOrigin-RevId: 807959853 --- .../dev/cel/parser/CelUnparserVisitor.java | 11 + .../dev/cel/testing/testrunner/BUILD.bazel | 30 +- .../testing/testrunner/CelCoverageIndex.java | 296 ++++++++++++++++++ .../testrunner/CelUserTestTemplate.java | 14 +- .../testing/testrunner/JUnitXmlReporter.java | 64 +++- .../cel/testing/testrunner/TestExecutor.java | 23 +- .../testing/testrunner/TestRunnerLibrary.java | 81 ++++- .../dev/cel/testing/testrunner/BUILD.bazel | 48 +++ .../testrunner/CelCoverageIndexTest.java | 93 ++++++ .../cel/testing/testrunner/CoverageTest.java | 39 +++ .../testrunner/JUnitXmlReporterTest.java | 87 +++++ .../testrunner/TestRunnerLibraryTest.java | 18 ++ .../partial_coverage_tests.textproto | 26 ++ .../coverage_test_case/simple_expression.cel | 1 + .../coverage_test_case/tests.textproto | 26 ++ testing/testrunner/BUILD.bazel | 5 + testing/testrunner/cel_java_test.bzl | 4 + 17 files changed, 846 insertions(+), 20 deletions(-) create mode 100644 testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java create mode 100644 testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java create mode 100644 testing/src/test/java/dev/cel/testing/testrunner/CoverageTest.java create mode 100644 testing/src/test/resources/expressions/coverage_test_case/partial_coverage_tests.textproto create mode 100644 testing/src/test/resources/expressions/coverage_test_case/simple_expression.cel create mode 100644 testing/src/test/resources/expressions/coverage_test_case/tests.textproto diff --git a/parser/src/main/java/dev/cel/parser/CelUnparserVisitor.java b/parser/src/main/java/dev/cel/parser/CelUnparserVisitor.java index 398a554ca..a743c0439 100644 --- a/parser/src/main/java/dev/cel/parser/CelUnparserVisitor.java +++ b/parser/src/main/java/dev/cel/parser/CelUnparserVisitor.java @@ -66,6 +66,17 @@ public String unparse() { return stringBuilder.toString(); } + /** + * Unparses a specific {@link CelExpr} node within the AST. + * + *

This method exists to allow unparsing of an arbitrary node within the stored AST in this + * visitor. + */ + public String unparse(CelExpr expr) { + visit(expr); + return stringBuilder.toString(); + } + private static String maybeQuoteField(String field) { if (RESTRICTED_FIELD_NAMES.contains(field) || !IDENTIFIER_SEGMENT_PATTERN.matcher(field).matches()) { diff --git a/testing/src/main/java/dev/cel/testing/testrunner/BUILD.bazel b/testing/src/main/java/dev/cel/testing/testrunner/BUILD.bazel index bfbe68cab..6924f753f 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/testrunner/BUILD.bazel @@ -15,6 +15,7 @@ java_library( ], deps = [ ":annotations", + ":cel_coverage_index", ":cel_test_suite", ":cel_test_suite_exception", ":cel_test_suite_text_proto_parser", @@ -33,7 +34,11 @@ java_library( srcs = ["JUnitXmlReporter.java"], tags = [ ], - deps = ["@maven//:com_google_guava_guava"], + deps = [ + ":cel_coverage_index", + "@maven//:com_google_guava_guava", + "@maven//:org_jspecify_jspecify", + ], ) java_library( @@ -42,11 +47,32 @@ java_library( tags = [ ], deps = [ + ":cel_coverage_index", ":cel_expression_source", ":cel_test_context", ":cel_test_suite", ":test_runner_library", "@maven//:junit_junit", + "@maven//:org_jspecify_jspecify", + ], +) + +java_library( + name = "cel_coverage_index", + srcs = ["CelCoverageIndex.java"], + tags = [ + ], + deps = [ + "//:auto_value", + "//common:cel_ast", + "//common/ast", + "//common/navigation", + "//common/types:type_providers", + "//parser:unparser_visitor", + "//runtime:evaluation_listener", + "@maven//:com_google_code_findbugs_annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", ], ) @@ -56,6 +82,7 @@ java_library( tags = [ ], deps = [ + ":cel_coverage_index", ":cel_expression_source", ":cel_test_context", ":cel_test_suite", @@ -80,6 +107,7 @@ java_library( "@cel_spec//proto/cel/expr:expr_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", + "@maven//:org_jspecify_jspecify", ], ) diff --git a/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java b/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java new file mode 100644 index 000000000..4c4ff4291 --- /dev/null +++ b/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java @@ -0,0 +1,296 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package dev.cel.testing.testrunner; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import javax.annotation.concurrent.ThreadSafe; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.ast.CelExpr; +import dev.cel.common.ast.CelExpr.ExprKind; +import dev.cel.common.navigation.CelNavigableAst; +import dev.cel.common.navigation.CelNavigableExpr; +import dev.cel.common.types.CelKind; +import dev.cel.parser.CelUnparserVisitor; +import dev.cel.runtime.CelEvaluationListener; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import java.util.logging.Logger; + +/** + * A class for managing the coverage index for CEL tests. + * + *

This class is used to manage the coverage index for CEL tests. It provides a method for + * getting the coverage index for a given test case. + */ +final class CelCoverageIndex { + + private static final Logger logger = Logger.getLogger(CelCoverageIndex.class.getName()); + + private CelAbstractSyntaxTree ast; + private final ConcurrentHashMap nodeCoverageStatsMap = + new ConcurrentHashMap<>(); + + public void init(CelAbstractSyntaxTree ast) { + this.ast = ast; + CelNavigableExpr.fromExpr(ast.getExpr()) + .allNodes() + .forEach( + celNavigableExpr -> { + NodeCoverageStats nodeCoverageStats = new NodeCoverageStats(); + nodeCoverageStats.isBooleanNode.set(isNodeTypeBoolean(celNavigableExpr.expr())); + nodeCoverageStatsMap.put(celNavigableExpr.id(), nodeCoverageStats); + }); + } + + /** + * Returns the evaluation listener for the CEL test suite. + * + *

This listener is used to track the coverage of the CEL test suite. + */ + public CelEvaluationListener newEvaluationListener() { + return new EvaluationListener(nodeCoverageStatsMap); + } + + /** Returns the coverage report for the CEL test suite. */ + public CoverageReport generateCoverageReport() { + CoverageReport.Builder reportBuilder = + CoverageReport.builder().setCelExpression(new CelUnparserVisitor(ast).unparse()); + traverseAndCalculateCoverage( + CelNavigableAst.fromAst(ast).getRoot(), nodeCoverageStatsMap, true, "", reportBuilder); + CoverageReport report = reportBuilder.build(); + logger.info("CEL Expression: " + report.celExpression()); + logger.info("Nodes: " + report.nodes()); + logger.info("Covered Nodes: " + report.coveredNodes()); + logger.info("Branches: " + report.branches()); + logger.info("Covered Boolean Outcomes: " + report.coveredBooleanOutcomes()); + logger.info("Unencountered Nodes: \n" + String.join("\n", report.unencounteredNodes())); + logger.info("Unencountered Branches: \n" + String.join("\n", + report.unencounteredBranches())); + return report; + } + + /** A class for managing the coverage report for a CEL test suite. */ + @AutoValue + public abstract static class CoverageReport { + public abstract String celExpression(); + + public abstract long nodes(); + + public abstract long coveredNodes(); + + public abstract long branches(); + + public abstract long coveredBooleanOutcomes(); + + public abstract ImmutableList unencounteredNodes(); + + public abstract ImmutableList unencounteredBranches(); + + public static Builder builder() { + return new AutoValue_CelCoverageIndex_CoverageReport.Builder() + .setNodes(0L) + .setCoveredNodes(0L) + .setBranches(0L) + .setCelExpression("") + .setCoveredBooleanOutcomes(0L); + } + + /** Builder for {@link CoverageReport}. */ + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setCelExpression(String value); + + public abstract long nodes(); + + public abstract Builder setNodes(long value); + + public abstract long coveredNodes(); + + public abstract Builder setCoveredNodes(long value); + + public abstract long branches(); + + public abstract Builder setBranches(long value); + + public abstract long coveredBooleanOutcomes(); + + public abstract Builder setCoveredBooleanOutcomes(long value); + + public abstract ImmutableList.Builder unencounteredNodesBuilder(); + + public abstract ImmutableList.Builder unencounteredBranchesBuilder(); + + @CanIgnoreReturnValue + public final Builder addUnencounteredNodes(String value) { + unencounteredNodesBuilder().add(value); + return this; + } + + @CanIgnoreReturnValue + public final Builder addUnencounteredBranches(String value) { + unencounteredBranchesBuilder().add(value); + return this; + } + + public abstract CoverageReport build(); + } + } + + /** A class for managing the coverage stats for a CEL node. */ + @ThreadSafe + private static final class NodeCoverageStats { + final AtomicBoolean isBooleanNode = new AtomicBoolean(false); + final AtomicBoolean covered = new AtomicBoolean(false); + final AtomicBoolean hasTrueBranch = new AtomicBoolean(false); + final AtomicBoolean hasFalseBranch = new AtomicBoolean(false); + } + + private Boolean isNodeTypeBoolean(CelExpr celExpr) { + return ast.getTypeMap().containsKey(celExpr.id()) + && ast.getTypeMap().get(celExpr.id()).kind().equals(CelKind.BOOL); + } + + private void traverseAndCalculateCoverage( + CelNavigableExpr node, + Map statsMap, + boolean logUnencountered, + String precedingTabs, + CoverageReport.Builder reportBuilder) { + long nodeId = node.id(); + NodeCoverageStats stats = statsMap.getOrDefault(nodeId, new NodeCoverageStats()); + reportBuilder.setNodes(reportBuilder.nodes() + 1); + + boolean isInterestingBooleanNode = isInterestingBooleanNode(node, stats); + + // Only unparse if the node is interesting (boolean node) and we need to log + // unencountered nodes. + String exprText = ""; + if (isInterestingBooleanNode && logUnencountered) { + exprText = new CelUnparserVisitor(ast).unparse(node.expr()); + } + + // Update coverage for the current node and determine if we should continue logging + // unencountered. + logUnencountered = + updateNodeCoverage( + nodeId, stats, isInterestingBooleanNode, exprText, logUnencountered, reportBuilder); + + if (isInterestingBooleanNode) { + precedingTabs = + updateBooleanBranchCoverage( + nodeId, stats, exprText, precedingTabs, logUnencountered, reportBuilder); + } + + for (CelNavigableExpr child : node.children().collect(toImmutableList())) { + traverseAndCalculateCoverage(child, statsMap, logUnencountered, precedingTabs, reportBuilder); + } + } + + private boolean isInterestingBooleanNode(CelNavigableExpr node, NodeCoverageStats stats) { + return stats.isBooleanNode.get() + && !node.expr().getKind().equals(ExprKind.Kind.CONSTANT) + && !(node.expr().getKind().equals(ExprKind.Kind.CALL) + && node.expr().call().function().equals("cel.@block")); + } + + /** + * Updates the coverage report based on whether the current node was covered. Returns true if + * logging of unencountered nodes should continue for children, false otherwise. + */ + private boolean updateNodeCoverage( + long nodeId, + NodeCoverageStats stats, + boolean isInterestingBooleanNode, + String exprText, + boolean logUnencountered, + CoverageReport.Builder reportBuilder) { + if (stats.covered.get()) { + reportBuilder.setCoveredNodes(reportBuilder.coveredNodes() + 1); + } else { + if (logUnencountered) { + if (isInterestingBooleanNode) { + reportBuilder.addUnencounteredNodes( + String.format("Expression ID %d ('%s')", nodeId, exprText)); + } + // Once an unencountered node is found, we don't log further unencountered nodes in its + // subtree to avoid noise. + return false; + } + } + return logUnencountered; + } + + /** + * Updates the coverage report for boolean nodes, including branch coverage. Returns the + * potentially modified `precedingTabs` string. + */ + private String updateBooleanBranchCoverage( + long nodeId, + NodeCoverageStats stats, + String exprText, + String precedingTabs, + boolean logUnencountered, + CoverageReport.Builder reportBuilder) { + reportBuilder.setBranches(reportBuilder.branches() + 2); + if (stats.hasTrueBranch.get()) { + reportBuilder.setCoveredBooleanOutcomes(reportBuilder.coveredBooleanOutcomes() + 1); + } else if (logUnencountered) { + reportBuilder.addUnencounteredBranches( + String.format( + "%sExpression ID %d ('%s'): lacks 'true' coverage", precedingTabs, nodeId, exprText)); + precedingTabs += "\t\t"; + } + if (stats.hasFalseBranch.get()) { + reportBuilder.setCoveredBooleanOutcomes(reportBuilder.coveredBooleanOutcomes() + 1); + } else if (logUnencountered) { + reportBuilder.addUnencounteredBranches( + String.format( + "%sExpression ID %d ('%s'): lacks 'false' coverage", + precedingTabs, nodeId, exprText)); + precedingTabs += "\t\t"; + } + return precedingTabs; + } + + @ThreadSafe + private static final class EvaluationListener implements CelEvaluationListener { + + private final ConcurrentHashMap nodeCoverageStatsMap; + + EvaluationListener(ConcurrentHashMap nodeCoverageStatsMap) { + this.nodeCoverageStatsMap = nodeCoverageStatsMap; + } + + @Override + public void callback(CelExpr celExpr, Object evaluationResult) { + NodeCoverageStats nodeCoverageStats = nodeCoverageStatsMap.get(celExpr.id()); + nodeCoverageStats.covered.set(true); + if (nodeCoverageStats.isBooleanNode.get()) { + if (evaluationResult instanceof Boolean) { + if ((Boolean) evaluationResult) { + nodeCoverageStats.hasTrueBranch.set(true); + } else { + nodeCoverageStats.hasFalseBranch.set(true); + } + } + } + } + } +} diff --git a/testing/src/main/java/dev/cel/testing/testrunner/CelUserTestTemplate.java b/testing/src/main/java/dev/cel/testing/testrunner/CelUserTestTemplate.java index 15c68d6b4..02180ffb7 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/CelUserTestTemplate.java +++ b/testing/src/main/java/dev/cel/testing/testrunner/CelUserTestTemplate.java @@ -15,6 +15,7 @@ package dev.cel.testing.testrunner; import dev.cel.testing.testrunner.CelTestSuite.CelTestSection.CelTestCase; +import org.jspecify.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -27,7 +28,12 @@ @RunWith(Parameterized.class) public abstract class CelUserTestTemplate { - @Parameter public CelTestCase testCase; + @Parameter(0) + public CelTestCase testCase; + + @Parameter(1) + public @Nullable CelCoverageIndex celCoverageIndex; + private final CelTestContext celTestContext; public CelUserTestTemplate(CelTestContext celTestContext) { @@ -36,7 +42,11 @@ public CelUserTestTemplate(CelTestContext celTestContext) { @Test public void test() throws Exception { - TestRunnerLibrary.runTest(testCase, updateCelTestContext(celTestContext)); + if (celCoverageIndex != null) { + TestRunnerLibrary.runTest(testCase, updateCelTestContext(celTestContext), celCoverageIndex); + } else { + TestRunnerLibrary.runTest(testCase, updateCelTestContext(celTestContext)); + } } /** diff --git a/testing/src/main/java/dev/cel/testing/testrunner/JUnitXmlReporter.java b/testing/src/main/java/dev/cel/testing/testrunner/JUnitXmlReporter.java index df45f4c2c..5cf4b716d 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/JUnitXmlReporter.java +++ b/testing/src/main/java/dev/cel/testing/testrunner/JUnitXmlReporter.java @@ -31,6 +31,7 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import org.jspecify.annotations.Nullable; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; @@ -70,9 +71,13 @@ void onStart(TestContext context) { testContext = context; } - /** Called after all tests are run */ void onFinish() { - generateReport(); + generateReport(/* coverageReport= */ null); + } + + /** Called after all tests are run */ + void onFinish(CelCoverageIndex.@Nullable CoverageReport coverageReport) { + generateReport(coverageReport); } /** Returns the number of failed tests */ @@ -84,7 +89,7 @@ int getNumFailed() { * Generates junit equivalent xml report that sponge/fusion can understand. Called after all tests * are run */ - void generateReport() { + void generateReport(CelCoverageIndex.@Nullable CoverageReport coverageReport) { try { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); @@ -123,6 +128,7 @@ void generateReport() { prevSuite = currentSuite; currentSuite = doc.createElement(XmlConstants.TESTSUITE); rootElement.appendChild(currentSuite); + addCoverageAttributes(currentSuite, coverageReport); currentSuite.setAttribute(XmlConstants.ATTR_NAME, tr.getTestClassName()); if (prevSuite != null) { prevSuite.setAttribute(XmlConstants.ATTR_TESTS, "" + testsInSuite); @@ -186,6 +192,50 @@ void generateReport() { } } + private void addCoverageAttributes( + Element currentSuite, CelCoverageIndex.@Nullable CoverageReport coverageReport) { + if (coverageReport == null) { + return; + } + if (coverageReport.nodes() == 0) { + currentSuite.setAttribute(XmlConstants.ATTR_CEL_COVERAGE, "No coverage stats found"); + } else { + // CEL expression + currentSuite.setAttribute(XmlConstants.ATTR_CEL_EXPR, coverageReport.celExpression()); + // Node coverage + double nodeCoverage = + (double) coverageReport.coveredNodes() / (double) coverageReport.nodes() * 100.0; + String nodeCoverageString = + String.format( + "%.2f%% (%d out of %d nodes covered)", + nodeCoverage, coverageReport.coveredNodes(), coverageReport.nodes()); + currentSuite.setAttribute(XmlConstants.ATTR_AST_NODE_COVERAGE, nodeCoverageString); + if (!coverageReport.unencounteredNodes().isEmpty()) { + currentSuite.setAttribute( + XmlConstants.ATTR_INTERESTING_UNENCOUNTERED_NODES, + String.join("\n", coverageReport.unencounteredNodes())); + } + // Branch coverage + double branchCoverage = 0.0; + if (coverageReport.branches() > 0) { + branchCoverage = + (double) coverageReport.coveredBooleanOutcomes() + / (double) coverageReport.branches() + * 100.0; + } + String branchCoverageString = + String.format( + "%.2f%% (%d out of %d branch outcomes covered)", + branchCoverage, coverageReport.coveredBooleanOutcomes(), coverageReport.branches()); + currentSuite.setAttribute(XmlConstants.ATTR_AST_BRANCH_COVERAGE, branchCoverageString); + if (!coverageReport.unencounteredBranches().isEmpty()) { + currentSuite.setAttribute( + XmlConstants.ATTR_INTERESTING_UNENCOUNTERED_BRANCH_PATHS, + String.join("\n", coverageReport.unencounteredBranches())); + } + } + } + /** Description of a test suite execution. */ static interface TestContext { String getSuiteName(); @@ -226,5 +276,13 @@ private static final class XmlConstants { static final String ATTR_TYPE = "type"; static final String ATTR_MESSAGE = "message"; static final String ATTR_CLASSNAME = "classname"; + // Coverage attributes. + static final String ATTR_CEL_EXPR = "Cel_Expr"; + static final String ATTR_CEL_COVERAGE = "Cel_Coverage"; + static final String ATTR_AST_NODE_COVERAGE = "Ast_Node_Coverage"; + static final String ATTR_INTERESTING_UNENCOUNTERED_NODES = "Interesting_Unencountered_Nodes"; + static final String ATTR_AST_BRANCH_COVERAGE = "Ast_Branch_Coverage"; + static final String ATTR_INTERESTING_UNENCOUNTERED_BRANCH_PATHS = + "Interesting_Unencountered_Branch_Paths"; } } diff --git a/testing/src/main/java/dev/cel/testing/testrunner/TestExecutor.java b/testing/src/main/java/dev/cel/testing/testrunner/TestExecutor.java index f853b117b..6f6dff3c1 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/TestExecutor.java +++ b/testing/src/main/java/dev/cel/testing/testrunner/TestExecutor.java @@ -20,7 +20,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.time.ZoneId.systemDefault; -import com.google.common.collect.ImmutableList; import com.google.common.io.Files; import dev.cel.testing.testrunner.Annotations.TestSuiteSupplier; import dev.cel.testing.testrunner.CelTestSuite.CelTestSection; @@ -32,6 +31,7 @@ import java.time.Instant; import java.time.LocalDate; import java.time.ZoneOffset; +import java.util.ArrayList; import java.util.Arrays; import org.junit.runner.Description; import org.junit.runner.JUnitCore; @@ -187,15 +187,21 @@ public static void runTests() throws Exception { testReporter.onStart(testContext); boolean allTestsPassed = true; + CelCoverageIndex celCoverageIndex = null; + // If coverage is enabled, the celCoverageIndex parameter will be added in the test class. + // This will be used to run the test case multiple times with different inputs and collect + // the coverage data. + String isCoverageEnabled = System.getProperty("is_coverage_enabled"); + if (isCoverageEnabled != null && Boolean.parseBoolean(isCoverageEnabled)) { + celCoverageIndex = new CelCoverageIndex(); + } for (CelTestSection testSection : testSuite.sections()) { for (CelTestCase testCase : testSection.tests()) { String testName = testSection.name() + "." + testCase.name(); - - Object[] parameter = new Object[] {testCase}; + ArrayList parameter = new ArrayList<>(Arrays.asList(testCase, celCoverageIndex)); TestWithParameters test = - new TestWithParameters( - testName, new TestClass(testClass), ImmutableList.copyOf(parameter)); + new TestWithParameters(testName, new TestClass(testClass), parameter); TestResult testResult = new TestResult(testName, testClass.getName()); testReporter.onTestStart(testResult); @@ -235,7 +241,12 @@ public String describe() { } testContext.done(); - testReporter.onFinish(); + if (celCoverageIndex != null) { + CelCoverageIndex.CoverageReport report = celCoverageIndex.generateCoverageReport(); + testReporter.onFinish(report); + } else { + testReporter.onFinish(); + } if (!allTestsPassed) { throw new RuntimeException(testReporter.getNumFailed() + " tests failed"); } diff --git a/testing/src/main/java/dev/cel/testing/testrunner/TestRunnerLibrary.java b/testing/src/main/java/dev/cel/testing/testrunner/TestRunnerLibrary.java index 13b660036..a5e912ccb 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/TestRunnerLibrary.java +++ b/testing/src/main/java/dev/cel/testing/testrunner/TestRunnerLibrary.java @@ -58,6 +58,7 @@ import java.util.NoSuchElementException; import java.util.Optional; import java.util.logging.Logger; +import org.jspecify.annotations.Nullable; /** Runner library for creating the environment and running the assertions. */ public final class TestRunnerLibrary { @@ -80,9 +81,40 @@ public static void runTest(CelTestCase testCase, CelTestContext celTestContext) } } + /** + * Run the assertions for a given raw/checked expression test case with coverage enabled. + * + *

This method is used for generating coverage data. It will be used to run the test case + * multiple times with different inputs and collect the coverage data. + * + * @param testCase The test case to run. + * @param celTestContext The test context containing the {@link Cel} bundle and other + * configurations. + * @param celCoverageIndex The coverage index to use for the test case. + */ + public static void runTest( + CelTestCase testCase, + CelTestContext celTestContext, + @Nullable CelCoverageIndex celCoverageIndex) + throws Exception { + if (celTestContext.celExpression().isPresent()) { + evaluateTestCase(testCase, celTestContext, celCoverageIndex); + } else { + throw new IllegalArgumentException("No cel expression provided."); + } + } + @VisibleForTesting static void evaluateTestCase(CelTestCase testCase, CelTestContext celTestContext) throws Exception { + evaluateTestCase(testCase, celTestContext, /* celCoverageIndex= */ null); + } + + static void evaluateTestCase( + CelTestCase testCase, + CelTestContext celTestContext, + @Nullable CelCoverageIndex celCoverageIndex) + throws Exception { celTestContext = extendCelTestContext(celTestContext); CelAbstractSyntaxTree ast; CelExpressionSource celExpressionSource = celTestContext.celExpression().get(); @@ -108,7 +140,10 @@ static void evaluateTestCase(CelTestCase testCase, CelTestContext celTestContext throw new IllegalArgumentException( "Unsupported expression type: " + celExpressionSource.type()); } - evaluate(ast, testCase, celTestContext); + if (celCoverageIndex != null) { + celCoverageIndex.init(ast); + } + evaluate(ast, testCase, celTestContext, celCoverageIndex); } private static CelAbstractSyntaxTree readAstFromCheckedExpression( @@ -198,7 +233,10 @@ private static CelAbstractSyntaxTree compilePolicy( } private static void evaluate( - CelAbstractSyntaxTree ast, CelTestCase testCase, CelTestContext celTestContext) + CelAbstractSyntaxTree ast, + CelTestCase testCase, + CelTestContext celTestContext, + @Nullable CelCoverageIndex celCoverageIndex) throws Exception { Cel cel = celTestContext.cel(); Program program = cel.createProgram(ast); @@ -206,7 +244,7 @@ private static void evaluate( CelEvaluationException error = null; Object evaluationResult = null; try { - evaluationResult = getEvaluationResult(testCase, celTestContext, program); + evaluationResult = getEvaluationResult(testCase, celTestContext, program, celCoverageIndex); exprValue = toExprValue(evaluationResult, ast.getResultType()); } catch (CelEvaluationException e) { String errorMessage = @@ -245,7 +283,10 @@ private static void evaluate( } private static Object getEvaluationResult( - CelTestCase testCase, CelTestContext celTestContext, Program program) + CelTestCase testCase, + CelTestContext celTestContext, + Program program, + @Nullable CelCoverageIndex celCoverageIndex) throws CelEvaluationException, IOException, CelValidationException { if (celTestContext.celLateFunctionBindings().isPresent()) { return program.eval( @@ -253,11 +294,16 @@ private static Object getEvaluationResult( } switch (testCase.input().kind()) { case CONTEXT_MESSAGE: - return program.eval(unpackAny(testCase.input().contextMessage(), celTestContext)); + return getEvaluationResultWithMessage( + unpackAny(testCase.input().contextMessage(), celTestContext), + program, + celCoverageIndex); case CONTEXT_EXPR: - return program.eval(getEvaluatedContextExpr(testCase, celTestContext)); + return getEvaluationResultWithMessage( + getEvaluatedContextExpr(testCase, celTestContext), program, celCoverageIndex); case BINDINGS: - return program.eval(getBindings(testCase, celTestContext)); + return getEvaluationResultWithBindings( + getBindings(testCase, celTestContext), program, celCoverageIndex); case NO_INPUT: ImmutableMap.Builder newBindings = ImmutableMap.builder(); for (Map.Entry entry : celTestContext.variableBindings().entrySet()) { @@ -267,11 +313,30 @@ private static Object getEvaluationResult( newBindings.put(entry); } } - return program.eval(newBindings.buildOrThrow()); + return getEvaluationResultWithBindings( + newBindings.buildOrThrow(), program, celCoverageIndex); } throw new IllegalArgumentException("Unexpected input type: " + testCase.input().kind()); } + private static Object getEvaluationResultWithBindings( + Map bindings, Program program, @Nullable CelCoverageIndex celCoverageIndex) + throws CelEvaluationException { + if (celCoverageIndex != null) { + return program.trace(bindings, celCoverageIndex.newEvaluationListener()); + } + return program.eval(bindings); + } + + private static Object getEvaluationResultWithMessage( + Message message, Program program, @Nullable CelCoverageIndex celCoverageIndex) + throws CelEvaluationException { + if (celCoverageIndex != null) { + return program.trace(message, celCoverageIndex.newEvaluationListener()); + } + return program.eval(message); + } + private static Message unpackAny(Any any, CelTestContext celTestContext) throws IOException { if (!celTestContext.fileDescriptorSetPath().isPresent()) { throw new IllegalArgumentException( diff --git a/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel b/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel index db1202aa8..4d57fac3e 100644 --- a/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel +++ b/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel @@ -82,12 +82,30 @@ java_test( test_class = "dev.cel.testing.testrunner.JUnitXmlReporterTest", deps = [ "//:java_truth", + "//testing/testrunner:cel_coverage_index", "//testing/testrunner:junit_xml_reporter", "@maven//:junit_junit", "@maven//:org_mockito_mockito_core", ], ) +java_test( + name = "cel_coverage_index_test", + srcs = ["CelCoverageIndexTest.java"], + test_class = "dev.cel.testing.testrunner.CelCoverageIndexTest", + deps = [ + "//:java_truth", + "//bundle:cel", + "//common:cel_ast", + "//common/types", + "//runtime", + "//runtime:evaluation_listener", + "//testing/testrunner:cel_coverage_index", + "@maven//:com_google_guava_guava", + "@maven//:junit_junit", + ], +) + java_test( name = "default_result_matcher_test", srcs = ["DefaultResultMatcherTest.java"], @@ -355,3 +373,33 @@ cel_java_test( test_src = ":user_test", test_suite = "simple_test_case/tests.textproto", ) + +java_library( + name = "coverage_test", + srcs = ["CoverageTest.java"], + deps = [ + "//bundle:cel", + "//common/types", + "//testing/testrunner:cel_test_context", + "//testing/testrunner:cel_user_test_template", + "@maven//:junit_junit", + ], +) + +cel_java_test( + name = "cel_coverage_test", + cel_expr = "coverage_test_case/simple_expression.cel", + enable_coverage = True, + test_data_path = "//testing/src/test/resources/expressions", + test_src = ":coverage_test", + test_suite = "coverage_test_case/tests.textproto", +) + +cel_java_test( + name = "cel_partial_coverage_test", + cel_expr = "coverage_test_case/simple_expression.cel", + enable_coverage = True, + test_data_path = "//testing/src/test/resources/expressions", + test_src = ":coverage_test", + test_suite = "coverage_test_case/partial_coverage_tests.textproto", +) diff --git a/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java b/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java new file mode 100644 index 000000000..7e088f97d --- /dev/null +++ b/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java @@ -0,0 +1,93 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package dev.cel.testing.testrunner; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableMap; +import dev.cel.bundle.Cel; +import dev.cel.bundle.CelFactory; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.types.SimpleType; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelRuntime; +import dev.cel.testing.testrunner.CelCoverageIndex.CoverageReport; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class CelCoverageIndexTest { + + private Cel cel; + private CelAbstractSyntaxTree ast; + private CelRuntime.Program program; + + @Before + public void setUp() throws Exception { + cel = + CelFactory.standardCelBuilder() + .addVar("x", SimpleType.INT) + .addVar("y", SimpleType.INT) + .build(); + ast = cel.compile("x > 1 && y > 1").getAst(); + program = cel.createProgram(ast); + } + + @Test + public void getCoverageReport_fullCoverage() throws Exception { + CelCoverageIndex coverageIndex = new CelCoverageIndex(); + coverageIndex.init(ast); + CelEvaluationListener listener = coverageIndex.newEvaluationListener(); + + program.trace(ImmutableMap.of("x", 2L, "y", 2L), listener); + + CoverageReport report = coverageIndex.generateCoverageReport(); + assertThat(report.nodes()).isGreaterThan(0); + assertThat(report.coveredNodes()).isEqualTo(report.nodes()); + assertThat(report.branches()).isEqualTo(6); + assertThat(report.coveredBooleanOutcomes()) + .isEqualTo(3); // x>1 -> true, y>1 -> true, && -> true + assertThat(report.unencounteredNodes()).isEmpty(); + assertThat(report.unencounteredBranches()) + .containsExactly( + "Expression ID 4 ('x > 1 && y > 1'): lacks 'false' coverage", + "\t\tExpression ID 2 ('x > 1'): lacks 'false' coverage", + "\t\tExpression ID 6 ('y > 1'): lacks 'false' coverage"); + } + + @Test + public void getCoverageReport_partialCoverage_shortCircuit() throws Exception { + CelCoverageIndex coverageIndex = new CelCoverageIndex(); + coverageIndex.init(ast); + CelEvaluationListener listener = coverageIndex.newEvaluationListener(); + + program.trace(ImmutableMap.of("x", 0L, "y", 2L), listener); + + CoverageReport report = coverageIndex.generateCoverageReport(); + assertThat(report.celExpression()).isEqualTo("x > 1 && y > 1"); + assertThat(report.nodes()).isGreaterThan(0); + assertThat(report.coveredNodes()).isLessThan(report.nodes()); + assertThat(report.branches()).isEqualTo(6); + assertThat(report.coveredBooleanOutcomes()).isEqualTo(2); // x>1 -> false, && -> false + assertThat(report.unencounteredNodes()).containsExactly("Expression ID 6 ('y > 1')"); + // y > 1 is unencountered, so logUnencountered becomes false, and branch coverage for y > 1 + // isn't logged to unencounteredBranches. + assertThat(report.unencounteredBranches()) + .containsExactly( + "Expression ID 4 ('x > 1 && y > 1'): lacks 'true' coverage", + "\t\tExpression ID 2 ('x > 1'): lacks 'true' coverage"); + } +} diff --git a/testing/src/test/java/dev/cel/testing/testrunner/CoverageTest.java b/testing/src/test/java/dev/cel/testing/testrunner/CoverageTest.java new file mode 100644 index 000000000..c7b0c395d --- /dev/null +++ b/testing/src/test/java/dev/cel/testing/testrunner/CoverageTest.java @@ -0,0 +1,39 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.testing.testrunner; + +import dev.cel.bundle.CelFactory; +import dev.cel.common.types.SimpleType; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** + * Sample test class used for coverage analysis, demonstrating the use of the CEL test runner + * without a config file. + */ +@RunWith(Parameterized.class) +public class CoverageTest extends CelUserTestTemplate { + + public CoverageTest() { + super( + CelTestContext.newBuilder() + .setCel( + CelFactory.standardCelBuilder() + .addVar("x", SimpleType.INT) + .addVar("y", SimpleType.INT) + .build()) + .build()); + } +} diff --git a/testing/src/test/java/dev/cel/testing/testrunner/JUnitXmlReporterTest.java b/testing/src/test/java/dev/cel/testing/testrunner/JUnitXmlReporterTest.java index efdd9fedc..b70f1d460 100644 --- a/testing/src/test/java/dev/cel/testing/testrunner/JUnitXmlReporterTest.java +++ b/testing/src/test/java/dev/cel/testing/testrunner/JUnitXmlReporterTest.java @@ -123,4 +123,91 @@ public void testGenerateReport_failure() throws IOException { assertThat(concatenatedFileContent).contains("failures=\"1\""); assertThat(concatenatedFileContent).contains("failure message=\"Test Exception\""); } + + @Test + public void testGenerateReport_coverageReport_noCoverage() throws IOException { + String outputFileName = "test-report-with-coverage.xml"; + File subFolder = tempFolder.newFolder("subFolder"); + File outputFile = new File(subFolder.getAbsolutePath(), outputFileName); + JUnitXmlReporter reporter = new JUnitXmlReporter(outputFile.getAbsolutePath()); + long startTime = 100L; + long test1EndTime = startTime + 400; + long endTime = startTime + 900; + + CelCoverageIndex.CoverageReport coverageReport = + CelCoverageIndex.CoverageReport.builder().build(); + + when(context.getSuiteName()).thenReturn(SUITE_NAME); + when(context.getStartTime()).thenReturn(startTime); + when(context.getEndTime()).thenReturn(endTime); + reporter.onStart(context); + + when(result1.getTestClassName()).thenReturn(TEST_CLASS_NAME); + when(result1.getName()).thenReturn(TEST_METHOD_NAME); + when(result1.getStartMillis()).thenReturn(startTime); + when(result1.getEndMillis()).thenReturn(test1EndTime); + when(result1.getStatus()).thenReturn(JUnitXmlReporter.TestResult.SUCCESS); + reporter.onTestSuccess(result1); + + reporter.onFinish(coverageReport); + assertThat(outputFile.exists()).isTrue(); + String concatenatedFileContent = String.join("\n", Files.readAllLines(outputFile.toPath())); + + assertThat(concatenatedFileContent).contains("No coverage stats found"); + + outputFile.delete(); + } + + @Test + public void testGenerateReport_coverageReport_withCoverage() throws IOException { + String outputFileName = "test-report-with-coverage.xml"; + File subFolder = tempFolder.newFolder("subFolder"); + File outputFile = new File(subFolder.getAbsolutePath(), outputFileName); + JUnitXmlReporter reporter = new JUnitXmlReporter(outputFile.getAbsolutePath()); + long startTime = 100L; + long test1EndTime = startTime + 400; + long endTime = startTime + 900; + + CelCoverageIndex.CoverageReport coverageReport = + CelCoverageIndex.CoverageReport.builder() + .setNodes(10L) + .setCoveredNodes(10L) + .setBranches(10L) + .setCoveredBooleanOutcomes(5L) + .addUnencounteredNodes("Node 1") + .addUnencounteredNodes("Node 2") + .addUnencounteredBranches("Branch 1") + .addUnencounteredBranches("Branch 2") + .build(); + + when(context.getSuiteName()).thenReturn(SUITE_NAME); + when(context.getStartTime()).thenReturn(startTime); + when(context.getEndTime()).thenReturn(endTime); + reporter.onStart(context); + + when(result1.getTestClassName()).thenReturn(TEST_CLASS_NAME); + when(result1.getName()).thenReturn(TEST_METHOD_NAME); + when(result1.getStartMillis()).thenReturn(startTime); + when(result1.getEndMillis()).thenReturn(test1EndTime); + when(result1.getStatus()).thenReturn(JUnitXmlReporter.TestResult.SUCCESS); + reporter.onTestSuccess(result1); + + reporter.onFinish(coverageReport); + assertThat(outputFile.exists()).isTrue(); + String concatenatedFileContent = String.join("\n", Files.readAllLines(outputFile.toPath())); + + assertThat(concatenatedFileContent) + .contains( + ""); + + outputFile.delete(); + } } diff --git a/testing/src/test/java/dev/cel/testing/testrunner/TestRunnerLibraryTest.java b/testing/src/test/java/dev/cel/testing/testrunner/TestRunnerLibraryTest.java index 8c1129b0c..d5a5248a8 100644 --- a/testing/src/test/java/dev/cel/testing/testrunner/TestRunnerLibraryTest.java +++ b/testing/src/test/java/dev/cel/testing/testrunner/TestRunnerLibraryTest.java @@ -263,4 +263,22 @@ public void runTest_missingProtoDescriptors_failure() throws Exception { .hasMessageThat() .contains("Proto descriptors are required for unpacking Any messages."); } + + @Test + public void triggerRunTest_evaluateRawExpr_withCoverage() throws Exception { + CelCoverageIndex celCoverageIndex = new CelCoverageIndex(); + CelTestCase simpleOutputTestCase = + CelTestCase.newBuilder() + .setName("simple_output_test_case") + .setDescription("simple_output_test_case_description") + .setOutput(CelTestSuite.CelTestSection.CelTestCase.Output.ofResultValue(true)) + .build(); + + TestRunnerLibrary.runTest( + simpleOutputTestCase, + CelTestContext.newBuilder() + .setCelExpression(CelExpressionSource.fromRawExpr("1 > 0")) + .build(), + celCoverageIndex); + } } diff --git a/testing/src/test/resources/expressions/coverage_test_case/partial_coverage_tests.textproto b/testing/src/test/resources/expressions/coverage_test_case/partial_coverage_tests.textproto new file mode 100644 index 000000000..61b57637c --- /dev/null +++ b/testing/src/test/resources/expressions/coverage_test_case/partial_coverage_tests.textproto @@ -0,0 +1,26 @@ +# proto-file: google3/third_party/cel/spec/proto/cel/expr/conformance/test/suite.proto +# proto-message: cel.expr.conformance.test.TestSuite + +name: "partial_coverage_tests" +description: "Tests for partial coverage." +sections: { + name: "simple_map_operations" + description: "Tests for map operations." + tests: { + name: "literal_and_sum" + description: "Test that a map can be created and values can be accessed." + input: { + key: "x" + value { value { int64_value: 2 } } + } + input { + key: "y" + value { value { int64_value: 2 } } + } + output { + result_value { + bool_value: true + } + } + } +} diff --git a/testing/src/test/resources/expressions/coverage_test_case/simple_expression.cel b/testing/src/test/resources/expressions/coverage_test_case/simple_expression.cel new file mode 100644 index 000000000..0e8256613 --- /dev/null +++ b/testing/src/test/resources/expressions/coverage_test_case/simple_expression.cel @@ -0,0 +1 @@ +{'sum': x + y, 'literal': 3}.sum == 3 || x == y \ No newline at end of file diff --git a/testing/src/test/resources/expressions/coverage_test_case/tests.textproto b/testing/src/test/resources/expressions/coverage_test_case/tests.textproto new file mode 100644 index 000000000..fefee8d6c --- /dev/null +++ b/testing/src/test/resources/expressions/coverage_test_case/tests.textproto @@ -0,0 +1,26 @@ +# proto-file: google3/third_party/cel/spec/proto/cel/expr/conformance/test/suite.proto +# proto-message: cel.expr.conformance.test.TestSuite + +name: "coverage_tests" +description: "Tests for coverage." +sections: { + name: "simple_map_operations" + description: "Tests for map operations." + tests: { + name: "literal_and_sum" + description: "Test that a map can be created and values can be accessed." + input: { + key: "x" + value { value { int64_value: 1 } } + } + input { + key: "y" + value { value { int64_value: 2 } } + } + output { + result_value { + bool_value: true + } + } + } +} diff --git a/testing/testrunner/BUILD.bazel b/testing/testrunner/BUILD.bazel index 00cb4b832..043d62c88 100644 --- a/testing/testrunner/BUILD.bazel +++ b/testing/testrunner/BUILD.bazel @@ -102,6 +102,11 @@ java_library( exports = ["//testing/src/main/java/dev/cel/testing/testrunner:cel_expression_source"], ) +java_library( + name = "cel_coverage_index", + exports = ["//testing/src/main/java/dev/cel/testing/testrunner:cel_coverage_index"], +) + bzl_library( name = "cel_java_test", srcs = ["cel_java_test.bzl"], diff --git a/testing/testrunner/cel_java_test.bzl b/testing/testrunner/cel_java_test.bzl index b2a19f996..b3457f6f0 100644 --- a/testing/testrunner/cel_java_test.bzl +++ b/testing/testrunner/cel_java_test.bzl @@ -30,6 +30,7 @@ def cel_java_test( config = "", deps = [], proto_deps = [], + enable_coverage = False, test_data_path = "", data = []): """trigger the java impl of the CEL test runner. @@ -56,6 +57,8 @@ def cel_java_test( deps: list of dependencies for the java_binary rule. data: list of data dependencies for the java_binary rule. proto_deps: str label of the proto dependencies for the test. Note: This only supports proto_library rules. + enable_coverage: bool whether to enable coverage for the test. This is needed only if the + test runner is being used for gathering coverage data. test_data_path: absolute path of the directory containing the test files. This is needed only if the test files are not located in the same directory as the BUILD file. This would be of the form "//foo/bar". @@ -101,6 +104,7 @@ def cel_java_test( deps = deps + [":" + name + "_proto_descriptor_set_java_proto"] jvm_flags.append("-Dis_raw_expr=%s" % is_raw_expr) + jvm_flags.append("-Dis_coverage_enabled=%s" % enable_coverage) java_binary( name = name + "_test_runner_binary", From b202efcec808c4e0ef1eba4893f684c6eaeb08d8 Mon Sep 17 00:00:00 2001 From: Chinmay Madeshi Date: Tue, 16 Sep 2025 20:55:43 -0700 Subject: [PATCH 08/27] Support for Dot graph via graphviz PiperOrigin-RevId: 807965747 --- .../testing/testrunner/CelCoverageIndex.java | 149 +++++++++++++++--- .../testing/testrunner/JUnitXmlReporter.java | 3 + .../dev/cel/testing/testrunner/BUILD.bazel | 4 + .../testrunner/CelCoverageIndexTest.java | 35 ++++ .../testrunner/JUnitXmlReporterTest.java | 2 + 5 files changed, 168 insertions(+), 25 deletions(-) diff --git a/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java b/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java index 4c4ff4291..b67d5a1e6 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java +++ b/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java @@ -14,6 +14,7 @@ package dev.cel.testing.testrunner; import static com.google.common.collect.ImmutableList.toImmutableList; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; @@ -27,6 +28,8 @@ import dev.cel.common.types.CelKind; import dev.cel.parser.CelUnparserVisitor; import dev.cel.runtime.CelEvaluationListener; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; @@ -43,6 +46,13 @@ final class CelCoverageIndex { private static final Logger logger = Logger.getLogger(CelCoverageIndex.class.getName()); + private static final String DIGRAPH_HEADER = "digraph {\n"; + private static final String UNCOVERED_NODE_STYLE = "color=\"indianred2\", style=filled"; + private static final String PARTIALLY_COVERED_NODE_STYLE = "color=\"lightyellow\"," + + "style=filled"; + private static final String COMPLETELY_COVERED_NODE_STYLE = "color=\"lightgreen\"," + + "style=filled"; + private CelAbstractSyntaxTree ast; private final ConcurrentHashMap nodeCoverageStatsMap = new ConcurrentHashMap<>(); @@ -68,24 +78,6 @@ public CelEvaluationListener newEvaluationListener() { return new EvaluationListener(nodeCoverageStatsMap); } - /** Returns the coverage report for the CEL test suite. */ - public CoverageReport generateCoverageReport() { - CoverageReport.Builder reportBuilder = - CoverageReport.builder().setCelExpression(new CelUnparserVisitor(ast).unparse()); - traverseAndCalculateCoverage( - CelNavigableAst.fromAst(ast).getRoot(), nodeCoverageStatsMap, true, "", reportBuilder); - CoverageReport report = reportBuilder.build(); - logger.info("CEL Expression: " + report.celExpression()); - logger.info("Nodes: " + report.nodes()); - logger.info("Covered Nodes: " + report.coveredNodes()); - logger.info("Branches: " + report.branches()); - logger.info("Covered Boolean Outcomes: " + report.coveredBooleanOutcomes()); - logger.info("Unencountered Nodes: \n" + String.join("\n", report.unencounteredNodes())); - logger.info("Unencountered Branches: \n" + String.join("\n", - report.unencounteredBranches())); - return report; - } - /** A class for managing the coverage report for a CEL test suite. */ @AutoValue public abstract static class CoverageReport { @@ -103,12 +95,19 @@ public abstract static class CoverageReport { public abstract ImmutableList unencounteredBranches(); + public abstract String dotGraph(); + + // Currently only supported inside google3. + public abstract String graphUrl(); + public static Builder builder() { return new AutoValue_CelCoverageIndex_CoverageReport.Builder() .setNodes(0L) .setCoveredNodes(0L) .setBranches(0L) .setCelExpression("") + .setDotGraph("") + .setGraphUrl("") .setCoveredBooleanOutcomes(0L); } @@ -133,6 +132,10 @@ public abstract static class Builder { public abstract Builder setCoveredBooleanOutcomes(long value); + public abstract Builder setDotGraph(String value); + + public abstract Builder setGraphUrl(String value); + public abstract ImmutableList.Builder unencounteredNodesBuilder(); public abstract ImmutableList.Builder unencounteredBranchesBuilder(); @@ -153,6 +156,33 @@ public final Builder addUnencounteredBranches(String value) { } } + /** Returns the coverage report for the CEL test suite. */ + public CoverageReport generateCoverageReport() { + CoverageReport.Builder reportBuilder = + CoverageReport.builder().setCelExpression(new CelUnparserVisitor(ast).unparse()); + StringBuilder dotGraphBuilder = new StringBuilder(DIGRAPH_HEADER); + traverseAndCalculateCoverage( + CelNavigableAst.fromAst(ast).getRoot(), + nodeCoverageStatsMap, + true, + "", + reportBuilder, + dotGraphBuilder); + dotGraphBuilder.append("}"); + String dotGraph = dotGraphBuilder.toString(); + CoverageReport report = reportBuilder.setDotGraph(dotGraph).build(); + logger.info("CEL Expression: " + report.celExpression()); + logger.info("Nodes: " + report.nodes()); + logger.info("Covered Nodes: " + report.coveredNodes()); + logger.info("Branches: " + report.branches()); + logger.info("Covered Boolean Outcomes: " + report.coveredBooleanOutcomes()); + logger.info("Unencountered Nodes: \n" + String.join("\n", report.unencounteredNodes())); + logger.info("Unencountered Branches: \n" + String.join("\n", + report.unencounteredBranches())); + logger.info("Dot Graph: " + report.dotGraph()); + return report; + } + /** A class for managing the coverage stats for a CEL node. */ @ThreadSafe private static final class NodeCoverageStats { @@ -172,19 +202,32 @@ private void traverseAndCalculateCoverage( Map statsMap, boolean logUnencountered, String precedingTabs, - CoverageReport.Builder reportBuilder) { + CoverageReport.Builder reportBuilder, + StringBuilder dotGraphBuilder) { long nodeId = node.id(); NodeCoverageStats stats = statsMap.getOrDefault(nodeId, new NodeCoverageStats()); reportBuilder.setNodes(reportBuilder.nodes() + 1); boolean isInterestingBooleanNode = isInterestingBooleanNode(node, stats); - // Only unparse if the node is interesting (boolean node) and we need to log - // unencountered nodes. - String exprText = ""; - if (isInterestingBooleanNode && logUnencountered) { - exprText = new CelUnparserVisitor(ast).unparse(node.expr()); + String exprText = new CelUnparserVisitor(ast).unparse(node.expr()); + String nodeCoverageStyle = UNCOVERED_NODE_STYLE; + if (stats.covered.get()) { + if (isInterestingBooleanNode) { + if (stats.hasTrueBranch.get() && stats.hasFalseBranch.get()) { + nodeCoverageStyle = COMPLETELY_COVERED_NODE_STYLE; + } else { + nodeCoverageStyle = PARTIALLY_COVERED_NODE_STYLE; + } + } else { + nodeCoverageStyle = COMPLETELY_COVERED_NODE_STYLE; + } } + String escapedExprText = escapeSpecialCharacters(exprText); + dotGraphBuilder.append( + String.format( + "%d [shape=record, %s, label=\"{<1> exprID: %d | <2> %s} | <3> %s\"];\n", + nodeId, nodeCoverageStyle, nodeId, kindToString(node), escapedExprText)); // Update coverage for the current node and determine if we should continue logging // unencountered. @@ -199,7 +242,9 @@ private void traverseAndCalculateCoverage( } for (CelNavigableExpr child : node.children().collect(toImmutableList())) { - traverseAndCalculateCoverage(child, statsMap, logUnencountered, precedingTabs, reportBuilder); + dotGraphBuilder.append(String.format("%d -> %d;\n", nodeId, child.id())); + traverseAndCalculateCoverage( + child, statsMap, logUnencountered, precedingTabs, reportBuilder, dotGraphBuilder); } } @@ -293,4 +338,58 @@ public void callback(CelExpr celExpr, Object evaluationResult) { } } } + + private String kindToString(CelNavigableExpr node) { + if (node.parent().isPresent() + && node.parent().get().expr().getKind().equals(ExprKind.Kind.COMPREHENSION)) { + CelExpr.CelComprehension comp = node.parent().get().expr().comprehension(); + if (node.id() == comp.iterRange().id()) { + return "IterRange"; + } + if (node.id() == comp.accuInit().id()) { + return "AccuInit"; + } + if (node.id() == comp.loopCondition().id()) { + return "LoopCondition"; + } + if (node.id() == comp.loopStep().id()) { + return "LoopStep"; + } + if (node.id() == comp.result().id()) { + return "Result"; + } + } + + switch (node.getKind()) { + case CALL: + return "Call Node"; + case COMPREHENSION: + return "Comprehension Node"; + case IDENT: + return "Ident Node"; + case LIST: + return "List Node"; + case CONSTANT: + return "Literal Node"; + case MAP: + return "Map Node"; + case SELECT: + return "Select Node"; + case STRUCT: + return "Struct Node"; + default: + return "Unspecified Node"; + } + } + + private String escapeSpecialCharacters(String exprText) { + return exprText + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("||", " \\| \\| ") + .replace("<", "\\<") + .replace(">", "\\>") + .replace("{", "\\{") + .replace("}", "\\}"); + } } diff --git a/testing/src/main/java/dev/cel/testing/testrunner/JUnitXmlReporter.java b/testing/src/main/java/dev/cel/testing/testrunner/JUnitXmlReporter.java index 5cf4b716d..7be21e1e3 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/JUnitXmlReporter.java +++ b/testing/src/main/java/dev/cel/testing/testrunner/JUnitXmlReporter.java @@ -233,6 +233,8 @@ private void addCoverageAttributes( XmlConstants.ATTR_INTERESTING_UNENCOUNTERED_BRANCH_PATHS, String.join("\n", coverageReport.unencounteredBranches())); } + currentSuite.setAttribute( + XmlConstants.ATTR_CEL_TEST_COVERAGE_GRAPH_URL, coverageReport.graphUrl()); } } @@ -284,5 +286,6 @@ private static final class XmlConstants { static final String ATTR_AST_BRANCH_COVERAGE = "Ast_Branch_Coverage"; static final String ATTR_INTERESTING_UNENCOUNTERED_BRANCH_PATHS = "Interesting_Unencountered_Branch_Paths"; + static final String ATTR_CEL_TEST_COVERAGE_GRAPH_URL = "Cel_Test_Coverage_Graph_URL"; } } diff --git a/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel b/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel index 4d57fac3e..8ae4ea580 100644 --- a/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel +++ b/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel @@ -97,7 +97,11 @@ java_test( "//:java_truth", "//bundle:cel", "//common:cel_ast", + "//common:options", "//common/types", + "//compiler:compiler_builder", + "//extensions", + "//parser:macro", "//runtime", "//runtime:evaluation_listener", "//testing/testrunner:cel_coverage_index", diff --git a/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java b/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java index 7e088f97d..400147d35 100644 --- a/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java +++ b/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java @@ -19,7 +19,11 @@ import dev.cel.bundle.Cel; import dev.cel.bundle.CelFactory; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelOptions; import dev.cel.common.types.SimpleType; +import dev.cel.compiler.CelCompiler; +import dev.cel.extensions.CelExtensions; +import dev.cel.parser.CelStandardMacro; import dev.cel.runtime.CelEvaluationListener; import dev.cel.runtime.CelRuntime; import dev.cel.testing.testrunner.CelCoverageIndex.CoverageReport; @@ -90,4 +94,35 @@ public void getCoverageReport_partialCoverage_shortCircuit() throws Exception { "Expression ID 4 ('x > 1 && y > 1'): lacks 'true' coverage", "\t\tExpression ID 2 ('x > 1'): lacks 'true' coverage"); } + + @Test + public void getCoverageReport_comprehension_generatesDotGraph() throws Exception { + cel = CelFactory.standardCelBuilder().build(); + CelCompiler compiler = + cel.toCompilerBuilder() + .setOptions(CelOptions.newBuilder().populateMacroCalls(true).build()) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addLibraries(CelExtensions.comprehensions()) + .build(); + ast = compiler.compile("[1, 2, 3].all(i, i % 2 != 0)").getAst(); + program = cel.createProgram(ast); + CelCoverageIndex coverageIndex = new CelCoverageIndex(); + coverageIndex.init(ast); + CelEvaluationListener listener = coverageIndex.newEvaluationListener(); + + program.trace(ImmutableMap.of(), listener); + + CoverageReport report = coverageIndex.generateCoverageReport(); + assertThat(report.dotGraph()) + .contains("label=\"{<1> exprID: 1 | <2> IterRange} | <3> [1, 2, 3]\""); + assertThat(report.dotGraph()).contains("label=\"{<1> exprID: 12 | <2> AccuInit} | <3> true\""); + assertThat(report.dotGraph()).doesNotContain("red"); // No unencountered nodes. + assertThat(report.dotGraph()) + .contains( + "label=\"{<1> exprID: 14 | <2> LoopCondition} | <3>" + + " @not_strictly_false(@result)\""); + assertThat(report.dotGraph()) + .contains("label=\"{<1> exprID: 16 | <2> LoopStep} | <3> @result && i % 2 != 0\""); + assertThat(report.dotGraph()).contains("label=\"{<1> exprID: 17 | <2> Result} | <3> @result\""); + } } diff --git a/testing/src/test/java/dev/cel/testing/testrunner/JUnitXmlReporterTest.java b/testing/src/test/java/dev/cel/testing/testrunner/JUnitXmlReporterTest.java index b70f1d460..b2a610ccb 100644 --- a/testing/src/test/java/dev/cel/testing/testrunner/JUnitXmlReporterTest.java +++ b/testing/src/test/java/dev/cel/testing/testrunner/JUnitXmlReporterTest.java @@ -178,6 +178,7 @@ public void testGenerateReport_coverageReport_withCoverage() throws IOException .addUnencounteredNodes("Node 2") .addUnencounteredBranches("Branch 1") .addUnencounteredBranches("Branch 2") + .setGraphUrl("http://graphviz/url") .build(); when(context.getSuiteName()).thenReturn(SUITE_NAME); @@ -202,6 +203,7 @@ public void testGenerateReport_coverageReport_withCoverage() throws IOException + " name=\"TestSuiteName\" tests=\"1\" time=\"0.9\"> Date: Thu, 18 Sep 2025 01:20:52 -0700 Subject: [PATCH 09/27] Internal Changes PiperOrigin-RevId: 808456677 --- .../testing/testrunner/CelCoverageIndex.java | 31 ++++++++- .../testrunner/CelCoverageIndexTest.java | 63 ++++++++++++++----- 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java b/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java index b67d5a1e6..e7b08da79 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java +++ b/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java @@ -18,6 +18,7 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; +import com.google.common.io.Files; import com.google.errorprone.annotations.CanIgnoreReturnValue; import javax.annotation.concurrent.ThreadSafe; import dev.cel.common.CelAbstractSyntaxTree; @@ -28,6 +29,8 @@ import dev.cel.common.types.CelKind; import dev.cel.parser.CelUnparserVisitor; import dev.cel.runtime.CelEvaluationListener; +import java.io.File; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Map; @@ -156,7 +159,13 @@ public final Builder addUnencounteredBranches(String value) { } } - /** Returns the coverage report for the CEL test suite. */ + /** + * Generates a coverage report for the CEL test suite. + * + *

Note: If the generated graph URL results in a `Request Entity Too Large` error, download the + * `coverage_graph.txt` file from the test artifacts and upload its contents to Graphviz to render the coverage graph. + */ public CoverageReport generateCoverageReport() { CoverageReport.Builder reportBuilder = CoverageReport.builder().setCelExpression(new CelUnparserVisitor(ast).unparse()); @@ -170,6 +179,7 @@ public CoverageReport generateCoverageReport() { dotGraphBuilder); dotGraphBuilder.append("}"); String dotGraph = dotGraphBuilder.toString(); + CoverageReport report = reportBuilder.setDotGraph(dotGraph).build(); logger.info("CEL Expression: " + report.celExpression()); logger.info("Nodes: " + report.nodes()); @@ -180,6 +190,8 @@ public CoverageReport generateCoverageReport() { logger.info("Unencountered Branches: \n" + String.join("\n", report.unencounteredBranches())); logger.info("Dot Graph: " + report.dotGraph()); + + writeDotGraphToArtifact(dotGraph); return report; } @@ -392,4 +404,21 @@ private String escapeSpecialCharacters(String exprText) { .replace("{", "\\{") .replace("}", "\\}"); } + + private void writeDotGraphToArtifact(String dotGraph) { + String testOutputsDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR"); + if (testOutputsDir == null) { + // Only for non-bazel/blaze users, we write to a subdirectory under the cwd. + testOutputsDir = "cel_artifacts"; + } + File outputDir = new File(testOutputsDir, "cel_test_coverage"); + if (!outputDir.exists()) { + outputDir.mkdirs(); + } + try { + Files.asCharSink(new File(outputDir, "coverage_graph.txt"), UTF_8).write(dotGraph); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } } diff --git a/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java b/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java index 400147d35..287d5435e 100644 --- a/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java +++ b/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java @@ -14,8 +14,10 @@ package dev.cel.testing.testrunner; import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableMap; +import com.google.common.io.Files; import dev.cel.bundle.Cel; import dev.cel.bundle.CelFactory; import dev.cel.common.CelAbstractSyntaxTree; @@ -27,7 +29,7 @@ import dev.cel.runtime.CelEvaluationListener; import dev.cel.runtime.CelRuntime; import dev.cel.testing.testrunner.CelCoverageIndex.CoverageReport; -import org.junit.Before; +import java.io.File; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -35,23 +37,15 @@ @RunWith(JUnit4.class) public final class CelCoverageIndexTest { - private Cel cel; - private CelAbstractSyntaxTree ast; - private CelRuntime.Program program; - - @Before - public void setUp() throws Exception { - cel = + @Test + public void getCoverageReport_fullCoverage() throws Exception { + Cel cel = CelFactory.standardCelBuilder() .addVar("x", SimpleType.INT) .addVar("y", SimpleType.INT) .build(); - ast = cel.compile("x > 1 && y > 1").getAst(); - program = cel.createProgram(ast); - } - - @Test - public void getCoverageReport_fullCoverage() throws Exception { + CelAbstractSyntaxTree ast = cel.compile("x > 1 && y > 1").getAst(); + CelRuntime.Program program = cel.createProgram(ast); CelCoverageIndex coverageIndex = new CelCoverageIndex(); coverageIndex.init(ast); CelEvaluationListener listener = coverageIndex.newEvaluationListener(); @@ -74,6 +68,13 @@ public void getCoverageReport_fullCoverage() throws Exception { @Test public void getCoverageReport_partialCoverage_shortCircuit() throws Exception { + Cel cel = + CelFactory.standardCelBuilder() + .addVar("x", SimpleType.INT) + .addVar("y", SimpleType.INT) + .build(); + CelAbstractSyntaxTree ast = cel.compile("x > 1 && y > 1").getAst(); + CelRuntime.Program program = cel.createProgram(ast); CelCoverageIndex coverageIndex = new CelCoverageIndex(); coverageIndex.init(ast); CelEvaluationListener listener = coverageIndex.newEvaluationListener(); @@ -97,15 +98,15 @@ public void getCoverageReport_partialCoverage_shortCircuit() throws Exception { @Test public void getCoverageReport_comprehension_generatesDotGraph() throws Exception { - cel = CelFactory.standardCelBuilder().build(); + Cel cel = CelFactory.standardCelBuilder().build(); CelCompiler compiler = cel.toCompilerBuilder() .setOptions(CelOptions.newBuilder().populateMacroCalls(true).build()) .setStandardMacros(CelStandardMacro.STANDARD_MACROS) .addLibraries(CelExtensions.comprehensions()) .build(); - ast = compiler.compile("[1, 2, 3].all(i, i % 2 != 0)").getAst(); - program = cel.createProgram(ast); + CelAbstractSyntaxTree ast = compiler.compile("[1, 2, 3].all(i, i % 2 != 0)").getAst(); + CelRuntime.Program program = cel.createProgram(ast); CelCoverageIndex coverageIndex = new CelCoverageIndex(); coverageIndex.init(ast); CelEvaluationListener listener = coverageIndex.newEvaluationListener(); @@ -125,4 +126,32 @@ public void getCoverageReport_comprehension_generatesDotGraph() throws Exception .contains("label=\"{<1> exprID: 16 | <2> LoopStep} | <3> @result && i % 2 != 0\""); assertThat(report.dotGraph()).contains("label=\"{<1> exprID: 17 | <2> Result} | <3> @result\""); } + + @Test + public void getCoverageReport_fullCoverage_writesToUndeclaredOutputs() throws Exception { + // Setup for a more complex graph to write. + Cel cel = CelFactory.standardCelBuilder().build(); + CelCompiler compiler = + cel.toCompilerBuilder() + .setOptions(CelOptions.newBuilder().populateMacroCalls(true).build()) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addLibraries(CelExtensions.comprehensions()) + .build(); + CelAbstractSyntaxTree ast = compiler.compile("[1, 2, 3].all(i, i % 2 != 0)").getAst(); + CelRuntime.Program program = cel.createProgram(ast); + CelCoverageIndex coverageIndex = new CelCoverageIndex(); + coverageIndex.init(ast); + CelEvaluationListener listener = coverageIndex.newEvaluationListener(); + program.trace(ImmutableMap.of(), listener); + + CoverageReport report = coverageIndex.generateCoverageReport(); + + String undeclaredOutputsDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR"); + assertThat(undeclaredOutputsDir).isNotNull(); + + File outputFile = new File(undeclaredOutputsDir, "cel_test_coverage/coverage_graph.txt"); + + String fileContent = Files.asCharSource(outputFile, UTF_8).read(); + assertThat(fileContent).isEqualTo(report.dotGraph()); + } } From 66fde129c7f4fef3b70a1ab8dcfff14c0d49abc9 Mon Sep 17 00:00:00 2001 From: Chinmay Madeshi Date: Thu, 18 Sep 2025 10:29:59 -0700 Subject: [PATCH 10/27] Internal Changes PiperOrigin-RevId: 808640842 --- .../testing/testrunner/CelCoverageIndex.java | 22 ++++++++++------- .../testrunner/CelCoverageIndexTest.java | 24 +++++++++++++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java b/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java index e7b08da79..72b146516 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java +++ b/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java @@ -61,15 +61,19 @@ final class CelCoverageIndex { new ConcurrentHashMap<>(); public void init(CelAbstractSyntaxTree ast) { - this.ast = ast; - CelNavigableExpr.fromExpr(ast.getExpr()) - .allNodes() - .forEach( - celNavigableExpr -> { - NodeCoverageStats nodeCoverageStats = new NodeCoverageStats(); - nodeCoverageStats.isBooleanNode.set(isNodeTypeBoolean(celNavigableExpr.expr())); - nodeCoverageStatsMap.put(celNavigableExpr.id(), nodeCoverageStats); - }); + // If the AST and node coverage stats map are already initialized, then we don't need to + // re-initialize them. + if (this.ast == null && nodeCoverageStatsMap.isEmpty()) { + this.ast = ast; + CelNavigableExpr.fromExpr(ast.getExpr()) + .allNodes() + .forEach( + celNavigableExpr -> { + NodeCoverageStats nodeCoverageStats = new NodeCoverageStats(); + nodeCoverageStats.isBooleanNode.set(isNodeTypeBoolean(celNavigableExpr.expr())); + nodeCoverageStatsMap.put(celNavigableExpr.id(), nodeCoverageStats); + }); + } } /** diff --git a/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java b/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java index 287d5435e..4c6a6cf26 100644 --- a/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java +++ b/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java @@ -154,4 +154,28 @@ public void getCoverageReport_fullCoverage_writesToUndeclaredOutputs() throws Ex String fileContent = Files.asCharSource(outputFile, UTF_8).read(); assertThat(fileContent).isEqualTo(report.dotGraph()); } + + @Test + public void getCoverageReport_fullCoverage_multipleEvaluations() throws Exception { + Cel cel = CelFactory.standardCelBuilder().addVar("x", SimpleType.INT).build(); + CelAbstractSyntaxTree ast = cel.compile("x > 1").getAst(); + CelRuntime.Program program = cel.createProgram(ast); + CelCoverageIndex coverageIndex = new CelCoverageIndex(); + coverageIndex.init(ast); + CelEvaluationListener listener = coverageIndex.newEvaluationListener(); + + program.trace(ImmutableMap.of("x", 2L), listener); + coverageIndex.init(ast); // Re-initialize the coverage index. + program.trace(ImmutableMap.of("x", 0L), listener); + + CoverageReport report = coverageIndex.generateCoverageReport(); + assertThat(report.nodes()).isGreaterThan(0); + assertThat(report.coveredNodes()).isEqualTo(report.nodes()); + assertThat(report.branches()).isEqualTo(2); + // Despite re-initializing the coverage index now, the report should still + // be fully covered. Else, only the second evaluation would've been covered. + assertThat(report.coveredBooleanOutcomes()).isEqualTo(2); + assertThat(report.unencounteredNodes()).isEmpty(); + assertThat(report.unencounteredBranches()).isEmpty(); + } } From f174988498a41dd24ddda6f527bf211796b9b2ea Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 18 Sep 2025 11:27:34 -0700 Subject: [PATCH 11/27] Evaluate CEL's timestamp and duration types to their native equivalent values PiperOrigin-RevId: 808664776 --- common/internal/BUILD.bazel | 10 + .../src/main/java/dev/cel/common/BUILD.bazel | 1 + .../dev/cel/common/CelProtoJsonAdapter.java | 10 + .../java/dev/cel/common/internal/BUILD.bazel | 30 ++ .../cel/common/internal/DateTimeHelpers.java | 261 ++++++++++++++++++ .../dev/cel/common/internal/ProtoAdapter.java | 2 +- .../cel/common/internal/ProtoLiteAdapter.java | 47 +++- .../values/BaseProtoCelValueConverter.java | 17 -- .../values/ProtoCelValueConverterTest.java | 16 +- .../cel/extensions/CelOptionalLibrary.java | 5 + .../extensions/CelOptionalLibraryTest.java | 11 +- .../java/dev/cel/runtime/RuntimeHelpers.java | 11 +- .../java/dev/cel/runtime/TypeResolver.java | 14 +- .../dev/cel/runtime/standard/AddOperator.java | 47 +++- .../java/dev/cel/runtime/standard/BUILD.bazel | 70 ++--- .../cel/runtime/standard/DateTimeHelpers.java | 85 ------ .../runtime/standard/DurationFunction.java | 27 +- .../cel/runtime/standard/GetDateFunction.java | 34 ++- .../standard/GetDayOfMonthFunction.java | 34 ++- .../standard/GetDayOfWeekFunction.java | 42 ++- .../standard/GetDayOfYearFunction.java | 34 ++- .../runtime/standard/GetFullYearFunction.java | 34 ++- .../runtime/standard/GetHoursFunction.java | 45 ++- .../standard/GetMillisecondsFunction.java | 49 +++- .../runtime/standard/GetMinutesFunction.java | 46 ++- .../runtime/standard/GetMonthFunction.java | 34 ++- .../runtime/standard/GetSecondsFunction.java | 57 +++- .../standard/GreaterEqualsOperator.java | 31 ++- .../cel/runtime/standard/GreaterOperator.java | 31 ++- .../dev/cel/runtime/standard/IntFunction.java | 13 +- .../runtime/standard/LessEqualsOperator.java | 31 ++- .../cel/runtime/standard/LessOperator.java | 31 ++- .../cel/runtime/standard/StringFunction.java | 25 +- .../runtime/standard/SubtractOperator.java | 47 +++- .../runtime/standard/TimestampFunction.java | 44 ++- .../runtime/CelLiteRuntimeAndroidTest.java | 14 +- .../dev/cel/runtime/CelLiteRuntimeTest.java | 10 +- .../test/resources/arithmDuration.baseline | 16 +- .../test/resources/arithmTimestamp.baseline | 32 +-- runtime/src/test/resources/duration.baseline | 120 ++------ .../test/resources/durationFunctions.baseline | 50 +--- .../resources/dynamicMessage_adapted.baseline | 8 +- .../dynamicMessage_dynamicDescriptor.baseline | 3 +- .../test/resources/timeConversions.baseline | 23 +- runtime/src/test/resources/timestamp.baseline | 122 ++------ .../resources/timestampFunctions.baseline | 223 ++++----------- .../test/resources/typeComparisons.baseline | 11 + .../test/resources/unknownResultSet.baseline | 4 +- .../dev/cel/testing/BaseInterpreterTest.java | 62 +++-- .../java/dev/cel/testing/utils/BUILD.bazel | 1 + .../dev/cel/testing/utils/ExprValueUtils.java | 16 ++ 51 files changed, 1200 insertions(+), 841 deletions(-) create mode 100644 common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java delete mode 100644 runtime/src/main/java/dev/cel/runtime/standard/DateTimeHelpers.java diff --git a/common/internal/BUILD.bazel b/common/internal/BUILD.bazel index a39bb9365..c69f26b3e 100644 --- a/common/internal/BUILD.bazel +++ b/common/internal/BUILD.bazel @@ -137,3 +137,13 @@ cel_android_library( name = "proto_time_utils_android", exports = ["//common/src/main/java/dev/cel/common/internal:proto_time_utils_android"], ) + +java_library( + name = "date_time_helpers", + exports = ["//common/src/main/java/dev/cel/common/internal:date_time_helpers"], +) + +cel_android_library( + name = "date_time_helpers_android", + exports = ["//common/src/main/java/dev/cel/common/internal:date_time_helpers_android"], +) diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index 255ae5d8d..460a07f13 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -205,6 +205,7 @@ java_library( tags = [ ], deps = [ + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//common/values", "//common/values:cel_byte_string", diff --git a/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java b/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java index e0afb8c4e..e74d52f7a 100644 --- a/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java +++ b/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java @@ -26,8 +26,10 @@ import com.google.protobuf.Struct; import com.google.protobuf.Timestamp; import com.google.protobuf.Value; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelByteString; +import java.time.Instant; import java.util.ArrayList; import java.util.Base64; import java.util.List; @@ -118,6 +120,14 @@ public static Value adaptValueToJsonValue(Object value) { String duration = ProtoTimeUtils.toString((Duration) value); return json.setStringValue(duration).build(); } + if (value instanceof Instant) { + // Instant's toString follows RFC 3339 + return json.setStringValue(value.toString()).build(); + } + if (value instanceof java.time.Duration) { + String duration = DateTimeHelpers.toString((java.time.Duration) value); + return json.setStringValue(duration).build(); + } if (value instanceof FieldMask) { String fieldMaskStr = toJsonString((FieldMask) value); return json.setStringValue(fieldMaskStr).build(); diff --git a/common/src/main/java/dev/cel/common/internal/BUILD.bazel b/common/src/main/java/dev/cel/common/internal/BUILD.bazel index c508649a3..85b5df9a4 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -195,9 +195,11 @@ java_library( deps = [ ":well_known_proto", "//common:error_codes", + "//common:options", "//common:proto_json_adapter", "//common:runtime_exception", "//common/annotations", + "//common/internal:proto_time_utils", "//common/values", "//common/values:cel_byte_string", "@maven//:com_google_errorprone_error_prone_annotations", @@ -429,3 +431,31 @@ cel_android_library( "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) + +java_library( + name = "date_time_helpers", + srcs = ["DateTimeHelpers.java"], + tags = [ + ], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +cel_android_library( + name = "date_time_helpers_android", + srcs = ["DateTimeHelpers.java"], + tags = [ + ], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + "@maven_android//:com_google_guava_guava", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) diff --git a/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java b/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java new file mode 100644 index 000000000..9abe39b4b --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java @@ -0,0 +1,261 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.common.internal; + +import com.google.common.base.Strings; +import com.google.protobuf.Timestamp; +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelRuntimeException; +import dev.cel.common.annotations.Internal; +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.Locale; + +/** Collection of utility methods for CEL datetime handlings. */ +@Internal +@SuppressWarnings("JavaInstantGetSecondsGetNano") // Intended within CEL. +public final class DateTimeHelpers { + public static final String UTC = "UTC"; + + // Timestamp for "0001-01-01T00:00:00Z" + private static final long TIMESTAMP_SECONDS_MIN = -62135596800L; + // Timestamp for "9999-12-31T23:59:59Z" + private static final long TIMESTAMP_SECONDS_MAX = 253402300799L; + + private static final long DURATION_SECONDS_MIN = -315576000000L; + private static final long DURATION_SECONDS_MAX = 315576000000L; + private static final int NANOS_PER_SECOND = 1000000000; + + /** + * Constructs a new {@link LocalDateTime} instance + * + * @param ts Timestamp protobuf object + * @param tz Timezone based on the CEL specification. This is either the canonical name from tz + * database or a standard offset represented in (+/-)HH:MM. Few valid examples are: + *

    + *
  • UTC + *
  • America/Los_Angeles + *
  • -09:30 or -9:30 (Leading zeroes can be omitted though not allowed by spec) + *
+ * + * @return If an Invalid timezone is supplied. + */ + public static LocalDateTime newLocalDateTime(Timestamp ts, String tz) { + return Instant.ofEpochSecond(ts.getSeconds(), ts.getNanos()) + .atZone(timeZone(tz)) + .toLocalDateTime(); + } + + /** + * Constructs a new {@link LocalDateTime} instance from a Java Instant. + * + * @param instant Instant object + * @param tz Timezone based on the CEL specification. This is either the canonical name from tz + * database or a standard offset represented in (+/-)HH:MM. Few valid examples are: + *
    + *
  • UTC + *
  • America/Los_Angeles + *
  • -09:30 or -9:30 (Leading zeroes can be omitted though not allowed by spec) + *
+ * + * @return A new {@link LocalDateTime} instance. + */ + public static LocalDateTime newLocalDateTime(Instant instant, String tz) { + return instant.atZone(timeZone(tz)).toLocalDateTime(); + } + + /** + * Parse from RFC 3339 date string to {@link java.time.Instant}. + * + *

Example of accepted format: "1972-01-01T10:00:20.021-05:00" + */ + public static Instant parse(String text) { + OffsetDateTime offsetDateTime = OffsetDateTime.parse(text); + Instant instant = offsetDateTime.toInstant(); + checkValid(instant); + + return instant; + } + + /** Adds a duration to an instant. */ + public static Instant add(Instant ts, Duration dur) { + Instant newInstant = ts.plus(dur); + checkValid(newInstant); + + return newInstant; + } + + /** Adds two durations */ + public static Duration add(Duration d1, Duration d2) { + Duration newDuration = d1.plus(d2); + checkValid(newDuration); + + return newDuration; + } + + /** Subtracts a duration to an instant. */ + public static Instant subtract(Instant ts, Duration dur) { + Instant newInstant = ts.minus(dur); + checkValid(newInstant); + + return newInstant; + } + + /** Subtract a duration from another. */ + public static Duration subtract(Duration d1, Duration d2) { + Duration newDuration = d1.minus(d2); + checkValid(newDuration); + + return newDuration; + } + + /** + * Formats a {@link Duration} into a minimal seconds-based representation. + * + *

Note: follows {@code ProtoTimeUtils#toString(Duration)} implementation + */ + public static String toString(Duration duration) { + if (duration.isZero()) { + return "0s"; + } + + long totalNanos = duration.toNanos(); + StringBuilder sb = new StringBuilder(); + + if (totalNanos < 0) { + sb.append('-'); + totalNanos = -totalNanos; + } + + long seconds = totalNanos / 1_000_000_000; + int nanos = (int) (totalNanos % 1_000_000_000); + + sb.append(seconds); + + // Follows ProtoTimeUtils.toString(Duration) implementation + if (nanos > 0) { + sb.append('.'); + if (nanos % 1_000_000 == 0) { + // Millisecond precision (3 digits) + int millis = nanos / 1_000_000; + sb.append(String.format(Locale.US, "%03d", millis)); + } else if (nanos % 1_000 == 0) { + // Microsecond precision (6 digits) + int micros = nanos / 1_000; + sb.append(String.format(Locale.US, "%06d", micros)); + } else { + // Nanosecond precision (9 digits) + sb.append(String.format(Locale.US, "%09d", nanos)); + } + } + + sb.append('s'); + return sb.toString(); + } + + /** + * Get the DateTimeZone Instance. + * + * @param tz the ID of the datetime zone + * @return the ZoneId object + */ + private static ZoneId timeZone(String tz) { + try { + return ZoneId.of(tz); + } catch (DateTimeException e) { + // If timezone is not a string name (for example, 'US/Central'), it should be a numerical + // offset from UTC in the format [+/-]HH:MM. + try { + int ind = tz.indexOf(":"); + if (ind == -1) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } + + int hourOffset = Integer.parseInt(tz.substring(0, ind)); + int minOffset = Integer.parseInt(tz.substring(ind + 1)); + // Ensures that the offset are properly formatted in [+/-]HH:MM to conform with + // ZoneOffset's format requirements. + // Example: "-9:30" -> "-09:30" and "9:30" -> "+09:30" + String formattedOffset = + ((hourOffset < 0) ? "-" : "+") + + String.format(Locale.US, "%02d:%02d", Math.abs(hourOffset), minOffset); + + return ZoneId.of(formattedOffset); + + } catch (DateTimeException e2) { + throw new CelRuntimeException(e2, CelErrorCode.BAD_FORMAT); + } + } + } + + /** Throws an {@link IllegalArgumentException} if the given {@link Timestamp} is not valid. */ + private static void checkValid(Instant instant) { + long seconds = instant.getEpochSecond(); + + if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) { + throw new IllegalArgumentException( + Strings.lenientFormat( + "Timestamp is not valid. " + + "Seconds (%s) must be in range [-62,135,596,800, +253,402,300,799]. " + + "Nanos (%s) must be in range [0, +999,999,999].", + seconds, instant.getNano())); + } + } + + /** Throws an {@link IllegalArgumentException} if the given {@link Duration} is not valid. */ + private static void checkValid(Duration duration) { + long seconds = duration.getSeconds(); + int nanos = duration.getNano(); + if (!isDurationValid(seconds, nanos)) { + throw new IllegalArgumentException( + Strings.lenientFormat( + "Duration is not valid. " + + "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]. " + + "Nanos (%s) must be in range [-999,999,999, +999,999,999]. " + + "Nanos must have the same sign as seconds", + seconds, nanos)); + } + } + + /** + * Returns true if the given number of seconds and nanos is a valid {@link Duration}. The {@code + * seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The {@code nanos} + * value must be in the range [-999,999,999, +999,999,999]. + * + *

Note: Durations less than one second are represented with a 0 {@code seconds} field + * and a positive or negative {@code nanos} field. For durations of one second or more, a non-zero + * value for the {@code nanos} field must be of the same sign as the {@code seconds} field. + */ + private static boolean isDurationValid(long seconds, int nanos) { + if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) { + return false; + } + if (nanos < -999999999L || nanos >= NANOS_PER_SECOND) { + return false; + } + if (seconds < 0 || nanos < 0) { + if (seconds > 0 || nanos > 0) { + return false; + } + } + return true; + } + + private DateTimeHelpers() {} +} diff --git a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java index 3461106ae..cd4be2f5e 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java @@ -125,7 +125,7 @@ public final class ProtoAdapter { public ProtoAdapter(DynamicProto dynamicProto, CelOptions celOptions) { this.dynamicProto = checkNotNull(dynamicProto); - this.protoLiteAdapter = new ProtoLiteAdapter(celOptions.enableUnsignedLongs()); + this.protoLiteAdapter = new ProtoLiteAdapter(celOptions); this.celOptions = celOptions; } diff --git a/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java b/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java index eb8c42862..e05d92f68 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java @@ -43,10 +43,12 @@ import com.google.protobuf.UInt64Value; import com.google.protobuf.Value; import dev.cel.common.CelErrorCode; +import dev.cel.common.CelOptions; import dev.cel.common.CelProtoJsonAdapter; import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; import dev.cel.common.values.CelByteString; +import java.time.Instant; import java.util.Map; import java.util.Map.Entry; @@ -63,7 +65,7 @@ @Immutable public final class ProtoLiteAdapter { - private final boolean enableUnsignedLongs; + private final CelOptions celOptions; @SuppressWarnings("unchecked") public MessageLite adaptValueToWellKnownProto(Object value, WellKnownProto wellKnownProto) { @@ -94,9 +96,9 @@ public MessageLite adaptValueToWellKnownProto(Object value, WellKnownProto wellK case UINT64_VALUE: return adaptValueToUint64(value); case DURATION: - return (Duration) value; + return adaptValueToProtoDuration(value); case TIMESTAMP: - return (Timestamp) value; + return adaptValueToProtoTimestamp(value); case EMPTY: case FIELD_MASK: // These two WKTs are typically used in context of JSON conversions, in which they are @@ -114,7 +116,6 @@ public Any adaptValueToAny(Object value, String typeName) { return packAnyMessage((MessageLite) value, typeName); } - // if (value instanceof NullValue) { if (value instanceof dev.cel.common.values.NullValue) { return packAnyMessage( Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build(), WellKnownProto.JSON_VALUE); @@ -140,6 +141,10 @@ public Any adaptValueToAny(Object value, String typeName) { wellKnownProto = WellKnownProto.JSON_LIST_VALUE; } else if (value instanceof Map) { wellKnownProto = WellKnownProto.JSON_STRUCT_VALUE; + } else if (value instanceof Instant) { + wellKnownProto = WellKnownProto.TIMESTAMP; + } else if (value instanceof java.time.Duration) { + wellKnownProto = WellKnownProto.DURATION; } else { throw new IllegalArgumentException("Unsupported value conversion to any: " + value); } @@ -175,16 +180,26 @@ public Object adaptWellKnownProtoToValue( case STRING_VALUE: return ((StringValue) proto).getValue(); case UINT32_VALUE: - if (enableUnsignedLongs) { + if (celOptions.enableUnsignedLongs()) { return UnsignedLong.fromLongBits( Integer.toUnsignedLong(((UInt32Value) proto).getValue())); } return (long) ((UInt32Value) proto).getValue(); case UINT64_VALUE: - if (enableUnsignedLongs) { + if (celOptions.enableUnsignedLongs()) { return UnsignedLong.fromLongBits(((UInt64Value) proto).getValue()); } return ((UInt64Value) proto).getValue(); + case TIMESTAMP: + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return ProtoTimeUtils.toJavaInstant((Timestamp) proto); + } + return proto; + case DURATION: + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return ProtoTimeUtils.toJavaDuration((Duration) proto); + } + return proto; default: return proto; } @@ -324,7 +339,23 @@ private static Any packAnyMessage(MessageLite msg, String typeUrl) { .build(); } - public ProtoLiteAdapter(boolean enableUnsignedLongs) { - this.enableUnsignedLongs = enableUnsignedLongs; + private Timestamp adaptValueToProtoTimestamp(Object value) { + if (!celOptions.evaluateCanonicalTypesToNativeValues()) { + return (Timestamp) value; + } + + return ProtoTimeUtils.toProtoTimestamp((Instant) value); + } + + private Duration adaptValueToProtoDuration(Object value) { + if (!celOptions.evaluateCanonicalTypesToNativeValues()) { + return (Duration) value; + } + + return ProtoTimeUtils.toProtoDuration((java.time.Duration) value); + } + + public ProtoLiteAdapter(CelOptions celOptions) { + this.celOptions = celOptions; } } diff --git a/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java index 38741f555..450b273c2 100644 --- a/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java @@ -53,23 +53,6 @@ public abstract class BaseProtoCelValueConverter extends CelValueConverter { public abstract CelValue fromProtoMessageToCelValue(MessageLite msg); - /** - * Adapts a {@link CelValue} to a native Java object. The CelValue is adapted into protobuf object - * when an equivalent exists. - */ - @Override - public Object fromCelValueToJavaObject(CelValue celValue) { - Preconditions.checkNotNull(celValue); - - if (celValue instanceof TimestampValue) { - return ProtoTimeUtils.toProtoTimestamp(((TimestampValue) celValue).value()); - } else if (celValue instanceof DurationValue) { - return ProtoTimeUtils.toProtoDuration(((DurationValue) celValue).value()); - } - - return super.fromCelValueToJavaObject(celValue); - } - /** {@inheritDoc} Protobuf semantics take precedence for conversion. */ @Override public CelValue fromJavaObjectToCelValue(Object value) { diff --git a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java index b7e72c6b8..234411ce7 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java @@ -16,12 +16,10 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.protobuf.Duration; -import com.google.protobuf.Timestamp; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; -import dev.cel.common.internal.ProtoTimeUtils; +import java.time.Duration; import java.time.Instant; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,13 +33,13 @@ public class ProtoCelValueConverterTest { DefaultDescriptorPool.INSTANCE, DynamicProto.create(DefaultMessageFactory.INSTANCE)); @Test - public void fromCelValueToJavaObject_returnsTimestampValue() { - Timestamp timestamp = - (Timestamp) + public void fromCelValueToJavaObject_returnsInstantValue() { + Instant timestamp = + (Instant) PROTO_CEL_VALUE_CONVERTER.fromCelValueToJavaObject( TimestampValue.create(Instant.ofEpochSecond(50))); - assertThat(timestamp).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(50)); + assertThat(timestamp).isEqualTo(Instant.ofEpochSecond(50)); } @Test @@ -49,9 +47,9 @@ public void fromCelValueToJavaObject_returnsDurationValue() { Duration duration = (Duration) PROTO_CEL_VALUE_CONVERTER.fromCelValueToJavaObject( - DurationValue.create(java.time.Duration.ofSeconds(10))); + DurationValue.create(Duration.ofSeconds(10))); - assertThat(duration).isEqualTo(ProtoTimeUtils.fromSecondsToDuration(10)); + assertThat(duration).isEqualTo(Duration.ofSeconds(10)); } @Test diff --git a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java index a384b09e2..6ed4d1491 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java +++ b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java @@ -50,6 +50,7 @@ import dev.cel.runtime.CelInternalRuntimeLibrary; import dev.cel.runtime.CelRuntimeBuilder; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Collection; import java.util.List; import java.util.Map; @@ -394,6 +395,10 @@ private static boolean isZeroValue(Object val) { } else if (val instanceof NullValue) { // A null value always represents an absent value return true; + } else if (val instanceof Instant) { + return val.equals(Instant.EPOCH); + } else if (val instanceof java.time.Duration) { + return val.equals(java.time.Duration.ZERO); } // Unknown. Assume that it is non-zero. diff --git a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java index a806037da..a51fbedbf 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java @@ -34,7 +34,6 @@ import dev.cel.common.CelOverloadDecl; import dev.cel.common.CelValidationException; import dev.cel.common.CelVarDecl; -import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.types.CelType; import dev.cel.common.types.ListType; import dev.cel.common.types.MapType; @@ -52,6 +51,8 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelRuntime; import dev.cel.runtime.InterpreterUtil; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.Optional; @@ -70,16 +71,12 @@ private enum ConstantTestCases { UINT("5u", "0u", SimpleType.UINT, UnsignedLong.valueOf(5)), BOOL("true", "false", SimpleType.BOOL, true), BYTES("b'abc'", "b''", SimpleType.BYTES, CelByteString.copyFromUtf8("abc")), - DURATION( - "duration('180s')", - "duration('0s')", - SimpleType.DURATION, - ProtoTimeUtils.fromSecondsToDuration(180)), + DURATION("duration('180s')", "duration('0s')", SimpleType.DURATION, Duration.ofMinutes(3)), TIMESTAMP( "timestamp(1685552643)", "timestamp(0)", SimpleType.TIMESTAMP, - ProtoTimeUtils.fromSecondsToTimestamp(1685552643)); + Instant.ofEpochSecond(1685552643)); private final String sourceWithNonZeroValue; private final String sourceWithZeroValue; diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java index 37247f3d3..0e02af273 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java @@ -55,16 +55,23 @@ public static RuntimeHelpers create() { // Functions // ========= - /** Convert a string to a Duration. */ + /** Convert a string to a Protobuf Duration. */ @SuppressWarnings("AndroidJdkLibsChecker") // DateTimeParseException added in 26 public static Duration createDurationFromString(String d) { + java.time.Duration dv = createJavaDurationFromString(d); + return Duration.newBuilder().setSeconds(dv.getSeconds()).setNanos(dv.getNano()).build(); + } + + /** Convert a string to a native Java Duration. */ + @SuppressWarnings("AndroidJdkLibsChecker") // DateTimeParseException added in 26 + public static java.time.Duration createJavaDurationFromString(String d) { try { java.time.Duration dv = AmountFormats.parseUnitBasedDuration(d); // Ensure that the duration value can be adequately represented within a protobuf.Duration. checkArgument( dv.compareTo(DURATION_MAX) <= 0 && dv.compareTo(DURATION_MIN) >= 0, "invalid duration range"); - return Duration.newBuilder().setSeconds(dv.getSeconds()).setNanos(dv.getNano()).build(); + return dv; } catch (DateTimeParseException e) { throw new IllegalArgumentException("invalid duration format", e); } diff --git a/runtime/src/main/java/dev/cel/runtime/TypeResolver.java b/runtime/src/main/java/dev/cel/runtime/TypeResolver.java index b9cc42cc1..905120988 100644 --- a/runtime/src/main/java/dev/cel/runtime/TypeResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/TypeResolver.java @@ -35,6 +35,7 @@ import dev.cel.common.types.StructTypeReference; import dev.cel.common.types.TypeType; import dev.cel.common.values.CelByteString; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -64,8 +65,17 @@ static TypeResolver create() { .put(UnsignedLong.class, TypeType.create(SimpleType.UINT)) .put(String.class, TypeType.create(SimpleType.STRING)) .put(NullValue.class, TypeType.create(SimpleType.NULL_TYPE)) - .put(Duration.class, TypeType.create(SimpleType.DURATION)) - .put(Timestamp.class, TypeType.create(SimpleType.TIMESTAMP)) + .put(java.time.Duration.class, TypeType.create(SimpleType.DURATION)) + .put(Instant.class, TypeType.create(SimpleType.TIMESTAMP)) + .put( + Duration.class, + TypeType.create( + SimpleType.DURATION)) // TODO: Remove once clients have been migrated + .put( + Timestamp.class, + TypeType.create( + SimpleType + .TIMESTAMP)) // TODO: Remove once clients have been migrated .put(ArrayList.class, TypeType.create(ListType.create(SimpleType.DYN))) .put(HashMap.class, TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN))) .put(Optional.class, TypeType.create(OptionalType.create(SimpleType.DYN))) diff --git a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java index 9d75b15ce..42386a942 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java @@ -23,11 +23,13 @@ import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelByteString; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; import java.util.List; @@ -104,24 +106,51 @@ public enum AddOverload implements CelStandardOverload { (celOptions, runtimeEquality) -> CelFunctionBinding.from("add_double", Double.class, Double.class, Double::sum)), ADD_DURATION_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "add_duration_duration", Duration.class, Duration.class, ProtoTimeUtils::add)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "add_duration_duration", + java.time.Duration.class, + java.time.Duration.class, + DateTimeHelpers::add); + } else { + return CelFunctionBinding.from( + "add_duration_duration", Duration.class, Duration.class, ProtoTimeUtils::add); + } + }), ADD_TIMESTAMP_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "add_timestamp_duration", Timestamp.class, Duration.class, ProtoTimeUtils::add)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "add_timestamp_duration", + Instant.class, + java.time.Duration.class, + DateTimeHelpers::add); + } else { + return CelFunctionBinding.from( + "add_timestamp_duration", Timestamp.class, Duration.class, ProtoTimeUtils::add); + } + }), ADD_STRING( (celOptions, runtimeEquality) -> CelFunctionBinding.from( "add_string", String.class, String.class, (String x, String y) -> x + y)), ADD_DURATION_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "add_duration_timestamp", + java.time.Duration.class, + Instant.class, + (java.time.Duration d, Instant i) -> DateTimeHelpers.add(i, d)); + } else { + return CelFunctionBinding.from( "add_duration_timestamp", Duration.class, Timestamp.class, - (Duration x, Timestamp y) -> ProtoTimeUtils.add(y, x))), + (Duration d, Timestamp t) -> ProtoTimeUtils.add(t, d)); + } + }), @SuppressWarnings({"unchecked"}) ADD_LIST( (celOptions, runtimeEquality) -> diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel index bac80475d..08177a474 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -50,6 +50,7 @@ java_library( ":standard_overload", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//common/values:cel_byte_string", "//runtime:function_binding", @@ -71,6 +72,7 @@ cel_android_library( ":standard_overload_android", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//common/values:cel_byte_string", "//runtime:function_binding_android", @@ -92,6 +94,7 @@ java_library( ":standard_overload", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -112,6 +115,7 @@ cel_android_library( ":standard_overload_android", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -389,9 +393,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -406,10 +410,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -423,9 +427,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -440,10 +444,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -457,9 +461,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -474,10 +478,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -491,9 +495,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -508,10 +512,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -525,9 +529,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -542,10 +546,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -559,9 +563,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -577,10 +581,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -595,9 +599,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -613,10 +617,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -631,9 +635,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -649,10 +653,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -667,9 +671,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -684,10 +688,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -701,9 +705,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -719,10 +723,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -1307,6 +1311,7 @@ java_library( "//common:error_codes", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//common/values:cel_byte_string", "//runtime:function_binding", @@ -1328,6 +1333,7 @@ cel_android_library( "//common:error_codes", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//common/values:cel_byte_string", "//runtime:function_binding_android", @@ -1347,6 +1353,7 @@ java_library( "//common:error_codes", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -1367,6 +1374,7 @@ cel_android_library( "//common:error_codes", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -1444,25 +1452,3 @@ java_library( "//common:error_codes", ], ) - -java_library( - name = "date_time_helpers", - srcs = ["DateTimeHelpers.java"], - visibility = ["//visibility:private"], - deps = [ - "//common:error_codes", - "//common:runtime_exception", - "@maven//:com_google_protobuf_protobuf_java", - ], -) - -cel_android_library( - name = "date_time_helpers_android", - srcs = ["DateTimeHelpers.java"], - visibility = ["//visibility:private"], - deps = [ - "//common:error_codes", - "//common:runtime_exception", - "@maven_android//:com_google_protobuf_protobuf_javalite", - ], -) diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DateTimeHelpers.java b/runtime/src/main/java/dev/cel/runtime/standard/DateTimeHelpers.java deleted file mode 100644 index d05125ed1..000000000 --- a/runtime/src/main/java/dev/cel/runtime/standard/DateTimeHelpers.java +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package dev.cel.runtime.standard; - -import com.google.protobuf.Timestamp; -import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; -import java.time.DateTimeException; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Locale; - -final class DateTimeHelpers { - static final String UTC = "UTC"; - - /** - * Constructs a new {@link LocalDateTime} instance - * - * @param ts Timestamp protobuf object - * @param tz Timezone based on the CEL specification. This is either the canonical name from tz - * database or a standard offset represented in (+/-)HH:MM. Few valid examples are: - *

    - *
  • UTC - *
  • America/Los_Angeles - *
  • -09:30 or -9:30 (Leading zeroes can be omitted though not allowed by spec) - *
- * - * @return If an Invalid timezone is supplied. - */ - static LocalDateTime newLocalDateTime(Timestamp ts, String tz) { - return Instant.ofEpochSecond(ts.getSeconds(), ts.getNanos()) - .atZone(timeZone(tz)) - .toLocalDateTime(); - } - - /** - * Get the DateTimeZone Instance. - * - * @param tz the ID of the datetime zone - * @return the ZoneId object - */ - private static ZoneId timeZone(String tz) { - try { - return ZoneId.of(tz); - } catch (DateTimeException e) { - // If timezone is not a string name (for example, 'US/Central'), it should be a numerical - // offset from UTC in the format [+/-]HH:MM. - try { - int ind = tz.indexOf(":"); - if (ind == -1) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); - } - - int hourOffset = Integer.parseInt(tz.substring(0, ind)); - int minOffset = Integer.parseInt(tz.substring(ind + 1)); - // Ensures that the offset are properly formatted in [+/-]HH:MM to conform with - // ZoneOffset's format requirements. - // Example: "-9:30" -> "-09:30" and "9:30" -> "+09:30" - String formattedOffset = - ((hourOffset < 0) ? "-" : "+") - + String.format(Locale.getDefault(), "%02d:%02d", Math.abs(hourOffset), minOffset); - - return ZoneId.of(formattedOffset); - - } catch (DateTimeException e2) { - throw new CelRuntimeException(e2, CelErrorCode.BAD_FORMAT); - } - } - } - - private DateTimeHelpers() {} -} diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java index d54e2999b..b17a9871e 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java @@ -43,18 +43,33 @@ public static DurationFunction create(Iterable - CelFunctionBinding.from("duration_to_duration", Duration.class, (Duration x) -> x)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_duration", java.time.Duration.class, (java.time.Duration d) -> d); + } else { + return CelFunctionBinding.from( + "duration_to_duration", Duration.class, (Duration d) -> d); + } + }), STRING_TO_DURATION( (celOptions, runtimeEquality) -> CelFunctionBinding.from( "string_to_duration", String.class, (String d) -> { - try { - return RuntimeHelpers.createDurationFromString(d); - } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + try { + return RuntimeHelpers.createJavaDurationFromString(d); + } catch (IllegalArgumentException e) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } + } else { + try { + return RuntimeHelpers.createDurationFromString(d); + } catch (IllegalArgumentException e) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } } })), ; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java index bd48ec7da..1c1b7ffd4 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getDate}. */ @@ -43,18 +44,35 @@ public static GetDateFunction create(Iterable o /** Overloads for the standard function. */ public enum GetDateOverload implements CelStandardOverload { TIMESTAMP_TO_DAY_OF_MONTH_1_BASED( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_month_1_based", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth()); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_month_1_based", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth()); + } + }), TIMESTAMP_TO_DAY_OF_MONTH_1_BASED_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_month_1_based_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth()); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_month_1_based_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth()); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java index 171353c04..1efa43620 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getDayOfMonth}. */ @@ -45,18 +46,35 @@ public static GetDayOfMonthFunction create( /** Overloads for the standard function. */ public enum GetDayOfMonthOverload implements CelStandardOverload { TIMESTAMP_TO_DAY_OF_MONTH( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_month", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_month", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth() - 1)), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth() - 1); + } + }), TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_month_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_month_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth() - 1)), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth() - 1); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java index 92ef90e79..4fa7000eb 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; @@ -23,6 +23,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import java.time.DayOfWeek; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getDayOfWeek}. */ @@ -46,18 +47,41 @@ public static GetDayOfWeekFunction create( /** Overloads for the standard function. */ public enum GetDayOfWeekOverload implements CelStandardOverload { TIMESTAMP_TO_DAY_OF_WEEK( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_week", + Instant.class, + (Instant ts) -> { + // CEL treats Sunday as day 0, but Java.time treats it as day 7. + DayOfWeek dayOfWeek = newLocalDateTime(ts, UTC).getDayOfWeek(); + return (long) dayOfWeek.getValue() % 7; + }); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_week", Timestamp.class, (Timestamp ts) -> { // CEL treats Sunday as day 0, but Java.time treats it as day 7. DayOfWeek dayOfWeek = newLocalDateTime(ts, UTC).getDayOfWeek(); return (long) dayOfWeek.getValue() % 7; - })), + }); + } + }), TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_week_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> { + // CEL treats Sunday as day 0, but Java.time treats it as day 7. + DayOfWeek dayOfWeek = newLocalDateTime(ts, tz).getDayOfWeek(); + return (long) dayOfWeek.getValue() % 7; + }); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_week_with_tz", Timestamp.class, String.class, @@ -65,7 +89,9 @@ public enum GetDayOfWeekOverload implements CelStandardOverload { // CEL treats Sunday as day 0, but Java.time treats it as day 7. DayOfWeek dayOfWeek = newLocalDateTime(ts, tz).getDayOfWeek(); return (long) dayOfWeek.getValue() % 7; - })); + }); + } + }); private final FunctionBindingCreator bindingCreator; @Override diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java index 8c4d90e64..c74a85c1c 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getDayOfYear}. */ @@ -45,18 +46,35 @@ public static GetDayOfYearFunction create( /** Overloads for the standard function. */ public enum GetDayOfYearOverload implements CelStandardOverload { TIMESTAMP_TO_DAY_OF_YEAR( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_year", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getDayOfYear() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_year", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfYear() - 1)), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfYear() - 1); + } + }), TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_year_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfYear() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_year_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfYear() - 1)), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfYear() - 1); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java index 61fe189cb..ca816eb80 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getFullYear}. */ @@ -44,18 +45,35 @@ public static GetFullYearFunction create( /** Overloads for the standard function. */ public enum GetFullYearOverload implements CelStandardOverload { TIMESTAMP_TO_YEAR( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_year", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getYear()); + } else { + return CelFunctionBinding.from( "timestamp_to_year", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getYear())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getYear()); + } + }), TIMESTAMP_TO_YEAR_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_year_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getYear()); + } else { + return CelFunctionBinding.from( "timestamp_to_year_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getYear())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getYear()); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java index ee36bfbb0..28d559248 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; @@ -24,6 +24,7 @@ import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getHours}. */ @@ -45,21 +46,45 @@ public static GetHoursFunction create(Iterable - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_hours", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getHour()); + } else { + return CelFunctionBinding.from( "timestamp_to_hours", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getHour())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getHour()); + } + }), TIMESTAMP_TO_HOURS_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_hours_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getHour()); + } else { + return CelFunctionBinding.from( "timestamp_to_hours_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getHour())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getHour()); + } + }), DURATION_TO_HOURS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from("duration_to_hours", Duration.class, ProtoTimeUtils::toHours)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_hours", java.time.Duration.class, java.time.Duration::toHours); + } else { + return CelFunctionBinding.from( + "duration_to_hours", Duration.class, ProtoTimeUtils::toHours); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java index 369cd9ea2..53ec92a82 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; @@ -24,6 +24,7 @@ import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getMilliseconds}. */ @@ -51,27 +52,51 @@ public enum GetMillisecondsOverload implements CelStandardOverload { // timestamp_to_milliseconds overload @SuppressWarnings("JavaLocalDateTimeGetNano") TIMESTAMP_TO_MILLISECONDS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_milliseconds", + Instant.class, + (Instant ts) -> (long) (newLocalDateTime(ts, UTC).getNano() / 1e+6)); + } else { + return CelFunctionBinding.from( "timestamp_to_milliseconds", Timestamp.class, - (Timestamp ts) -> (long) (newLocalDateTime(ts, UTC).getNano() / 1e+6))), - + (Timestamp ts) -> (long) (newLocalDateTime(ts, UTC).getNano() / 1e+6)); + } + }), @SuppressWarnings("JavaLocalDateTimeGetNano") TIMESTAMP_TO_MILLISECONDS_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_milliseconds_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) (newLocalDateTime(ts, tz).getNano() / 1e+6)); + } else { + return CelFunctionBinding.from( "timestamp_to_milliseconds_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) (newLocalDateTime(ts, tz).getNano() / 1e+6))), + (Timestamp ts, String tz) -> (long) (newLocalDateTime(ts, tz).getNano() / 1e+6)); + } + }), DURATION_TO_MILLISECONDS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_milliseconds", + java.time.Duration.class, + (java.time.Duration d) -> d.toMillis() % 1_000); + } else { + return CelFunctionBinding.from( "duration_to_milliseconds", Duration.class, (Duration arg) -> - ProtoTimeUtils.toMillis(arg) % java.time.Duration.ofSeconds(1).toMillis())); + ProtoTimeUtils.toMillis(arg) % java.time.Duration.ofSeconds(1).toMillis()); + } + }); private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java index 24c285c46..62ee130b6 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; @@ -24,6 +24,7 @@ import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getMinutes}. */ @@ -46,22 +47,45 @@ public static GetMinutesFunction create( /** Overloads for the standard function. */ public enum GetMinutesOverload implements CelStandardOverload { TIMESTAMP_TO_MINUTES( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_minutes", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getMinute()); + } else { + return CelFunctionBinding.from( "timestamp_to_minutes", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getMinute())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getMinute()); + } + }), TIMESTAMP_TO_MINUTES_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_minutes_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getMinute()); + } else { + return CelFunctionBinding.from( "timestamp_to_minutes_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getMinute())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getMinute()); + } + }), DURATION_TO_MINUTES( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "duration_to_minutes", Duration.class, ProtoTimeUtils::toMinutes)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_minutes", java.time.Duration.class, java.time.Duration::toMinutes); + } else { + return CelFunctionBinding.from( + "duration_to_minutes", Duration.class, ProtoTimeUtils::toMinutes); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java index 34a33815d..99770e69f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function runtime definition for {@code getMonth}. */ @@ -43,18 +44,35 @@ public static GetMonthFunction create(Iterable - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_month", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getMonthValue() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_month", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getMonthValue() - 1)), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getMonthValue() - 1); + } + }), TIMESTAMP_TO_MONTH_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_month_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getMonthValue() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_month_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getMonthValue() - 1)), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getMonthValue() - 1); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java index e40fc62c5..f0357990b 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; @@ -24,6 +24,7 @@ import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getSeconds}. */ @@ -47,22 +48,56 @@ public static GetSecondsFunction create( /** Overloads for the standard function. */ public enum GetSecondsOverload implements CelStandardOverload { TIMESTAMP_TO_SECONDS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_seconds", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getSecond()); + } else { + return CelFunctionBinding.from( "timestamp_to_seconds", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getSecond())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getSecond()); + } + }), TIMESTAMP_TO_SECONDS_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_seconds_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getSecond()); + } else { + return CelFunctionBinding.from( "timestamp_to_seconds_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getSecond())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getSecond()); + } + }), DURATION_TO_SECONDS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "duration_to_seconds", Duration.class, ProtoTimeUtils::toSeconds)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_seconds", + java.time.Duration.class, + dur -> { + long truncatedSeconds = dur.getSeconds(); + // Preserve the existing behavior from protobuf where seconds is truncated towards + // 0 when negative. + if (dur.isNegative() && dur.getNano() > 0) { + truncatedSeconds++; + } + + return truncatedSeconds; + }); + } else { + return CelFunctionBinding.from( + "duration_to_seconds", Duration.class, ProtoTimeUtils::toSeconds); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java index 1eaced251..03be9c41b 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java @@ -26,6 +26,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the greater equals (>=) operator. */ @@ -81,12 +82,21 @@ public enum GreaterEqualsOverload implements CelStandardOverload { Double.class, (Double x, Double y) -> x >= y)), GREATER_EQUALS_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "greater_equals_duration", + java.time.Duration.class, + java.time.Duration.class, + (java.time.Duration d1, java.time.Duration d2) -> d1.compareTo(d2) >= 0); + } else { + return CelFunctionBinding.from( "greater_equals_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) >= 0)), + (Duration d1, Duration d2) -> ProtoTimeUtils.compare(d1, d2) >= 0); + } + }), GREATER_EQUALS_INT64( (celOptions, runtimeEquality) -> CelFunctionBinding.from( @@ -99,12 +109,21 @@ public enum GreaterEqualsOverload implements CelStandardOverload { String.class, (String x, String y) -> x.compareTo(y) >= 0)), GREATER_EQUALS_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "greater_equals_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> i1.compareTo(i2) >= 0); + } else { + return CelFunctionBinding.from( "greater_equals_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) >= 0)), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.compare(t1, t2) >= 0); + } + }), GREATER_EQUALS_UINT64( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java index 9eed5747b..f0db202a8 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java @@ -26,6 +26,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the greater (>) operator. */ @@ -73,12 +74,21 @@ public enum GreaterOverload implements CelStandardOverload { CelFunctionBinding.from( "greater_double", Double.class, Double.class, (Double x, Double y) -> x > y)), GREATER_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "greater_duration", + java.time.Duration.class, + java.time.Duration.class, + (java.time.Duration d1, java.time.Duration d2) -> d1.compareTo(d2) > 0); + } else { + return CelFunctionBinding.from( "greater_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) > 0)), + (Duration d1, Duration d2) -> ProtoTimeUtils.compare(d1, d2) > 0); + } + }), GREATER_INT64( (celOptions, runtimeEquality) -> CelFunctionBinding.from( @@ -91,12 +101,21 @@ public enum GreaterOverload implements CelStandardOverload { String.class, (String x, String y) -> x.compareTo(y) > 0)), GREATER_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "greater_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> i1.isAfter(i2)); + } else { + return CelFunctionBinding.from( "greater_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) > 0)), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.compare(t1, t2) > 0); + } + }), GREATER_UINT64( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { diff --git a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java index b29ca37a6..17ad835cb 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java @@ -24,6 +24,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code int} conversion function. */ @@ -104,9 +105,15 @@ public enum IntOverload implements CelStandardOverload { } })), TIMESTAMP_TO_INT64( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "timestamp_to_int64", Timestamp.class, ProtoTimeUtils::toSeconds)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_int64", Instant.class, Instant::getEpochSecond); + } else { + return CelFunctionBinding.from( + "timestamp_to_int64", Timestamp.class, ProtoTimeUtils::toSeconds); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java index 3da852f8a..8ff6f5248 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java @@ -26,6 +26,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the less equals (<=) operator. */ @@ -77,12 +78,21 @@ public enum LessEqualsOverload implements CelStandardOverload { CelFunctionBinding.from( "less_equals_double", Double.class, Double.class, (Double x, Double y) -> x <= y)), LESS_EQUALS_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "less_equals_duration", + java.time.Duration.class, + java.time.Duration.class, + (java.time.Duration d1, java.time.Duration d2) -> d1.compareTo(d2) <= 0); + } else { + return CelFunctionBinding.from( "less_equals_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) <= 0)), + (Duration d1, Duration d2) -> ProtoTimeUtils.compare(d1, d2) <= 0); + } + }), LESS_EQUALS_INT64( (celOptions, runtimeEquality) -> CelFunctionBinding.from( @@ -95,12 +105,21 @@ public enum LessEqualsOverload implements CelStandardOverload { String.class, (String x, String y) -> x.compareTo(y) <= 0)), LESS_EQUALS_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "less_equals_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> i1.compareTo(i2) <= 0); + } else { + return CelFunctionBinding.from( "less_equals_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) <= 0)), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.compare(t1, t2) <= 0); + } + }), LESS_EQUALS_UINT64( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { diff --git a/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java index 8bae06a85..d348555b7 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java @@ -26,6 +26,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the less (<) operator. */ @@ -135,12 +136,21 @@ public enum LessOverload implements CelStandardOverload { Double.class, (UnsignedLong x, Double y) -> ComparisonFunctions.compareUintDouble(x, y) == -1)), LESS_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "less_duration", + java.time.Duration.class, + java.time.Duration.class, + (java.time.Duration d1, java.time.Duration d2) -> d1.compareTo(d2) < 0); + } else { + return CelFunctionBinding.from( "less_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) < 0)), + (Duration d1, Duration d2) -> ProtoTimeUtils.compare(d1, d2) < 0); + } + }), LESS_STRING( (celOptions, runtimeEquality) -> CelFunctionBinding.from( @@ -149,12 +159,21 @@ public enum LessOverload implements CelStandardOverload { String.class, (String x, String y) -> x.compareTo(y) < 0)), LESS_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "less_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> i1.isBefore(i2)); + } else { + return CelFunctionBinding.from( "less_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) < 0)), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.compare(t1, t2) < 0); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java index 3525ba5e0..aa1935876 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java @@ -23,10 +23,12 @@ import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelByteString; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code string} conversion function. */ @@ -90,13 +92,24 @@ public enum StringOverload implements CelStandardOverload { } }), TIMESTAMP_TO_STRING( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "timestamp_to_string", Timestamp.class, ProtoTimeUtils::toString)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from("timestamp_to_string", Instant.class, Instant::toString); + } else { + return CelFunctionBinding.from( + "timestamp_to_string", Timestamp.class, ProtoTimeUtils::toString); + } + }), DURATION_TO_STRING( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "duration_to_string", Duration.class, ProtoTimeUtils::toString)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_string", java.time.Duration.class, DateTimeHelpers::toString); + } else { + return CelFunctionBinding.from( + "duration_to_string", Duration.class, ProtoTimeUtils::toString); + } + }), UINT64_TO_STRING( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { diff --git a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java index 695650390..a5d1359cc 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java @@ -22,10 +22,12 @@ import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the subtraction (-) operator. */ @@ -60,19 +62,37 @@ public enum SubtractOverload implements CelStandardOverload { } })), SUBTRACT_TIMESTAMP_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "subtract_timestamp_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> java.time.Duration.between(i2, i1)); + } else { + return CelFunctionBinding.from( "subtract_timestamp_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.between(y, x))), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.between(t2, t1)); + } + }), SUBTRACT_TIMESTAMP_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "subtract_timestamp_duration", + Instant.class, + java.time.Duration.class, + DateTimeHelpers::subtract); + } else { + return CelFunctionBinding.from( "subtract_timestamp_duration", Timestamp.class, Duration.class, - ProtoTimeUtils::subtract)), + ProtoTimeUtils::subtract); + } + }), SUBTRACT_UINT64( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { @@ -106,12 +126,21 @@ public enum SubtractOverload implements CelStandardOverload { CelFunctionBinding.from( "subtract_double", Double.class, Double.class, (Double x, Double y) -> x - y)), SUBTRACT_DURATION_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "subtract_duration_duration", + java.time.Duration.class, + java.time.Duration.class, + DateTimeHelpers::subtract); + } else { + return CelFunctionBinding.from( "subtract_duration_duration", Duration.class, Duration.class, - ProtoTimeUtils::subtract)), + ProtoTimeUtils::subtract); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java index 9b64b78cb..8ed250143 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java @@ -19,10 +19,13 @@ import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import java.text.ParseException; +import java.time.Instant; +import java.time.format.DateTimeParseException; import java.util.Arrays; /** Standard function for {@code timestamp} conversion function. */ @@ -42,6 +45,7 @@ public static TimestampFunction create(Iterable @@ -49,19 +53,41 @@ public enum TimestampOverload implements CelStandardOverload { "string_to_timestamp", String.class, (String ts) -> { - try { - return ProtoTimeUtils.parse(ts); - } catch (ParseException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + try { + return DateTimeHelpers.parse(ts); + } catch (DateTimeParseException e) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } + + } else { + try { + return ProtoTimeUtils.parse(ts); + } catch (ParseException e) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } } })), TIMESTAMP_TO_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from("timestamp_to_timestamp", Timestamp.class, (Timestamp x) -> x)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_timestamp", Instant.class, (Instant x) -> x); + } else { + return CelFunctionBinding.from( + "timestamp_to_timestamp", Timestamp.class, (Timestamp x) -> x); + } + }), INT64_TO_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "int64_to_timestamp", Long.class, ProtoTimeUtils::fromSecondsToTimestamp)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "int64_to_timestamp", Long.class, Instant::ofEpochSecond); + } else { + return CelFunctionBinding.from( + "int64_to_timestamp", Long.class, ProtoTimeUtils::fromSecondsToTimestamp); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java index 2896e36f4..a960b6249 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java @@ -51,6 +51,8 @@ import dev.cel.runtime.standard.EqualsOperator; import dev.cel.runtime.standard.IntFunction; import dev.cel.runtime.standard.IntFunction.IntOverload; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Map; import org.junit.Test; @@ -705,16 +707,8 @@ public void eval_protoMessage_mapFields(String checkedExpr) throws Exception { ImmutableMap.of(true, 9.1d, false, 10.2d), ImmutableMap.of(true, 11.3d, false, 12.4d), ImmutableMap.of(true, 1L, false, 2L), // Note: Enums are converted into integers - ImmutableMap.of( - true, - ProtoTimeUtils.fromSecondsToDuration(15), - false, - ProtoTimeUtils.fromSecondsToDuration(16)), - ImmutableMap.of( - true, - ProtoTimeUtils.fromSecondsToTimestamp(17), - false, - ProtoTimeUtils.fromSecondsToTimestamp(18))) + ImmutableMap.of(true, Duration.ofSeconds(15), false, Duration.ofSeconds(16)), + ImmutableMap.of(true, Instant.ofEpochSecond(17), false, Instant.ofEpochSecond(18))) .inOrder(); } diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java index 4083ae494..4ffe0941c 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java @@ -25,14 +25,12 @@ import com.google.protobuf.ByteString; import com.google.protobuf.BytesValue; import com.google.protobuf.DoubleValue; -import com.google.protobuf.Duration; import com.google.protobuf.FloatValue; import com.google.protobuf.Int32Value; import com.google.protobuf.Int64Value; import com.google.protobuf.ListValue; import com.google.protobuf.StringValue; import com.google.protobuf.Struct; -import com.google.protobuf.Timestamp; import com.google.protobuf.UInt32Value; import com.google.protobuf.UInt64Value; import com.google.protobuf.util.Values; @@ -63,6 +61,8 @@ import dev.cel.testing.testdata.SimpleEnum; import dev.cel.testing.testdata.SingleFileCelDescriptor; import dev.cel.testing.testdata.SingleFileProto.SingleFile; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -301,7 +301,7 @@ public void fieldSelection_duration() throws Exception { Duration result = (Duration) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); - assertThat(result).isEqualTo(ProtoTimeUtils.fromSecondsToDuration(600)); + assertThat(result).isEqualTo(Duration.ofMinutes(10)); } @Test @@ -313,9 +313,9 @@ public void fieldSelection_timestamp() throws Exception { .setSingleTimestamp(ProtoTimeUtils.fromSecondsToTimestamp(50)) .build(); - Timestamp result = (Timestamp) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + Instant result = (Instant) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); - assertThat(result).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(50)); + assertThat(result).isEqualTo(Instant.ofEpochSecond(50)); } @Test diff --git a/runtime/src/test/resources/arithmDuration.baseline b/runtime/src/test/resources/arithmDuration.baseline index 3422aced4..9aa73405d 100644 --- a/runtime/src/test/resources/arithmDuration.baseline +++ b/runtime/src/test/resources/arithmDuration.baseline @@ -9,13 +9,7 @@ declare d3 { value google.protobuf.Duration } =====> -bindings: {d3=seconds: 25 -nanos: 45 -} +> {d2=seconds: 10 -nanos: 20 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {d3=PT25.000000045S} +> {d2=PT10.00000002S} +> {d1=PT15.000000025S} result: true Source: d3 - d1 == d2 @@ -29,11 +23,5 @@ declare d3 { value google.protobuf.Duration } =====> -bindings: {d3=seconds: 25 -nanos: 45 -} +> {d2=seconds: 10 -nanos: 20 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {d3=PT25.000000045S} +> {d2=PT10.00000002S} +> {d1=PT15.000000025S} result: true \ No newline at end of file diff --git a/runtime/src/test/resources/arithmTimestamp.baseline b/runtime/src/test/resources/arithmTimestamp.baseline index d7a1f318c..7ddf702ae 100644 --- a/runtime/src/test/resources/arithmTimestamp.baseline +++ b/runtime/src/test/resources/arithmTimestamp.baseline @@ -9,13 +9,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {ts2=seconds: 10 -nanos: 10 -} +> {ts1=seconds: 25 -nanos: 35 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {ts2=1970-01-01T00:00:10.000000010Z} +> {ts1=1970-01-01T00:00:25.000000035Z} +> {d1=PT15.000000025S} result: true Source: ts1 - d1 == ts2 @@ -29,13 +23,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {ts2=seconds: 10 -nanos: 10 -} +> {ts1=seconds: 25 -nanos: 35 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {ts2=1970-01-01T00:00:10.000000010Z} +> {ts1=1970-01-01T00:00:25.000000035Z} +> {d1=PT15.000000025S} result: true Source: ts2 + d1 == ts1 @@ -49,13 +37,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {ts2=seconds: 10 -nanos: 10 -} +> {ts1=seconds: 25 -nanos: 35 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {ts2=1970-01-01T00:00:10.000000010Z} +> {ts1=1970-01-01T00:00:25.000000035Z} +> {d1=PT15.000000025S} result: true Source: d1 + ts2 == ts1 @@ -69,11 +51,5 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {ts2=seconds: 10 -nanos: 10 -} +> {ts1=seconds: 25 -nanos: 35 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {ts2=1970-01-01T00:00:10.000000010Z} +> {ts1=1970-01-01T00:00:25.000000035Z} +> {d1=PT15.000000025S} result: true \ No newline at end of file diff --git a/runtime/src/test/resources/duration.baseline b/runtime/src/test/resources/duration.baseline index 9737a23c7..89d058086 100644 --- a/runtime/src/test/resources/duration.baseline +++ b/runtime/src/test/resources/duration.baseline @@ -6,11 +6,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 9 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.000000009S} +> {d1=PT10.00000001S} result: false Source: d1 < d2 @@ -21,11 +17,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 9 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT9.00000001S} +> {d1=PT10.00000001S} result: false Source: d1 < d2 @@ -36,11 +28,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.00000001S} result: false Source: d1 < d2 @@ -51,11 +39,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 9 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.000000009S} result: true Source: d1 < d2 @@ -66,11 +50,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 9 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT9.00000001S} result: true Source: d1 <= d2 @@ -81,11 +61,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 9 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.000000009S} +> {d1=PT10.00000001S} result: false Source: d1 <= d2 @@ -96,11 +72,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 9 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT9.00000001S} +> {d1=PT10.00000001S} result: false Source: d1 <= d2 @@ -111,11 +83,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.00000001S} result: true Source: d1 <= d2 @@ -126,11 +94,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 9 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.000000009S} result: true Source: d1 <= d2 @@ -141,11 +105,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 9 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT9.00000001S} result: true Source: d1 > d2 @@ -156,11 +116,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 9 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.000000009S} +> {d1=PT10.00000001S} result: true Source: d1 > d2 @@ -171,11 +127,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 9 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT9.00000001S} +> {d1=PT10.00000001S} result: true Source: d1 > d2 @@ -186,11 +138,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.00000001S} result: false Source: d1 > d2 @@ -201,11 +149,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 9 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.000000009S} result: false Source: d1 > d2 @@ -216,11 +160,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 9 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT9.00000001S} result: false Source: d1 >= d2 @@ -231,11 +171,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 9 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.000000009S} +> {d1=PT10.00000001S} result: true Source: d1 >= d2 @@ -246,11 +182,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 9 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT9.00000001S} +> {d1=PT10.00000001S} result: true Source: d1 >= d2 @@ -261,11 +193,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.00000001S} result: true Source: d1 >= d2 @@ -276,11 +204,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 9 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.000000009S} result: false Source: d1 >= d2 @@ -291,9 +215,5 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 9 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT9.00000001S} result: false \ No newline at end of file diff --git a/runtime/src/test/resources/durationFunctions.baseline b/runtime/src/test/resources/durationFunctions.baseline index 8e942e0d8..9bc61d4ae 100644 --- a/runtime/src/test/resources/durationFunctions.baseline +++ b/runtime/src/test/resources/durationFunctions.baseline @@ -3,9 +3,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {d1=PT25H59M1.011S} result: 25 Source: d1.getHours() @@ -13,9 +11,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: -93541 -nanos: -11000000 -} +bindings: {d1=PT-25H-59M-1.011S} result: -25 Source: d1.getMinutes() @@ -23,9 +19,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {d1=PT25H59M1.011S} result: 1559 Source: d1.getMinutes() @@ -33,9 +27,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: -93541 -nanos: -11000000 -} +bindings: {d1=PT-25H-59M-1.011S} result: -1559 Source: d1.getSeconds() @@ -43,9 +35,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {d1=PT25H59M1.011S} result: 93541 Source: d1.getSeconds() @@ -53,9 +43,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: -93541 -nanos: -11000000 -} +bindings: {d1=PT-25H-59M-1.011S} result: -93541 Source: d1.getMilliseconds() @@ -63,9 +51,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {d1=PT25H59M1.011S} result: 11 Source: d1.getMilliseconds() @@ -73,9 +59,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: -93541 -nanos: -11000000 -} +bindings: {d1=PT-25H-59M-1.011S} result: -11 Source: d1.getHours() < val @@ -86,9 +70,7 @@ declare val { value int } =====> -bindings: {val=30} +> {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {val=30} +> {d1=PT25H59M1.011S} result: true Source: d1.getMinutes() > val @@ -99,9 +81,7 @@ declare val { value int } =====> -bindings: {val=30} +> {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {val=30} +> {d1=PT25H59M1.011S} result: true Source: d1.getSeconds() > val @@ -112,9 +92,7 @@ declare val { value int } =====> -bindings: {val=30} +> {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {val=30} +> {d1=PT25H59M1.011S} result: true Source: d1.getMilliseconds() < val @@ -125,7 +103,5 @@ declare val { value int } =====> -bindings: {val=30} +> {d1=seconds: 93541 -nanos: 11000000 -} -result: true \ No newline at end of file +bindings: {val=30} +> {d1=PT25H59M1.011S} +result: true diff --git a/runtime/src/test/resources/dynamicMessage_adapted.baseline b/runtime/src/test/resources/dynamicMessage_adapted.baseline index 789a39488..b012c3727 100644 --- a/runtime/src/test/resources/dynamicMessage_adapted.baseline +++ b/runtime/src/test/resources/dynamicMessage_adapted.baseline @@ -690,9 +690,7 @@ list_value { } } } -result: seconds: 10 -nanos: 20 - +result: PT10.00000002S Source: msg.single_timestamp declare msg { @@ -755,9 +753,7 @@ list_value { } } } -result: seconds: 100 -nanos: 200 - +result: 1970-01-01T00:01:40.000000200Z Source: msg.single_value declare msg { diff --git a/runtime/src/test/resources/dynamicMessage_dynamicDescriptor.baseline b/runtime/src/test/resources/dynamicMessage_dynamicDescriptor.baseline index 36dac04c3..8f91353b4 100644 --- a/runtime/src/test/resources/dynamicMessage_dynamicDescriptor.baseline +++ b/runtime/src/test/resources/dynamicMessage_dynamicDescriptor.baseline @@ -103,8 +103,7 @@ declare dur { bindings: {dur=type_url: "type.googleapis.com/google.protobuf.Duration" value: "\bd" } -result: seconds: 100 - +result: PT1M40S Source: TestAllTypes { single_any: any_packed_test_msg }.single_any declare any_packed_test_msg { diff --git a/runtime/src/test/resources/timeConversions.baseline b/runtime/src/test/resources/timeConversions.baseline index 68892ee2d..12a521441 100644 --- a/runtime/src/test/resources/timeConversions.baseline +++ b/runtime/src/test/resources/timeConversions.baseline @@ -4,9 +4,7 @@ declare t1 { } =====> bindings: {} -result: seconds: 63126020 -nanos: 21000000 - +result: 1972-01-01T15:00:20.021Z Source: timestamp(123) declare t1 { @@ -14,8 +12,7 @@ declare t1 { } =====> bindings: {} -result: seconds: 123 - +result: 1970-01-01T00:02:03Z Source: duration("15.11s") declare t1 { @@ -23,17 +20,14 @@ declare t1 { } =====> bindings: {} -result: seconds: 15 -nanos: 110000000 - +result: PT15.11S Source: int(t1) == 100 declare t1 { value google.protobuf.Timestamp } =====> -bindings: {t1=seconds: 100 -} +bindings: {t1=1970-01-01T00:01:40Z} result: true Source: duration("1h2m3.4s") @@ -42,9 +36,7 @@ declare t1 { } =====> bindings: {} -result: seconds: 3723 -nanos: 400000000 - +result: PT1H2M3.4S Source: duration(duration('15.0s')) declare t1 { @@ -52,8 +44,7 @@ declare t1 { } =====> bindings: {} -result: seconds: 15 - +result: PT15S Source: timestamp(timestamp(123)) declare t1 { @@ -61,4 +52,4 @@ declare t1 { } =====> bindings: {} -result: seconds: 123 \ No newline at end of file +result: 1970-01-01T00:02:03Z \ No newline at end of file diff --git a/runtime/src/test/resources/timestamp.baseline b/runtime/src/test/resources/timestamp.baseline index c215b581f..828e7f912 100644 --- a/runtime/src/test/resources/timestamp.baseline +++ b/runtime/src/test/resources/timestamp.baseline @@ -6,11 +6,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 9 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000009Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 < t2 @@ -21,11 +17,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 9 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:09.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 < t2 @@ -36,11 +28,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 < t2 @@ -51,11 +39,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 9 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000009Z} result: true Source: t1 < t2 @@ -66,11 +50,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 9 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:09.000000010Z} result: true Source: t1 <= t2 @@ -81,11 +61,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 9 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000009Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 <= t2 @@ -96,11 +72,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 9 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:09.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 <= t2 @@ -111,11 +83,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 <= t2 @@ -126,11 +94,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 9 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000009Z} result: true Source: t1 <= t2 @@ -141,11 +105,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 9 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:09.000000010Z} result: true Source: t1 > t2 @@ -156,11 +116,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 9 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000009Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 > t2 @@ -171,11 +127,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 9 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:09.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 > t2 @@ -186,11 +138,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 > t2 @@ -201,11 +149,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 9 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000009Z} result: false Source: t1 > t2 @@ -216,11 +160,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 9 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:09.000000010Z} result: false Source: t1 >= t2 @@ -231,11 +171,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 9 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000009Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 >= t2 @@ -246,11 +182,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 9 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:09.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 >= t2 @@ -261,11 +193,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 >= t2 @@ -276,11 +204,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 9 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000009Z} result: false Source: t1 >= t2 @@ -291,9 +215,5 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 9 -nanos: 10 -} -result: false +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:09.000000010Z} +result: false \ No newline at end of file diff --git a/runtime/src/test/resources/timestampFunctions.baseline b/runtime/src/test/resources/timestampFunctions.baseline index 322137892..e932867e8 100644 --- a/runtime/src/test/resources/timestampFunctions.baseline +++ b/runtime/src/test/resources/timestampFunctions.baseline @@ -3,9 +3,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1969 Source: ts1.getFullYear() @@ -13,9 +11,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1970 Source: ts1.getFullYear() @@ -23,8 +19,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 1969 Source: ts1.getFullYear("Indian/Cocos") @@ -32,9 +27,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1970 Source: ts1.getFullYear("2:00") @@ -42,9 +35,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1970 Source: ts1.getMonth("America/Los_Angeles") @@ -52,9 +43,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getMonth() @@ -62,9 +51,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getMonth() @@ -72,8 +59,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 11 Source: ts1.getMonth("Indian/Cocos") @@ -81,9 +67,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getMonth("-8:15") @@ -91,9 +75,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getDayOfYear("America/Los_Angeles") @@ -101,9 +83,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 364 Source: ts1.getDayOfYear() @@ -111,9 +91,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDayOfYear() @@ -121,8 +99,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 364 Source: ts1.getDayOfYear("Indian/Cocos") @@ -130,9 +107,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDayOfYear("-9:00") @@ -140,9 +115,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 364 Source: ts1.getDayOfMonth("America/Los_Angeles") @@ -150,9 +123,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 30 Source: ts1.getDayOfMonth() @@ -160,9 +131,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDayOfMonth() @@ -170,8 +139,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 30 Source: ts1.getDayOfMonth("Indian/Cocos") @@ -179,9 +147,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDayOfMonth("8:00") @@ -189,9 +155,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDate("America/Los_Angeles") @@ -199,9 +163,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 31 Source: ts1.getDate() @@ -209,9 +171,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getDate() @@ -219,8 +179,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 31 Source: ts1.getDate("Indian/Cocos") @@ -228,9 +187,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getDate("9:30") @@ -238,9 +195,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getDayOfWeek("America/Los_Angeles") @@ -248,8 +203,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 259200 -} +bindings: {ts1=1970-01-04T00:00:00Z} result: 6 Source: ts1.getDayOfWeek() @@ -257,8 +211,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 259200 -} +bindings: {ts1=1970-01-04T00:00:00Z} result: 0 Source: ts1.getDayOfWeek() @@ -266,8 +219,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 3 Source: ts1.getDayOfWeek("Indian/Cocos") @@ -275,8 +227,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 259200 -} +bindings: {ts1=1970-01-04T00:00:00Z} result: 0 Source: ts1.getDayOfWeek("-9:30") @@ -284,8 +235,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 259200 -} +bindings: {ts1=1970-01-04T00:00:00Z} result: 6 Source: ts1.getHours("America/Los_Angeles") @@ -293,9 +243,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 16 Source: ts1.getHours() @@ -303,9 +251,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getHours() @@ -313,8 +259,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 23 Source: ts1.getHours("Indian/Cocos") @@ -322,9 +267,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 6 Source: ts1.getHours("6:30") @@ -332,9 +275,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 6 Source: ts1.getMinutes("America/Los_Angeles") @@ -342,9 +283,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getMinutes() @@ -352,9 +291,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getMinutes() @@ -362,8 +299,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 59 Source: ts1.getMinutes("Indian/Cocos") @@ -371,9 +307,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 30 Source: ts1.getMinutes("-8:00") @@ -381,9 +315,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getSeconds("America/Los_Angeles") @@ -391,9 +323,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getSeconds() @@ -401,9 +331,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getSeconds() @@ -411,8 +339,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 59 Source: ts1.getSeconds("Indian/Cocos") @@ -420,9 +347,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getSeconds("-8:00") @@ -430,9 +355,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getMilliseconds("America/Los_Angeles") @@ -440,9 +363,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getMilliseconds() @@ -450,9 +371,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getMilliseconds("Indian/Cocos") @@ -460,9 +379,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getMilliseconds("-8:00") @@ -470,9 +387,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getFullYear() < val @@ -483,9 +398,7 @@ declare val { value int } =====> -bindings: {val=2013} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=2013} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getMonth() < val @@ -496,9 +409,7 @@ declare val { value int } =====> -bindings: {val=12} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=12} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getDayOfYear() < val @@ -509,9 +420,7 @@ declare val { value int } =====> -bindings: {val=13} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=13} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getDayOfMonth() < val @@ -522,9 +431,7 @@ declare val { value int } =====> -bindings: {val=10} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=10} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getDate() < val @@ -535,9 +442,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getDayOfWeek() < val @@ -548,9 +453,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getHours() < val @@ -561,9 +464,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getMinutes() < val @@ -574,9 +475,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getSeconds() < val @@ -587,9 +486,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getMilliseconds() < val @@ -600,7 +497,5 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true \ No newline at end of file diff --git a/runtime/src/test/resources/typeComparisons.baseline b/runtime/src/test/resources/typeComparisons.baseline index ae3e8aa01..e8fc3473f 100644 --- a/runtime/src/test/resources/typeComparisons.baseline +++ b/runtime/src/test/resources/typeComparisons.baseline @@ -42,3 +42,14 @@ Source: type(null) == null_type =====> bindings: {} result: true + +Source: type(duration) == google.protobuf.Duration && type(timestamp) == google.protobuf.Timestamp +declare duration { + value dyn +} +declare timestamp { + value dyn +} +=====> +bindings: {duration=PT0S, timestamp=1970-01-01T00:00:00Z} +result: true diff --git a/runtime/src/test/resources/unknownResultSet.baseline b/runtime/src/test/resources/unknownResultSet.baseline index 8c7183761..ad97004f3 100644 --- a/runtime/src/test/resources/unknownResultSet.baseline +++ b/runtime/src/test/resources/unknownResultSet.baseline @@ -60,7 +60,7 @@ declare x { } =====> bindings: {} -error: evaluation error at test_location:31: Failed to parse timestamp: invalid timestamp "bad timestamp string" +error: evaluation error at test_location:31: Text 'bad timestamp string' could not be parsed at index 0 error_code: BAD_FORMAT Source: x.single_int32 == 1 || x.single_string == "test" @@ -125,7 +125,7 @@ declare x { } =====> bindings: {} -error: evaluation error at test_location:31: Failed to parse timestamp: invalid timestamp "bad timestamp string" +error: evaluation error at test_location:31: Text 'bad timestamp string' could not be parsed at index 0 error_code: BAD_FORMAT Source: x.single_int32.f(1) diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index cd0da5b80..147c5baf9 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -37,7 +37,6 @@ import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.DoubleValue; -import com.google.protobuf.Duration; import com.google.protobuf.DynamicMessage; import com.google.protobuf.FloatValue; import com.google.protobuf.Int32Value; @@ -83,6 +82,8 @@ import dev.cel.runtime.CelVariableResolver; import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum; import java.io.IOException; +import java.time.Duration; +import java.time.Instant; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -344,9 +345,9 @@ public void arithmTimestamp() { declareVariable("ts1", SimpleType.TIMESTAMP); declareVariable("ts2", SimpleType.TIMESTAMP); declareVariable("d1", SimpleType.DURATION); - Duration d1 = Duration.newBuilder().setSeconds(15).setNanos(25).build(); - Timestamp ts1 = Timestamp.newBuilder().setSeconds(25).setNanos(35).build(); - Timestamp ts2 = Timestamp.newBuilder().setSeconds(10).setNanos(10).build(); + Duration d1 = Duration.ofSeconds(15, 25); + Instant ts1 = Instant.ofEpochSecond(25, 35); + Instant ts2 = Instant.ofEpochSecond(10, 10); CelVariableResolver resolver = extend( extend(ImmutableMap.of("d1", d1), ImmutableMap.of("ts1", ts1)), @@ -371,9 +372,9 @@ public void arithmDuration() { declareVariable("d1", SimpleType.DURATION); declareVariable("d2", SimpleType.DURATION); declareVariable("d3", SimpleType.DURATION); - Duration d1 = Duration.newBuilder().setSeconds(15).setNanos(25).build(); - Duration d2 = Duration.newBuilder().setSeconds(10).setNanos(20).build(); - Duration d3 = Duration.newBuilder().setSeconds(25).setNanos(45).build(); + java.time.Duration d1 = java.time.Duration.ofSeconds(15, 25); + java.time.Duration d2 = java.time.Duration.ofSeconds(10, 20); + java.time.Duration d3 = java.time.Duration.ofSeconds(25, 45); CelVariableResolver resolver = extend( @@ -606,9 +607,9 @@ public void has() throws Exception { public void duration() throws Exception { declareVariable("d1", SimpleType.DURATION); declareVariable("d2", SimpleType.DURATION); - Duration d1010 = Duration.newBuilder().setSeconds(10).setNanos(10).build(); - Duration d1009 = Duration.newBuilder().setSeconds(10).setNanos(9).build(); - Duration d0910 = Duration.newBuilder().setSeconds(9).setNanos(10).build(); + java.time.Duration d1010 = java.time.Duration.ofSeconds(10, 10); + java.time.Duration d1009 = java.time.Duration.ofSeconds(10, 9); + java.time.Duration d0910 = java.time.Duration.ofSeconds(9, 10); container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); source = "d1 < d2"; @@ -644,9 +645,9 @@ public void duration() throws Exception { public void timestamp() throws Exception { declareVariable("t1", SimpleType.TIMESTAMP); declareVariable("t2", SimpleType.TIMESTAMP); - Timestamp ts1010 = Timestamp.newBuilder().setSeconds(10).setNanos(10).build(); - Timestamp ts1009 = Timestamp.newBuilder().setSeconds(10).setNanos(9).build(); - Timestamp ts0910 = Timestamp.newBuilder().setSeconds(9).setNanos(10).build(); + Instant ts1010 = Instant.ofEpochSecond(10, 10); + Instant ts1009 = Instant.ofEpochSecond(10, 9); + Instant ts0910 = Instant.ofEpochSecond(9, 10); container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); source = "t1 < t2"; @@ -691,7 +692,7 @@ public void packUnpackAny() { declareVariable( "message", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); declareVariable("list", ListType.create(SimpleType.DYN)); - Duration duration = ProtoTimeUtils.fromSecondsToDuration(100); + com.google.protobuf.Duration duration = ProtoTimeUtils.fromSecondsToDuration(100); Any any = Any.pack(duration); TestAllTypes message = TestAllTypes.newBuilder().setSingleAny(any).build(); @@ -960,10 +961,11 @@ public void namespacedVariables() { @Test public void durationFunctions() { declareVariable("d1", SimpleType.DURATION); - Duration d1 = - Duration.newBuilder().setSeconds(25 * 3600 + 59 * 60 + 1).setNanos(11000000).build(); - Duration d2 = - Duration.newBuilder().setSeconds(-(25 * 3600 + 59 * 60 + 1)).setNanos(-11000000).build(); + long totalSeconds = 25 * 3600 + 59 * 60 + 1; + long nanos = 11000000; + Duration d1 = Duration.ofSeconds(totalSeconds, nanos); + Duration d2 = Duration.ofSeconds(-totalSeconds, -nanos); + container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); source = "d1.getHours()"; @@ -997,8 +999,8 @@ public void durationFunctions() { public void timestampFunctions() { declareVariable("ts1", SimpleType.TIMESTAMP); container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); - Timestamp ts1 = Timestamp.newBuilder().setSeconds(1).setNanos(11000000).build(); - Timestamp ts2 = ProtoTimeUtils.fromSecondsToTimestamp(-1); + Instant ts1 = Instant.ofEpochSecond(1, 11000000); + Instant ts2 = Instant.ofEpochSecond(-1, 0); source = "ts1.getFullYear(\"America/Los_Angeles\")"; runTest(ImmutableMap.of("ts1", ts1)); @@ -1050,7 +1052,7 @@ public void timestampFunctions() { source = "ts1.getDate(\"9:30\")"; runTest(ImmutableMap.of("ts1", ts1)); - Timestamp tsSunday = ProtoTimeUtils.fromSecondsToTimestamp(3 * 24 * 3600); + Instant tsSunday = Instant.ofEpochSecond(3 * 24 * 3600); source = "ts1.getDayOfWeek(\"America/Los_Angeles\")"; runTest(ImmutableMap.of("ts1", tsSunday)); source = "ts1.getDayOfWeek()"; @@ -1372,7 +1374,7 @@ public void timeConversions() { runTest(); source = "int(t1) == 100"; - runTest(ImmutableMap.of("t1", ProtoTimeUtils.fromSecondsToTimestamp(100))); + runTest(ImmutableMap.of("t1", Instant.ofEpochSecond(100))); source = "duration(\"1h2m3.4s\")"; runTest(); @@ -1927,6 +1929,15 @@ public void typeComparisons() { // Test whether null resolves to null_type. source = "type(null) == null_type"; runTest(); + + // Test runtime resolution of types + source = + "type(duration) == google.protobuf.Duration && " + + "type(timestamp) == google.protobuf.Timestamp"; + // Intentionally declare as dyns + declareVariable("duration", SimpleType.DYN); + declareVariable("timestamp", SimpleType.DYN); + runTest(ImmutableMap.of("duration", java.time.Duration.ZERO, "timestamp", Instant.EPOCH)); } @Test @@ -2075,7 +2086,8 @@ public void dynamicMessage_adapted() throws Exception { .setSingleStringWrapper(StringValue.of("hello")) .setSingleUint32Wrapper(UInt32Value.of(12)) .setSingleUint64Wrapper(UInt64Value.of(34)) - .setSingleDuration(Duration.newBuilder().setSeconds(10).setNanos(20)) + .setSingleDuration( + com.google.protobuf.Duration.newBuilder().setSeconds(10).setNanos(20)) .setSingleTimestamp(Timestamp.newBuilder().setSeconds(100).setNanos(200)) .setSingleValue(Value.newBuilder().setStringValue("a")) .setSingleStruct( @@ -2127,10 +2139,10 @@ public void dynamicMessage_adapted() throws Exception { .isInstanceOf(BASE_CEL_OPTIONS.enableUnsignedLongs() ? UnsignedLong.class : Long.class); source = "msg.single_duration"; - assertThat(runTest(input)).isInstanceOf(Duration.class); + assertThat(runTest(input)).isInstanceOf(java.time.Duration.class); source = "msg.single_timestamp"; - assertThat(runTest(input)).isInstanceOf(Timestamp.class); + assertThat(runTest(input)).isInstanceOf(Instant.class); source = "msg.single_value"; assertThat(runTest(input)).isInstanceOf(String.class); diff --git a/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel b/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel index a7e4c628c..3e32bbd2a 100644 --- a/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel @@ -17,6 +17,7 @@ java_library( deps = [ "//common:cel_descriptors", "//common/internal:default_instance_message_factory", + "//common/internal:proto_time_utils", "//common/types", "//common/types:type_providers", "//common/values", diff --git a/testing/src/main/java/dev/cel/testing/utils/ExprValueUtils.java b/testing/src/main/java/dev/cel/testing/utils/ExprValueUtils.java index 0b27fa452..041c0f52d 100644 --- a/testing/src/main/java/dev/cel/testing/utils/ExprValueUtils.java +++ b/testing/src/main/java/dev/cel/testing/utils/ExprValueUtils.java @@ -31,6 +31,7 @@ import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelDescriptors; import dev.cel.common.internal.DefaultInstanceMessageFactory; +import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.types.CelType; import dev.cel.common.types.ListType; import dev.cel.common.types.MapType; @@ -41,6 +42,8 @@ import dev.cel.runtime.CelUnknownSet; import dev.cel.testing.testrunner.RegistryUtils; import java.io.IOException; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -252,6 +255,19 @@ public static Value toValue(Object object, CelType type) throws Exception { } return Value.newBuilder().setMapValue(builder.build()).build(); } + + if (object instanceof Instant) { + return Value.newBuilder() + .setObjectValue(Any.pack(ProtoTimeUtils.toProtoTimestamp((Instant) object))) + .build(); + } + + if (object instanceof Duration) { + return Value.newBuilder() + .setObjectValue(Any.pack(ProtoTimeUtils.toProtoDuration((Duration) object))) + .build(); + } + if (object instanceof Message) { return Value.newBuilder().setObjectValue(Any.pack((Message) object)).build(); } From f3a1b2b670a323104da7ede9514a4b7dbea1e9f1 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 19 Sep 2025 12:06:32 -0700 Subject: [PATCH 12/27] Fix signed zero equality behavior for doubles to match specification PiperOrigin-RevId: 809130214 --- .../java/dev/cel/common/internal/ComparisonFunctions.java | 8 +++----- runtime/src/test/resources/arithmDouble.baseline | 5 +++++ .../main/java/dev/cel/testing/BaseInterpreterTest.java | 3 +++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/dev/cel/common/internal/ComparisonFunctions.java b/common/src/main/java/dev/cel/common/internal/ComparisonFunctions.java index daec1a464..4d498d526 100644 --- a/common/src/main/java/dev/cel/common/internal/ComparisonFunctions.java +++ b/common/src/main/java/dev/cel/common/internal/ComparisonFunctions.java @@ -81,7 +81,8 @@ public static int compareUintInt(UnsignedLong ul, long l) { public static boolean numericEquals(Number x, Number y) { if (x instanceof Double) { if (y instanceof Double) { - return !(Double.isNaN((Double) x) || Double.isNaN((Double) y)) && x.equals(y); + return !(Double.isNaN((Double) x) || Double.isNaN((Double) y)) + && x.doubleValue() == y.doubleValue(); } if (y instanceof Long) { return compareDoubleInt((Double) x, (Long) y) == 0; @@ -115,10 +116,7 @@ public static boolean numericEquals(Number x, Number y) { return false; } - - /** - * Compare two numeric values of any type (double, int, uint). - */ + /** Compare two numeric values of any type (double, int, uint). */ public static int numericCompare(Number x, Number y) { if (x instanceof Double) { if (y instanceof Double) { diff --git a/runtime/src/test/resources/arithmDouble.baseline b/runtime/src/test/resources/arithmDouble.baseline index 8d914202a..3c219ef29 100644 --- a/runtime/src/test/resources/arithmDouble.baseline +++ b/runtime/src/test/resources/arithmDouble.baseline @@ -1,3 +1,8 @@ +Source: 0.0 == -0.0 +=====> +bindings: {} +result: true + Source: 1.9 < 2.0 && 1.1 <= 1.1 && 2.0 > 1.9 && 1.1 >= 1.1 && 1.1 == 1.1 && 2.0 != 1.9 =====> bindings: {} diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 147c5baf9..8f5dae8af 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -306,6 +306,9 @@ public void arithmUInt64_error() { @Test public void arithmDouble() { + source = "0.0 == -0.0"; + runTest(); + source = "1.9 < 2.0 && 1.1 <= 1.1 && 2.0 > 1.9 && 1.1 >= 1.1 && 1.1 == 1.1 && 2.0 != 1.9"; runTest(); From f773edf4bf559e38bdfebad2a865bc2e03389e3b Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 23 Sep 2025 11:35:40 -0700 Subject: [PATCH 13/27] Allow constant folding to fold equals operator PiperOrigin-RevId: 810518582 --- .../optimizers/ConstantFoldingOptimizer.java | 46 +++++++++++++++++++ .../ConstantFoldingOptimizerTest.java | 28 ++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java index fd52f4138..b40250fe6 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java @@ -167,6 +167,16 @@ private boolean canFold(CelNavigableMutableExpr navigableExpr) { && cond.constant().getKind().equals(CelConstant.Kind.BOOLEAN_VALUE); } + if (functionName.equals(Operator.EQUALS.getFunction()) + || functionName.equals(Operator.NOT_EQUALS.getFunction())) { + if (mutableCall.args().stream() + .anyMatch(node -> isExprConstantOfKind(node, CelConstant.Kind.BOOLEAN_VALUE)) + || mutableCall.args().stream() + .allMatch(node -> node.getKind().equals(Kind.CONSTANT))) { + return true; + } + } + if (functionName.equals(Operator.IN.getFunction())) { return canFoldInOperator(navigableExpr); } @@ -393,6 +403,38 @@ private Optional maybePruneBranches( } } } + } else if (function.equals(Operator.EQUALS.getFunction()) + || function.equals(Operator.NOT_EQUALS.getFunction())) { + CelMutableExpr lhs = call.args().get(0); + CelMutableExpr rhs = call.args().get(1); + boolean lhsIsBoolean = isExprConstantOfKind(lhs, CelConstant.Kind.BOOLEAN_VALUE); + boolean rhsIsBoolean = isExprConstantOfKind(rhs, CelConstant.Kind.BOOLEAN_VALUE); + boolean invertCondition = function.equals(Operator.NOT_EQUALS.getFunction()); + Optional replacementExpr = Optional.empty(); + + if (lhs.getKind().equals(Kind.CONSTANT) && rhs.getKind().equals(Kind.CONSTANT)) { + // If both args are const, don't prune any branches and let maybeFold method evaluate this + // subExpr + return Optional.empty(); + } else if (lhsIsBoolean) { + boolean cond = invertCondition != lhs.constant().booleanValue(); + replacementExpr = + Optional.of( + cond + ? rhs + : CelMutableExpr.ofCall( + CelMutableCall.create(Operator.LOGICAL_NOT.getFunction(), rhs))); + } else if (rhsIsBoolean) { + boolean cond = invertCondition != rhs.constant().booleanValue(); + replacementExpr = + Optional.of( + cond + ? lhs + : CelMutableExpr.ofCall( + CelMutableCall.create(Operator.LOGICAL_NOT.getFunction(), lhs))); + } + + return replacementExpr.map(node -> astMutator.replaceSubtree(mutableAst, node, expr.id())); } return Optional.empty(); @@ -663,6 +705,10 @@ public static Builder newBuilder() { ConstantFoldingOptions() {} } + private static boolean isExprConstantOfKind(CelMutableExpr expr, CelConstant.Kind constantKind) { + return expr.getKind().equals(Kind.CONSTANT) && expr.constant().getKind().equals(constantKind); + } + private ConstantFoldingOptimizer(ConstantFoldingOptions constantFoldingOptions) { this.constantFoldingOptions = constantFoldingOptions; this.astMutator = AstMutator.newInstance(constantFoldingOptions.maxIterationLimit()); diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java index ea1e77edb..4efbeb1c2 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java @@ -66,13 +66,13 @@ public class ConstantFoldingOptimizerTest { CelExtensions.math(CelOptions.DEFAULT), CelExtensions.strings(), CelExtensions.sets(CelOptions.DEFAULT), - CelExtensions.encoders()) + CelExtensions.encoders(CelOptions.DEFAULT)) .addRuntimeLibraries( CelOptionalLibrary.INSTANCE, CelExtensions.math(CelOptions.DEFAULT), CelExtensions.strings(), CelExtensions.sets(CelOptions.DEFAULT), - CelExtensions.encoders()) + CelExtensions.encoders(CelOptions.DEFAULT)) .build(); private static final CelOptimizer CEL_OPTIMIZER = @@ -189,6 +189,28 @@ public class ConstantFoldingOptimizerTest { @TestParameters("{source: 'sets.contains([1], [1])', expected: 'true'}") @TestParameters( "{source: 'cel.bind(r0, [1, 2, 3], cel.bind(r1, 1 in r0, r1))', expected: 'true'}") + @TestParameters("{source: 'x == true', expected: 'x'}") + @TestParameters("{source: 'true == x', expected: 'x'}") + @TestParameters("{source: 'x == false', expected: '!x'}") + @TestParameters("{source: 'false == x', expected: '!x'}") + @TestParameters("{source: 'true == false', expected: 'false'}") + @TestParameters("{source: 'true == true', expected: 'true'}") + @TestParameters("{source: 'false == true', expected: 'false'}") + @TestParameters("{source: 'false == false', expected: 'true'}") + @TestParameters("{source: '10 == 42', expected: 'false'}") + @TestParameters("{source: '42 == 42', expected: 'true'}") + @TestParameters("{source: 'x != true', expected: '!x'}") + @TestParameters("{source: 'true != x', expected: '!x'}") + @TestParameters("{source: 'x != false', expected: 'x'}") + @TestParameters("{source: 'false != x', expected: 'x'}") + @TestParameters("{source: 'true != false', expected: 'true'}") + @TestParameters("{source: 'true != true', expected: 'false'}") + @TestParameters("{source: 'false != true', expected: 'true'}") + @TestParameters("{source: 'false != false', expected: 'false'}") + @TestParameters("{source: '10 != 42', expected: 'true'}") + @TestParameters("{source: '42 != 42', expected: 'false'}") + @TestParameters("{source: '[\"foo\",\"bar\"] == [\"foo\",\"bar\"]', expected: 'true'}") + @TestParameters("{source: '[\"bar\",\"foo\"] == [\"foo\",\"bar\"]', expected: 'false'}") // TODO: Support folding lists with mixed types. This requires mutable lists. // @TestParameters("{source: 'dyn([1]) + [1.0]'}") public void constantFold_success(String source, String expected) throws Exception { @@ -324,6 +346,8 @@ public void constantFold_macros_withoutMacroCallMetadata(String source) throws E @TestParameters("{source: 'TestAllTypes{single_int32: x, repeated_int32: [1, 2, 3]}'}") @TestParameters("{source: 'get_true() == get_true()'}") @TestParameters("{source: 'get_true() == true'}") + @TestParameters("{source: 'x == x'}") + @TestParameters("{source: 'x == 42'}") public void constantFold_noOp(String source) throws Exception { CelAbstractSyntaxTree ast = CEL.compile(source).getAst(); From 668d3e5f86bfeba4a92bc3b86b518a4d4ccf0180 Mon Sep 17 00:00:00 2001 From: CEL Dev Team Date: Mon, 29 Sep 2025 22:04:31 -0700 Subject: [PATCH 14/27] Internal Changes PiperOrigin-RevId: 813084482 --- .../main/java/dev/cel/testing/testrunner/CelCoverageIndex.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java b/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java index 72b146516..d99525934 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java +++ b/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java @@ -398,8 +398,9 @@ private String kindToString(CelNavigableExpr node) { } } - private String escapeSpecialCharacters(String exprText) { + private static String escapeSpecialCharacters(String exprText) { return exprText + .replace("\\\"", "\"") .replace("\"", "\\\"") .replace("\n", "\\n") .replace("||", " \\| \\| ") From 30bd91fd6df062b327643ec87065b8fc272fd819 Mon Sep 17 00:00:00 2001 From: CEL Dev Team Date: Tue, 30 Sep 2025 15:24:58 -0700 Subject: [PATCH 15/27] Fix Typo: "descedent" to "descendent". PiperOrigin-RevId: 813451085 --- checker/src/main/java/dev/cel/checker/ProtoTypeMask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java b/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java index c019604d5..c9ff89989 100644 --- a/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java +++ b/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java @@ -80,7 +80,7 @@ public ProtoTypeMask withFieldsAsVariableDeclarations() { * *
    *
  • All descendent fields after the last element in the field mask path are visible. - *
  • The asterisk '*' can be used as an explicit indicator that all descedent fields are + *
  • The asterisk '*' can be used as an explicit indicator that all descendent fields are * visible to CEL. *
  • Repeated fields are not supported. *
From 0c7b10f892969fb9ea935081108957c8082f96df Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 1 Oct 2025 14:45:39 -0700 Subject: [PATCH 16/27] Enhance ConstantFoldingOptimizer to fold arithmetics involving timestamps and durations Note: `evaluateCanonicalTypesToNativeValues` must be enabled for optimization to work PiperOrigin-RevId: 813922645 --- .../cel/checker/CelStandardDeclarations.java | 2 +- .../dev/cel/optimizer/optimizers/BUILD.bazel | 2 + .../optimizers/ConstantFoldingOptimizer.java | 45 +++++++++++++++---- .../ConstantFoldingOptimizerTest.java | 29 +++++++++--- 4 files changed, 63 insertions(+), 15 deletions(-) diff --git a/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java b/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java index 3865430ec..94b7ad313 100644 --- a/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java +++ b/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java @@ -1488,7 +1488,7 @@ public CelFunctionDecl functionDecl() { return celFunctionDecl; } - String functionName() { + public String functionName() { return functionName; } diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel b/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel index 7674870a8..6057b3105 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel @@ -19,12 +19,14 @@ java_library( ":default_optimizer_constants", "//:auto_value", "//bundle:cel", + "//checker:standard_decl", "//common:cel_ast", "//common:cel_source", "//common:compiler_common", "//common:mutable_ast", "//common/ast", "//common/ast:mutable_expr", + "//common/internal:date_time_helpers", "//common/navigation:mutable_navigation", "//common/types", "//extensions:optional_library", diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java index b40250fe6..294589af2 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java @@ -16,6 +16,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.MoreCollectors.onlyElement; +import static dev.cel.checker.CelStandardDeclarations.StandardFunction.DURATION; +import static dev.cel.checker.CelStandardDeclarations.StandardFunction.TIMESTAMP; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; @@ -36,6 +38,7 @@ import dev.cel.common.ast.CelMutableExpr.CelMutableMap; import dev.cel.common.ast.CelMutableExpr.CelMutableStruct; import dev.cel.common.ast.CelMutableExprConverter; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.navigation.CelNavigableMutableAst; import dev.cel.common.navigation.CelNavigableMutableExpr; import dev.cel.common.types.SimpleType; @@ -45,6 +48,8 @@ import dev.cel.optimizer.CelOptimizationException; import dev.cel.parser.Operator; import dev.cel.runtime.CelEvaluationException; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -142,6 +147,14 @@ private boolean canFold(CelNavigableMutableExpr navigableExpr) { return false; } + // Timestamps/durations in CEL are calls, but they are effectively treated as literals. + // Expressions like timestamp(123) cannot be folded directly, but arithmetics involving + // timestamps can be optimized. + // Ex: timestamp(123) - timestamp(100) = duration("23s") + if (isCallTimestampOrDuration(navigableExpr.expr().call())) { + return false; + } + CelMutableCall mutableCall = navigableExpr.expr().call(); String functionName = mutableCall.function(); @@ -197,14 +210,14 @@ private boolean canFold(CelNavigableMutableExpr navigableExpr) { private boolean containsFoldableFunctionOnly(CelNavigableMutableExpr navigableExpr) { return navigableExpr .allNodes() - .allMatch( - node -> { - if (node.getKind().equals(Kind.CALL)) { - return foldableFunctions.contains(node.expr().call().function()); - } - - return true; - }); + .filter(node -> node.getKind().equals(Kind.CALL)) + .map(node -> node.expr().call()) + .allMatch(call -> foldableFunctions.contains(call.function())); + } + + private static boolean isCallTimestampOrDuration(CelMutableCall call) { + return call.function().equals(TIMESTAMP.functionName()) + || call.function().equals(DURATION.functionName()); } private static boolean canFoldInOperator(CelNavigableMutableExpr navigableExpr) { @@ -318,6 +331,22 @@ private Optional maybeAdaptEvaluatedResult(Object result) { } return Optional.of(CelMutableExpr.ofMap(CelMutableMap.create(mapEntries))); + } else if (result instanceof Duration) { + String durationStrArg = DateTimeHelpers.toString((Duration) result); + CelMutableCall durationCall = + CelMutableCall.create( + DURATION.functionName(), + CelMutableExpr.ofConstant(CelConstant.ofValue(durationStrArg))); + + return Optional.of(CelMutableExpr.ofCall(durationCall)); + } else if (result instanceof Instant) { + String timestampStrArg = result.toString(); + CelMutableCall timestampCall = + CelMutableCall.create( + TIMESTAMP.functionName(), + CelMutableExpr.ofConstant(CelConstant.ofValue(timestampStrArg))); + + return Optional.of(CelMutableExpr.ofCall(timestampCall)); } // Evaluated result cannot be folded (e.g: unknowns) diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java index 4efbeb1c2..05e2e5457 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java @@ -46,6 +46,11 @@ @RunWith(TestParameterInjector.class) public class ConstantFoldingOptimizerTest { + private static final CelOptions CEL_OPTIONS = + CelOptions.current() + .enableTimestampEpoch(true) + .evaluateCanonicalTypesToNativeValues(true) + .build(); private static final Cel CEL = CelFactory.standardCelBuilder() .addVar("x", SimpleType.DYN) @@ -60,19 +65,20 @@ public class ConstantFoldingOptimizerTest { CelFunctionBinding.from("get_true_overload", ImmutableList.of(), unused -> true)) .addMessageTypes(TestAllTypes.getDescriptor()) .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) + .setOptions(CEL_OPTIONS) .addCompilerLibraries( CelExtensions.bindings(), CelOptionalLibrary.INSTANCE, - CelExtensions.math(CelOptions.DEFAULT), + CelExtensions.math(CEL_OPTIONS), CelExtensions.strings(), - CelExtensions.sets(CelOptions.DEFAULT), - CelExtensions.encoders(CelOptions.DEFAULT)) + CelExtensions.sets(CEL_OPTIONS), + CelExtensions.encoders(CEL_OPTIONS)) .addRuntimeLibraries( CelOptionalLibrary.INSTANCE, - CelExtensions.math(CelOptions.DEFAULT), + CelExtensions.math(CEL_OPTIONS), CelExtensions.strings(), - CelExtensions.sets(CelOptions.DEFAULT), - CelExtensions.encoders(CelOptions.DEFAULT)) + CelExtensions.sets(CEL_OPTIONS), + CelExtensions.encoders(CEL_OPTIONS)) .build(); private static final CelOptimizer CEL_OPTIMIZER = @@ -211,6 +217,15 @@ public class ConstantFoldingOptimizerTest { @TestParameters("{source: '42 != 42', expected: 'false'}") @TestParameters("{source: '[\"foo\",\"bar\"] == [\"foo\",\"bar\"]', expected: 'true'}") @TestParameters("{source: '[\"bar\",\"foo\"] == [\"foo\",\"bar\"]', expected: 'false'}") + @TestParameters("{source: 'duration(\"1h\") - duration(\"60m\")', expected: 'duration(\"0s\")'}") + @TestParameters( + "{source: 'duration(\"2h23m42s12ms42us92ns\") + duration(\"129481231298125ns\")', expected:" + + " 'duration(\"138103.243340217s\")'}") + @TestParameters( + "{source: 'timestamp(900000) - timestamp(100)', expected: 'duration(\"899900s\")'}") + @TestParameters( + "{source: 'timestamp(\"2000-01-01T00:02:03.2123Z\") + duration(\"25h2m32s42ms53us29ns\")'," + + " expected: 'timestamp(\"2000-01-02T01:04:35.254353029Z\")'}") // TODO: Support folding lists with mixed types. This requires mutable lists. // @TestParameters("{source: 'dyn([1]) + [1.0]'}") public void constantFold_success(String source, String expected) throws Exception { @@ -348,6 +363,8 @@ public void constantFold_macros_withoutMacroCallMetadata(String source) throws E @TestParameters("{source: 'get_true() == true'}") @TestParameters("{source: 'x == x'}") @TestParameters("{source: 'x == 42'}") + @TestParameters("{source: 'timestamp(100)'}") + @TestParameters("{source: 'duration(\"1h\")'}") public void constantFold_noOp(String source) throws Exception { CelAbstractSyntaxTree ast = CEL.compile(source).getAst(); From ac7c96b9e571d6e5156f86c1e952771afa20c6bb Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 1 Oct 2025 18:48:10 -0700 Subject: [PATCH 17/27] Fix bytes(string) standard function to respect evaluateCanonicalTypesToNativeValues flag PiperOrigin-RevId: 814012812 --- .../test/java/dev/cel/bundle/CelImplTest.java | 17 +++++++++++++++++ .../dev/cel/runtime/standard/BytesFunction.java | 11 +++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index 2a702538a..b417517fe 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -2107,6 +2107,23 @@ public void program_evaluateCanonicalTypesToNativeTypesDisabled_producesProtoVal assertThat(result).containsExactly(com.google.protobuf.NullValue.NULL_VALUE, expectedNestedMap); } + @Test + public void program_evaluateCanonicalTypesToNativeTypesDisabled_producesBytesProto() + throws Exception { + Cel cel = + standardCelBuilderWithMacros() + .addMessageTypes(TestAllTypes.getDescriptor()) + .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) + .setOptions(CelOptions.current().evaluateCanonicalTypesToNativeValues(false).build()) + .build(); + CelAbstractSyntaxTree ast = + cel.compile("TestAllTypes{single_bytes: bytes('abc')}.single_bytes").getAst(); + + ByteString result = (ByteString) cel.createProgram(ast).eval(); + + assertThat(result).isEqualTo(ByteString.copyFromUtf8("abc")); + } + @Test public void toBuilder_isImmutable() { CelBuilder celBuilder = CelFactory.standardCelBuilder(); diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java index b45d6d65b..d9b4babd5 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java @@ -50,8 +50,15 @@ public enum BytesOverload implements CelStandardOverload { } }), STRING_TO_BYTES( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from("string_to_bytes", String.class, CelByteString::copyFromUtf8)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "string_to_bytes", String.class, CelByteString::copyFromUtf8); + } else { + return CelFunctionBinding.from( + "string_to_bytes", String.class, ByteString::copyFromUtf8); + } + }), ; private final FunctionBindingCreator bindingCreator; From 1fc89222f81e5292b1121a22c4cd4bcefedfdfc7 Mon Sep 17 00:00:00 2001 From: Jonathan Tatum Date: Mon, 6 Oct 2025 14:57:56 -0700 Subject: [PATCH 18/27] Add comprehension nesting validator. PiperOrigin-RevId: 815897444 --- .../dev/cel/validator/validators/BUILD.bazel | 17 ++ .../ComprehensionNestingLimitValidator.java | 85 +++++++++ .../dev/cel/validator/validators/BUILD.bazel | 3 + ...omprehensionNestingLimitValidatorTest.java | 176 ++++++++++++++++++ validator/validators/BUILD.bazel | 5 + 5 files changed, 286 insertions(+) create mode 100644 validator/src/main/java/dev/cel/validator/validators/ComprehensionNestingLimitValidator.java create mode 100644 validator/src/test/java/dev/cel/validator/validators/ComprehensionNestingLimitValidatorTest.java diff --git a/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel b/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel index b7050466c..67e791317 100644 --- a/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel +++ b/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel @@ -84,6 +84,23 @@ java_library( ], ) +java_library( + name = "comprehension_nesting_limit_validator", + srcs = [ + "ComprehensionNestingLimitValidator.java", + ], + tags = [ + ], + deps = [ + "//bundle:cel", + "//common/ast", + "//common/navigation", + "//common/navigation:common", + "//validator:ast_validator", + "@maven//:com_google_guava_guava", + ], +) + java_library( name = "literal_validator", srcs = [ diff --git a/validator/src/main/java/dev/cel/validator/validators/ComprehensionNestingLimitValidator.java b/validator/src/main/java/dev/cel/validator/validators/ComprehensionNestingLimitValidator.java new file mode 100644 index 000000000..55dadacc5 --- /dev/null +++ b/validator/src/main/java/dev/cel/validator/validators/ComprehensionNestingLimitValidator.java @@ -0,0 +1,85 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.validator.validators; + +import static com.google.common.base.Preconditions.checkArgument; + +import dev.cel.bundle.Cel; +import dev.cel.common.ast.CelExpr.ExprKind; +import dev.cel.common.navigation.CelNavigableAst; +import dev.cel.common.navigation.CelNavigableExpr; +import dev.cel.common.navigation.TraversalOrder; +import dev.cel.validator.CelAstValidator; + +/** + * Checks that the nesting depth of comprehensions does not exceed the configured limit. Nesting + * occurs when a comprehension is used in the range expression or the body of a comprehension. + * + *

Trivial comprehensions (comprehensions over an empty range) do not count towards the limit. + */ +public final class ComprehensionNestingLimitValidator implements CelAstValidator { + private final int nestingLimit; + + /** + * Constructs a new instance of {@link ComprehensionNestingLimitValidator} with the configured + * maxNesting as its limit. A limit of 0 means no comprehensions are allowed. + */ + public static ComprehensionNestingLimitValidator newInstance(int maxNesting) { + checkArgument(maxNesting >= 0); + return new ComprehensionNestingLimitValidator(maxNesting); + } + + @Override + public void validate(CelNavigableAst navigableAst, Cel cel, IssuesFactory issuesFactory) { + navigableAst + .getRoot() + .allNodes(TraversalOrder.PRE_ORDER) + .filter(node -> node.getKind().equals(ExprKind.Kind.COMPREHENSION)) + .filter(node -> nestingLevel(node) > nestingLimit) + .forEach( + node -> + issuesFactory.addError( + node.id(), + String.format( + "comprehension nesting exceeds the configured limit: %s.", nestingLimit))); + } + + private static boolean isTrivialComprehension(CelNavigableExpr node) { + return (node.expr().comprehension().iterRange().getKind().equals(ExprKind.Kind.LIST) + && node.expr().comprehension().iterRange().list().elements().isEmpty()) + || (node.expr().comprehension().iterRange().getKind().equals(ExprKind.Kind.MAP) + && node.expr().comprehension().iterRange().map().entries().isEmpty()); + } + + private static int nestingLevel(CelNavigableExpr node) { + if (isTrivialComprehension(node)) { + return 0; + } + int count = 1; + while (node.parent().isPresent()) { + CelNavigableExpr parent = node.parent().get(); + + if (parent.getKind().equals(ExprKind.Kind.COMPREHENSION) && !isTrivialComprehension(parent)) { + count++; + } + node = parent; + } + return count; + } + + private ComprehensionNestingLimitValidator(int maxNesting) { + this.nestingLimit = maxNesting; + } +} diff --git a/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel b/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel index 9e88e0d16..cb35e7a6b 100644 --- a/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel +++ b/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel @@ -16,12 +16,15 @@ java_library( "//common:proto_ast", "//common/internal:proto_time_utils", "//common/types", + "//extensions", "//extensions:optional_library", + "//parser:macro", "//runtime", "//runtime:function_binding", "//validator", "//validator:validator_builder", "//validator/validators:ast_depth_limit_validator", + "//validator/validators:comprehension_nesting_limit_validator", "//validator/validators:duration", "//validator/validators:homogeneous_literal", "//validator/validators:regex", diff --git a/validator/src/test/java/dev/cel/validator/validators/ComprehensionNestingLimitValidatorTest.java b/validator/src/test/java/dev/cel/validator/validators/ComprehensionNestingLimitValidatorTest.java new file mode 100644 index 000000000..ccf625d88 --- /dev/null +++ b/validator/src/test/java/dev/cel/validator/validators/ComprehensionNestingLimitValidatorTest.java @@ -0,0 +1,176 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cel.validator.validators; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.bundle.Cel; +import dev.cel.bundle.CelFactory; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelIssue.Severity; +import dev.cel.common.CelValidationException; +import dev.cel.common.CelValidationResult; +import dev.cel.common.types.SimpleType; +import dev.cel.extensions.CelExtensions; +import dev.cel.parser.CelStandardMacro; +import dev.cel.validator.CelValidator; +import dev.cel.validator.CelValidatorFactory; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public class ComprehensionNestingLimitValidatorTest { + + private static final Cel CEL = + CelFactory.standardCelBuilder() + .addCompilerLibraries( + CelExtensions.optional(), CelExtensions.comprehensions(), CelExtensions.bindings()) + .addRuntimeLibraries(CelExtensions.optional(), CelExtensions.comprehensions()) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addVar("x", SimpleType.DYN) + .build(); + + private static final CelValidator CEL_VALIDATOR = + CelValidatorFactory.standardCelValidatorBuilder(CEL) + .addAstValidators(ComprehensionNestingLimitValidator.newInstance(1)) + .build(); + + @Test + public void comprehensionNestingLimit_populatesErrors( + @TestParameter({ + "[1, 2, 3].map(x, [1, 2, 3].map(y, x + y))", + "[1, 2, 3].map(x, [1, 2, 3].map(y, x + y).size() > 0, x)", + "[1, 2, 3].all(x, [1, 2, 3].exists(y, x + y > 0))", + "[1, 2, 3].map(x, {x: [1, 2, 3].map(y, x + y)})", + "[1, 2, 3].exists(i, v, i < 3 && [1, 2, 3].all(j, v2, j < 3 && v2 > 0))", + "{1: 2}.all(k, {2: 3}.all(k2, k != k2))" + }) + String expr) + throws Exception { + CelAbstractSyntaxTree ast = CEL.compile(expr).getAst(); + + CelValidationResult result = CEL_VALIDATOR.validate(ast); + + assertThat(result.hasError()).isTrue(); + assertThat(result.getAllIssues()).hasSize(1); + assertThat(result.getAllIssues().get(0).getSeverity()).isEqualTo(Severity.ERROR); + assertThat(result.getAllIssues().get(0).toDisplayString(ast.getSource())) + .contains("comprehension nesting exceeds the configured limit: 1."); + } + + @Test + public void comprehensionNestingLimit_accumulatesErrors( + @TestParameter({ + "[1, 2, 3].map(x, [1, 2, 3].map(y, [1, 2, 3].map(z, x + y + z)))", + }) + String expr) + throws Exception { + CelAbstractSyntaxTree ast = CEL.compile(expr).getAst(); + + CelValidationResult result = CEL_VALIDATOR.validate(ast); + + assertThat(result.hasError()).isTrue(); + assertThat(result.getAllIssues()).hasSize(2); + } + + @Test + public void comprehensionNestingLimit_limitConfigurable( + @TestParameter({ + "[1, 2, 3].map(x, [1, 2, 3].map(y, x + y))", + "[1, 2, 3].map(x, [1, 2, 3].map(y, x + y).size() > 0, x)", + "[1, 2, 3].all(x, [1, 2, 3].exists(y, x + y > 0))", + "[1, 2, 3].map(x, {x: [1, 2, 3].map(y, x + y)})", + "[1, 2, 3].exists(i, v, i < 3 && [1, 2, 3].all(j, v2, j < 3 && v2 > 0))", + "{1: 2}.all(k, {2: 3}.all(k2, k != k2))" + }) + String expr) + throws Exception { + CelAbstractSyntaxTree ast = CEL.compile(expr).getAst(); + CelValidator celValidator = + CelValidatorFactory.standardCelValidatorBuilder(CEL) + .addAstValidators(ComprehensionNestingLimitValidator.newInstance(2)) + .build(); + + CelValidationResult result = celValidator.validate(ast); + + assertThat(result.hasError()).isFalse(); + } + + @Test + public void comprehensionNestingLimit_trivialLoopsDontCount( + @TestParameter({ + "cel.bind(x, [1, 2].map(x, x + 1), x + [1, 2].map(x, x + 1))", + "optional.of(1).optMap(x, [1, 2, 3].exists(y, y == x))", + "[].map(x, [1, 2, 3].map(y, x + y))", + "{}.map(k1, {1: 2, 3: 4}.map(k2, k1 + k2))", + "[1, 2, 3].map(x, cel.bind(y, 2, x + y))", + "[1, 2, 3].map(x, optional.of(1).optMap(y, x + y).orValue(0))", + }) + String expr) + throws Exception { + CelAbstractSyntaxTree ast = CEL.compile(expr).getAst(); + + CelValidationResult result = CEL_VALIDATOR.validate(ast); + + assertThat(result.hasError()).isFalse(); + } + + @Test + public void comprehensionNestingLimit_zeroLimitAcceptedComprehenions( + @TestParameter({ + "cel.bind(x, 1, x + 1)", + "optional.of(1).optMap(x, x + 1)", + "[].map(x, int(x))", + "cel.bind(x, 1 + [].map(x, int(x)).size(), x + 1)" + }) + String expr) + throws Exception { + CelAbstractSyntaxTree ast = CEL.compile(expr).getAst(); + + CelValidator celValidator = + CelValidatorFactory.standardCelValidatorBuilder(CEL) + .addAstValidators(ComprehensionNestingLimitValidator.newInstance(0)) + .build(); + + CelValidationResult result = celValidator.validate(ast); + + assertThat(result.hasError()).isFalse(); + } + + @Test + public void comprehensionNestingLimit_zeroLimitRejectedComprehensions( + @TestParameter({ + "[1].map(x, x)", + "[1].exists(x, x > 0)", + "[].exists(x, [1].all(y, y > 0))", + }) + String expr) + throws Exception { + CelAbstractSyntaxTree ast = CEL.compile(expr).getAst(); + + CelValidator celValidator = + CelValidatorFactory.standardCelValidatorBuilder(CEL) + .addAstValidators(ComprehensionNestingLimitValidator.newInstance(0)) + .build(); + + CelValidationException e = + assertThrows(CelValidationException.class, () -> celValidator.validate(ast).getAst()); + + assertThat(e.getMessage()).contains("comprehension nesting exceeds the configured limit: 0."); + } +} diff --git a/validator/validators/BUILD.bazel b/validator/validators/BUILD.bazel index b5498d682..ab0919f20 100644 --- a/validator/validators/BUILD.bazel +++ b/validator/validators/BUILD.bazel @@ -29,3 +29,8 @@ java_library( name = "ast_depth_limit_validator", exports = ["//validator/src/main/java/dev/cel/validator/validators:ast_depth_limit_validator"], ) + +java_library( + name = "comprehension_nesting_limit_validator", + exports = ["//validator/src/main/java/dev/cel/validator/validators:comprehension_nesting_limit_validator"], +) From 6997259a43b89cbca1455f2b62ddaa7fdcd4fcdf Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 7 Oct 2025 12:52:55 -0700 Subject: [PATCH 19/27] Internal Changes PiperOrigin-RevId: 816339345 --- .../src/main/java/dev/cel/bundle/BUILD.bazel | 2 ++ .../main/java/dev/cel/bundle/CelBuilder.java | 22 ++++++++++++++++++- .../src/main/java/dev/cel/bundle/CelImpl.java | 16 ++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel index 35b4679f7..50b78c63b 100644 --- a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel @@ -25,6 +25,7 @@ java_library( "//checker:checker_builder", "//checker:checker_legacy_environment", "//checker:proto_type_mask", + "//checker:standard_decl", "//common:cel_ast", "//common:cel_source", "//common:compiler_common", @@ -42,6 +43,7 @@ java_library( "//parser:parser_builder", "//runtime", "//runtime:function_binding", + "//runtime:standard_functions", "@cel_spec//proto/cel/expr:checked_java_proto", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", diff --git a/bundle/src/main/java/dev/cel/bundle/CelBuilder.java b/bundle/src/main/java/dev/cel/bundle/CelBuilder.java index 9580dc555..1dadaeb39 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelBuilder.java +++ b/bundle/src/main/java/dev/cel/bundle/CelBuilder.java @@ -22,6 +22,7 @@ import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; +import dev.cel.checker.CelStandardDeclarations; import dev.cel.checker.ProtoTypeMask; import dev.cel.checker.TypeProvider; import dev.cel.common.CelContainer; @@ -36,6 +37,7 @@ import dev.cel.parser.CelStandardMacro; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelRuntimeLibrary; +import dev.cel.runtime.CelStandardFunctions; import java.util.function.Function; /** Interface for building an instance of Cel. */ @@ -84,7 +86,7 @@ public interface CelBuilder { /** Retrieves the currently configured {@link CelContainer} in the builder. */ CelContainer container(); - /* + /** * Set the {@link CelContainer} to use as the namespace for resolving CEL expression variables and * functions. */ @@ -299,6 +301,24 @@ public interface CelBuilder { @CanIgnoreReturnValue CelBuilder addRuntimeLibraries(Iterable libraries); + /** + * Override the standard declarations for the type-checker. This can be used to subset the + * standard environment to only expose the desired declarations to the type-checker. {@link + * #setStandardEnvironmentEnabled(boolean)} must be set to false for this to take effect. + */ + @CanIgnoreReturnValue + CelBuilder setStandardDeclarations(CelStandardDeclarations standardDeclarations); + + /** + * Override the standard functions for the runtime. This can be used to subset the standard + * environment to only expose the desired function overloads to the runtime. + * + *

{@link #setStandardEnvironmentEnabled(boolean)} must be set to false for this to take + * effect. + */ + @CanIgnoreReturnValue + CelBuilder setStandardFunctions(CelStandardFunctions standardFunctions); + /** * Sets a proto ExtensionRegistry to assist with unpacking Any messages containing a proto2 extension field. diff --git a/bundle/src/main/java/dev/cel/bundle/CelImpl.java b/bundle/src/main/java/dev/cel/bundle/CelImpl.java index 795e202e3..bc92cca7a 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelImpl.java +++ b/bundle/src/main/java/dev/cel/bundle/CelImpl.java @@ -28,6 +28,7 @@ import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; import dev.cel.checker.CelCheckerBuilder; +import dev.cel.checker.CelStandardDeclarations; import dev.cel.checker.ProtoTypeMask; import dev.cel.checker.TypeProvider; import dev.cel.common.CelAbstractSyntaxTree; @@ -54,6 +55,7 @@ import dev.cel.runtime.CelRuntime; import dev.cel.runtime.CelRuntimeBuilder; import dev.cel.runtime.CelRuntimeLibrary; +import dev.cel.runtime.CelStandardFunctions; import java.util.Arrays; import java.util.function.Function; @@ -382,6 +384,20 @@ public CelBuilder addRuntimeLibraries(Iterable libraries) { return this; } + @Override + public CelBuilder setStandardDeclarations(CelStandardDeclarations standardDeclarations) { + checkNotNull(standardDeclarations); + compilerBuilder.setStandardDeclarations(standardDeclarations); + return this; + } + + @Override + public CelBuilder setStandardFunctions(CelStandardFunctions standardFunctions) { + checkNotNull(standardFunctions); + runtimeBuilder.setStandardFunctions(standardFunctions); + return this; + } + @Override public CelBuilder setExtensionRegistry(ExtensionRegistry extensionRegistry) { checkNotNull(extensionRegistry); From 4df37724594283f0346385903eeb90e8d134a7f9 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 7 Oct 2025 16:43:03 -0700 Subject: [PATCH 20/27] Internal Changes PiperOrigin-RevId: 816431984 --- policy/src/main/java/dev/cel/policy/CelPolicy.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/policy/src/main/java/dev/cel/policy/CelPolicy.java b/policy/src/main/java/dev/cel/policy/CelPolicy.java index 00c23e7e9..9980d0cad 100644 --- a/policy/src/main/java/dev/cel/policy/CelPolicy.java +++ b/policy/src/main/java/dev/cel/policy/CelPolicy.java @@ -60,6 +60,8 @@ public static Builder newBuilder() { .setMetadata(ImmutableMap.of()); } + public abstract Builder toBuilder(); + /** Builder for {@link CelPolicy}. */ @AutoValue.Builder public abstract static class Builder { From f665c8a15af06271a7f6c447911431d2504523ea Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 7 Oct 2025 21:08:41 -0700 Subject: [PATCH 21/27] Fix type-checker to always rewrite resolved identifiers and functions through container or alias resolutions PiperOrigin-RevId: 816514528 --- .../java/dev/cel/checker/ExprChecker.java | 7 +++ .../cel/checker/CelProtoExprVisitorTest.java | 10 ++-- .../java/dev/cel/checker/ExprCheckerTest.java | 51 ++++++++++++++++--- .../test/resources/aggregateMessage.baseline | 5 +- .../src/test/resources/containers.baseline | 38 ++++++++++++++ .../src/test/resources/nestedEnums.baseline | 4 +- .../test/resources/nullableMessage.baseline | 6 +-- checker/src/test/resources/optionals.baseline | 4 +- .../cel/common/ast/CelExprFormatterTest.java | 2 +- .../cel/common/ast/CelExprVisitorTest.java | 11 ++-- .../CelNavigableExprVisitorTest.java | 4 +- .../dev/cel/optimizer/AstMutatorTest.java | 2 +- .../ConstantFoldingOptimizerTest.java | 14 ++--- ...ion_ast_block_common_subexpr_only.baseline | 2 +- ...ssion_ast_block_recursion_depth_1.baseline | 2 +- ...ssion_ast_block_recursion_depth_2.baseline | 2 +- ...ssion_ast_block_recursion_depth_3.baseline | 2 +- ...ssion_ast_block_recursion_depth_4.baseline | 2 +- ...ssion_ast_block_recursion_depth_5.baseline | 2 +- ...ssion_ast_block_recursion_depth_6.baseline | 2 +- ...ssion_ast_block_recursion_depth_7.baseline | 2 +- ...ssion_ast_block_recursion_depth_8.baseline | 2 +- ...ssion_ast_block_recursion_depth_9.baseline | 2 +- .../resources/subexpression_unparsed.baseline | 20 ++++---- .../java/dev/cel/runtime/CelRuntimeTest.java | 3 +- .../dev/cel/testing/BaseInterpreterTest.java | 2 +- 26 files changed, 149 insertions(+), 54 deletions(-) create mode 100644 checker/src/test/resources/containers.baseline diff --git a/checker/src/main/java/dev/cel/checker/ExprChecker.java b/checker/src/main/java/dev/cel/checker/ExprChecker.java index c2a2929b1..d4dbf7994 100644 --- a/checker/src/main/java/dev/cel/checker/ExprChecker.java +++ b/checker/src/main/java/dev/cel/checker/ExprChecker.java @@ -362,6 +362,13 @@ private CelExpr visit(CelExpr expr, CelExpr.CelStruct struct) { CelType messageType = SimpleType.ERROR; CelIdentDecl decl = env.lookupIdent(expr.id(), getPosition(expr), container, struct.messageName()); + if (!struct.messageName().equals(decl.name())) { + expr = + expr.toBuilder() + .setStruct(struct.toBuilder().setMessageName(decl.name()).build()) + .build(); + } + env.setRef(expr, CelReference.newBuilder().setName(decl.name()).build()); CelType type = decl.type(); if (type.kind() != CelKind.ERROR) { diff --git a/checker/src/test/java/dev/cel/checker/CelProtoExprVisitorTest.java b/checker/src/test/java/dev/cel/checker/CelProtoExprVisitorTest.java index f4b389552..d9e311ca2 100644 --- a/checker/src/test/java/dev/cel/checker/CelProtoExprVisitorTest.java +++ b/checker/src/test/java/dev/cel/checker/CelProtoExprVisitorTest.java @@ -175,7 +175,9 @@ public void visitSelect() throws Exception { .isEqualTo( VisitedReference.newBuilder() .setCreateStruct( - Expr.CreateStruct.newBuilder().setMessageName("TestAllTypes").build()) + Expr.CreateStruct.newBuilder() + .setMessageName("cel.expr.conformance.proto3.TestAllTypes") + .build()) .setSelect( Expr.Select.newBuilder() .setOperand( @@ -183,7 +185,7 @@ public void visitSelect() throws Exception { .setId(1) .setStructExpr( Expr.CreateStruct.newBuilder() - .setMessageName("TestAllTypes") + .setMessageName("cel.expr.conformance.proto3.TestAllTypes") .build())) .setField("single_int64") .build()) @@ -227,7 +229,9 @@ public void visitCreateStruct() throws Exception { .isEqualTo( VisitedReference.newBuilder() .setCreateStruct( - Expr.CreateStruct.newBuilder().setMessageName("TestAllTypes").build()) + Expr.CreateStruct.newBuilder() + .setMessageName("cel.expr.conformance.proto3.TestAllTypes") + .build()) .build()); } diff --git a/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java b/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java index 360a8c64a..c3b26785a 100644 --- a/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java +++ b/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java @@ -73,8 +73,9 @@ private void runTest() throws Exception { CelAbstractSyntaxTree ast = prepareTest( Arrays.asList( - TestAllTypes.getDescriptor(), - dev.cel.expr.conformance.proto2.TestAllTypes.getDescriptor())); + StandaloneGlobalEnum.getDescriptor().getFile(), + TestAllTypes.getDescriptor().getFile(), + dev.cel.expr.conformance.proto2.TestAllTypes.getDescriptor().getFile())); if (ast != null) { testOutput() .println( @@ -239,6 +240,45 @@ public void messageFieldSelect() throws Exception { runTest(); } + @Test + public void containers() throws Exception { + container = + CelContainer.newBuilder() + .setName("dev.cel.testing.testdata.proto3.StandaloneGlobalEnum") + .addAlias("p3_alias", "cel.expr.conformance.proto3") + .addAlias("foo_bar_alias", "foo.bar") + .addAlias("foo_bar_baz_alias", "foo.bar.baz") + .addAbbreviations("cel.expr.conformance.proto2", "cel.expr.conformance.proto3") + .build(); + source = "p3_alias.TestAllTypes{}"; + runTest(); + + source = "proto2.TestAllTypes{}"; + runTest(); + + source = "proto3.TestAllTypes{}"; + runTest(); + + source = "SGAR"; // From StandaloneGlobalEnum + runTest(); + + declareVariable("foo.bar", SimpleType.STRING); + declareFunction( + "baz", + memberOverload( + "foo_bar_baz_overload", ImmutableList.of(SimpleType.STRING), SimpleType.DYN)); + // Member call of "baz()" on "foo.bar" identifier + source = "foo_bar_alias.baz()"; + runTest(); + + declareFunction( + "foo.bar.baz.qux", + globalOverload("foo_bar_baz_qux_overload", ImmutableList.of(), SimpleType.DYN)); + // Global call of "foo.bar.baz.qux" as a fully qualified name + source = "foo_bar_baz_alias.qux()"; + runTest(); + } + @Test public void messageCreationError() throws Exception { declareVariable("x", SimpleType.INT); @@ -851,17 +891,16 @@ public void twoVarComprehensions_incorrectIterVars() throws Exception { public void twoVarComprehensions_duplicateIterVars() throws Exception { CelType messageType = StructTypeReference.create("cel.expr.conformance.proto3.TestAllTypes"); declareVariable("x", messageType); - source = - "x.repeated_int64.exists(i, i, i < v)"; + source = "x.repeated_int64.exists(i, i, i < v)"; runTest(); } - + @Test public void twoVarComprehensions_incorrectNumberOfArgs() throws Exception { CelType messageType = StructTypeReference.create("cel.expr.conformance.proto3.TestAllTypes"); declareVariable("x", messageType); source = - "[1, 2, 3, 4].exists_one(i, v, i < v, v)" + "[1, 2, 3, 4].exists_one(i, v, i < v, v)" + "&& x.map_string_string.transformList(i, i < v) " + "&& [1, 2, 3].transformList(i, v, i > 0 && x < 3, (i * v) + v) == [4]"; runTest(); diff --git a/checker/src/test/resources/aggregateMessage.baseline b/checker/src/test/resources/aggregateMessage.baseline index 7bace8021..eb138b0e8 100644 --- a/checker/src/test/resources/aggregateMessage.baseline +++ b/checker/src/test/resources/aggregateMessage.baseline @@ -1,7 +1,6 @@ Source: TestAllTypes{single_int32: 1, single_int64: 2} =====> -TestAllTypes{ +cel.expr.conformance.proto3.TestAllTypes{ single_int32:1~int, single_int64:2~int -}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes - +}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes \ No newline at end of file diff --git a/checker/src/test/resources/containers.baseline b/checker/src/test/resources/containers.baseline new file mode 100644 index 000000000..cdf26eb63 --- /dev/null +++ b/checker/src/test/resources/containers.baseline @@ -0,0 +1,38 @@ +Source: p3_alias.TestAllTypes{} +=====> +cel.expr.conformance.proto3.TestAllTypes{}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes + +Source: proto2.TestAllTypes{} +=====> +cel.expr.conformance.proto2.TestAllTypes{}~cel.expr.conformance.proto2.TestAllTypes^cel.expr.conformance.proto2.TestAllTypes + +Source: proto3.TestAllTypes{} +=====> +cel.expr.conformance.proto3.TestAllTypes{}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes + +Source: SGAR +=====> +dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGAR~int^dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGAR + +Source: foo_bar_alias.baz() +declare foo.bar { + value string +} +declare baz { + function foo_bar_baz_overload string.() -> dyn +} +=====> +foo.bar~string^foo.bar.baz()~dyn^foo_bar_baz_overload + +Source: foo_bar_baz_alias.qux() +declare foo.bar { + value string +} +declare baz { + function foo_bar_baz_overload string.() -> dyn +} +declare foo.bar.baz.qux { + function foo_bar_baz_qux_overload () -> dyn +} +=====> +foo.bar.baz.qux()~dyn^foo_bar_baz_qux_overload \ No newline at end of file diff --git a/checker/src/test/resources/nestedEnums.baseline b/checker/src/test/resources/nestedEnums.baseline index 211fbf4c0..f05594997 100644 --- a/checker/src/test/resources/nestedEnums.baseline +++ b/checker/src/test/resources/nestedEnums.baseline @@ -30,8 +30,8 @@ declare single_nested_enum { } =====> _==_( - TestAllTypes{ + cel.expr.conformance.proto3.TestAllTypes{ single_nested_enum:cel.expr.conformance.proto3.TestAllTypes.NestedEnum.BAR~int^cel.expr.conformance.proto3.TestAllTypes.NestedEnum.BAR }~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes.single_nested_enum~int, 1~int -)~bool^equals +)~bool^equals \ No newline at end of file diff --git a/checker/src/test/resources/nullableMessage.baseline b/checker/src/test/resources/nullableMessage.baseline index 6b5c13a68..54f8e56d5 100644 --- a/checker/src/test/resources/nullableMessage.baseline +++ b/checker/src/test/resources/nullableMessage.baseline @@ -16,10 +16,10 @@ declare x { _||_( _==_( null~null, - TestAllTypes{}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes + cel.expr.conformance.proto3.TestAllTypes{}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes )~bool^equals, _==_( - TestAllTypes{}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes, + cel.expr.conformance.proto3.TestAllTypes{}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes, null~null )~bool^equals -)~bool^logical_or +)~bool^logical_or \ No newline at end of file diff --git a/checker/src/test/resources/optionals.baseline b/checker/src/test/resources/optionals.baseline index a86fb1fc7..be50e44ba 100644 --- a/checker/src/test/resources/optionals.baseline +++ b/checker/src/test/resources/optionals.baseline @@ -72,7 +72,7 @@ Source: {?'key': {'a': 'b'}.?value}.key Source: TestAllTypes{?single_int32: {}.?i} =====> -TestAllTypes{ +cel.expr.conformance.proto3.TestAllTypes{ ?single_int32:_?._( {}~map(dyn, int), "i" @@ -118,4 +118,4 @@ declare b { { ?"str"~string:a~optional_type(string)^a, 2~int:3~int -}~map(dyn, dyn) +}~map(dyn, dyn) \ No newline at end of file diff --git a/common/src/test/java/dev/cel/common/ast/CelExprFormatterTest.java b/common/src/test/java/dev/cel/common/ast/CelExprFormatterTest.java index 56b386ba3..116222d68 100644 --- a/common/src/test/java/dev/cel/common/ast/CelExprFormatterTest.java +++ b/common/src/test/java/dev/cel/common/ast/CelExprFormatterTest.java @@ -191,7 +191,7 @@ public void struct() throws Exception { assertThat(formattedExpr) .isEqualTo( "STRUCT [1] {\n" - + " name: TestAllTypes\n" + + " name: cel.expr.conformance.proto3.TestAllTypes\n" + " entries: {\n" + " ENTRY [2] {\n" + " field_key: single_int64\n" diff --git a/common/src/test/java/dev/cel/common/ast/CelExprVisitorTest.java b/common/src/test/java/dev/cel/common/ast/CelExprVisitorTest.java index 1610acb52..7e75c9610 100644 --- a/common/src/test/java/dev/cel/common/ast/CelExprVisitorTest.java +++ b/common/src/test/java/dev/cel/common/ast/CelExprVisitorTest.java @@ -206,14 +206,19 @@ public void visitSelect() throws Exception { assertThat(visited) .isEqualTo( VisitedReference.newBuilder() - .setStruct(CelStruct.newBuilder().setMessageName("TestAllTypes").build()) + .setStruct( + CelStruct.newBuilder() + .setMessageName("cel.expr.conformance.proto3.TestAllTypes") + .build()) .setSelect( CelSelect.newBuilder() .setOperand( CelExpr.newBuilder() .setId(1) .setStruct( - CelStruct.newBuilder().setMessageName("TestAllTypes").build()) + CelStruct.newBuilder() + .setMessageName("cel.expr.conformance.proto3.TestAllTypes") + .build()) .build()) .setField("single_int64") .build()) @@ -268,7 +273,7 @@ public void visitStruct_fieldkey() throws Exception { .setFieldKey("single_int64") .setValue(CelExpr.ofConstant(3, longConstant)) .build()) - .setMessageName("TestAllTypes") + .setMessageName("cel.expr.conformance.proto3.TestAllTypes") .build()) .build()); } diff --git a/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java b/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java index 11bf79f27..ed58a93cc 100644 --- a/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java +++ b/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java @@ -595,7 +595,7 @@ public void messageConstruction_allNodesReturned() throws Exception { constExpr, CelExpr.ofStruct( 1, - "TestAllTypes", + "cel.expr.conformance.proto3.TestAllTypes", ImmutableList.of(CelExpr.ofStructEntry(2, "single_int64", constExpr, false)))); } @@ -621,7 +621,7 @@ public void messageConstruction_filterStruct_allNodesReturned() throws Exception .isEqualTo( CelExpr.ofStruct( 1, - "TestAllTypes", + "cel.expr.conformance.proto3.TestAllTypes", ImmutableList.of( CelExpr.ofStructEntry( 2, "single_int64", CelExpr.ofConstant(3, CelConstant.ofValue(1)), false)))); diff --git a/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java b/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java index 31b5da6fa..e03f95aa4 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java @@ -584,7 +584,7 @@ public void struct_replaceValue() throws Exception { CelMutableAst result = AST_MUTATOR.replaceSubtree(mutableAst, newExpr, 3); assertThat(CEL_UNPARSER.unparse(result.toParsedAst())) - .isEqualTo("TestAllTypes{single_int64: 5}"); + .isEqualTo("cel.expr.conformance.proto3.TestAllTypes{single_int64: 5}"); } @Test diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java index 05e2e5457..821a31d69 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java @@ -159,18 +159,18 @@ public class ConstantFoldingOptimizerTest { + " optional.of(1), ?y: optional.none()}'}") @TestParameters( "{source: 'TestAllTypes{single_int64: 1 + 2 + 3 + x}', " - + " expected: 'TestAllTypes{single_int64: 6 + x}'}") + + " expected: 'cel.expr.conformance.proto3.TestAllTypes{single_int64: 6 + x}'}") @TestParameters( "{source: 'TestAllTypes{?single_int64: optional.ofNonZeroValue(1)}', " - + " expected: 'TestAllTypes{single_int64: 1}'}") + + " expected: 'cel.expr.conformance.proto3.TestAllTypes{single_int64: 1}'}") @TestParameters( "{source: 'TestAllTypes{?single_int64: optional.ofNonZeroValue(0)}', " - + " expected: 'TestAllTypes{}'}") + + " expected: 'cel.expr.conformance.proto3.TestAllTypes{}'}") @TestParameters( "{source: 'TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32:" + " optional.of(4), ?single_uint64: optional.ofNonZeroValue(x)}', expected:" - + " 'TestAllTypes{single_int64: 1, single_int32: 4, ?single_uint64:" - + " optional.ofNonZeroValue(x)}'}") + + " 'cel.expr.conformance.proto3.TestAllTypes{single_int64: 1, single_int32: 4," + + " ?single_uint64: optional.ofNonZeroValue(x)}'}") @TestParameters("{source: '{\"hello\": \"world\"}.hello == x', expected: '\"world\" == x'}") @TestParameters("{source: '{\"hello\": \"world\"}[\"hello\"] == x', expected: '\"world\" == x'}") @TestParameters("{source: '{\"hello\": \"world\"}.?hello', expected: 'optional.of(\"world\")'}") @@ -358,7 +358,9 @@ public void constantFold_macros_withoutMacroCallMetadata(String source) throws E @TestParameters("{source: 'optional.none()'}") @TestParameters("{source: '[optional.none()]'}") @TestParameters("{source: '[?x.?y]'}") - @TestParameters("{source: 'TestAllTypes{single_int32: x, repeated_int32: [1, 2, 3]}'}") + @TestParameters( + "{source: 'cel.expr.conformance.proto3.TestAllTypes{" + + "single_int32: x, repeated_int32: [1, 2, 3]}'}") @TestParameters("{source: 'get_true() == get_true()'}") @TestParameters("{source: 'get_true() == true'}") @TestParameters("{source: 'x == x'}") diff --git a/optimizer/src/test/resources/subexpression_ast_block_common_subexpr_only.baseline b/optimizer/src/test/resources/subexpression_ast_block_common_subexpr_only.baseline index c5a603bbd..103fcac54 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_common_subexpr_only.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_common_subexpr_only.baseline @@ -4242,7 +4242,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_1.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_1.baseline index e00dd4bdc..134e7effd 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_1.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_1.baseline @@ -5426,7 +5426,7 @@ CALL [1] { } } STRUCT [7] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [8] { field_key: single_int64 diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_2.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_2.baseline index 9d22f2e7c..e161e091a 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_2.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_2.baseline @@ -5155,7 +5155,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_3.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_3.baseline index f91f528f5..6123928e3 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_3.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_3.baseline @@ -4830,7 +4830,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline index 8d221f3de..c3de5d723 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline @@ -4401,7 +4401,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline index b80e2883f..e9797057a 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline @@ -4362,7 +4362,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline index c8424586d..d220cfff4 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline @@ -4338,7 +4338,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_7.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_7.baseline index bab77a361..6d8187a17 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_7.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_7.baseline @@ -4299,7 +4299,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_8.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_8.baseline index 1ce112b6c..726b6aa91 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_8.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_8.baseline @@ -4296,7 +4296,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_9.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_9.baseline index aa13c7633..ba147d0df 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_9.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_9.baseline @@ -4284,7 +4284,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 diff --git a/optimizer/src/test/resources/subexpression_unparsed.baseline b/optimizer/src/test/resources/subexpression_unparsed.baseline index 0ad89f2fc..684c0ccb5 100644 --- a/optimizer/src/test/resources/subexpression_unparsed.baseline +++ b/optimizer/src/test/resources/subexpression_unparsed.baseline @@ -677,16 +677,16 @@ Test case: OPTIONAL_MESSAGE Source: TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}.single_int32 + TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}.single_int64 == 5 =====> Result: true -[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_1]: cel.@block([optional.ofNonZeroValue(1), optional.of(4), TestAllTypes{?single_int64: @index0, ?single_int32: @index1}, @index2.single_int32, @index2.single_int64, @index3 + @index4], @index5 == 5) -[BLOCK_RECURSION_DEPTH_2]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}, @index0.single_int32 + @index0.single_int64], @index1 == 5) -[BLOCK_RECURSION_DEPTH_3]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_5]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_6]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_7]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_8]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_9]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([optional.ofNonZeroValue(1), optional.of(4), cel.expr.conformance.proto3.TestAllTypes{?single_int64: @index0, ?single_int32: @index1}, @index2.single_int32, @index2.single_int64, @index3 + @index4], @index5 == 5) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}, @index0.single_int32 + @index0.single_int64], @index1 == 5) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) Test case: CALL Source: ('h' + 'e' + 'l' + 'l' + 'o' + ' world').matches('h' + 'e' + 'l' + 'l' + 'o') diff --git a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java index 3a2bcc1f4..39723083e 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java @@ -340,7 +340,8 @@ public void trace_struct() throws Exception { CelEvaluationListener listener = (expr, res) -> { assertThat(res).isEqualTo(TestAllTypes.getDefaultInstance()); - assertThat(expr.struct().messageName()).isEqualTo("TestAllTypes"); + assertThat(expr.struct().messageName()) + .isEqualTo("cel.expr.conformance.proto3.TestAllTypes"); }; Cel cel = CelFactory.standardCelBuilder() diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 8f5dae8af..24ed1ea8d 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -576,7 +576,7 @@ public void containers() { source = "proto3.TestAllTypes{} == cel.expr.conformance.proto3.TestAllTypes{}"; runTest(); - source = "SGAR"; // From StandaloneGLobaLEnum + source = "SGAR"; // From StandaloneGlobalEnum runTest(); } From 1ffe7c43a7f4e858a30b5108d446d3b265cf76eb Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 9 Oct 2025 12:36:57 -0700 Subject: [PATCH 22/27] Fix FileDescriptorSetConverter to always reference WellKnownTypes descriptors from generated ones PiperOrigin-RevId: 817294888 --- .../src/test/java/dev/cel/bundle/BUILD.bazel | 1 + .../test/java/dev/cel/bundle/CelImplTest.java | 64 +++++++++- .../src/main/java/dev/cel/checker/BUILD.bazel | 2 +- common/BUILD.bazel | 13 ++ .../src/main/java/dev/cel/common/BUILD.bazel | 19 ++- .../dev/cel/common/CelDescriptorUtil.java | 3 + .../java/dev/cel/common/internal/BUILD.bazel | 2 + .../internal/DefaultDescriptorPool.java | 4 + .../internal/FileDescriptorSetConverter.java | 11 +- .../cel/common/internal/WellKnownProto.java | 114 ++++++++++++++---- .../java/dev/cel/common/types/BUILD.bazel | 1 + .../src/test/java/dev/cel/common/BUILD.bazel | 1 + .../dev/cel/common/CelDescriptorUtilTest.java | 16 ++- .../java/dev/cel/common/internal/BUILD.bazel | 1 + .../common/internal/WellKnownProtoTest.java | 80 ++++++++++++ .../java/dev/cel/common/values/BUILD.bazel | 3 +- .../java/dev/cel/compiler/tools/BUILD.bazel | 2 +- .../main/java/dev/cel/protobuf/BUILD.bazel | 2 +- .../src/main/java/dev/cel/runtime/BUILD.bazel | 2 + .../src/test/java/dev/cel/runtime/BUILD.bazel | 1 + .../java/dev/cel/testing/utils/BUILD.bazel | 2 + 21 files changed, 304 insertions(+), 40 deletions(-) diff --git a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel index a4c6f513d..26cbe392d 100644 --- a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel @@ -25,6 +25,7 @@ java_library( "//checker:checker_legacy_environment", "//checker:proto_type_mask", "//common:cel_ast", + "//common:cel_descriptor_util", "//common:cel_source", "//common:compiler_common", "//common:container", diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index b417517fe..d781c80b4 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -43,14 +43,18 @@ import com.google.protobuf.ByteString; import com.google.protobuf.DescriptorProtos.FileDescriptorProto; import com.google.protobuf.DescriptorProtos.FileDescriptorSet; +import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Duration; +import com.google.protobuf.DynamicMessage; import com.google.protobuf.Empty; +import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import com.google.protobuf.Struct; import com.google.protobuf.TextFormat; import com.google.protobuf.Timestamp; +import com.google.protobuf.TypeRegistry; import com.google.protobuf.WrappersProto; import com.google.rpc.context.AttributeContext; import com.google.testing.junit.testparameterinjector.TestParameter; @@ -62,6 +66,7 @@ import dev.cel.checker.TypeProvider; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; +import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelErrorCode; import dev.cel.common.CelIssue; import dev.cel.common.CelOptions; @@ -91,6 +96,7 @@ import dev.cel.compiler.CelCompilerFactory; import dev.cel.compiler.CelCompilerImpl; import dev.cel.expr.conformance.proto2.Proto2ExtensionScopedMessage; +import dev.cel.expr.conformance.proto2.TestAllTypesExtensions; import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.parser.CelParserImpl; import dev.cel.parser.CelStandardMacro; @@ -196,13 +202,11 @@ public void build_badFileDescriptorSet() { IllegalArgumentException.class, () -> standardCelBuilderWithMacros() - .setContainer(CelContainer.ofName("google.rpc.context.AttributeContext")) + .setContainer(CelContainer.ofName("cel.expr.conformance.proto2")) .addFileTypes( FileDescriptorSet.newBuilder() - .addFile(AttributeContext.getDescriptor().getFile().toProto()) + .addFile(TestAllTypesExtensions.getDescriptor().getFile().toProto()) .build()) - .setProtoResultType( - CelProtoTypes.createMessage("google.rpc.context.AttributeContext.Resource")) .build()); assertThat(e).hasMessageThat().contains("file descriptor set with unresolved proto file"); } @@ -2124,6 +2128,58 @@ public void program_evaluateCanonicalTypesToNativeTypesDisabled_producesBytesPro assertThat(result).isEqualTo(ByteString.copyFromUtf8("abc")); } + @Test + public void program_fdsContainsWktDependency_descriptorInstancesMatch() throws Exception { + // Force serialization of the descriptor to get a unique instance + FileDescriptorProto proto = TestAllTypes.getDescriptor().getFile().toProto(); + FileDescriptorSet fds = FileDescriptorSet.newBuilder().addFile(proto).build(); + ImmutableSet fileDescriptors = + CelDescriptorUtil.getFileDescriptorsFromFileDescriptorSet(fds); + ImmutableSet descriptors = + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(fileDescriptors) + .messageTypeDescriptors(); + Descriptor testAllTypesDescriptor = + descriptors.stream() + .filter(x -> x.getFullName().equals(TestAllTypes.getDescriptor().getFullName())) + .findAny() + .get(); + + // Parse text proto using this fds + TypeRegistry typeRegistry = TypeRegistry.newBuilder().add(descriptors).build(); + TestAllTypes.Builder testAllTypesBuilder = TestAllTypes.newBuilder(); + TextFormat.Parser textFormatParser = + TextFormat.Parser.newBuilder().setTypeRegistry(typeRegistry).build(); + String textProto = + "single_timestamp {\n" // + + " seconds: 100\n" // + + "}"; + textFormatParser.merge(textProto, testAllTypesBuilder); + TestAllTypes testAllTypesFromTextProto = testAllTypesBuilder.build(); + DynamicMessage dynamicMessage = + DynamicMessage.parseFrom( + testAllTypesDescriptor, + testAllTypesFromTextProto.toByteArray(), + ExtensionRegistry.getEmptyRegistry()); + // Setup CEL environment with the same descriptors obtained from FDS + Cel cel = + standardCelBuilderWithMacros() + .addMessageTypes(descriptors) + .setOptions( + CelOptions.current() + .evaluateCanonicalTypesToNativeValues(true) + .enableTimestampEpoch(true) + .build()) + .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) + .build(); + CelAbstractSyntaxTree ast = + cel.compile("TestAllTypes{single_timestamp: timestamp(100)}").getAst(); + + DynamicMessage evalResult = (DynamicMessage) cel.createProgram(ast).eval(); + + // This should strictly equal regardless of where the descriptors came from for WKTs + assertThat(evalResult).isEqualTo(dynamicMessage); + } + @Test public void toBuilder_isImmutable() { CelBuilder celBuilder = CelFactory.standardCelBuilder(); diff --git a/checker/src/main/java/dev/cel/checker/BUILD.bazel b/checker/src/main/java/dev/cel/checker/BUILD.bazel index 80f6e1a8a..5479934c4 100644 --- a/checker/src/main/java/dev/cel/checker/BUILD.bazel +++ b/checker/src/main/java/dev/cel/checker/BUILD.bazel @@ -73,7 +73,7 @@ java_library( ":type_provider_legacy_impl", "//:auto_value", "//common:cel_ast", - "//common:cel_descriptors", + "//common:cel_descriptor_util", "//common:cel_source", "//common:compiler_common", "//common:container", diff --git a/common/BUILD.bazel b/common/BUILD.bazel index 77a336dbf..f86ea04f9 100644 --- a/common/BUILD.bazel +++ b/common/BUILD.bazel @@ -106,5 +106,18 @@ java_library( java_library( name = "cel_descriptors", + visibility = ["//:internal"], exports = ["//common/src/main/java/dev/cel/common:cel_descriptors"], ) + +java_library( + name = "cel_descriptor_util", + visibility = [ + "//:internal", + # TODO: Remove references to the following clients + "//java/com/google/abuse/admin/notebook/compiler/checkedtypes:__pkg__", + "//java/com/google/paymentfraud/v2/util/featurereplay/common/risklogrecordio:__pkg__", + "//java/com/google/payments/consumer/growth/treatmentconfig/management/backend/service/config/utils:__pkg__", + ], + exports = ["//common/src/main/java/dev/cel/common:cel_descriptor_util"], +) diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index 460a07f13..ce6963206 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -36,18 +36,31 @@ PROTO_V1ALPHA1_AST_SOURCE = [ ] java_library( - name = "cel_descriptors", + name = "cel_descriptor_util", srcs = [ "CelDescriptorUtil.java", - "CelDescriptors.java", ], tags = [ ], deps = [ - "//:auto_value", + ":cel_descriptors", "//common/annotations", "//common/internal:file_descriptor_converter", "//common/types:cel_types", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +java_library( + name = "cel_descriptors", + srcs = [ + "CelDescriptors.java", + ], + tags = [ + ], + deps = [ + "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", diff --git a/common/src/main/java/dev/cel/common/CelDescriptorUtil.java b/common/src/main/java/dev/cel/common/CelDescriptorUtil.java index db1f22120..7695e96d8 100644 --- a/common/src/main/java/dev/cel/common/CelDescriptorUtil.java +++ b/common/src/main/java/dev/cel/common/CelDescriptorUtil.java @@ -166,10 +166,12 @@ private static void collectMessageTypeDescriptors( if (visited.contains(messageName)) { return; } + if (!descriptor.getOptions().getMapEntry()) { visited.add(messageName); celDescriptors.addMessageTypeDescriptors(descriptor); } + if (CelTypes.getWellKnownCelType(messageName).isPresent()) { return; } @@ -234,6 +236,7 @@ private static void copyToFileDescriptorSet( if (visited.contains(fd.getFullName())) { return; } + visited.add(fd.getFullName()); for (FileDescriptor dep : fd.getDependencies()) { copyToFileDescriptorSet(visited, dep, files); diff --git a/common/src/main/java/dev/cel/common/internal/BUILD.bazel b/common/src/main/java/dev/cel/common/internal/BUILD.bazel index 85b5df9a4..690b1cc75 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -232,7 +232,9 @@ java_library( tags = [ ], deps = [ + ":cel_descriptor_pools", "//:auto_value", + "//common/internal:well_known_proto", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", diff --git a/common/src/main/java/dev/cel/common/internal/DefaultDescriptorPool.java b/common/src/main/java/dev/cel/common/internal/DefaultDescriptorPool.java index df1907e42..a4614a2cb 100644 --- a/common/src/main/java/dev/cel/common/internal/DefaultDescriptorPool.java +++ b/common/src/main/java/dev/cel/common/internal/DefaultDescriptorPool.java @@ -119,6 +119,10 @@ public static DefaultDescriptorPool create( extensionRegistry); } + public static Descriptor getWellKnownProtoDescriptor(WellKnownProto wellKnownProto) { + return WELL_KNOWN_PROTO_TO_DESCRIPTORS.get(wellKnownProto); + } + @Override public Optional findDescriptor(String name) { return Optional.ofNullable(descriptorMap.get(name)); diff --git a/common/src/main/java/dev/cel/common/internal/FileDescriptorSetConverter.java b/common/src/main/java/dev/cel/common/internal/FileDescriptorSetConverter.java index c6fd4a0da..5bde34097 100644 --- a/common/src/main/java/dev/cel/common/internal/FileDescriptorSetConverter.java +++ b/common/src/main/java/dev/cel/common/internal/FileDescriptorSetConverter.java @@ -15,6 +15,7 @@ package dev.cel.common.internal; import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; @@ -76,7 +77,15 @@ private static FileDescriptor readDescriptor( // Read dependencies first, they are needed to create the logical descriptor from the proto. List deps = new ArrayList<>(); for (String dep : fileProto.getDependencyList()) { - deps.add(readDescriptor(dep, descriptorProtos, descriptors)); + ImmutableCollection wktProtos = WellKnownProto.getByPathName(dep); + if (wktProtos.isEmpty()) { + deps.add(readDescriptor(dep, descriptorProtos, descriptors)); + } else { + // Ensure the generated message's descriptor is used as a dependency for WKTs to avoid + // issues with descriptor instance mismatch. + WellKnownProto wellKnownProto = wktProtos.iterator().next(); + deps.add(DefaultDescriptorPool.getWellKnownProtoDescriptor(wellKnownProto).getFile()); + } } // Create the file descriptor, cache, and return. try { diff --git a/common/src/main/java/dev/cel/common/internal/WellKnownProto.java b/common/src/main/java/dev/cel/common/internal/WellKnownProto.java index 78041e3be..21ee8053e 100644 --- a/common/src/main/java/dev/cel/common/internal/WellKnownProto.java +++ b/common/src/main/java/dev/cel/common/internal/WellKnownProto.java @@ -17,7 +17,9 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; import static java.util.Arrays.stream; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; import com.google.protobuf.Any; import com.google.protobuf.BoolValue; import com.google.protobuf.BytesValue; @@ -46,28 +48,71 @@ */ @Internal public enum WellKnownProto { - ANY_VALUE("google.protobuf.Any", Any.class), - DURATION("google.protobuf.Duration", Duration.class), - JSON_LIST_VALUE("google.protobuf.ListValue", ListValue.class), - JSON_STRUCT_VALUE("google.protobuf.Struct", Struct.class), - JSON_VALUE("google.protobuf.Value", Value.class), - TIMESTAMP("google.protobuf.Timestamp", Timestamp.class), + ANY_VALUE("google.protobuf.Any", "google/protobuf/any.proto", Any.class), + DURATION("google.protobuf.Duration", "google/protobuf/duration.proto", Duration.class), + JSON_LIST_VALUE("google.protobuf.ListValue", "google/protobuf/struct.proto", ListValue.class), + JSON_STRUCT_VALUE("google.protobuf.Struct", "google/protobuf/struct.proto", Struct.class), + JSON_VALUE("google.protobuf.Value", "google/protobuf/struct.proto", Value.class), + TIMESTAMP("google.protobuf.Timestamp", "google/protobuf/timestamp.proto", Timestamp.class), // Wrapper types - FLOAT_VALUE("google.protobuf.FloatValue", FloatValue.class, /* isWrapperType= */ true), - INT32_VALUE("google.protobuf.Int32Value", Int32Value.class, /* isWrapperType= */ true), - INT64_VALUE("google.protobuf.Int64Value", Int64Value.class, /* isWrapperType= */ true), - STRING_VALUE("google.protobuf.StringValue", StringValue.class, /* isWrapperType= */ true), - BOOL_VALUE("google.protobuf.BoolValue", BoolValue.class, /* isWrapperType= */ true), - BYTES_VALUE("google.protobuf.BytesValue", BytesValue.class, /* isWrapperType= */ true), - DOUBLE_VALUE("google.protobuf.DoubleValue", DoubleValue.class, /* isWrapperType= */ true), - UINT32_VALUE("google.protobuf.UInt32Value", UInt32Value.class, /* isWrapperType= */ true), - UINT64_VALUE("google.protobuf.UInt64Value", UInt64Value.class, /* isWrapperType= */ true), + FLOAT_VALUE( + "google.protobuf.FloatValue", + "google/protobuf/wrappers.proto", + FloatValue.class, + /* isWrapperType= */ true), + INT32_VALUE( + "google.protobuf.Int32Value", + "google/protobuf/wrappers.proto", + Int32Value.class, + /* isWrapperType= */ true), + INT64_VALUE( + "google.protobuf.Int64Value", + "google/protobuf/wrappers.proto", + Int64Value.class, + /* isWrapperType= */ true), + STRING_VALUE( + "google.protobuf.StringValue", + "google/protobuf/wrappers.proto", + StringValue.class, + /* isWrapperType= */ true), + BOOL_VALUE( + "google.protobuf.BoolValue", + "google/protobuf/wrappers.proto", + BoolValue.class, + /* isWrapperType= */ true), + BYTES_VALUE( + "google.protobuf.BytesValue", + "google/protobuf/wrappers.proto", + BytesValue.class, + /* isWrapperType= */ true), + DOUBLE_VALUE( + "google.protobuf.DoubleValue", + "google/protobuf/wrappers.proto", + DoubleValue.class, + /* isWrapperType= */ true), + UINT32_VALUE( + "google.protobuf.UInt32Value", + "google/protobuf/wrappers.proto", + UInt32Value.class, + /* isWrapperType= */ true), + UINT64_VALUE( + "google.protobuf.UInt64Value", + "google/protobuf/wrappers.proto", + UInt64Value.class, + /* isWrapperType= */ true), // These aren't explicitly called out as wrapper types in the spec, but behave like one, because // they are still converted into an equivalent primitive type. - EMPTY("google.protobuf.Empty", Empty.class, /* isWrapperType= */ true), - FIELD_MASK("google.protobuf.FieldMask", FieldMask.class, /* isWrapperType= */ true), - ; + EMPTY( + "google.protobuf.Empty", + "google/protobuf/empty.proto", + Empty.class, + /* isWrapperType= */ true), + FIELD_MASK( + "google.protobuf.FieldMask", + "google/protobuf/field_mask.proto", + FieldMask.class, + /* isWrapperType= */ true); private static final ImmutableMap TYPE_NAME_TO_WELL_KNOWN_PROTO_MAP = stream(WellKnownProto.values()) @@ -78,10 +123,27 @@ public enum WellKnownProto { stream(WellKnownProto.values()) .collect(toImmutableMap(WellKnownProto::messageClass, Function.identity())); + private static final ImmutableMultimap PATH_NAME_TO_WELL_KNOWN_PROTO_MAP = + initPathNameMap(); + + private static ImmutableMultimap initPathNameMap() { + ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); + for (WellKnownProto proto : values()) { + builder.put(proto.pathName(), proto); + } + return builder.build(); + } + private final String wellKnownProtoTypeName; + private final String pathName; private final Class clazz; private final boolean isWrapperType; + /** Gets the full proto path name (ex: google/protobuf/any.proto) */ + public String pathName() { + return pathName; + } + /** Gets the fully qualified prototype name (ex: google.protobuf.FloatValue) */ public String typeName() { return wellKnownProtoTypeName; @@ -92,6 +154,14 @@ public Class messageClass() { return clazz; } + /** + * Returns the well known proto given the full proto path (example: + * google/protobuf/timestamp.proto) + */ + public static ImmutableCollection getByPathName(String typeName) { + return PATH_NAME_TO_WELL_KNOWN_PROTO_MAP.get(typeName); + } + public static Optional getByTypeName(String typeName) { return Optional.ofNullable(TYPE_NAME_TO_WELL_KNOWN_PROTO_MAP.get(typeName)); } @@ -112,12 +182,14 @@ public boolean isWrapperType() { return isWrapperType; } - WellKnownProto(String wellKnownProtoTypeName, Class clazz) { - this(wellKnownProtoTypeName, clazz, /* isWrapperType= */ false); + WellKnownProto(String wellKnownProtoTypeName, String pathName, Class clazz) { + this(wellKnownProtoTypeName, pathName, clazz, /* isWrapperType= */ false); } - WellKnownProto(String wellKnownProtoFullName, Class clazz, boolean isWrapperType) { + WellKnownProto( + String wellKnownProtoFullName, String pathName, Class clazz, boolean isWrapperType) { this.wellKnownProtoTypeName = wellKnownProtoFullName; + this.pathName = pathName; this.clazz = clazz; this.isWrapperType = isWrapperType; } diff --git a/common/src/main/java/dev/cel/common/types/BUILD.bazel b/common/src/main/java/dev/cel/common/types/BUILD.bazel index ccb72f9e5..579fd31c5 100644 --- a/common/src/main/java/dev/cel/common/types/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/types/BUILD.bazel @@ -174,6 +174,7 @@ java_library( ":type_providers", ":types", "//:auto_value", + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common/internal:file_descriptor_converter", "@maven//:com_google_errorprone_error_prone_annotations", diff --git a/common/src/test/java/dev/cel/common/BUILD.bazel b/common/src/test/java/dev/cel/common/BUILD.bazel index c2ce1efdb..1ff6deb3b 100644 --- a/common/src/test/java/dev/cel/common/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/BUILD.bazel @@ -12,6 +12,7 @@ java_library( deps = [ "//:java_truth", "//common:cel_ast", + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common:cel_source", "//common:compiler_common", diff --git a/common/src/test/java/dev/cel/common/CelDescriptorUtilTest.java b/common/src/test/java/dev/cel/common/CelDescriptorUtilTest.java index df4fc35fb..3184f5624 100644 --- a/common/src/test/java/dev/cel/common/CelDescriptorUtilTest.java +++ b/common/src/test/java/dev/cel/common/CelDescriptorUtilTest.java @@ -16,7 +16,6 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -183,11 +182,16 @@ public void getFileDescriptorsFromFileDescriptorSet_incompleteFileSet() { .addFile(Timestamp.getDescriptor().getFile().toProto()) .addFile(TestAllTypes.getDescriptor().getFile().toProto()) .build(); - IllegalArgumentException e = - assertThrows( - IllegalArgumentException.class, - () -> CelDescriptorUtil.getFileDescriptorsFromFileDescriptorSet(fds)); - assertThat(e).hasMessageThat().contains("google/protobuf/any.proto"); + + ImmutableSet fileDescriptors = + CelDescriptorUtil.getFileDescriptorsFromFileDescriptorSet(fds); + + assertThat(fileDescriptors.stream().map(FileDescriptor::getName).collect(toImmutableSet())) + .containsExactly( + TestAllTypes.getDescriptor().getFile().getName(), + Duration.getDescriptor().getFile().getName(), + Struct.getDescriptor().getFile().getName(), + Timestamp.getDescriptor().getFile().getName()); } @Test diff --git a/common/src/test/java/dev/cel/common/internal/BUILD.bazel b/common/src/test/java/dev/cel/common/internal/BUILD.bazel index d46607118..295c48a6e 100644 --- a/common/src/test/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/internal/BUILD.bazel @@ -13,6 +13,7 @@ java_library( deps = [ "//:auto_value", "//:java_truth", + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common:options", "//common/ast", diff --git a/common/src/test/java/dev/cel/common/internal/WellKnownProtoTest.java b/common/src/test/java/dev/cel/common/internal/WellKnownProtoTest.java index bb75a341a..c12411d95 100644 --- a/common/src/test/java/dev/cel/common/internal/WellKnownProtoTest.java +++ b/common/src/test/java/dev/cel/common/internal/WellKnownProtoTest.java @@ -16,7 +16,24 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Any; +import com.google.protobuf.BoolValue; +import com.google.protobuf.BytesValue; +import com.google.protobuf.DoubleValue; +import com.google.protobuf.Duration; +import com.google.protobuf.Empty; +import com.google.protobuf.FieldMask; import com.google.protobuf.FloatValue; +import com.google.protobuf.Int32Value; +import com.google.protobuf.Int64Value; +import com.google.protobuf.ListValue; +import com.google.protobuf.StringValue; +import com.google.protobuf.Struct; +import com.google.protobuf.Timestamp; +import com.google.protobuf.UInt32Value; +import com.google.protobuf.UInt64Value; +import com.google.protobuf.Value; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import java.util.List; @@ -63,4 +80,67 @@ public void getByClass_success() { public void getByClass_unknownClass_returnsEmpty() { assertThat(WellKnownProto.getByClass(List.class)).isEmpty(); } + + @Test + public void getByPathName_singular() { + assertThat(WellKnownProto.getByPathName(Any.getDescriptor().getFile().getName())) + .containsExactly(WellKnownProto.ANY_VALUE); + assertThat(WellKnownProto.getByPathName(Duration.getDescriptor().getFile().getName())) + .containsExactly(WellKnownProto.DURATION); + assertThat(WellKnownProto.getByPathName(Timestamp.getDescriptor().getFile().getName())) + .containsExactly(WellKnownProto.TIMESTAMP); + assertThat(WellKnownProto.getByPathName(Empty.getDescriptor().getFile().getName())) + .containsExactly(WellKnownProto.EMPTY); + assertThat(WellKnownProto.getByPathName(FieldMask.getDescriptor().getFile().getName())) + .containsExactly(WellKnownProto.FIELD_MASK); + } + + @Test + public void getByPathName_json() { + ImmutableList expectedWellKnownProtos = + ImmutableList.of( + WellKnownProto.JSON_STRUCT_VALUE, + WellKnownProto.JSON_VALUE, + WellKnownProto.JSON_LIST_VALUE); + assertThat(WellKnownProto.getByPathName(Struct.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(Value.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(ListValue.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + } + + @Test + public void getByPathName_wrappers() { + ImmutableList expectedWellKnownProtos = + ImmutableList.of( + WellKnownProto.FLOAT_VALUE, + WellKnownProto.DOUBLE_VALUE, + WellKnownProto.INT32_VALUE, + WellKnownProto.INT64_VALUE, + WellKnownProto.UINT32_VALUE, + WellKnownProto.UINT64_VALUE, + WellKnownProto.BOOL_VALUE, + WellKnownProto.STRING_VALUE, + WellKnownProto.BYTES_VALUE); + + assertThat(WellKnownProto.getByPathName(FloatValue.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(DoubleValue.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(Int32Value.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(Int64Value.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(UInt32Value.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(UInt64Value.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(BoolValue.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(StringValue.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(BytesValue.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + } } diff --git a/common/src/test/java/dev/cel/common/values/BUILD.bazel b/common/src/test/java/dev/cel/common/values/BUILD.bazel index 9d9132cbb..131344e1a 100644 --- a/common/src/test/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/values/BUILD.bazel @@ -11,7 +11,7 @@ java_library( "//:java_truth", "//bundle:cel", "//common:cel_ast", - "//common:cel_descriptors", + "//common:cel_descriptor_util", "//common:options", "//common:runtime_exception", "//common/internal:cel_descriptor_pools", @@ -21,7 +21,6 @@ java_library( "//common/internal:dynamic_proto", "//common/internal:proto_message_factory", "//common/internal:proto_time_utils", - "//common/internal:well_known_proto", "//common/types", "//common/types:type_providers", "//common/values", diff --git a/compiler/src/main/java/dev/cel/compiler/tools/BUILD.bazel b/compiler/src/main/java/dev/cel/compiler/tools/BUILD.bazel index aa10b1324..92375ec8f 100644 --- a/compiler/src/main/java/dev/cel/compiler/tools/BUILD.bazel +++ b/compiler/src/main/java/dev/cel/compiler/tools/BUILD.bazel @@ -19,7 +19,7 @@ java_binary( "//bundle:environment_exception", "//bundle:environment_yaml_parser", "//common:cel_ast", - "//common:cel_descriptors", + "//common:cel_descriptor_util", "//common:options", "//common:proto_ast", "//compiler", diff --git a/protobuf/src/main/java/dev/cel/protobuf/BUILD.bazel b/protobuf/src/main/java/dev/cel/protobuf/BUILD.bazel index 666915526..6e7b473eb 100644 --- a/protobuf/src/main/java/dev/cel/protobuf/BUILD.bazel +++ b/protobuf/src/main/java/dev/cel/protobuf/BUILD.bazel @@ -20,7 +20,7 @@ java_binary( ":debug_printer", ":java_file_generator", ":proto_descriptor_collector", - "//common:cel_descriptors", + "//common:cel_descriptor_util", "//common/internal:proto_java_qualified_names", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 7c0539d00..184a01534 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -100,6 +100,7 @@ java_library( ], deps = [ ":runtime_type_provider", + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common:error_codes", "//common:options", @@ -842,6 +843,7 @@ java_library( ":unknown_attributes", "//:auto_value", "//common:cel_ast", + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common:options", "//common/annotations", diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 5d083cdc9..5137cad39 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -28,6 +28,7 @@ java_library( "//:java_truth", "//bundle:cel", "//common:cel_ast", + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common:cel_exception", "//common:cel_source", diff --git a/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel b/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel index 3e32bbd2a..2947709e5 100644 --- a/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel @@ -15,6 +15,7 @@ java_library( tags = [ ], deps = [ + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common/internal:default_instance_message_factory", "//common/internal:proto_time_utils", @@ -51,6 +52,7 @@ java_library( tags = [ ], deps = [ + "//common:cel_descriptor_util", "//common:cel_descriptors", "//testing/testrunner:class_loader_utils", "@maven//:com_google_guava_guava", From c9f7e9ac45516f0737e73da1098f7fc2c5ed7d1f Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 9 Oct 2025 14:32:16 -0700 Subject: [PATCH 23/27] Relax constraint for fully qualifying a package name when aliasing PiperOrigin-RevId: 817340436 --- .../java/dev/cel/common/CelContainer.java | 19 +++++++++++++------ .../java/dev/cel/common/CelContainerTest.java | 4 ---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/common/src/main/java/dev/cel/common/CelContainer.java b/common/src/main/java/dev/cel/common/CelContainer.java index 6d9fcd612..ccb1715ba 100644 --- a/common/src/main/java/dev/cel/common/CelContainer.java +++ b/common/src/main/java/dev/cel/common/CelContainer.java @@ -148,6 +148,19 @@ public Builder addAbbreviations(ImmutableSet qualifiedNames) { * useful for remapping poorly chosen protobuf message / package names. * *

Note: all the rules that apply to abbreviations also apply to aliasing. + * + *

Note: It is also possible to alias a top-level package or a name that does not contain a + * period. When resolving an identifier, CEL checks for variables and functions before + * attempting to expand aliases for type resolution. Therefore, if an expression consists solely + * of an identifier that matches both an alias and a declared variable (e.g., {@code + * short_alias}), the variable will take precedence and the compilation will succeed. The alias + * expansion will only be used when the alias is a prefix to a longer name (e.g., {@code + * short_alias.TestRequest}) or if no variable with the same name exists, in which case using + * the alias as a standalone identifier will likely result in a compilation error. + * + * @param alias Simple name to be expanded. Must be a valid identifier. + * @param qualifiedName The fully qualified name to expand to. This may be a simple name (e.g. a + * package name) but it must be a valid identifier. */ @CanIgnoreReturnValue public Builder addAlias(String alias, String qualifiedName) { @@ -172,12 +185,6 @@ private void validateAliasOrThrow(AliasKind kind, String qualifiedName, String a String.format("qualified name must not begin with a leading '.': %s", qualifiedName)); } - int index = qualifiedName.lastIndexOf("."); - if (index <= 0 || index == qualifiedName.length() - 1) { - throw new IllegalArgumentException( - String.format("%s must refer to a valid qualified name: %s", kind, qualifiedName)); - } - String aliasRef = aliases.get(alias); if (aliasRef != null) { throw new IllegalArgumentException( diff --git a/common/src/test/java/dev/cel/common/CelContainerTest.java b/common/src/test/java/dev/cel/common/CelContainerTest.java index f116039c8..cd7b73576 100644 --- a/common/src/test/java/dev/cel/common/CelContainerTest.java +++ b/common/src/test/java/dev/cel/common/CelContainerTest.java @@ -121,10 +121,6 @@ public void containerBuilder_addAliasError_throws(@TestParameter AliasingErrorTe private enum AliasingErrorTestCase { BAD_QUALIFIED_NAME( - "foo", - "invalid_qualified_name", - "alias must refer to a valid qualified name: invalid_qualified_name"), - BAD_QUALIFIED_NAME_2( "foo", ".bad.name", "qualified name must not begin with a leading '.': .bad.name"), BAD_ALIAS_NAME_1( "bad.alias", "b.c", "alias must be non-empty and simple (not qualified): alias=bad.alias"), From f1ab6e514ec4c35a8af4f947bc09c752bcc4c48d Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 10 Oct 2025 13:36:43 -0700 Subject: [PATCH 24/27] Include comprehensions in CelEnvironment PiperOrigin-RevId: 817770672 --- .../java/dev/cel/bundle/CelEnvironment.java | 12 ++++++++---- .../dev/cel/bundle/CelEnvironmentTest.java | 18 ++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java index 3bdc7c894..9f33032a8 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java @@ -70,7 +70,8 @@ public abstract class CelEnvironment { "optional", CanonicalCelExtension.OPTIONAL, "protos", CanonicalCelExtension.PROTOS, "sets", CanonicalCelExtension.SETS, - "strings", CanonicalCelExtension.STRINGS); + "strings", CanonicalCelExtension.STRINGS, + "comprehensions", CanonicalCelExtension.COMPREHENSIONS); /** Environment source in textual format (ex: textproto, YAML). */ public abstract Optional source(); @@ -687,8 +688,8 @@ enum CanonicalCelExtension { BINDINGS((options, version) -> CelExtensions.bindings()), PROTOS((options, version) -> CelExtensions.protos()), ENCODERS( - (options, version) -> CelExtensions.encoders(), - (options, version) -> CelExtensions.encoders()), + (options, version) -> CelExtensions.encoders(options), + (options, version) -> CelExtensions.encoders(options)), MATH( (options, version) -> CelExtensions.math(options, version), (options, version) -> CelExtensions.math(options, version)), @@ -701,7 +702,10 @@ enum CanonicalCelExtension { SETS( (options, version) -> CelExtensions.sets(options), (options, version) -> CelExtensions.sets(options)), - LISTS((options, version) -> CelExtensions.lists(), (options, version) -> CelExtensions.lists()); + LISTS((options, version) -> CelExtensions.lists(), (options, version) -> CelExtensions.lists()), + COMPREHENSIONS( + (options, version) -> CelExtensions.comprehensions(), + (options, version) -> CelExtensions.comprehensions()); @SuppressWarnings("ImmutableEnumChecker") private final CompilerExtensionProvider compilerExtensionProvider; diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java index bafa20ea6..56a4a241c 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java @@ -60,7 +60,8 @@ public void extend_allExtensions() throws Exception { ExtensionConfig.latest("optional"), ExtensionConfig.latest("protos"), ExtensionConfig.latest("sets"), - ExtensionConfig.latest("strings")); + ExtensionConfig.latest("strings"), + ExtensionConfig.latest("comprehensions")); CelEnvironment environment = CelEnvironment.newBuilder().addExtensions(extensionConfigs).build(); @@ -100,9 +101,7 @@ public void extensionVersion_specific() throws Exception { @Test public void extensionVersion_latest() throws Exception { CelEnvironment environment = - CelEnvironment.newBuilder() - .addExtensions(ExtensionConfig.latest("math")) - .build(); + CelEnvironment.newBuilder().addExtensions(ExtensionConfig.latest("math")).build(); Cel cel = environment.extend(CelFactory.standardCelBuilder().build(), CelOptions.DEFAULT); CelAbstractSyntaxTree ast = cel.compile("math.sqrt(4)").getAst(); @@ -240,10 +239,10 @@ public void stdlibSubset_functionsIncluded() throws Exception { LibrarySubset.newBuilder() .setDisabled(false) .setIncludedFunctions( - ImmutableSet.of( - FunctionSelector.create("_==_", ImmutableSet.of()), - FunctionSelector.create("_!=_", ImmutableSet.of()), - FunctionSelector.create("_&&_", ImmutableSet.of()))) + ImmutableSet.of( + FunctionSelector.create("_==_", ImmutableSet.of()), + FunctionSelector.create("_!=_", ImmutableSet.of()), + FunctionSelector.create("_&&_", ImmutableSet.of()))) .build()) .build(); @@ -287,8 +286,7 @@ public void stdlibSubset_functionsExcluded() throws Exception { LibrarySubset.newBuilder() .setDisabled(false) .setExcludedFunctions( - ImmutableSet.of( - FunctionSelector.create("_+_", ImmutableSet.of()))) + ImmutableSet.of(FunctionSelector.create("_+_", ImmutableSet.of()))) .build()) .build(); From 9c7c4312d0d99958605c6ed3d45b68ad50fb5a52 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 14 Oct 2025 16:33:30 -0700 Subject: [PATCH 25/27] Update evaluateCanonicalTypesToNativeValues documentation and fix tests PiperOrigin-RevId: 819452263 --- .../test/java/dev/cel/bundle/CelImplTest.java | 12 ++++++++---- .../main/java/dev/cel/common/CelOptions.java | 2 ++ .../cel/common/internal/ProtoAdapterTest.java | 17 +++++++++-------- .../values/ProtoMessageValueProviderTest.java | 8 ++++---- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index d781c80b4..2d337e4cf 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -77,7 +77,6 @@ import dev.cel.common.CelVarDecl; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelList; -import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.testing.RepeatedTestProvider; import dev.cel.common.types.CelKind; import dev.cel.common.types.CelProtoMessageTypes; @@ -114,6 +113,7 @@ import dev.cel.runtime.CelVariableResolver; import dev.cel.runtime.UnknownContext; import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum; +import java.time.Instant; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -817,6 +817,7 @@ public void program_messageConstruction() throws Exception { public void program_duplicateTypeDescriptor() throws Exception { Cel cel = standardCelBuilderWithMacros() + .setOptions(CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build()) .addMessageTypes(Timestamp.getDescriptor()) .addMessageTypes(ImmutableList.of(Timestamp.getDescriptor())) .setContainer(CelContainer.ofName("google")) @@ -824,20 +825,22 @@ public void program_duplicateTypeDescriptor() throws Exception { .build(); CelRuntime.Program program = cel.createProgram(cel.compile("protobuf.Timestamp{seconds: 12}").getAst()); - assertThat(program.eval()).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(12)); + + assertThat(program.eval()).isEqualTo(Instant.ofEpochSecond(12)); } @Test public void program_hermeticDescriptors_wellKnownProtobuf() throws Exception { Cel cel = standardCelBuilderWithMacros() + .setOptions(CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build()) .addMessageTypes(Timestamp.getDescriptor()) .setContainer(CelContainer.ofName("google")) .setResultType(SimpleType.TIMESTAMP) .build(); CelRuntime.Program program = cel.createProgram(cel.compile("protobuf.Timestamp{seconds: 12}").getAst()); - assertThat(program.eval()).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(12)); + assertThat(program.eval()).isEqualTo(Instant.ofEpochSecond(12)); } @Test @@ -959,6 +962,7 @@ public void program_deepTypeResolutionDisabledForRuntime_fails() throws Exceptio public void program_typeProvider() throws Exception { Cel cel = standardCelBuilderWithMacros() + .setOptions(CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build()) .setTypeProvider( new DescriptorTypeProvider(ImmutableList.of(Timestamp.getDescriptor()))) .setContainer(CelContainer.ofName("google")) @@ -966,7 +970,7 @@ public void program_typeProvider() throws Exception { .build(); CelRuntime.Program program = cel.createProgram(cel.compile("protobuf.Timestamp{seconds: 12}").getAst()); - assertThat(program.eval()).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(12)); + assertThat(program.eval()).isEqualTo(Instant.ofEpochSecond(12)); } @Test diff --git a/common/src/main/java/dev/cel/common/CelOptions.java b/common/src/main/java/dev/cel/common/CelOptions.java index 23654235d..3cbfb62b1 100644 --- a/common/src/main/java/dev/cel/common/CelOptions.java +++ b/common/src/main/java/dev/cel/common/CelOptions.java @@ -462,6 +462,8 @@ public abstract static class Builder { * com.google.protobuf.ByteString}. *

  • CEL null: {@code dev.cel.common.values.NullValue} instead of {@code * com.google.protobuf.NullValue}. + *
  • Timestamp: {@code java.time.Instant} instead of {@code com.google.protobuf.Timestamp}. + *
  • Duration: {@code java.time.Duration} instead of {@code com.google.protobuf.Duration}. * */ public abstract Builder evaluateCanonicalTypesToNativeValues(boolean value); diff --git a/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java b/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java index 3172258f4..0ce37ac1d 100644 --- a/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java +++ b/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java @@ -41,6 +41,7 @@ import com.google.type.Expr; import dev.cel.common.CelOptions; import dev.cel.common.values.CelByteString; +import java.time.Instant; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -88,11 +89,10 @@ public static List data() { {1.5D, Any.pack(DoubleValue.of(1.5D))}, {1.5D, Value.newBuilder().setNumberValue(1.5D).build()}, { - Duration.newBuilder().setSeconds(123).build(), - Duration.newBuilder().setSeconds(123).build(), + java.time.Duration.ofSeconds(123), Duration.newBuilder().setSeconds(123).build(), }, { - Duration.newBuilder().setSeconds(123).build(), + java.time.Duration.ofSeconds(123), Any.pack(Duration.newBuilder().setSeconds(123).build()), }, {1L, Int64Value.of(1L)}, @@ -132,12 +132,10 @@ public static List data() { .build(), }, { - Timestamp.newBuilder().setSeconds(123).build(), - Timestamp.newBuilder().setSeconds(123).build(), + Instant.ofEpochSecond(123), Timestamp.newBuilder().setSeconds(123).build(), }, { - Timestamp.newBuilder().setSeconds(123).build(), - Any.pack(Timestamp.newBuilder().setSeconds(123).build()), + Instant.ofEpochSecond(123), Any.pack(Timestamp.newBuilder().setSeconds(123).build()), }, {UnsignedLong.valueOf(1L), UInt64Value.of(1L)}, {UnsignedLong.valueOf(1L), Any.pack(UInt64Value.of(1L))}, @@ -152,7 +150,10 @@ public static List data() { @Test public void adaptValueToProto_bidirectionalConversion() { DynamicProto dynamicProto = DynamicProto.create(DefaultMessageFactory.INSTANCE); - ProtoAdapter protoAdapter = new ProtoAdapter(dynamicProto, CelOptions.DEFAULT); + ProtoAdapter protoAdapter = + new ProtoAdapter( + dynamicProto, + CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build()); assertThat(protoAdapter.adaptValueToProto(value, proto.getDescriptorForType().getFullName())) .isEqualTo(proto); assertThat(protoAdapter.adaptProtoToValue(proto)).isEqualTo(value); diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java index 891a7d0e2..f9bee1af1 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java @@ -28,7 +28,6 @@ import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; import dev.cel.common.internal.ProtoMessageFactory; -import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.expr.conformance.proto2.TestAllTypes; import dev.cel.expr.conformance.proto2.TestAllTypesExtensions; import java.time.Duration; @@ -66,7 +65,8 @@ public void newValue_createEmptyProtoMessage() { @Test public void newValue_createProtoMessage_fieldsPopulated() { ProtoMessageValueProvider protoMessageValueProvider = - ProtoMessageValueProvider.newInstance(CelOptions.DEFAULT, DYNAMIC_PROTO); + ProtoMessageValueProvider.newInstance( + CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build(), DYNAMIC_PROTO); ProtoMessageValue protoMessageValue = (ProtoMessageValue) @@ -89,9 +89,9 @@ public void newValue_createProtoMessage_fieldsPopulated() { "single_string", "hello", "single_timestamp", - ProtoTimeUtils.fromSecondsToTimestamp(50), + Instant.ofEpochSecond(50), "single_duration", - ProtoTimeUtils.fromSecondsToDuration(100))) + Duration.ofSeconds(100))) .get(); assertThat(protoMessageValue.isZeroValue()).isFalse(); From 7caf057c4306e4e57bc8a2a731c1946ef58efba5 Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Mon, 20 Oct 2025 14:19:34 -0700 Subject: [PATCH 26/27] Prepare 0.11.1 release PiperOrigin-RevId: 821803417 --- MODULE.bazel | 8 ++++---- README.md | 4 ++-- conformance/src/test/java/dev/cel/conformance/BUILD.bazel | 1 - publish/cel_version.bzl | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index de3cbdace..8e0e79068 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -35,11 +35,11 @@ use_repo(switched_rules, "com_google_googleapis_imports") maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") -GUAVA_VERSION = "33.4.8" +GUAVA_VERSION = "33.5.0" TRUTH_VERSION = "1.4.4" -PROTOBUF_JAVA_VERSION = "4.32.0" +PROTOBUF_JAVA_VERSION = "4.33.0" # Compile only artifacts [ @@ -51,7 +51,7 @@ PROTOBUF_JAVA_VERSION = "4.32.0" ) for group, artifact, version in [coord.split(":") for coord in [ "com.google.code.findbugs:annotations:3.0.1", - "com.google.errorprone:error_prone_annotations:2.41.0", + "com.google.errorprone:error_prone_annotations:2.42.0", ]] ] @@ -114,7 +114,7 @@ maven.install( maven.install( name = "maven_conformance", - artifacts = ["dev.cel:cel:0.11.0"], + artifacts = ["dev.cel:cel:0.11.1"], repositories = [ "https://maven.google.com", "https://repo1.maven.org/maven2", diff --git a/README.md b/README.md index 61ac687b9..f46a1f8c6 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,14 @@ CEL-Java is available in Maven Central Repository. [Download the JARs here][8] o dev.cel cel - 0.11.0 + 0.11.1 ``` **Gradle** ```gradle -implementation 'dev.cel:cel:0.11.0' +implementation 'dev.cel:cel:0.11.1' ``` Then run this example: diff --git a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel index 69755324d..7283e2977 100644 --- a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel +++ b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel @@ -49,7 +49,6 @@ java_library( tags = ["conformance_maven"], deps = MAVEN_JAR_DEPS + [ "//:java_truth", - "//parser:parser_factory", # TODO: Remove next OSS release "//testing:expr_value_utils", "@cel_spec//proto/cel/expr:expr_java_proto", "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", diff --git a/publish/cel_version.bzl b/publish/cel_version.bzl index 935b2f7b1..7938f15a1 100644 --- a/publish/cel_version.bzl +++ b/publish/cel_version.bzl @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. """Maven artifact version for CEL.""" -CEL_VERSION = "0.11.0" +CEL_VERSION = "0.11.1" From cc130d7a1392e2a82190d778106e585db96caa45 Mon Sep 17 00:00:00 2001 From: CEL Dev Team Date: Mon, 20 Oct 2025 15:42:58 -0700 Subject: [PATCH 27/27] Fix Typos: add space after "e.g." and remove commas after "e.g.". PiperOrigin-RevId: 821835750 --- checker/src/main/java/dev/cel/checker/ProtoTypeMask.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java b/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java index c9ff89989..ec1c31840 100644 --- a/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java +++ b/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java @@ -75,7 +75,7 @@ public ProtoTypeMask withFieldsAsVariableDeclarations() { * treated as variable identifiers bound to the protobuf field name and its associated field type. * *

    A {@code FieldMask} contains one or more {@code paths} which contain identifier characters - * that have been dot delimited, e.g.resource.name, request.auth.claims. Here are a few things to + * that have been dot delimited, e.g. resource.name, request.auth.claims. Here are a few things to * keep in mind: * *

      @@ -99,7 +99,7 @@ public static ProtoTypeMask of(String typeName, FieldMask fieldMask) { * Construct a new {@code ProtoTypeMask} which exposes all fields in the given {@code typeName} * for use within CEL expressions. * - *

      The {@code typeName} should be a fully-qualified path, e.g., {@code + *

      The {@code typeName} should be a fully-qualified path, e.g. {@code * "google.rpc.context.AttributeContext"}. * *

      All top-level fields in the given {@code typeName} should be treated as variable identifiers @@ -113,7 +113,7 @@ public static ProtoTypeMask ofAllFields(String fullyQualifiedTypeName) { * Construct a new {@code ProtoTypeMask} which hides all fields in the given {@code typeName} for * use within CEL expressions. * - *

      The {@code typeName} should be a fully-qualified path, e.g., {@code + *

      The {@code typeName} should be a fully-qualified path, e.g. {@code * "google.rpc.context.AttributeContext"}. */ public static ProtoTypeMask ofAllFieldsHidden(String fullyQualifiedTypeName) {