2929import dev .cel .common .CelAbstractSyntaxTree ;
3030import dev .cel .common .CelFunctionDecl ;
3131import dev .cel .common .CelOptions ;
32+ import dev .cel .common .CelOverloadDecl ;
33+ import dev .cel .common .CelValidationException ;
34+ import dev .cel .common .CelVarDecl ;
3235import dev .cel .common .ast .CelConstant ;
3336import dev .cel .common .ast .CelExpr ;
37+ import dev .cel .common .ast .CelExpr .ExprKind .Kind ;
38+ import dev .cel .common .navigation .CelNavigableAst ;
39+ import dev .cel .common .navigation .CelNavigableExpr ;
40+ import dev .cel .common .types .ListType ;
3441import dev .cel .common .types .OptionalType ;
3542import dev .cel .common .types .SimpleType ;
3643import dev .cel .common .types .StructTypeReference ;
3946import dev .cel .optimizer .CelOptimizationException ;
4047import dev .cel .optimizer .CelOptimizer ;
4148import dev .cel .optimizer .CelOptimizerFactory ;
49+ import dev .cel .optimizer .MutableAst ;
4250import dev .cel .optimizer .optimizers .SubexpressionOptimizer .SubexpressionOptimizerOptions ;
4351import dev .cel .parser .CelStandardMacro ;
4452import dev .cel .parser .CelUnparser ;
4553import dev .cel .parser .CelUnparserFactory ;
4654import dev .cel .parser .Operator ;
55+ import dev .cel .runtime .CelRuntime ;
56+ import dev .cel .runtime .CelRuntime .CelFunctionBinding ;
57+ import dev .cel .runtime .CelRuntimeFactory ;
4758import dev .cel .testing .testdata .proto3 .TestAllTypesProto .NestedTestAllTypes ;
4859import dev .cel .testing .testdata .proto3 .TestAllTypesProto .TestAllTypes ;
4960import java .util .Optional ;
61+ import java .util .concurrent .atomic .AtomicInteger ;
5062import org .junit .Test ;
5163import org .junit .runner .RunWith ;
5264
@@ -55,6 +67,37 @@ public class SubexpressionOptimizerTest {
5567
5668 private static final Cel CEL = newCelBuilder ().build ();
5769
70+ private static final Cel CEL_FOR_EVALUATING_BLOCK =
71+ CelFactory .standardCelBuilder ()
72+ .setStandardMacros (CelStandardMacro .STANDARD_MACROS )
73+ .addFunctionDeclarations (
74+ // These are test only declarations, as the actual function is made internal using @
75+ // symbol.
76+ // If the main function declaration needs updating, be sure to update the test
77+ // declaration as well.
78+ CelFunctionDecl .newFunctionDeclaration (
79+ "cel.block" ,
80+ CelOverloadDecl .newGlobalOverload (
81+ "block_test_only_overload" ,
82+ SimpleType .DYN ,
83+ ListType .create (SimpleType .DYN ),
84+ SimpleType .DYN )),
85+ SubexpressionOptimizer .newCelBlockFunctionDecl (SimpleType .DYN ),
86+ CelFunctionDecl .newFunctionDeclaration (
87+ "get_true" ,
88+ CelOverloadDecl .newGlobalOverload ("get_true_overload" , SimpleType .BOOL )))
89+ // Similarly, this is a test only decl (index0 -> @index0)
90+ .addVarDeclarations (
91+ CelVarDecl .newVarDeclaration ("index0" , SimpleType .DYN ),
92+ CelVarDecl .newVarDeclaration ("index1" , SimpleType .DYN ),
93+ CelVarDecl .newVarDeclaration ("index2" , SimpleType .DYN ),
94+ CelVarDecl .newVarDeclaration ("@index0" , SimpleType .DYN ),
95+ CelVarDecl .newVarDeclaration ("@index1" , SimpleType .DYN ),
96+ CelVarDecl .newVarDeclaration ("@index2" , SimpleType .DYN ))
97+ .addMessageTypes (TestAllTypes .getDescriptor ())
98+ .addVar ("msg" , StructTypeReference .create (TestAllTypes .getDescriptor ().getFullName ()))
99+ .build ();
100+
58101 private static final CelOptimizer CEL_OPTIMIZER =
59102 CelOptimizerFactory .standardCelOptimizerBuilder (CEL )
60103 .addAstOptimizers (
@@ -659,4 +702,213 @@ public void iterationLimitReached_throws() throws Exception {
659702 .optimize (ast ));
660703 assertThat (e ).hasMessageThat ().isEqualTo ("Optimization failure: Max iteration count reached." );
661704 }
705+
706+ private enum BlockTestCase {
707+ BOOL_LITERAL ("cel.block([true, false], index0 || index1)" ),
708+ STRING_CONCAT ("cel.block(['a' + 'b', index0 + 'c'], index1 + 'd') == 'abcd'" ),
709+
710+ BLOCK_WITH_EXISTS_TRUE ("cel.block([[1, 2, 3], [3, 4, 5].exists(e, e in index0)], index1)" ),
711+ BLOCK_WITH_EXISTS_FALSE ("cel.block([[1, 2, 3], ![4, 5].exists(e, e in index0)], index1)" ),
712+ ;
713+
714+ private final String source ;
715+
716+ BlockTestCase (String source ) {
717+ this .source = source ;
718+ }
719+ }
720+
721+ @ Test
722+ public void block_success (@ TestParameter BlockTestCase testCase ) throws Exception {
723+ CelAbstractSyntaxTree ast = compileUsingInternalFunctions (testCase .source );
724+
725+ Object evaluatedResult = CEL_FOR_EVALUATING_BLOCK .createProgram (ast ).eval ();
726+
727+ assertThat (evaluatedResult ).isNotNull ();
728+ }
729+
730+ @ Test
731+ @ SuppressWarnings ("Immutable" ) // Test only
732+ public void lazyEval_blockIndexNeverReferenced () throws Exception {
733+ AtomicInteger invocation = new AtomicInteger ();
734+ CelRuntime celRuntime =
735+ CelRuntimeFactory .standardCelRuntimeBuilder ()
736+ .addMessageTypes (TestAllTypes .getDescriptor ())
737+ .addFunctionBindings (
738+ CelFunctionBinding .from (
739+ "get_true_overload" ,
740+ ImmutableList .of (),
741+ arg -> {
742+ invocation .getAndIncrement ();
743+ return true ;
744+ }))
745+ .build ();
746+ CelAbstractSyntaxTree ast =
747+ compileUsingInternalFunctions (
748+ "cel.block([get_true()], has(msg.single_int64) ? index0 : false)" );
749+
750+ boolean result =
751+ (boolean )
752+ celRuntime
753+ .createProgram (ast )
754+ .eval (ImmutableMap .of ("msg" , TestAllTypes .getDefaultInstance ()));
755+
756+ assertThat (result ).isFalse ();
757+ assertThat (invocation .get ()).isEqualTo (0 );
758+ }
759+
760+ @ Test
761+ @ SuppressWarnings ("Immutable" ) // Test only
762+ public void lazyEval_blockIndexEvaluatedOnlyOnce () throws Exception {
763+ AtomicInteger invocation = new AtomicInteger ();
764+ CelRuntime celRuntime =
765+ CelRuntimeFactory .standardCelRuntimeBuilder ()
766+ .addMessageTypes (TestAllTypes .getDescriptor ())
767+ .addFunctionBindings (
768+ CelFunctionBinding .from (
769+ "get_true_overload" ,
770+ ImmutableList .of (),
771+ arg -> {
772+ invocation .getAndIncrement ();
773+ return true ;
774+ }))
775+ .build ();
776+ CelAbstractSyntaxTree ast =
777+ compileUsingInternalFunctions ("cel.block([get_true()], index0 && index0 && index0)" );
778+
779+ boolean result = (boolean ) celRuntime .createProgram (ast ).eval ();
780+
781+ assertThat (result ).isTrue ();
782+ assertThat (invocation .get ()).isEqualTo (1 );
783+ }
784+
785+ @ Test
786+ @ SuppressWarnings ("Immutable" ) // Test only
787+ public void lazyEval_multipleBlockIndices_inResultExpr () throws Exception {
788+ AtomicInteger invocation = new AtomicInteger ();
789+ CelRuntime celRuntime =
790+ CelRuntimeFactory .standardCelRuntimeBuilder ()
791+ .addMessageTypes (TestAllTypes .getDescriptor ())
792+ .addFunctionBindings (
793+ CelFunctionBinding .from (
794+ "get_true_overload" ,
795+ ImmutableList .of (),
796+ arg -> {
797+ invocation .getAndIncrement ();
798+ return true ;
799+ }))
800+ .build ();
801+ CelAbstractSyntaxTree ast =
802+ compileUsingInternalFunctions (
803+ "cel.block([get_true(), get_true(), get_true()], index0 && index0 && index1 && index1"
804+ + " && index2 && index2)" );
805+
806+ boolean result = (boolean ) celRuntime .createProgram (ast ).eval ();
807+
808+ assertThat (result ).isTrue ();
809+ assertThat (invocation .get ()).isEqualTo (3 );
810+ }
811+
812+ @ Test
813+ @ SuppressWarnings ("Immutable" ) // Test only
814+ public void lazyEval_multipleBlockIndices_cascaded () throws Exception {
815+ AtomicInteger invocation = new AtomicInteger ();
816+ CelRuntime celRuntime =
817+ CelRuntimeFactory .standardCelRuntimeBuilder ()
818+ .addMessageTypes (TestAllTypes .getDescriptor ())
819+ .addFunctionBindings (
820+ CelFunctionBinding .from (
821+ "get_true_overload" ,
822+ ImmutableList .of (),
823+ arg -> {
824+ invocation .getAndIncrement ();
825+ return true ;
826+ }))
827+ .build ();
828+ CelAbstractSyntaxTree ast =
829+ compileUsingInternalFunctions ("cel.block([get_true(), index0, index1], index2)" );
830+
831+ boolean result = (boolean ) celRuntime .createProgram (ast ).eval ();
832+
833+ assertThat (result ).isTrue ();
834+ assertThat (invocation .get ()).isEqualTo (1 );
835+ }
836+
837+ @ Test
838+ @ TestParameters ("{source: 'cel.block([])'}" )
839+ @ TestParameters ("{source: 'cel.block([1])'}" )
840+ @ TestParameters ("{source: 'cel.block(1, 2)'}" )
841+ @ TestParameters ("{source: 'cel.block(1, [1])'}" )
842+ public void block_invalidArguments_throws (String source ) {
843+ CelValidationException e =
844+ assertThrows (CelValidationException .class , () -> compileUsingInternalFunctions (source ));
845+
846+ assertThat (e ).hasMessageThat ().contains ("found no matching overload for 'cel.block'" );
847+ }
848+
849+ @ Test
850+ public void blockIndex_invalidArgument_throws () {
851+ CelValidationException e =
852+ assertThrows (
853+ CelValidationException .class ,
854+ () -> compileUsingInternalFunctions ("cel.block([1], index)" ));
855+
856+ assertThat (e ).hasMessageThat ().contains ("undeclared reference" );
857+ }
858+
859+ /**
860+ * Converts AST containing cel.block related test functions to internal functions (e.g: cel.block
861+ * -> cel.@block)
862+ */
863+ private static CelAbstractSyntaxTree compileUsingInternalFunctions (String expression )
864+ throws CelValidationException {
865+ MutableAst mutableAst = MutableAst .newInstance (1000 );
866+ CelAbstractSyntaxTree astToModify = CEL_FOR_EVALUATING_BLOCK .compile (expression ).getAst ();
867+ while (true ) {
868+ CelExpr celExpr =
869+ CelNavigableAst .fromAst (astToModify )
870+ .getRoot ()
871+ .allNodes ()
872+ .filter (node -> node .getKind ().equals (Kind .CALL ))
873+ .map (CelNavigableExpr ::expr )
874+ .filter (expr -> expr .call ().function ().equals ("cel.block" ))
875+ .findAny ()
876+ .orElse (null );
877+ if (celExpr == null ) {
878+ break ;
879+ }
880+ astToModify =
881+ mutableAst .replaceSubtree (
882+ astToModify ,
883+ celExpr .toBuilder ()
884+ .setCall (celExpr .call ().toBuilder ().setFunction ("cel.@block" ).build ())
885+ .build (),
886+ celExpr .id ());
887+ }
888+
889+ while (true ) {
890+ CelExpr celExpr =
891+ CelNavigableAst .fromAst (astToModify )
892+ .getRoot ()
893+ .allNodes ()
894+ .filter (node -> node .getKind ().equals (Kind .IDENT ))
895+ .map (CelNavigableExpr ::expr )
896+ .filter (expr -> expr .ident ().name ().startsWith ("index" ))
897+ .findAny ()
898+ .orElse (null );
899+ if (celExpr == null ) {
900+ break ;
901+ }
902+ String internalIdentName = "@" + celExpr .ident ().name ();
903+ astToModify =
904+ mutableAst .replaceSubtree (
905+ astToModify ,
906+ celExpr .toBuilder ()
907+ .setIdent (celExpr .ident ().toBuilder ().setName (internalIdentName ).build ())
908+ .build (),
909+ celExpr .id ());
910+ }
911+
912+ return CEL_FOR_EVALUATING_BLOCK .check (astToModify ).getAst ();
913+ }
662914}
0 commit comments