3131import dev .cel .common .CelOptions ;
3232import dev .cel .common .ast .CelConstant ;
3333import dev .cel .common .ast .CelExpr ;
34+ import dev .cel .common .types .OptionalType ;
3435import dev .cel .common .types .SimpleType ;
3536import dev .cel .common .types .StructTypeReference ;
3637import dev .cel .extensions .CelExtensions ;
@@ -75,7 +76,8 @@ public class SubexpressionOptimizerTest {
7576 .setSingleInt64 (10L )
7677 .putMapInt32Int64 (0 , 1 )
7778 .putMapInt32Int64 (1 , 5 )
78- .putMapInt32Int64 (2 , 2 )))
79+ .putMapInt32Int64 (2 , 2 )
80+ .putMapStringString ("key" , "A" )))
7981 .build ();
8082
8183 private static CelBuilder newCelBuilder () {
@@ -92,6 +94,7 @@ private static CelBuilder newCelBuilder() {
9294 "custom_func" ,
9395 newGlobalOverload ("custom_func_overload" , SimpleType .INT , SimpleType .INT )))
9496 .addVar ("x" , SimpleType .DYN )
97+ .addVar ("opt_x" , OptionalType .create (SimpleType .DYN ))
9598 .addVar ("msg" , StructTypeReference .create (TestAllTypes .getDescriptor ().getFullName ()));
9699 }
97100
@@ -314,6 +317,13 @@ private enum CseTestCase {
314317 "[1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]]" ,
315318 "cel.bind(@r0, [1, 2, 3], @r0.map(@c0, @r0.map(@c1, @c1 + 1))) == "
316319 + "cel.bind(@r1, [2, 3, 4], [@r1, @r1, @r1])" ),
320+ INCLUSION_LIST (
321+ "1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3]" ,
322+ "cel.bind(@r0, [1, 2, 3], cel.bind(@r1, 1 in @r0, @r1 && 2 in @r0 && 3 in [3, @r0] &&"
323+ + " @r1))" ),
324+ INCLUSION_MAP (
325+ "2 in {'a': 1, 2: {true: false}, 3: {true: false}}" ,
326+ "2 in cel.bind(@r0, {true: false}, {\" a\" : 1, 2: @r0, 3: @r0})" ),
317327 MACRO_SHADOWED_VARIABLE (
318328 "[x - 1 > 3 ? x - 1 : 5].exists(x, x - 1 > 3) || x - 1 > 3" ,
319329 "cel.bind(@r0, x - 1, cel.bind(@r1, @r0 > 3, [@r1 ? @r0 : 5].exists(@c0, @c0 - 1 > 3) ||"
@@ -322,6 +332,86 @@ private enum CseTestCase {
322332 "size([\" foo\" , \" bar\" ].map(x, [x + x, x + x]).map(x, [x + x, x + x])) == 2" ,
323333 "size([\" foo\" , \" bar\" ].map(@c1, cel.bind(@r0, @c1 + @c1, [@r0, @r0]))"
324334 + ".map(@c0, cel.bind(@r1, @c0 + @c0, [@r1, @r1]))) == 2" ),
335+ PRESENCE_TEST (
336+ "has({'a': true}.a) && {'a':true}['a']" ,
337+ "cel.bind(@r0, {\" a\" : true}, has(@r0.a) && @r0[\" a\" ])" ),
338+ PRESENCE_TEST_WITH_TERNARY (
339+ "(has(msg.oneof_type.payload) ? msg.oneof_type.payload.single_int64 : 0) == 10" ,
340+ "cel.bind(@r0, msg.oneof_type, has(@r0.payload) ? @r0.payload.single_int64 : 0) == 10" ),
341+ PRESENCE_TEST_WITH_TERNARY_2 (
342+ "(has(msg.oneof_type.payload) ? msg.oneof_type.payload.single_int64 :"
343+ + " msg.oneof_type.payload.single_int64 * 0) == 10" ,
344+ "cel.bind(@r0, msg.oneof_type, cel.bind(@r1, @r0.payload.single_int64, has(@r0.payload) ?"
345+ + " @r1 : (@r1 * 0))) == 10" ),
346+ PRESENCE_TEST_WITH_TERNARY_3 (
347+ "(has(msg.oneof_type.payload.single_int64) ? msg.oneof_type.payload.single_int64 :"
348+ + " msg.oneof_type.payload.single_int64 * 0) == 10" ,
349+ "cel.bind(@r0, msg.oneof_type.payload, cel.bind(@r1, @r0.single_int64,"
350+ + " has(@r0.single_int64) ? @r1 : (@r1 * 0))) == 10" ),
351+ /**
352+ * Input:
353+ *
354+ * <pre>{@code
355+ * (
356+ * has(msg.oneof_type) &&
357+ * has(msg.oneof_type.payload) &&
358+ * has(msg.oneof_type.payload.single_int64)
359+ * ) ?
360+ * (
361+ * (
362+ * has(msg.oneof_type.payload.map_string_string) &&
363+ * has(msg.oneof_type.payload.map_string_string.key)
364+ * ) ?
365+ * msg.oneof_type.payload.map_string_string.key == "A"
366+ * : false
367+ * )
368+ * : false
369+ * }</pre>
370+ *
371+ * Unparsed:
372+ *
373+ * <pre>{@code
374+ * cel.bind(
375+ * @r0, msg.oneof_type,
376+ * cel.bind(
377+ * @r1, @r0.payload,
378+ * has(msg.oneof_type) && has(@r0.payload) && has(@r1.single_int64) ?
379+ * cel.bind(
380+ * @r2, @r1.map_string_string,
381+ * has(@r1.map_string_string) && has(@r2.key) ? @r2.key == "A" : false,
382+ * )
383+ * : false,
384+ * ),
385+ * )
386+ * }</pre>
387+ */
388+ PRESENCE_TEST_WITH_TERNARY_NESTED (
389+ "(has(msg.oneof_type) && has(msg.oneof_type.payload) &&"
390+ + " has(msg.oneof_type.payload.single_int64)) ?"
391+ + " ((has(msg.oneof_type.payload.map_string_string) &&"
392+ + " has(msg.oneof_type.payload.map_string_string.key)) ?"
393+ + " msg.oneof_type.payload.map_string_string.key == 'A' : false) : false" ,
394+ "cel.bind(@r0, msg.oneof_type, cel.bind(@r1, @r0.payload, (has(msg.oneof_type) &&"
395+ + " has(@r0.payload) && has(@r1.single_int64)) ? cel.bind(@r2, @r1.map_string_string,"
396+ + " (has(@r1.map_string_string) && has(@r2.key)) ? (@r2.key == \" A\" ) : false) :"
397+ + " false))" ),
398+ OPTIONAL_LIST (
399+ "[10, ?optional.none(), [?optional.none(), ?opt_x], [?optional.none(), ?opt_x]] == [10,"
400+ + " [5], [5]]" ,
401+ "cel.bind(@r0, [?optional.none(), ?opt_x], [10, ?optional.none(), @r0, @r0]) =="
402+ + " cel.bind(@r1, [5], [10, @r1, @r1])" ),
403+ OPTIONAL_MAP (
404+ "{?'hello': optional.of('hello')}['hello'] + {?'hello': optional.of('hello')}['hello'] =="
405+ + " 'hellohello'" ,
406+ "cel.bind(@r0, {?\" hello\" : optional.of(\" hello\" )}[\" hello\" ], @r0 + @r0) =="
407+ + " \" hellohello\" " ),
408+ OPTIONAL_MESSAGE (
409+ "TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32:"
410+ + " optional.of(4)}.single_int32 + TestAllTypes{?single_int64:"
411+ + " optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}.single_int64 == 5" ,
412+ "cel.bind(@r0, TestAllTypes{"
413+ + "?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}, "
414+ + "@r0.single_int32 + @r0.single_int64) == 5" ),
325415 ;
326416
327417 private final String source ;
@@ -342,7 +432,9 @@ public void cse_withMacroMapPopulated_success(@TestParameter CseTestCase testCas
342432
343433 assertThat (
344434 CEL .createProgram (optimizedAst )
345- .eval (ImmutableMap .of ("msg" , TEST_ALL_TYPES_INPUT , "x" , 5L )))
435+ .eval (
436+ ImmutableMap .of (
437+ "msg" , TEST_ALL_TYPES_INPUT , "x" , 5L , "opt_x" , Optional .of (5L ))))
346438 .isEqualTo (true );
347439 assertThat (CEL_UNPARSER .unparse (optimizedAst )).isEqualTo (testCase .unparsed );
348440 }
@@ -366,7 +458,9 @@ public void cse_withoutMacroMap_success(@TestParameter CseTestCase testCase) thr
366458 assertThat (
367459 celWithoutMacroMap
368460 .createProgram (optimizedAst )
369- .eval (ImmutableMap .of ("msg" , TEST_ALL_TYPES_INPUT , "x" , 5L )))
461+ .eval (
462+ ImmutableMap .of (
463+ "msg" , TEST_ALL_TYPES_INPUT , "x" , 5L , "opt_x" , Optional .of (5L ))))
370464 .isEqualTo (true );
371465 }
372466
@@ -384,7 +478,8 @@ public void cse_withoutMacroMap_success(@TestParameter CseTestCase testCase) thr
384478 @ TestParameters ("{source: 'custom_func(1) + custom_func(1)'}" )
385479 // Duplicated but nested calls.
386480 @ TestParameters ("{source: 'int(timestamp(int(timestamp(1000000000))))'}" )
387- // Ternary with presence test is not supported yet.
481+ // This cannot be optimized. Extracting the common subexpression would presence test
482+ // the bound identifier (e.g: has(@r0)), which is not valid.
388483 @ TestParameters ("{source: 'has(msg.single_any) ? msg.single_any : 10'}" )
389484 public void cse_noop (String source ) throws Exception {
390485 CelAbstractSyntaxTree ast = CEL .compile (source ).getAst ();
0 commit comments