@@ -76,7 +76,7 @@ final class CelAstToZ3Translator {
7676 private final CelZ3ComprehensionTranslator comprehensionTranslator ;
7777 private final CelZ3StructTranslator structTranslator ;
7878 private final ImmutableSet <String > unknownIdentifiers ;
79- private final Map <Object , Expr <?>> listLiteralCache ;
79+ private final Map <SeqExpr <?> , Expr <?>> listLiteralCache ;
8080
8181 /**
8282 * Returns a set of Z3 boolean expressions representing type constraints for the translated AST.
@@ -268,51 +268,49 @@ private TranslatedValue translateIdent(CelExpr celExpr, CelAbstractSyntaxTree as
268268
269269 private TranslatedValue translateList (CelExpr celExpr , CelAbstractSyntaxTree ast ) {
270270 CelList createList = celExpr .list ();
271- Optional <Object > cacheKey = toCacheKey (celExpr );
272271 List <TranslatedValue > elementsTv = new ArrayList <>();
273- Expr <?> listRef = cacheKey .map (listLiteralCache ::get ).orElse (null );
274-
275- // Cache list literals containing only constants. If the same list literal appears multiple
276- // times (e.g., in equality checks like `[1, 2] == [1, 2]`), caching allows us to reuse the
277- // same Z3 constant. This avoids expensive sequence equality reasoning in Z3 by reducing the
278- // check to a trivial identity check (e.g., `list_ref_0 == list_ref_0`).
279- if (listRef == null ) {
280- SeqExpr seq = ctx .mkEmptySeq (ctx .mkSeqSort (typeSystem .celValueSort ()));
281- List <Expr <?>> values = new ArrayList <>();
282- ImmutableList <Integer > optionalIndices = createList .optionalIndices ();
283- ImmutableList <CelExpr > elements = createList .elements ();
284- for (int i = 0 ; i < elements .size (); i ++) {
285- CelExpr element = elements .get (i );
286- TranslatedValue elem = translateExpr (element , ast );
287-
288- if (optionalIndices .contains (i )) {
289- Expr <?> checkedValue =
290- typeSystem .withRuntimeError (
291- elem .z3Expr (), ctx .mkNot (typeSystem .isOptional (elem .z3Expr ())));
292- elem = TranslatedValue .create (checkedValue , element , typeSystem , elem .isApproximate ());
293-
294- Expr <?> optRef = typeSystem .getOptionalRef (elem .z3Expr ());
295- Expr <?> value = typeSystem .getOptionalValue (optRef );
296- seq =
297- (SeqExpr )
298- ctx .mkITE (
299- typeSystem .optHasValue (optRef ),
300- typeSystem .mkConcatSafe (seq , ctx .mkUnit (value )),
301- seq );
302- values .add (value );
303- } else {
304- seq = typeSystem .mkConcatSafe (seq , ctx .mkUnit (elem .z3Expr ()));
305- values .add (elem .z3Expr ());
306- }
307- elementsTv .add (elem );
272+ SeqExpr seq = ctx .mkEmptySeq (ctx .mkSeqSort (typeSystem .celValueSort ()));
273+ List <Expr <?>> values = new ArrayList <>();
274+ ImmutableList <Integer > optionalIndices = createList .optionalIndices ();
275+ ImmutableList <CelExpr > elements = createList .elements ();
276+ for (int i = 0 ; i < elements .size (); i ++) {
277+ CelExpr element = elements .get (i );
278+ TranslatedValue elem = translateExpr (element , ast );
279+
280+ if (optionalIndices .contains (i )) {
281+ Expr <?> checkedValue =
282+ typeSystem .withRuntimeError (
283+ elem .z3Expr (), ctx .mkNot (typeSystem .isOptional (elem .z3Expr ())));
284+ elem = TranslatedValue .create (checkedValue , element , typeSystem , elem .isApproximate ());
285+
286+ Expr <?> optRef = typeSystem .getOptionalRef (elem .z3Expr ());
287+ Expr <?> value = typeSystem .getOptionalValue (optRef );
288+ seq =
289+ (SeqExpr )
290+ ctx .mkITE (
291+ typeSystem .optHasValue (optRef ),
292+ typeSystem .mkConcatSafe (seq , ctx .mkUnit (value )),
293+ seq );
294+ values .add (value );
295+ } else {
296+ seq = typeSystem .mkConcatSafe (seq , ctx .mkUnit (elem .z3Expr ()));
297+ values .add (elem .z3Expr ());
308298 }
309- listRef = typeSystem .mkListRefConst (LIST_REF_PREFIX );
310- context .addConstraint (ctx .mkEq (typeSystem .getSeq (listRef ), seq ));
311- propagateTruncatedTypeConstraint (typeSystem .wrapList (listRef ), values );
312- Expr <?> finalListRef = listRef ;
313- cacheKey .ifPresent (key -> listLiteralCache .put (key , finalListRef ));
299+ elementsTv .add (elem );
314300 }
315301
302+ // Reuse the same Z3 ListRef whenever two list constructions yield the exact same sequence term
303+ // (e.g., identical constant lists or identical unrolled map/filter step lists across ASTs).
304+ Expr <?> listRef =
305+ listLiteralCache .computeIfAbsent (
306+ seq ,
307+ s -> {
308+ Expr <?> ref = typeSystem .mkListRefConst (LIST_REF_PREFIX );
309+ context .addConstraint (ctx .mkEq (typeSystem .getSeq (ref ), s ));
310+ propagateTruncatedTypeConstraint (typeSystem .wrapList (ref ), values );
311+ return ref ;
312+ });
313+
316314 Expr <?> result = typeSystem .wrapList (listRef );
317315 return TranslatedValue .propagateStrict (ctx , typeSystem , result , celExpr , elementsTv );
318316 }
@@ -481,31 +479,6 @@ private static boolean mayEmbedArgs(String functionName, CelType resultType) {
481479 && !functionName .equals (Operator .CONDITIONAL .getFunction ());
482480 }
483481
484- private Optional <Object > toCacheKey (CelExpr expr ) {
485- switch (expr .exprKind ().getKind ()) {
486- case CONSTANT :
487- return Optional .of (expr .constant ());
488- case LIST :
489- if (!expr .list ().optionalIndices ().isEmpty ()) {
490- // Do not cache lists with optional elements. Optional elements conditionally alter
491- // sequence length and presence via ITE branches at runtime; caching would collide
492- // [1, 2] with [?1, 2] and freeze conditional evaluations to a static reference.
493- return Optional .empty ();
494- }
495- ImmutableList .Builder <Object > builder = ImmutableList .builder ();
496- for (CelExpr elem : expr .list ().elements ()) {
497- Optional <Object > elemKey = toCacheKey (elem );
498- if (!elemKey .isPresent ()) {
499- return Optional .empty (); // Contains non-constants
500- }
501- builder .add (elemKey .get ());
502- }
503- return Optional .of (builder .build ());
504- default :
505- return Optional .empty ();
506- }
507- }
508-
509482 CelAstToZ3Translator (
510483 Context ctx ,
511484 int comprehensionUnrollLimit ,
0 commit comments