@@ -92,6 +92,7 @@ final class CelAstToZ3Translator {
9292 private final Context ctx ;
9393 private final CelZ3TypeSystem typeSystem ;
9494 private final CelZ3OperatorTranslator operatorTranslator ;
95+ private final CelZ3TypeConstraintGenerator typeConstraintGenerator ;
9596 private final Map <String , TranslatedValue > symbolTable ;
9697 private final Set <BoolExpr > typeConstraints ;
9798 private final Map <Expr <?>, CelType > inputVariableTypes ;
@@ -265,7 +266,7 @@ private TranslatedValue translateIdent(CelExpr celExpr, CelAbstractSyntaxTree as
265266 // Basically prevents error being a counterexample of var == var
266267 typeConstraints .add (ctx .mkNot (typeSystem .isError (v )));
267268
268- BoolExpr constraint = createTypeConstraint (v , exprId , ast );
269+ BoolExpr constraint = typeConstraintGenerator . createTypeConstraint (v , exprId , ast );
269270 if (unknownIdentifiers .contains (name )) {
270271 typeConstraints .add (ctx .mkOr (typeSystem .isUnknown (v ), constraint ));
271272 } else {
@@ -582,7 +583,8 @@ private TranslatedValue translateWrapperWktStruct(
582583 }
583584 entryTvs .add (entryTv );
584585 }
585- BoolExpr validValue = createTypeConstraintForType (currentVal , targetType );
586+ BoolExpr validValue =
587+ typeConstraintGenerator .createTypeConstraintForType (currentVal , targetType );
586588 if (typeName .equals (CelTypes .INT32_WRAPPER_MESSAGE )) {
587589 IntExpr intVal = typeSystem .getInt (currentVal );
588590 validValue =
@@ -871,7 +873,7 @@ private TranslatedValue translateSelect(CelExpr celExpr, CelAbstractSyntaxTree a
871873
872874 Expr <?> fieldAccess = select .testOnly () ? typeSystem .wrapBool (presenceResult ) : valueResult ;
873875
874- typeConstraints .add (createTypeConstraint (fieldAccess , exprId , ast ));
876+ typeConstraints .add (typeConstraintGenerator . createTypeConstraint (fieldAccess , exprId , ast ));
875877
876878 TranslatedValue result =
877879 TranslatedValue .propagateStrict (
@@ -936,7 +938,8 @@ private TranslatedValue translateCall(CelExpr expr, CelAbstractSyntaxTree ast) {
936938
937939 Expr <?>[] exprArgs = args .stream ().map (TranslatedValue ::z3Expr ).toArray (Expr []::new );
938940 Expr <?> callRes = ctx .mkApp (funcDecl , exprArgs );
939- typeConstraints .add (createTypeConstraint (callRes , exprId , ast ));
941+ typeConstraints .add (
942+ typeConstraintGenerator .createTypeConstraint (callRes , exprId , ast ));
940943 typeConstraints .add (ctx .mkNot (typeSystem .isUnknown (callRes )));
941944 typeConstraints .add (ctx .mkNot (typeSystem .isError (callRes )));
942945
@@ -1128,33 +1131,6 @@ private TranslatedValue translateDynamicComprehension(
11281131 }
11291132 }
11301133
1131- private BoolExpr getBoundedMapBijection (
1132- ArrayExpr mapPresence , SeqExpr <?> seq , ArithExpr lengthExpr ) {
1133- List <BoolExpr > constraints = new ArrayList <>();
1134- for (int i = 0 ; i < comprehensionUnrollLimit ; i ++) {
1135- for (int j = i + 1 ; j < comprehensionUnrollLimit ; j ++) {
1136- BoolExpr validPair = ctx .mkLt (ctx .mkInt (j ), lengthExpr );
1137- BoolExpr notEqual =
1138- ctx .mkNot (ctx .mkEq (ctx .mkNth (seq , ctx .mkInt (i )), ctx .mkNth (seq , ctx .mkInt (j ))));
1139- constraints .add (ctx .mkImplies (validPair , notEqual ));
1140- }
1141- }
1142-
1143- BoolExpr isNotTruncated = ctx .mkLe (lengthExpr , ctx .mkInt (comprehensionUnrollLimit ));
1144-
1145- ArrayExpr seqMap = ctx .mkConstArray (typeSystem .celValueSort (), ctx .mkFalse ());
1146- for (int i = 0 ; i < comprehensionUnrollLimit ; i ++) {
1147- seqMap =
1148- (ArrayExpr )
1149- ctx .mkITE (
1150- ctx .mkLt (ctx .mkInt (i ), lengthExpr ),
1151- ctx .mkStore (seqMap , ctx .mkNth (seq , ctx .mkInt (i )), ctx .mkTrue ()),
1152- seqMap );
1153- }
1154- constraints .add (ctx .mkImplies (isNotTruncated , ctx .mkEq (mapPresence , seqMap )));
1155- return CelZ3Exprs .mkAndFlattened (ctx , constraints );
1156- }
1157-
11581134 private <T > T withIterationScope (
11591135 CelComprehension comp ,
11601136 Expr <?> keyOrIndex ,
@@ -1469,161 +1445,6 @@ private static boolean isNotStrictlyFalseLoopCondition(CelComprehension comp, bo
14691445 return arg .identOrDefault ().name ().equals (comp .accuVar ());
14701446 }
14711447
1472- private BoolExpr createTypeConstraint (Expr <?> val , long exprId , CelAbstractSyntaxTree ast ) {
1473- CelType type =
1474- ast .getType (exprId )
1475- .orElseThrow (
1476- () -> new IllegalArgumentException ("Type not found for expr ID: " + exprId ));
1477- BoolExpr typeConstraint = createTypeConstraintForType (val , type );
1478- return ctx .mkOr (typeSystem .isErrorOrUnknown (val ), typeConstraint );
1479- }
1480-
1481- private BoolExpr createTypeConstraintForType (Expr <?> val , CelType type ) {
1482- if (type instanceof NullableType ) {
1483- NullableType nullableType = (NullableType ) type ;
1484- return ctx .mkOr (
1485- typeSystem .isNull (val ), createTypeConstraintForType (val , nullableType .targetType ()));
1486- }
1487- if (type instanceof OptionalType ) {
1488- BoolExpr isOpt = typeSystem .isOptional (val );
1489- CelType paramType = type .parameters ().get (0 );
1490- if (paramType .kind ().isDyn () || paramType .kind ().isTypeParam ()) {
1491- return isOpt ;
1492- }
1493- Expr <?> optRef = typeSystem .getOptionalRef (val );
1494- BoolExpr hasValue = typeSystem .optHasValue (optRef );
1495- Expr <?> optVal = typeSystem .getOptionalValue (optRef );
1496- BoolExpr optValNotError = ctx .mkNot (typeSystem .isError (optVal ));
1497- BoolExpr valConstraint = createTypeConstraintForType (optVal , paramType );
1498- return ctx .mkAnd (isOpt , ctx .mkImplies (hasValue , ctx .mkAnd (optValNotError , valConstraint )));
1499- }
1500- if (type .equals (SimpleType .BOOL )) {
1501- return typeSystem .isBool (val );
1502- }
1503- if (type .equals (SimpleType .INT )) {
1504- IntExpr unwrapped = typeSystem .getInt (val );
1505- return ctx .mkAnd (typeSystem .isInt (val ), ctx .mkNot (typeSystem .checkIntOverflow (unwrapped )));
1506- }
1507- if (type .equals (SimpleType .UINT )) {
1508- IntExpr unwrapped = typeSystem .getUint (val );
1509- return ctx .mkAnd (typeSystem .isUint (val ), ctx .mkNot (typeSystem .checkUintOverflow (unwrapped )));
1510- }
1511- if (type .equals (SimpleType .DOUBLE )) {
1512- return typeSystem .isDouble (val );
1513- }
1514- if (type .equals (SimpleType .STRING )) {
1515- return typeSystem .isString (val );
1516- }
1517- if (type .equals (SimpleType .BYTES )) {
1518- return typeSystem .isBytes (val );
1519- }
1520- if (type .equals (SimpleType .TIMESTAMP )) {
1521- IntExpr nanos = typeSystem .getTimestamp (val );
1522- return ctx .mkAnd (
1523- typeSystem .isTimestamp (val ), ctx .mkNot (typeSystem .checkTimestampOverflow (nanos )));
1524- }
1525- if (type .equals (SimpleType .DURATION )) {
1526- IntExpr nanos = typeSystem .getDuration (val );
1527- return ctx .mkAnd (
1528- typeSystem .isDuration (val ), ctx .mkNot (typeSystem .checkDurationOverflow (nanos )));
1529- }
1530-
1531- if (type instanceof ListType ) {
1532- // Constrain list elements using bounded unrolling up to comprehensionUnrollLimit rather
1533- // than Z3 forall quantifiers to prevent MBQI quantifier instantiation loops.
1534- // Assert: isList(val) ∧ for all unrolled 0 <= i < length: ¬isError(seq[i]) ∧
1535- // typeConstraint(seq[i]).
1536- BoolExpr isList = typeSystem .isList (val );
1537- CelType elemType = ((ListType ) type ).elemType ();
1538-
1539- Expr <?> listRef = typeSystem .getListRef (val );
1540- SeqExpr seq = typeSystem .getSeq (listRef );
1541- Expr length = ctx .mkLength (seq );
1542-
1543- List <BoolExpr > boundsAndTypes = new ArrayList <>();
1544- boundsAndTypes .add (isList );
1545- // Elements beyond the limit are unconstrained, which only matters if elemType is known.
1546- if (CelZ3OperatorTranslator .isStaticallyKnown (elemType )) {
1547- boundsAndTypes .add (
1548- ctx .mkImplies (
1549- ctx .mkGt (length , ctx .mkInt (comprehensionUnrollLimit )),
1550- typeSystem .hasTruncatedTypeConstraint (val )));
1551- }
1552- for (int i = 0 ; i < comprehensionUnrollLimit ; i ++) {
1553- IntExpr idx = ctx .mkInt (i );
1554- Expr elem = ctx .mkNth (seq , idx );
1555- BoolExpr validIndex = ctx .mkLt (idx , length );
1556- // Assert ¬isError(elem) as a domain invariant so Z3 never synthesizes an Error element in
1557- // list(dyn). For concrete types, this is already implied by createTypeConstraintForType.
1558- boundsAndTypes .add (ctx .mkImplies (validIndex , ctx .mkNot (typeSystem .isError (elem ))));
1559- BoolExpr elemConstraint = createTypeConstraintForType (elem , elemType );
1560- boundsAndTypes .add (ctx .mkImplies (validIndex , elemConstraint ));
1561- }
1562-
1563- return CelZ3Exprs .mkAndFlattened (ctx , boundsAndTypes );
1564- }
1565- if (type instanceof MapType ) {
1566- // Do NOT emit a for-all quantifier over map keys or values here.
1567- // Doing so forces MBQI into an infinite loop. Instead, constrain keys and values using
1568- // bounded unrolling over the key sequence up to comprehensionUnrollLimit.
1569- // Assert: isMap(val) ∧ for all unrolled 0 <= i < length: isPrimitiveKey(key) ∧ ¬isError(key)
1570- // ∧ (presence(key) ⇒ ¬isError(val) ∧ typeConstraint(val))
1571- BoolExpr isMap = typeSystem .isMap (val );
1572- MapType mapType = (MapType ) type ;
1573- CelType keyType = mapType .keyType ();
1574- CelType valType = mapType .valueType ();
1575-
1576- Expr <?> mapRef = typeSystem .getMapRef (val );
1577- SeqExpr seq = typeSystem .getMapKeys (mapRef );
1578- Expr length = ctx .mkLength (seq );
1579- ArrayExpr mapValues = (ArrayExpr ) typeSystem .getMapValues (mapRef );
1580- ArrayExpr mapPresence = (ArrayExpr ) typeSystem .getMapPresence (mapRef );
1581-
1582- List <BoolExpr > boundsAndTypes = new ArrayList <>();
1583- boundsAndTypes .add (isMap );
1584- boundsAndTypes .add (getBoundedMapBijection (mapPresence , seq , (ArithExpr ) length ));
1585- // Entries beyond the limit are unconstrained, which matters if keyType or valType is known.
1586- if (CelZ3OperatorTranslator .isStaticallyKnown (keyType )
1587- || CelZ3OperatorTranslator .isStaticallyKnown (valType )) {
1588- boundsAndTypes .add (
1589- ctx .mkImplies (
1590- ctx .mkGt (length , ctx .mkInt (comprehensionUnrollLimit )),
1591- typeSystem .hasTruncatedTypeConstraint (val )));
1592- }
1593-
1594- for (int i = 0 ; i < comprehensionUnrollLimit ; i ++) {
1595- IntExpr idx = ctx .mkInt (i );
1596- Expr key = ctx .mkNth (seq , idx );
1597- BoolExpr validIndex = ctx .mkLt (idx , length );
1598-
1599- BoolExpr isKeyPrim = typeSystem .isPrimitiveKey (key );
1600- BoolExpr keyNotError = ctx .mkNot (typeSystem .isError (key ));
1601- // Assert isKeyPrim ∧ ¬isError(key) so Z3 never synthesizes a non-primitive or Error key in
1602- // map(dyn, ...). For concrete map types, this is already implied by keyType constraints.
1603- boundsAndTypes .add (ctx .mkImplies (validIndex , ctx .mkAnd (isKeyPrim , keyNotError )));
1604- boundsAndTypes .add (ctx .mkImplies (validIndex , createTypeConstraintForType (key , keyType )));
1605-
1606- BoolExpr presence = (BoolExpr ) ctx .mkSelect (mapPresence , key );
1607- BoolExpr validEntry = ctx .mkAnd (validIndex , presence );
1608-
1609- Expr mapVal = ctx .mkSelect (mapValues , key );
1610- BoolExpr valNotError = ctx .mkNot (typeSystem .isErrorOrUnknown (mapVal ));
1611- boundsAndTypes .add (ctx .mkImplies (validEntry , valNotError ));
1612- boundsAndTypes .add (ctx .mkImplies (validEntry , createTypeConstraintForType (mapVal , valType )));
1613- }
1614-
1615- return CelZ3Exprs .mkAndFlattened (ctx , boundsAndTypes );
1616- }
1617- if (type .kind () == CelKind .STRUCT ) {
1618- return ctx .mkAnd (
1619- typeSystem .isMessage (val ),
1620- ctx .mkEq (
1621- typeSystem .getMsgTypeName (typeSystem .getMessageRef (val )), ctx .mkString (type .name ())));
1622- }
1623- // Fallback: no type constraint
1624- return ctx .mkTrue ();
1625- }
1626-
16271448 private static final class IterationElement {
16281449 private final Expr <?> keyOrIndex ;
16291450 private final Expr <?> value ;
@@ -1683,12 +1504,14 @@ private Expr<?> mkParameterizedUnknown(CelExpr expr, CelAbstractSyntaxTree ast)
16831504 this .typeSystem = new CelZ3TypeSystem (ctx , parameterizeUnknowns );
16841505 this .typeConstraints = new LinkedHashSet <>();
16851506 this .inputVariableTypes = new HashMap <>();
1507+ this .typeConstraintGenerator =
1508+ new CelZ3TypeConstraintGenerator (ctx , typeSystem , comprehensionUnrollLimit );
16861509 this .operatorTranslator =
16871510 new CelZ3OperatorTranslator (
16881511 ctx ,
16891512 typeSystem ,
16901513 this .typeConstraints ::add ,
1691- this ::createTypeConstraintForType ,
1514+ typeConstraintGenerator ::createTypeConstraintForType ,
16921515 val -> inputVariableTypes .getOrDefault (val , SimpleType .DYN ),
16931516 functionRegistry ,
16941517 comprehensionUnrollLimit );
0 commit comments