@@ -1247,9 +1247,10 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
12471247 }
12481248 Expr <?> optRef = typeSystem .getOptionalRef (val );
12491249 BoolExpr hasValue = typeSystem .optHasValue (optRef );
1250- BoolExpr valConstraint =
1251- createTypeConstraintForType (typeSystem .getOptionalValue (optRef ), paramType );
1252- return ctx .mkAnd (isOpt , ctx .mkImplies (hasValue , valConstraint ));
1250+ Expr <?> optVal = typeSystem .getOptionalValue (optRef );
1251+ BoolExpr optValNotError = ctx .mkNot (typeSystem .isError (optVal ));
1252+ BoolExpr valConstraint = createTypeConstraintForType (optVal , paramType );
1253+ return ctx .mkAnd (isOpt , ctx .mkImplies (hasValue , ctx .mkAnd (optValNotError , valConstraint )));
12531254 }
12541255 if (type .equals (SimpleType .BOOL )) {
12551256 return (BoolExpr ) ctx .mkApp (typeSystem .boolCons ().getTesterDecl (), val );
@@ -1289,15 +1290,13 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
12891290 }
12901291
12911292 if (type instanceof ListType ) {
1292- // Lists are explicitly bounded (sequence theory). We're safe in using for-all quantifiers
1293- // here.
1293+ // Constrain list elements using bounded unrolling up to comprehensionUnrollLimit rather
1294+ // than Z3 forall quantifiers to prevent MBQI quantifier instantiation loops.
1295+ // Assert: isList(val) ∧ for all unrolled 0 <= i < length: ¬isError(seq[i]) ∧
1296+ // typeConstraint(seq[i])
12941297 BoolExpr isList = typeSystem .isList (val );
12951298 CelType elemType = ((ListType ) type ).elemType ();
1296- if (elemType .equals (SimpleType .DYN )) {
1297- return isList ;
1298- }
12991299
1300- // isList(val) ∧ ∀i. (0 <= i < length) ⇒ elemType(seq[i])
13011300 Expr <?> listRef = typeSystem .getListRef (val );
13021301 SeqExpr seq = typeSystem .getSeq (listRef );
13031302 Expr length = ctx .mkLength (seq );
@@ -1307,20 +1306,69 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
13071306 for (int i = 0 ; i < comprehensionUnrollLimit ; i ++) {
13081307 IntExpr idx = ctx .mkInt (i );
13091308 Expr elem = ctx .mkNth (seq , idx );
1310- BoolExpr elemConstraint = createTypeConstraintForType (elem , elemType );
13111309 BoolExpr validIndex = ctx .mkLt (idx , length );
1312- boundsAndTypes .add (ctx .mkImplies (validIndex , elemConstraint ));
1313- BoolExpr outOfBounds = ctx .mkGe (idx , length );
1314- boundsAndTypes .add (ctx .mkImplies (outOfBounds , ctx .mkEq (elem , typeSystem .mkUnknown ())));
1310+ // Assert ¬isError(elem) as a domain invariant so Z3 never synthesizes an Error element in
1311+ // list(dyn). For concrete types, this is already implied by createTypeConstraintForType.
1312+ boundsAndTypes .add (ctx .mkImplies (validIndex , ctx .mkNot (typeSystem .isError (elem ))));
1313+ // Short-circuit DYN element types to prevent generating redundant validIndex ⇒ TRUE
1314+ // clauses.
1315+ if (!elemType .equals (SimpleType .DYN )) {
1316+ BoolExpr elemConstraint = createTypeConstraintForType (elem , elemType );
1317+ boundsAndTypes .add (ctx .mkImplies (validIndex , elemConstraint ));
1318+ }
13151319 }
13161320
13171321 return CelZ3TypeSystem .mkAndFlattened (ctx , boundsAndTypes );
13181322 }
13191323 if (type instanceof MapType ) {
1320- // Do NOT emit a for-all quantifier over map keys here.
1321- // Doing so forces MBQI into an infinite loop. Structural equivalence of dynamic keys is
1322- // naturally constrained by the primitive key assertions in getStructuralEquality().
1323- return typeSystem .isMap (val );
1324+ // Do NOT emit a for-all quantifier over map keys or values here.
1325+ // Doing so forces MBQI into an infinite loop. Instead, constrain keys and values using
1326+ // bounded unrolling over the key sequence up to comprehensionUnrollLimit.
1327+ // Assert: isMap(val) ∧ for all unrolled 0 <= i < length: isPrimitiveKey(key) ∧ ¬isError(key)
1328+ // ∧ (presence(key) ⇒ ¬isError(val) ∧ typeConstraint(val))
1329+ BoolExpr isMap = typeSystem .isMap (val );
1330+ MapType mapType = (MapType ) type ;
1331+ CelType keyType = mapType .keyType ();
1332+ CelType valType = mapType .valueType ();
1333+
1334+ Expr <?> mapRef = typeSystem .getMapRef (val );
1335+ SeqExpr seq = typeSystem .getMapKeys (mapRef );
1336+ Expr length = ctx .mkLength (seq );
1337+ ArrayExpr mapValues = (ArrayExpr ) typeSystem .getMapValues (mapRef );
1338+ ArrayExpr mapPresence = (ArrayExpr ) typeSystem .getMapPresence (mapRef );
1339+
1340+ List <BoolExpr > boundsAndTypes = new ArrayList <>();
1341+ boundsAndTypes .add (isMap );
1342+
1343+ for (int i = 0 ; i < comprehensionUnrollLimit ; i ++) {
1344+ IntExpr idx = ctx .mkInt (i );
1345+ Expr key = ctx .mkNth (seq , idx );
1346+ BoolExpr validIndex = ctx .mkLt (idx , length );
1347+
1348+ BoolExpr isKeyPrim = typeSystem .isPrimitiveKey (key );
1349+ BoolExpr keyNotError = ctx .mkNot (typeSystem .isError (key ));
1350+ // Assert isKeyPrim ∧ ¬isError(key) so Z3 never synthesizes a non-primitive or Error key in
1351+ // map(dyn, ...). For concrete map types, this is already implied by keyType constraints.
1352+ boundsAndTypes .add (ctx .mkImplies (validIndex , ctx .mkAnd (isKeyPrim , keyNotError )));
1353+ // Short-circuit DYN key types to prevent generating redundant validIndex ⇒ TRUE clauses.
1354+ if (!keyType .equals (SimpleType .DYN )) {
1355+ boundsAndTypes .add (ctx .mkImplies (validIndex , createTypeConstraintForType (key , keyType )));
1356+ }
1357+
1358+ BoolExpr presence = (BoolExpr ) ctx .mkSelect (mapPresence , key );
1359+ BoolExpr validEntry = ctx .mkAnd (validIndex , presence );
1360+
1361+ Expr mapVal = ctx .mkSelect (mapValues , key );
1362+ BoolExpr valNotError = ctx .mkNot (typeSystem .isError (mapVal ));
1363+ boundsAndTypes .add (ctx .mkImplies (validEntry , valNotError ));
1364+ // Short-circuit DYN value types to prevent generating redundant validEntry ⇒ TRUE clauses.
1365+ if (!valType .equals (SimpleType .DYN )) {
1366+ boundsAndTypes .add (
1367+ ctx .mkImplies (validEntry , createTypeConstraintForType (mapVal , valType )));
1368+ }
1369+ }
1370+
1371+ return CelZ3TypeSystem .mkAndFlattened (ctx , boundsAndTypes );
13241372 }
13251373 if (type .kind () == CelKind .STRUCT ) {
13261374 return ctx .mkAnd (
0 commit comments