@@ -79,8 +79,9 @@ private static Cel setupCelEnv(CelBuilder celBuilder) {
7979 .populateMacroCalls (true )
8080 .enableHeterogeneousNumericComparisons (true )
8181 .build ())
82- .addCompilerLibraries (CelExtensions .bindings (), CelExtensions .strings ())
83- .addRuntimeLibraries (CelExtensions .strings ())
82+ .addCompilerLibraries (
83+ CelExtensions .bindings (), CelExtensions .strings (), CelExtensions .optional ())
84+ .addRuntimeLibraries (CelExtensions .strings (), CelExtensions .optional ())
8485 .addFunctionDeclarations (
8586 CelFunctionDecl .newFunctionDeclaration (
8687 "non_pure_custom_func" ,
@@ -756,12 +757,12 @@ public void cse_nestedMacro_noOp_assertAstIdCorrectness() throws Exception {
756757 .addAstOptimizers (SubexpressionOptimizer .getInstance ())
757758 .build ();
758759 CelAbstractSyntaxTree ast =
759- cel .compile ("[{}, {\" a\" : 1}, {\" b\" : 2}].filter(m, has(x .a))" ).getAst ();
760+ cel .compile ("[{}, {\" a\" : 1}, {\" b\" : 2}].filter(m, has(m .a))" ).getAst ();
760761
761762 CelAbstractSyntaxTree optimizedAst = celOptimizer .optimize (ast );
762763
763764 assertThat (CEL_UNPARSER .unparse (optimizedAst ))
764- .isEqualTo ("[{}, {\" a\" : 1}, {\" b\" : 2}].filter(m, has(x .a))" );
765+ .isEqualTo ("[{}, {\" a\" : 1}, {\" b\" : 2}].filter(m, has(m .a))" );
765766 assertThat (optimizedAst ).isSameInstanceAs (ast );
766767 }
767768
@@ -796,8 +797,8 @@ public void cse_nestedMacro_withOptimization_assertAstIdCorrectness() throws Exc
796797
797798 assertThat (CEL_UNPARSER .unparse (optimizedAst ))
798799 .isEqualTo (
799- "cel.@block([[{}, {\" a\" : 1}, {\" b\" : 2}].filter(@it:0:0, has(x.a)) ], @index0 == "
800- + " @index0 )" );
800+ "cel.@block([has(x.a), [{}, {\" a\" : 1}, {\" b\" : 2}].filter(@it:0:0, @index0) ], @index1 "
801+ + " == @index1 )" );
801802 }
802803
803804 @ Test
@@ -822,6 +823,175 @@ public void optimize_afterSelectOptimizer_throwsException(
822823 .contains ("SubexpressionOptimizer must run before SelectOptimizer." );
823824 }
824825
826+ private enum ValueNumberingTestCase {
827+ COMMUTATIVE_INT_ADD (
828+ "(msg.single_int64 + msg.single_int32) * (msg.single_int32 + msg.single_int64)" ,
829+ "cel.@block([msg.single_int64 + msg.single_int32], @index0 * @index0)" ),
830+ COMMUTATIVE_UINT_MULTIPLY (
831+ "(msg.single_uint64 * msg.single_uint32) + (msg.single_uint32 * msg.single_uint64)" ,
832+ "cel.@block([msg.single_uint64 * msg.single_uint32], @index0 + @index0)" ),
833+ COMMUTATIVE_DOUBLE_ADD (
834+ "(msg.single_double + 1.5) * (1.5 + msg.single_double)" ,
835+ "cel.@block([msg.single_double + 1.5], @index0 * @index0)" ),
836+ COMMUTATIVE_DURATION_TIMESTAMP_ADD (
837+ "(msg.single_duration + msg.single_timestamp) == (msg.single_timestamp +"
838+ + " msg.single_duration)" ,
839+ "cel.@block([msg.single_duration + msg.single_timestamp], @index0 == @index0)" ),
840+ NON_COMMUTATIVE_INT_SUBTRACT_PRESERVED (
841+ "(msg.single_int64 - msg.single_int32) == (msg.single_int32 - msg.single_int64)" ,
842+ "cel.@block([msg.single_int32, msg.single_int64], @index1 - @index0 == @index0 - @index1)" ),
843+ NON_COMMUTATIVE_STRING_CONCAT_PRESERVED (
844+ "(msg.single_string + \" a\" ) == (\" a\" + msg.single_string)" ,
845+ "cel.@block([msg.single_string], @index0 + \" a\" == \" a\" + @index0)" ),
846+ NON_COMMUTATIVE_DYN_ADD_PRESERVED (
847+ "(dyn(msg.single_string) + dyn(\" a\" )) == (dyn(\" a\" ) + dyn(msg.single_string))" ,
848+ "cel.@block([dyn(\" a\" ), dyn(msg.single_string)], @index1 + @index0 == @index0 +"
849+ + " @index1)" ),
850+ COMMUTATIVE_EQUALS (
851+ "(msg.single_int64 == 1) && (1 == msg.single_int64)" ,
852+ "cel.@block([msg.single_int64 == 1], @index0 && @index0)" ),
853+ COMMUTATIVE_NOT_EQUALS (
854+ "(msg.single_int64 != 1) || (1 != msg.single_int64)" ,
855+ "cel.@block([msg.single_int64 != 1], @index0 || @index0)" ),
856+ COMMUTATIVE_LOGICAL_AND (
857+ "((msg.single_int64 == 1) && (msg.single_int32 == 2)) || ((msg.single_int32 == 2) &&"
858+ + " (msg.single_int64 == 1))" ,
859+ "cel.@block([msg.single_int64 == 1 && msg.single_int32 == 2], @index0 || @index0)" ),
860+ COMMUTATIVE_LOGICAL_OR (
861+ "((msg.single_int64 == 1) || (msg.single_int32 == 2)) && ((msg.single_int32 == 2) ||"
862+ + " (msg.single_int64 == 1))" ,
863+ "cel.@block([msg.single_int64 == 1 || msg.single_int32 == 2], @index0 && @index0)" ),
864+ RELATIONAL_CONVERSE_LESS_GREATER (
865+ "(msg.single_int64 < msg.single_int32) && (msg.single_int32 > msg.single_int64)" ,
866+ "cel.@block([msg.single_int64 < msg.single_int32], @index0 && @index0)" ),
867+ RELATIONAL_CONVERSE_LESS_EQUALS_GREATER_EQUALS (
868+ "(msg.single_int64 <= msg.single_int32) && (msg.single_int32 >= msg.single_int64)" ,
869+ "cel.@block([msg.single_int64 <= msg.single_int32], @index0 && @index0)" ),
870+ NOT_EQUALS_WITHOUT_SHARED_EQUALS_PRESERVED (
871+ "[msg.single_int64 != 1, msg.single_int64 != 1, msg.single_int64 == 1]" ,
872+ "cel.@block([msg.single_int64 != 1], [@index0, @index0, msg.single_int64 == 1])" ),
873+ OPTIONAL_LIST_AND_MAP_DISTINCT_VALUE_NUMBERS (
874+ "([?optional.of(msg.single_int64)].size() == [optional.of(msg.single_int64)].size()) &&"
875+ + " ({?\" k\" : optional.of(msg.single_int64)}.size() == {\" k\" :"
876+ + " optional.of(msg.single_int64)}.size())" ,
877+ "cel.@block([optional.of(msg.single_int64)], [?@index0].size() == [@index0].size() &&"
878+ + " {?\" k\" : @index0}.size() == {\" k\" : @index0}.size())" ),
879+ ;
880+
881+ private final String source ;
882+ private final String expectedUnparsed ;
883+
884+ ValueNumberingTestCase (String source , String expectedUnparsed ) {
885+ this .source = source ;
886+ this .expectedUnparsed = expectedUnparsed ;
887+ }
888+ }
889+
890+ @ Test
891+ public void cse_valueNumbering (@ TestParameter ValueNumberingTestCase testCase ) throws Exception {
892+ CelAbstractSyntaxTree ast = cel .compile (testCase .source ).getAst ();
893+ CelOptimizer optimizer =
894+ newCseOptimizer (
895+ SubexpressionOptimizerOptions .newBuilder ().populateMacroCalls (true ).build ());
896+ ImmutableMap <String , Object > input =
897+ ImmutableMap .of (
898+ "msg" ,
899+ TestAllTypes .newBuilder ()
900+ .setSingleInt64 (1L )
901+ .setSingleInt32 (2 )
902+ .setSingleUint64 (3L )
903+ .setSingleUint32 (4 )
904+ .setSingleDouble (2.5 )
905+ .setSingleString ("hello" )
906+ .build ());
907+
908+ CelAbstractSyntaxTree optimizedAst = optimizer .optimize (ast );
909+
910+ assertThat (CEL_UNPARSER .unparse (optimizedAst )).isEqualTo (testCase .expectedUnparsed );
911+ assertThat (cel .createProgram (optimizedAst ).eval (input ))
912+ .isEqualTo (cel .createProgram (ast ).eval (input ));
913+ }
914+
915+ @ Test
916+ public void cse_customOperatorOverloads_preservesSemantics (
917+ @ TestParameter ({
918+ "(msg.single_string + msg.single_int64) == (msg.single_int64 + msg.single_string)" ,
919+ "(msg.single_string > msg.single_int64) == (msg.single_int64 < msg.single_string)"
920+ })
921+ String source )
922+ throws Exception {
923+ Cel cel =
924+ this .cel
925+ .toCelBuilder ()
926+ .addFunctionDeclarations (
927+ CelFunctionDecl .newFunctionDeclaration (
928+ "_+_" ,
929+ newGlobalOverload (
930+ "add_string_int" , SimpleType .INT , SimpleType .STRING , SimpleType .INT ),
931+ newGlobalOverload (
932+ "add_int_string" , SimpleType .INT , SimpleType .INT , SimpleType .STRING )),
933+ CelFunctionDecl .newFunctionDeclaration (
934+ "_>_" ,
935+ newGlobalOverload (
936+ "greater_string_int" , SimpleType .BOOL , SimpleType .STRING , SimpleType .INT )),
937+ CelFunctionDecl .newFunctionDeclaration (
938+ "_<_" ,
939+ newGlobalOverload (
940+ "less_int_string" , SimpleType .BOOL , SimpleType .INT , SimpleType .STRING )))
941+ .addFunctionBindings (
942+ CelFunctionBinding .from (
943+ "add_string_int" , String .class , Long .class , (s , i ) -> s .length () + i ),
944+ CelFunctionBinding .from (
945+ "add_int_string" , Long .class , String .class , (i , s ) -> i * 100 + s .length ()),
946+ CelFunctionBinding .from (
947+ "greater_string_int" , String .class , Long .class , (s , i ) -> s .length () > i ),
948+ CelFunctionBinding .from (
949+ "less_int_string" , Long .class , String .class , (i , s ) -> i >= 0 ))
950+ .build ();
951+ CelOptimizer optimizer =
952+ CelOptimizerFactory .standardCelOptimizerBuilder (cel )
953+ .addAstOptimizers (SubexpressionOptimizer .getInstance ())
954+ .build ();
955+ CelAbstractSyntaxTree ast = cel .compile (source ).getAst ();
956+ ImmutableMap <String , Object > input =
957+ ImmutableMap .of (
958+ "msg" , TestAllTypes .newBuilder ().setSingleString ("a" ).setSingleInt64 (1L ).build ());
959+
960+ CelAbstractSyntaxTree optimizedAst = optimizer .optimize (ast );
961+
962+ assertThat (cel .createProgram (optimizedAst ).eval (input ))
963+ .isEqualTo (cel .createProgram (ast ).eval (input ));
964+ }
965+
966+ @ Test
967+ public void cse_oneSidedCustomOverloadOnDyn_notMerged (
968+ @ TestParameter ({"(x < 5) == (5 > x)" , "(x + 1) == (1 + x)" }) String source ) throws Exception {
969+ // With x: dyn, only the left operand also resolves to the custom overload, so the right one is
970+ // standard-only and swappable. The two must not share a value number.
971+ Cel cel =
972+ this .cel
973+ .toCelBuilder ()
974+ .addFunctionDeclarations (
975+ CelFunctionDecl .newFunctionDeclaration (
976+ "_<_" ,
977+ newGlobalOverload (
978+ "less_string_int" , SimpleType .BOOL , SimpleType .STRING , SimpleType .INT )),
979+ CelFunctionDecl .newFunctionDeclaration (
980+ "_+_" ,
981+ newGlobalOverload (
982+ "add_string_int" , SimpleType .INT , SimpleType .STRING , SimpleType .INT )))
983+ .build ();
984+ CelOptimizer optimizer =
985+ CelOptimizerFactory .standardCelOptimizerBuilder (cel )
986+ .addAstOptimizers (SubexpressionOptimizer .getInstance ())
987+ .build ();
988+ CelAbstractSyntaxTree ast = cel .compile (source ).getAst ();
989+
990+ CelAbstractSyntaxTree optimizedAst = optimizer .optimize (ast );
991+
992+ assertThat (CEL_UNPARSER .unparse (optimizedAst )).isEqualTo (CEL_UNPARSER .unparse (ast ));
993+ }
994+
825995 /**
826996 * Converts AST containing cel.block related test functions to internal functions (e.g: cel.block
827997 * -> cel.@block)
0 commit comments