@@ -741,8 +741,10 @@ private TranslatedValue translateCall(CelExpr expr, CelAbstractSyntaxTree ast) {
741741 typeConstraints .add (ctx .mkNot (typeSystem .isUnknown (callRes )));
742742 typeConstraints .add (ctx .mkNot (typeSystem .isError (callRes )));
743743
744+ boolean isDynamic = ast .getTypeOrThrow (exprId ).equals (SimpleType .DYN );
745+ BoolExpr isApprox = ctx .mkBool (!isDynamic );
744746 return TranslatedValue .propagateStrict (
745- ctx , typeSystem , callRes , Optional .of (expr ), ctx . mkTrue () , args );
747+ ctx , typeSystem , callRes , Optional .of (expr ), isApprox , args );
746748 });
747749 }
748750
@@ -875,10 +877,6 @@ private TranslatedValue translateDynamicComprehension(
875877 ArrayExpr mapPresence =
876878 isMap ? (ArrayExpr ) typeSystem .getMapPresence (typeSystem .getMapRef (iterRange )) : null ;
877879
878- if (isMap ) {
879- applyBoundedMapBijection (mapPresence , seq , lengthExpr );
880- }
881-
882880 BoolExpr isTruncated = ctx .mkGt (lengthExpr , ctx .mkInt (comprehensionUnrollLimit ));
883881 truncationConditions .add (isTruncated );
884882
@@ -891,14 +889,15 @@ private TranslatedValue translateDynamicComprehension(
891889 }
892890 }
893891
894- private void applyBoundedMapBijection (
892+ private BoolExpr getBoundedMapBijection (
895893 ArrayExpr mapPresence , SeqExpr <?> seq , ArithExpr lengthExpr ) {
894+ List <BoolExpr > constraints = new ArrayList <>();
896895 for (int i = 0 ; i < comprehensionUnrollLimit ; i ++) {
897896 for (int j = i + 1 ; j < comprehensionUnrollLimit ; j ++) {
898897 BoolExpr validPair = ctx .mkLt (ctx .mkInt (j ), lengthExpr );
899898 BoolExpr notEqual =
900899 ctx .mkNot (ctx .mkEq (ctx .mkNth (seq , ctx .mkInt (i )), ctx .mkNth (seq , ctx .mkInt (j ))));
901- typeConstraints .add (ctx .mkImplies (validPair , notEqual ));
900+ constraints .add (ctx .mkImplies (validPair , notEqual ));
902901 }
903902 }
904903
@@ -913,7 +912,8 @@ private void applyBoundedMapBijection(
913912 ctx .mkStore (seqMap , ctx .mkNth (seq , ctx .mkInt (i )), ctx .mkTrue ()),
914913 seqMap );
915914 }
916- typeConstraints .add (ctx .mkImplies (isNotTruncated , ctx .mkEq (mapPresence , seqMap )));
915+ constraints .add (ctx .mkImplies (isNotTruncated , ctx .mkEq (mapPresence , seqMap )));
916+ return CelZ3TypeSystem .mkAndFlattened (ctx , constraints );
917917 }
918918
919919 private TranslatedValue [] evaluateLoopCondAndStep (
@@ -1245,9 +1245,10 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
12451245 }
12461246 Expr <?> optRef = typeSystem .getOptionalRef (val );
12471247 BoolExpr hasValue = typeSystem .optHasValue (optRef );
1248- BoolExpr valConstraint =
1249- createTypeConstraintForType (typeSystem .getOptionalValue (optRef ), paramType );
1250- return ctx .mkAnd (isOpt , ctx .mkImplies (hasValue , valConstraint ));
1248+ Expr <?> optVal = typeSystem .getOptionalValue (optRef );
1249+ BoolExpr optValNotError = ctx .mkNot (typeSystem .isError (optVal ));
1250+ BoolExpr valConstraint = createTypeConstraintForType (optVal , paramType );
1251+ return ctx .mkAnd (isOpt , ctx .mkImplies (hasValue , ctx .mkAnd (optValNotError , valConstraint )));
12511252 }
12521253 if (type .equals (SimpleType .BOOL )) {
12531254 return (BoolExpr ) ctx .mkApp (typeSystem .boolCons ().getTesterDecl (), val );
@@ -1287,15 +1288,13 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
12871288 }
12881289
12891290 if (type instanceof ListType ) {
1290- // Lists are explicitly bounded (sequence theory). We're safe in using for-all quantifiers
1291- // here.
1291+ // Constrain list elements using bounded unrolling up to comprehensionUnrollLimit rather
1292+ // than Z3 forall quantifiers to prevent MBQI quantifier instantiation loops.
1293+ // Assert: isList(val) ∧ for all unrolled 0 <= i < length: ¬isError(seq[i]) ∧
1294+ // typeConstraint(seq[i])
12921295 BoolExpr isList = typeSystem .isList (val );
12931296 CelType elemType = ((ListType ) type ).elemType ();
1294- if (elemType .equals (SimpleType .DYN )) {
1295- return isList ;
1296- }
12971297
1298- // isList(val) ∧ ∀i. (0 <= i < length) ⇒ elemType(seq[i])
12991298 Expr <?> listRef = typeSystem .getListRef (val );
13001299 SeqExpr seq = typeSystem .getSeq (listRef );
13011300 Expr length = ctx .mkLength (seq );
@@ -1305,20 +1304,70 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
13051304 for (int i = 0 ; i < comprehensionUnrollLimit ; i ++) {
13061305 IntExpr idx = ctx .mkInt (i );
13071306 Expr elem = ctx .mkNth (seq , idx );
1308- BoolExpr elemConstraint = createTypeConstraintForType (elem , elemType );
13091307 BoolExpr validIndex = ctx .mkLt (idx , length );
1310- boundsAndTypes .add (ctx .mkImplies (validIndex , elemConstraint ));
1311- BoolExpr outOfBounds = ctx .mkGe (idx , length );
1312- boundsAndTypes .add (ctx .mkImplies (outOfBounds , ctx .mkEq (elem , typeSystem .mkUnknown ())));
1308+ // Assert ¬isError(elem) as a domain invariant so Z3 never synthesizes an Error element in
1309+ // list(dyn). For concrete types, this is already implied by createTypeConstraintForType.
1310+ boundsAndTypes .add (ctx .mkImplies (validIndex , ctx .mkNot (typeSystem .isError (elem ))));
1311+ // Short-circuit DYN element types to prevent generating redundant validIndex ⇒ TRUE
1312+ // clauses.
1313+ if (!elemType .equals (SimpleType .DYN )) {
1314+ BoolExpr elemConstraint = createTypeConstraintForType (elem , elemType );
1315+ boundsAndTypes .add (ctx .mkImplies (validIndex , elemConstraint ));
1316+ }
13131317 }
13141318
13151319 return CelZ3TypeSystem .mkAndFlattened (ctx , boundsAndTypes );
13161320 }
13171321 if (type instanceof MapType ) {
1318- // Do NOT emit a for-all quantifier over map keys here.
1319- // Doing so forces MBQI into an infinite loop. Structural equivalence of dynamic keys is
1320- // naturally constrained by the primitive key assertions in getStructuralEquality().
1321- return typeSystem .isMap (val );
1322+ // Do NOT emit a for-all quantifier over map keys or values here.
1323+ // Doing so forces MBQI into an infinite loop. Instead, constrain keys and values using
1324+ // bounded unrolling over the key sequence up to comprehensionUnrollLimit.
1325+ // Assert: isMap(val) ∧ for all unrolled 0 <= i < length: isPrimitiveKey(key) ∧ ¬isError(key)
1326+ // ∧ (presence(key) ⇒ ¬isError(val) ∧ typeConstraint(val))
1327+ BoolExpr isMap = typeSystem .isMap (val );
1328+ MapType mapType = (MapType ) type ;
1329+ CelType keyType = mapType .keyType ();
1330+ CelType valType = mapType .valueType ();
1331+
1332+ Expr <?> mapRef = typeSystem .getMapRef (val );
1333+ SeqExpr seq = typeSystem .getMapKeys (mapRef );
1334+ Expr length = ctx .mkLength (seq );
1335+ ArrayExpr mapValues = (ArrayExpr ) typeSystem .getMapValues (mapRef );
1336+ ArrayExpr mapPresence = (ArrayExpr ) typeSystem .getMapPresence (mapRef );
1337+
1338+ List <BoolExpr > boundsAndTypes = new ArrayList <>();
1339+ boundsAndTypes .add (isMap );
1340+ boundsAndTypes .add (getBoundedMapBijection (mapPresence , seq , (ArithExpr ) length ));
1341+
1342+ for (int i = 0 ; i < comprehensionUnrollLimit ; i ++) {
1343+ IntExpr idx = ctx .mkInt (i );
1344+ Expr key = ctx .mkNth (seq , idx );
1345+ BoolExpr validIndex = ctx .mkLt (idx , length );
1346+
1347+ BoolExpr isKeyPrim = typeSystem .isPrimitiveKey (key );
1348+ BoolExpr keyNotError = ctx .mkNot (typeSystem .isError (key ));
1349+ // Assert isKeyPrim ∧ ¬isError(key) so Z3 never synthesizes a non-primitive or Error key in
1350+ // map(dyn, ...). For concrete map types, this is already implied by keyType constraints.
1351+ boundsAndTypes .add (ctx .mkImplies (validIndex , ctx .mkAnd (isKeyPrim , keyNotError )));
1352+ // Short-circuit DYN key types to prevent generating redundant validIndex ⇒ TRUE clauses.
1353+ if (!keyType .equals (SimpleType .DYN )) {
1354+ boundsAndTypes .add (ctx .mkImplies (validIndex , createTypeConstraintForType (key , keyType )));
1355+ }
1356+
1357+ BoolExpr presence = (BoolExpr ) ctx .mkSelect (mapPresence , key );
1358+ BoolExpr validEntry = ctx .mkAnd (validIndex , presence );
1359+
1360+ Expr mapVal = ctx .mkSelect (mapValues , key );
1361+ BoolExpr valNotError = ctx .mkNot (typeSystem .isError (mapVal ));
1362+ boundsAndTypes .add (ctx .mkImplies (validEntry , valNotError ));
1363+ // Short-circuit DYN value types to prevent generating redundant validEntry ⇒ TRUE clauses.
1364+ if (!valType .equals (SimpleType .DYN )) {
1365+ boundsAndTypes .add (
1366+ ctx .mkImplies (validEntry , createTypeConstraintForType (mapVal , valType )));
1367+ }
1368+ }
1369+
1370+ return CelZ3TypeSystem .mkAndFlattened (ctx , boundsAndTypes );
13221371 }
13231372 if (type .kind () == CelKind .STRUCT ) {
13241373 return ctx .mkAnd (
@@ -1371,6 +1420,9 @@ private Optional<Object> toCacheKey(CelExpr expr) {
13711420 case CONSTANT :
13721421 return Optional .of (expr .constant ());
13731422 case LIST :
1423+ if (!expr .list ().optionalIndices ().isEmpty ()) {
1424+ return Optional .empty (); // Safely skip caching lists with optional elements
1425+ }
13741426 ImmutableList .Builder <Object > builder = ImmutableList .builder ();
13751427 for (CelExpr elem : expr .list ().elements ()) {
13761428 Optional <Object > elemKey = toCacheKey (elem );
0 commit comments