@@ -741,7 +741,7 @@ 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 .getType (exprId ).map (SimpleType .DYN :: equals ). orElse ( true );
744+ boolean isDynamic = ast .getTypeOrThrow (exprId ).equals (SimpleType .DYN );
745745 BoolExpr isApprox = ctx .mkBool (!isDynamic );
746746 return TranslatedValue .propagateStrict (
747747 ctx , typeSystem , callRes , Optional .of (expr ), isApprox , args );
@@ -877,10 +877,6 @@ private TranslatedValue translateDynamicComprehension(
877877 ArrayExpr mapPresence =
878878 isMap ? (ArrayExpr ) typeSystem .getMapPresence (typeSystem .getMapRef (iterRange )) : null ;
879879
880- if (isMap ) {
881- applyBoundedMapBijection (mapPresence , seq , lengthExpr );
882- }
883-
884880 BoolExpr isTruncated = ctx .mkGt (lengthExpr , ctx .mkInt (comprehensionUnrollLimit ));
885881 truncationConditions .add (isTruncated );
886882
@@ -893,14 +889,15 @@ private TranslatedValue translateDynamicComprehension(
893889 }
894890 }
895891
896- private void applyBoundedMapBijection (
892+ private BoolExpr getBoundedMapBijection (
897893 ArrayExpr mapPresence , SeqExpr <?> seq , ArithExpr lengthExpr ) {
894+ List <BoolExpr > constraints = new ArrayList <>();
898895 for (int i = 0 ; i < comprehensionUnrollLimit ; i ++) {
899896 for (int j = i + 1 ; j < comprehensionUnrollLimit ; j ++) {
900897 BoolExpr validPair = ctx .mkLt (ctx .mkInt (j ), lengthExpr );
901898 BoolExpr notEqual =
902899 ctx .mkNot (ctx .mkEq (ctx .mkNth (seq , ctx .mkInt (i )), ctx .mkNth (seq , ctx .mkInt (j ))));
903- typeConstraints .add (ctx .mkImplies (validPair , notEqual ));
900+ constraints .add (ctx .mkImplies (validPair , notEqual ));
904901 }
905902 }
906903
@@ -915,7 +912,8 @@ private void applyBoundedMapBijection(
915912 ctx .mkStore (seqMap , ctx .mkNth (seq , ctx .mkInt (i )), ctx .mkTrue ()),
916913 seqMap );
917914 }
918- 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 );
919917 }
920918
921919 private TranslatedValue [] evaluateLoopCondAndStep (
@@ -1230,7 +1228,7 @@ private BoolExpr createTypeConstraint(Expr<?> val, long exprId, CelAbstractSynta
12301228 .orElseThrow (
12311229 () -> new IllegalArgumentException ("Type not found for expr ID: " + exprId ));
12321230 BoolExpr typeConstraint = createTypeConstraintForType (val , type );
1233- return ctx .mkOr (typeSystem .isError ( val ), typeSystem . isUnknown (val ), typeConstraint );
1231+ return ctx .mkOr (typeSystem .isErrorOrUnknown (val ), typeConstraint );
12341232 }
12351233
12361234 private BoolExpr createTypeConstraintForType (Expr <?> val , CelType type ) {
@@ -1247,9 +1245,10 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
12471245 }
12481246 Expr <?> optRef = typeSystem .getOptionalRef (val );
12491247 BoolExpr hasValue = typeSystem .optHasValue (optRef );
1250- BoolExpr valConstraint =
1251- createTypeConstraintForType (typeSystem .getOptionalValue (optRef ), paramType );
1252- 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 )));
12531252 }
12541253 if (type .equals (SimpleType .BOOL )) {
12551254 return (BoolExpr ) ctx .mkApp (typeSystem .boolCons ().getTesterDecl (), val );
@@ -1258,15 +1257,15 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
12581257 Expr <?> unwrapped = ctx .mkApp (typeSystem .intCons ().getAccessorDecls ()[0 ], val );
12591258 return ctx .mkAnd (
12601259 ctx .mkApp (typeSystem .intCons ().getTesterDecl (), val ),
1261- ctx .mkGe ((ArithExpr ) unwrapped , ctx .mkInt (CelZ3TypeSystem .MIN_INT64 )),
1262- ctx .mkLe ((ArithExpr ) unwrapped , ctx .mkInt (CelZ3TypeSystem .MAX_INT64 )));
1260+ ctx .mkGe ((ArithExpr ) unwrapped , ctx .mkInt (CelNumericBounds .MIN_INT64 )),
1261+ ctx .mkLe ((ArithExpr ) unwrapped , ctx .mkInt (CelNumericBounds .MAX_INT64 )));
12631262 }
12641263 if (type .equals (SimpleType .UINT )) {
12651264 Expr <?> unwrapped = ctx .mkApp (typeSystem .uintCons ().getAccessorDecls ()[0 ], val );
12661265 return ctx .mkAnd (
12671266 ctx .mkApp (typeSystem .uintCons ().getTesterDecl (), val ),
12681267 ctx .mkGe ((ArithExpr ) unwrapped , ctx .mkInt (0 )),
1269- ctx .mkLe ((ArithExpr ) unwrapped , ctx .mkInt (CelZ3TypeSystem .MAX_UINT64 )));
1268+ ctx .mkLe ((ArithExpr ) unwrapped , ctx .mkInt (CelNumericBounds .MAX_UINT64 )));
12701269 }
12711270 if (type .equals (SimpleType .DOUBLE )) {
12721271 return (BoolExpr ) ctx .mkApp (typeSystem .doubleCons ().getTesterDecl (), val );
@@ -1289,15 +1288,13 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
12891288 }
12901289
12911290 if (type instanceof ListType ) {
1292- // Lists are explicitly bounded (sequence theory). We're safe in using for-all quantifiers
1293- // 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])
12941295 BoolExpr isList = typeSystem .isList (val );
12951296 CelType elemType = ((ListType ) type ).elemType ();
1296- if (elemType .equals (SimpleType .DYN )) {
1297- return isList ;
1298- }
12991297
1300- // isList(val) ∧ ∀i. (0 <= i < length) ⇒ elemType(seq[i])
13011298 Expr <?> listRef = typeSystem .getListRef (val );
13021299 SeqExpr seq = typeSystem .getSeq (listRef );
13031300 Expr length = ctx .mkLength (seq );
@@ -1307,20 +1304,62 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
13071304 for (int i = 0 ; i < comprehensionUnrollLimit ; i ++) {
13081305 IntExpr idx = ctx .mkInt (i );
13091306 Expr elem = ctx .mkNth (seq , idx );
1310- BoolExpr elemConstraint = createTypeConstraintForType (elem , elemType );
13111307 BoolExpr validIndex = ctx .mkLt (idx , length );
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+ BoolExpr elemConstraint = createTypeConstraintForType (elem , elemType );
13121312 boundsAndTypes .add (ctx .mkImplies (validIndex , elemConstraint ));
1313- BoolExpr outOfBounds = ctx .mkGe (idx , length );
1314- boundsAndTypes .add (ctx .mkImplies (outOfBounds , ctx .mkEq (elem , typeSystem .mkUnknown ())));
13151313 }
13161314
13171315 return CelZ3TypeSystem .mkAndFlattened (ctx , boundsAndTypes );
13181316 }
13191317 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 );
1318+ // Do NOT emit a for-all quantifier over map keys or values here.
1319+ // Doing so forces MBQI into an infinite loop. Instead, constrain keys and values using
1320+ // bounded unrolling over the key sequence up to comprehensionUnrollLimit.
1321+ // Assert: isMap(val) ∧ for all unrolled 0 <= i < length: isPrimitiveKey(key) ∧ ¬isError(key)
1322+ // ∧ (presence(key) ⇒ ¬isError(val) ∧ typeConstraint(val))
1323+ BoolExpr isMap = typeSystem .isMap (val );
1324+ MapType mapType = (MapType ) type ;
1325+ CelType keyType = mapType .keyType ();
1326+ CelType valType = mapType .valueType ();
1327+
1328+ Expr <?> mapRef = typeSystem .getMapRef (val );
1329+ SeqExpr seq = typeSystem .getMapKeys (mapRef );
1330+ Expr length = ctx .mkLength (seq );
1331+ ArrayExpr mapValues = (ArrayExpr ) typeSystem .getMapValues (mapRef );
1332+ ArrayExpr mapPresence = (ArrayExpr ) typeSystem .getMapPresence (mapRef );
1333+
1334+ List <BoolExpr > boundsAndTypes = new ArrayList <>();
1335+ boundsAndTypes .add (isMap );
1336+ boundsAndTypes .add (getBoundedMapBijection (mapPresence , seq , (ArithExpr ) length ));
1337+
1338+ for (int i = 0 ; i < comprehensionUnrollLimit ; i ++) {
1339+ IntExpr idx = ctx .mkInt (i );
1340+ Expr key = ctx .mkNth (seq , idx );
1341+ BoolExpr validIndex = ctx .mkLt (idx , length );
1342+
1343+ BoolExpr isKeyPrim = typeSystem .isPrimitiveKey (key );
1344+ BoolExpr keyNotError = ctx .mkNot (typeSystem .isError (key ));
1345+ // Assert isKeyPrim ∧ ¬isError(key) so Z3 never synthesizes a non-primitive or Error key in
1346+ // map(dyn, ...). For concrete map types, this is already implied by keyType constraints.
1347+ boundsAndTypes .add (ctx .mkImplies (validIndex , ctx .mkAnd (isKeyPrim , keyNotError )));
1348+ boundsAndTypes .add (ctx .mkImplies (validIndex , createTypeConstraintForType (key , keyType )));
1349+
1350+ BoolExpr presence = (BoolExpr ) ctx .mkSelect (mapPresence , key );
1351+ BoolExpr validEntry = ctx .mkAnd (validIndex , presence );
1352+
1353+ Expr mapVal = ctx .mkSelect (mapValues , key );
1354+ BoolExpr valNotError =
1355+ unknownIdentifiers .isEmpty ()
1356+ ? ctx .mkNot (typeSystem .isErrorOrUnknown (mapVal ))
1357+ : ctx .mkNot (typeSystem .isError (mapVal ));
1358+ boundsAndTypes .add (ctx .mkImplies (validEntry , valNotError ));
1359+ boundsAndTypes .add (ctx .mkImplies (validEntry , createTypeConstraintForType (mapVal , valType )));
1360+ }
1361+
1362+ return CelZ3TypeSystem .mkAndFlattened (ctx , boundsAndTypes );
13241363 }
13251364 if (type .kind () == CelKind .STRUCT ) {
13261365 return ctx .mkAnd (
@@ -1373,6 +1412,12 @@ private Optional<Object> toCacheKey(CelExpr expr) {
13731412 case CONSTANT :
13741413 return Optional .of (expr .constant ());
13751414 case LIST :
1415+ if (!expr .list ().optionalIndices ().isEmpty ()) {
1416+ // Do not cache lists with optional elements. Optional elements conditionally alter
1417+ // sequence length and presence via ITE branches at runtime; caching would collide
1418+ // [1, 2] with [?1, 2] and freeze conditional evaluations to a static reference.
1419+ return Optional .empty ();
1420+ }
13761421 ImmutableList .Builder <Object > builder = ImmutableList .builder ();
13771422 for (CelExpr elem : expr .list ().elements ()) {
13781423 Optional <Object > elemKey = toCacheKey (elem );
0 commit comments