2727import dev .cel .common .CelContainer ;
2828import dev .cel .common .CelMutableAst ;
2929import dev .cel .common .CelOptions ;
30+ import dev .cel .common .ast .CelExpr .ExprKind .Kind ;
3031import dev .cel .common .ast .CelMutableExpr ;
3132import dev .cel .common .ast .CelMutableExpr .CelMutableCall ;
3233import dev .cel .common .types .ListType ;
@@ -263,16 +264,25 @@ private enum CanonicalizationTestCase {
263264 TWO_VAR_EXISTS_COMMUTATIVE_AND (
264265 "string_int_map.exists(k, v, v == 1 && k == 'foo')" ,
265266 "string_int_map.exists(k, v, k == \" foo\" && v == 1)" ),
267+ TWO_VAR_EXISTS_COMMUTATIVE_AND_REVERSE_ALPHABETICAL_VARS (
268+ "string_int_map.exists(z_key, a_val, a_val == 1 && z_key == 'foo')" ,
269+ "string_int_map.exists(z_key, a_val, z_key == \" foo\" && a_val == 1)" ),
266270 TWO_VAR_ALL_COMMUTATIVE_OR (
267271 "string_int_map.all(k, v, v == 1 || k == 'foo')" ,
268272 "string_int_map.all(k, v, k == \" foo\" || v == 1)" ),
273+ TWO_VAR_ALL_COMMUTATIVE_OR_REVERSE_ALPHABETICAL_VARS (
274+ "string_int_map.all(z_key, a_val, a_val == 1 || z_key == 'foo')" ,
275+ "string_int_map.all(z_key, a_val, z_key == \" foo\" || a_val == 1)" ),
269276 TWO_VAR_EXISTS_SYMMETRIC_EQUALITY (
270277 "string_int_map.exists(k, v, v == 1)" , "string_int_map.exists(k, v, v == 1)" ),
271278 TWO_VAR_ALL_SYMMETRIC_INEQUALITY (
272279 "string_int_map.all(k, v, v != 0)" , "string_int_map.all(k, v, v != 0)" ),
273280 TWO_VAR_EXISTS_INT_STRING_MAP (
274281 "int_string_map.exists(k, v, v == 'bar' && k == 1)" ,
275282 "int_string_map.exists(k, v, k == 1 && v == \" bar\" )" ),
283+ TWO_VAR_EXISTS_INT_STRING_MAP_REVERSE_ALPHABETICAL_VARS (
284+ "int_string_map.exists(z_key, a_val, a_val == 'bar' && z_key == 1)" ,
285+ "int_string_map.exists(z_key, a_val, z_key == 1 && a_val == \" bar\" )" ),
276286 TWO_VAR_ALL_INT_STRING_MAP (
277287 "!int_string_map.all(k, v, k == 1 || v == 'bar')" , "k != 1 && v != \" bar\" " ),
278288 TWO_VAR_EXISTS_LIST_INDEX_VALUE (
@@ -285,10 +295,10 @@ private enum CanonicalizationTestCase {
285295 "int_list.all(i, v, v == 100 || i == 0)" , "int_list.all(i, v, i == 0 || v == 100)" ),
286296 TWO_VAR_NESTED_COMPREHENSIONS (
287297 "string_int_map.exists(k, v, k == 'foo' && int_list.all(i, e, e == v && i == 0))" ,
288- "string_int_map.exists(k, v, k == \" foo\" && int_list.all(i, e, e == v && i == 0))" ),
298+ "string_int_map.exists(k, v, k == \" foo\" && int_list.all(i, e, v == e && i == 0))" ),
289299 DE_MORGAN_2VAR_NESTED_COMPREHENSIONS (
290300 "string_int_map.exists(k, v, k == 'foo' && !int_list.exists(i, e, e == v))" ,
291- "string_int_map.exists(k, v, k == \" foo\" && e != v )" ),
301+ "string_int_map.exists(k, v, k == \" foo\" && v != e )" ),
292302 TWO_VAR_COMPREHENSION_WITH_OPTIONALS (
293303 "!string_int_map.exists(k, v, optional.of(v).hasValue() && k == 'foo')" ,
294304 "!optional.of(v).hasValue() || k != \" foo\" " ),
@@ -304,18 +314,18 @@ private enum CanonicalizationTestCase {
304314 // Extension Coverage - cel.bind Macro
305315 CEL_BIND_COMMUTATIVE_AND (
306316 "cel.bind(x, int_var + 10, 1 == x && 2 == int_var2)" ,
307- "cel.bind(x, int_var + 10, int_var2 == 2 && x == 1 )" ),
317+ "cel.bind(x, int_var + 10, x == 1 && int_var2 == 2 )" ),
308318 CEL_BIND_COMMUTATIVE_OR (
309319 "cel.bind(x, int_var + 10, 1 == x || 2 == int_var2)" ,
310- "cel.bind(x, int_var + 10, int_var2 == 2 || x == 1 )" ),
320+ "cel.bind(x, int_var + 10, x == 1 || int_var2 == 2 )" ),
311321 CEL_BIND_SYMMETRIC_EQUALITY (
312322 "cel.bind(x, int_var + 10, 20 == x)" , "cel.bind(x, int_var + 10, x == 20)" ),
313323 CEL_BIND_NESTED (
314324 "cel.bind(x, int_var + 10, cel.bind(y, int_var2 + 20, 2 == y && 1 == x))" ,
315325 "cel.bind(x, int_var + 10, cel.bind(y, int_var2 + 20, x == 1 && y == 2))" ),
316326 CEL_BIND_DE_MORGAN (
317327 "cel.bind(x, int_var == 1, !(2 == int_var2 && x == true))" ,
318- "cel.bind(x, int_var == 1, int_var2 != 2 || x != true )" ),
328+ "cel.bind(x, int_var == 1, x != true || int_var2 != 2 )" ),
319329
320330 // Nested Lists, Maps, and Structs
321331 NESTED_LIST_EQUALITY_SYMMETRY (
@@ -464,7 +474,49 @@ private enum CanonicalizationTestCase {
464474 IDENT_INEQUALITY_SYMMETRY (
465475 "dyn_b != dyn_a || dyn_d != dyn_c" , "dyn_a != dyn_b || dyn_c != dyn_d" ),
466476 IDENT_SAME_NAME_DIFFERENT_OPERATORS (
467- "dyn_a != dyn_b && dyn_a == dyn_b" , "dyn_a != dyn_b && dyn_a == dyn_b" );
477+ "dyn_a != dyn_b && dyn_a == dyn_b" , "dyn_a != dyn_b && dyn_a == dyn_b" ),
478+
479+ // Comprehension Sorting & Structure Comparison (iterRange, accuInit, loopStep, iterVar2)
480+ COMPREHENSIONS_DIFFERENT_ITER_RANGE_EQUALITY (
481+ "[2, 3].all(x, x > 0) == [1, 2].all(x, x > 0)" ,
482+ "[1, 2].all(x, x > 0) == [2, 3].all(x, x > 0)" ),
483+ COMPREHENSIONS_DIFFERENT_ITER_RANGE_AND (
484+ "[2, 3].all(x, x > 0) && [1, 2].all(x, x > 0)" ,
485+ "[1, 2].all(x, x > 0) && [2, 3].all(x, x > 0)" ),
486+ COMPREHENSIONS_DIFFERENT_PREDICATES_AND (
487+ "[1, 2].all(x, x > 10) && [1, 2].all(x, x > 0)" ,
488+ "[1, 2].all(x, x > 0) && [1, 2].all(x, x > 10)" ),
489+ COMPREHENSIONS_EXISTS_VS_ALL_AND (
490+ "[1, 2].all(x, x == 1) && [1, 2].exists(x, x == 1)" ,
491+ "[1, 2].exists(x, x == 1) && [1, 2].all(x, x == 1)" ),
492+ COMPREHENSIONS_ONE_VAR_VS_TWO_VAR_AND (
493+ "string_int_map.all(k, v, v > 0) && string_int_map.all(k, k == 'a')" ,
494+ "string_int_map.all(k, k == \" a\" ) && string_int_map.all(k, v, v > 0)" ),
495+
496+ // Macro Scope Coverage (filter, map, exists_one, optMap, optFlatMap)
497+ FILTER_MACRO_PREDICATE_ORDER (
498+ "int_list.filter(x, x > 10 && x > 0)" , "int_list.filter(x, x > 0 && x > 10)" ),
499+ MAP_MACRO_PREDICATE_ORDER (
500+ "int_list.map(x, x == 2 && x == 1)" , "int_list.map(x, x == 1 && x == 2)" ),
501+ EXISTS_ONE_MACRO_PREDICATE_ORDER (
502+ "int_list.exists_one(x, x > 10 && x > 0)" , "int_list.exists_one(x, x > 0 && x > 10)" ),
503+ OPT_MAP_MACRO_PREDICATE_ORDER (
504+ "optional.of(int_var).optMap(x, x == 2 && x == 1)" ,
505+ "optional.of(int_var).optMap(x, x == 1 && x == 2)" ),
506+ OPT_FLAT_MAP_MACRO_PREDICATE_ORDER (
507+ "optional.of(int_var).optFlatMap(x, optional.of(x == 2 && x == 1))" ,
508+ "optional.of(int_var).optFlatMap(x, optional.of(x == 1 && x == 2))" ),
509+
510+ // Literal & Constant Comparator Branches
511+ CONST_UINT_SYMMETRIC_EQUALITY ("20u == 10u" , "10u == 20u" ),
512+ CONST_DOUBLE_SYMMETRIC_EQUALITY ("2.5 == 1.5" , "1.5 == 2.5" ),
513+ CONST_BYTES_SYMMETRIC_EQUALITY (
514+ "b'xyz' == b'abc'" , "b\" \\ 141\\ 142\\ 143\" == b\" \\ 170\\ 171\\ 172\" " ),
515+ MAP_DIFFERENT_KEYS_EQUALITY ("{'b': 1} == {'a': 1}" , "{\" a\" : 1} == {\" b\" : 1}" ),
516+ MAP_DIFFERENT_VALUES_EQUALITY ("{'a': 2} == {'a': 1}" , "{\" a\" : 1} == {\" a\" : 2}" ),
517+ LIST_DIFFERENT_ELEMENTS_EQUALITY ("[2, 1] == [1, 2]" , "[1, 2] == [2, 1]" ),
518+ SELECT_DIFFERENT_FIELDS_EQUALITY (
519+ "msg2.single_int64 == msg.single_int64" , "msg.single_int64 == msg2.single_int64" );
468520
469521 private final String input ;
470522 private final String expected ;
@@ -563,4 +615,34 @@ public void optimize_customMacroWithExistsStructure_notCanonicalized() throws Ex
563615 .optimizedAst ();
564616 assertThat (UNPARSER .unparse (optimizedAst )).isEqualTo ("!int_list.my_custom_exists(e, e == 1)" );
565617 }
618+
619+ @ Test
620+ public void optimize_comprehensionWithoutMacroCalls_deMorganSucceeds () throws Exception {
621+ CelAbstractSyntaxTree ast = CEL .compile ("!int_list.exists(e, e == 1)" ).getAst ();
622+ CelMutableAst mutableAst = CelMutableAst .fromCelAst (ast );
623+ mutableAst .source ().getMacroCalls ().clear ();
624+
625+ CelAbstractSyntaxTree optimizedAst =
626+ CanonicalizationOptimizer .newInstance (CanonicalizationOptions .newBuilder ().build ())
627+ .optimize (mutableAst .toParsedAst (), CEL )
628+ .optimizedAst ();
629+ assertThat (optimizedAst .getExpr ().getKind ()).isEqualTo (Kind .COMPREHENSION );
630+ assertThat (optimizedAst .getExpr ().comprehension ().accuInit ().constant ().booleanValue ())
631+ .isTrue ();
632+ }
633+
634+ @ Test
635+ public void optimize_comprehensionAllWithoutMacroCalls_deMorganSucceeds () throws Exception {
636+ CelAbstractSyntaxTree ast = CEL .compile ("!int_list.all(e, e == 1)" ).getAst ();
637+ CelMutableAst mutableAst = CelMutableAst .fromCelAst (ast );
638+ mutableAst .source ().getMacroCalls ().clear ();
639+
640+ CelAbstractSyntaxTree optimizedAst =
641+ CanonicalizationOptimizer .newInstance (CanonicalizationOptions .newBuilder ().build ())
642+ .optimize (mutableAst .toParsedAst (), CEL )
643+ .optimizedAst ();
644+ assertThat (optimizedAst .getExpr ().getKind ()).isEqualTo (Kind .COMPREHENSION );
645+ assertThat (optimizedAst .getExpr ().comprehension ().accuInit ().constant ().booleanValue ())
646+ .isFalse ();
647+ }
566648}
0 commit comments