@@ -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" ,
@@ -301,7 +302,9 @@ public void cse_applyConstFoldingAfter() throws Exception {
301302 CelAbstractSyntaxTree optimizedAst = optimizer .optimize (ast );
302303
303304 assertThat (CEL_UNPARSER .unparse (optimizedAst ))
304- .isEqualTo ("cel.@block([1, 2], @index0 + @index0 + @index1 + @index1 + x)" );
305+ .isEqualTo (
306+ "cel.@block([3, size([@index0]), size([1, @index0])], @index1 + @index1 + @index2 +"
307+ + " @index2 + x)" );
305308 }
306309
307310 @ Test
@@ -756,12 +759,12 @@ public void cse_nestedMacro_noOp_assertAstIdCorrectness() throws Exception {
756759 .addAstOptimizers (SubexpressionOptimizer .getInstance ())
757760 .build ();
758761 CelAbstractSyntaxTree ast =
759- cel .compile ("[{}, {\" a\" : 1}, {\" b\" : 2}].filter(m, has(x .a))" ).getAst ();
762+ cel .compile ("[{}, {\" a\" : 1}, {\" b\" : 2}].filter(m, has(m .a))" ).getAst ();
760763
761764 CelAbstractSyntaxTree optimizedAst = celOptimizer .optimize (ast );
762765
763766 assertThat (CEL_UNPARSER .unparse (optimizedAst ))
764- .isEqualTo ("[{}, {\" a\" : 1}, {\" b\" : 2}].filter(m, has(x .a))" );
767+ .isEqualTo ("[{}, {\" a\" : 1}, {\" b\" : 2}].filter(m, has(m .a))" );
765768 assertThat (optimizedAst ).isSameInstanceAs (ast );
766769 }
767770
@@ -796,8 +799,8 @@ public void cse_nestedMacro_withOptimization_assertAstIdCorrectness() throws Exc
796799
797800 assertThat (CEL_UNPARSER .unparse (optimizedAst ))
798801 .isEqualTo (
799- "cel.@block([[{}, {\" a\" : 1}, {\" b\" : 2}].filter(@it:0:0, has(x.a)) ], @index0 == "
800- + " @index0 )" );
802+ "cel.@block([has(x.a), [{}, {\" a\" : 1}, {\" b\" : 2}].filter(@it:0:0, @index0) ], @index1 "
803+ + " == @index1 )" );
801804 }
802805
803806 @ Test
@@ -822,6 +825,175 @@ public void optimize_afterSelectOptimizer_throwsException(
822825 .contains ("SubexpressionOptimizer must run before SelectOptimizer." );
823826 }
824827
828+ private enum ValueNumberingTestCase {
829+ COMMUTATIVE_INT_ADD (
830+ "(msg.single_int64 + msg.single_int32) * (msg.single_int32 + msg.single_int64)" ,
831+ "cel.@block([msg.single_int64 + msg.single_int32], @index0 * @index0)" ),
832+ COMMUTATIVE_UINT_MULTIPLY (
833+ "(msg.single_uint64 * msg.single_uint32) + (msg.single_uint32 * msg.single_uint64)" ,
834+ "cel.@block([msg.single_uint64 * msg.single_uint32], @index0 + @index0)" ),
835+ COMMUTATIVE_DOUBLE_ADD (
836+ "(msg.single_double + 1.5) * (1.5 + msg.single_double)" ,
837+ "cel.@block([msg.single_double + 1.5], @index0 * @index0)" ),
838+ COMMUTATIVE_DURATION_TIMESTAMP_ADD (
839+ "(msg.single_duration + msg.single_timestamp) == (msg.single_timestamp +"
840+ + " msg.single_duration)" ,
841+ "cel.@block([msg.single_duration + msg.single_timestamp], @index0 == @index0)" ),
842+ NON_COMMUTATIVE_INT_SUBTRACT_PRESERVED (
843+ "(msg.single_int64 - msg.single_int32) == (msg.single_int32 - msg.single_int64)" ,
844+ "cel.@block([msg.single_int32, msg.single_int64], @index1 - @index0 == @index0 - @index1)" ),
845+ NON_COMMUTATIVE_STRING_CONCAT_PRESERVED (
846+ "(msg.single_string + \" a\" ) == (\" a\" + msg.single_string)" ,
847+ "cel.@block([msg.single_string], @index0 + \" a\" == \" a\" + @index0)" ),
848+ NON_COMMUTATIVE_DYN_ADD_PRESERVED (
849+ "(dyn(msg.single_string) + dyn(\" a\" )) == (dyn(\" a\" ) + dyn(msg.single_string))" ,
850+ "cel.@block([dyn(\" a\" ), dyn(msg.single_string)], @index1 + @index0 == @index0 +"
851+ + " @index1)" ),
852+ COMMUTATIVE_EQUALS (
853+ "(msg.single_int64 == 1) && (1 == msg.single_int64)" ,
854+ "cel.@block([msg.single_int64 == 1], @index0 && @index0)" ),
855+ COMMUTATIVE_NOT_EQUALS (
856+ "(msg.single_int64 != 1) || (1 != msg.single_int64)" ,
857+ "cel.@block([msg.single_int64 != 1], @index0 || @index0)" ),
858+ COMMUTATIVE_LOGICAL_AND (
859+ "((msg.single_int64 == 1) && (msg.single_int32 == 2)) || ((msg.single_int32 == 2) &&"
860+ + " (msg.single_int64 == 1))" ,
861+ "cel.@block([msg.single_int64 == 1 && msg.single_int32 == 2], @index0 || @index0)" ),
862+ COMMUTATIVE_LOGICAL_OR (
863+ "((msg.single_int64 == 1) || (msg.single_int32 == 2)) && ((msg.single_int32 == 2) ||"
864+ + " (msg.single_int64 == 1))" ,
865+ "cel.@block([msg.single_int64 == 1 || msg.single_int32 == 2], @index0 && @index0)" ),
866+ RELATIONAL_CONVERSE_LESS_GREATER (
867+ "(msg.single_int64 < msg.single_int32) && (msg.single_int32 > msg.single_int64)" ,
868+ "cel.@block([msg.single_int64 < msg.single_int32], @index0 && @index0)" ),
869+ RELATIONAL_CONVERSE_LESS_EQUALS_GREATER_EQUALS (
870+ "(msg.single_int64 <= msg.single_int32) && (msg.single_int32 >= msg.single_int64)" ,
871+ "cel.@block([msg.single_int64 <= msg.single_int32], @index0 && @index0)" ),
872+ NOT_EQUALS_WITHOUT_SHARED_EQUALS_PRESERVED (
873+ "[msg.single_int64 != 1, msg.single_int64 != 1, msg.single_int64 == 1]" ,
874+ "cel.@block([msg.single_int64, @index0 != 1], [@index1, @index1, @index0 == 1])" ),
875+ OPTIONAL_LIST_AND_MAP_DISTINCT_VALUE_NUMBERS (
876+ "([?optional.of(msg.single_int64)].size() == [optional.of(msg.single_int64)].size()) &&"
877+ + " ({?\" k\" : optional.of(msg.single_int64)}.size() == {\" k\" :"
878+ + " optional.of(msg.single_int64)}.size())" ,
879+ "cel.@block([optional.of(msg.single_int64)], [?@index0].size() == [@index0].size() &&"
880+ + " {?\" k\" : @index0}.size() == {\" k\" : @index0}.size())" ),
881+ ;
882+
883+ private final String source ;
884+ private final String expectedUnparsed ;
885+
886+ ValueNumberingTestCase (String source , String expectedUnparsed ) {
887+ this .source = source ;
888+ this .expectedUnparsed = expectedUnparsed ;
889+ }
890+ }
891+
892+ @ Test
893+ public void cse_valueNumbering (@ TestParameter ValueNumberingTestCase testCase ) throws Exception {
894+ CelAbstractSyntaxTree ast = cel .compile (testCase .source ).getAst ();
895+ CelOptimizer optimizer =
896+ newCseOptimizer (
897+ SubexpressionOptimizerOptions .newBuilder ().populateMacroCalls (true ).build ());
898+ ImmutableMap <String , Object > input =
899+ ImmutableMap .of (
900+ "msg" ,
901+ TestAllTypes .newBuilder ()
902+ .setSingleInt64 (1L )
903+ .setSingleInt32 (2 )
904+ .setSingleUint64 (3L )
905+ .setSingleUint32 (4 )
906+ .setSingleDouble (2.5 )
907+ .setSingleString ("hello" )
908+ .build ());
909+
910+ CelAbstractSyntaxTree optimizedAst = optimizer .optimize (ast );
911+
912+ assertThat (CEL_UNPARSER .unparse (optimizedAst )).isEqualTo (testCase .expectedUnparsed );
913+ assertThat (cel .createProgram (optimizedAst ).eval (input ))
914+ .isEqualTo (cel .createProgram (ast ).eval (input ));
915+ }
916+
917+ @ Test
918+ public void cse_customOperatorOverloads_preservesSemantics (
919+ @ TestParameter ({
920+ "(msg.single_string + msg.single_int64) == (msg.single_int64 + msg.single_string)" ,
921+ "(msg.single_string > msg.single_int64) == (msg.single_int64 < msg.single_string)"
922+ })
923+ String source )
924+ throws Exception {
925+ Cel cel =
926+ this .cel
927+ .toCelBuilder ()
928+ .addFunctionDeclarations (
929+ CelFunctionDecl .newFunctionDeclaration (
930+ "_+_" ,
931+ newGlobalOverload (
932+ "add_string_int" , SimpleType .INT , SimpleType .STRING , SimpleType .INT ),
933+ newGlobalOverload (
934+ "add_int_string" , SimpleType .INT , SimpleType .INT , SimpleType .STRING )),
935+ CelFunctionDecl .newFunctionDeclaration (
936+ "_>_" ,
937+ newGlobalOverload (
938+ "greater_string_int" , SimpleType .BOOL , SimpleType .STRING , SimpleType .INT )),
939+ CelFunctionDecl .newFunctionDeclaration (
940+ "_<_" ,
941+ newGlobalOverload (
942+ "less_int_string" , SimpleType .BOOL , SimpleType .INT , SimpleType .STRING )))
943+ .addFunctionBindings (
944+ CelFunctionBinding .from (
945+ "add_string_int" , String .class , Long .class , (s , i ) -> s .length () + i ),
946+ CelFunctionBinding .from (
947+ "add_int_string" , Long .class , String .class , (i , s ) -> i * 100 + s .length ()),
948+ CelFunctionBinding .from (
949+ "greater_string_int" , String .class , Long .class , (s , i ) -> s .length () > i ),
950+ CelFunctionBinding .from (
951+ "less_int_string" , Long .class , String .class , (i , s ) -> i >= 0 ))
952+ .build ();
953+ CelOptimizer optimizer =
954+ CelOptimizerFactory .standardCelOptimizerBuilder (cel )
955+ .addAstOptimizers (SubexpressionOptimizer .getInstance ())
956+ .build ();
957+ CelAbstractSyntaxTree ast = cel .compile (source ).getAst ();
958+ ImmutableMap <String , Object > input =
959+ ImmutableMap .of (
960+ "msg" , TestAllTypes .newBuilder ().setSingleString ("a" ).setSingleInt64 (1L ).build ());
961+
962+ CelAbstractSyntaxTree optimizedAst = optimizer .optimize (ast );
963+
964+ assertThat (cel .createProgram (optimizedAst ).eval (input ))
965+ .isEqualTo (cel .createProgram (ast ).eval (input ));
966+ }
967+
968+ @ Test
969+ public void cse_oneSidedCustomOverloadOnDyn_notMerged (
970+ @ TestParameter ({"(x < 5) == (5 > x)" , "(x + 1) == (1 + x)" }) String source ) throws Exception {
971+ // With x: dyn, only the left operand also resolves to the custom overload, so the right one is
972+ // standard-only and swappable. The two must not share a value number.
973+ Cel cel =
974+ this .cel
975+ .toCelBuilder ()
976+ .addFunctionDeclarations (
977+ CelFunctionDecl .newFunctionDeclaration (
978+ "_<_" ,
979+ newGlobalOverload (
980+ "less_string_int" , SimpleType .BOOL , SimpleType .STRING , SimpleType .INT )),
981+ CelFunctionDecl .newFunctionDeclaration (
982+ "_+_" ,
983+ newGlobalOverload (
984+ "add_string_int" , SimpleType .INT , SimpleType .STRING , SimpleType .INT )))
985+ .build ();
986+ CelOptimizer optimizer =
987+ CelOptimizerFactory .standardCelOptimizerBuilder (cel )
988+ .addAstOptimizers (SubexpressionOptimizer .getInstance ())
989+ .build ();
990+ CelAbstractSyntaxTree ast = cel .compile (source ).getAst ();
991+
992+ CelAbstractSyntaxTree optimizedAst = optimizer .optimize (ast );
993+
994+ assertThat (CEL_UNPARSER .unparse (optimizedAst )).isEqualTo (CEL_UNPARSER .unparse (ast ));
995+ }
996+
825997 /**
826998 * Converts AST containing cel.block related test functions to internal functions (e.g: cel.block
827999 * -> cel.@block)
0 commit comments