|
37 | 37 | import dev.cel.common.types.CelKind; |
38 | 38 | import dev.cel.common.types.CelType; |
39 | 39 | import dev.cel.common.types.CelTypeProvider; |
| 40 | +import dev.cel.common.types.CelTypes; |
40 | 41 | import dev.cel.common.types.ListType; |
41 | 42 | import dev.cel.common.types.MapType; |
42 | 43 | import dev.cel.common.types.NullableType; |
@@ -79,6 +80,7 @@ final class CelAstToZ3Translator { |
79 | 80 | private static final String EMPTY_MSG_REF_PREFIX = "!empty_msg_ref_"; |
80 | 81 | private static final String EMPTY_LIST_PREFIX = "!empty_list"; |
81 | 82 | private static final String EMPTY_MAP_PREFIX = "!empty_map"; |
| 83 | + private static final String NULL_VALUE_FIELD = "null_value"; |
82 | 84 | private final Context ctx; |
83 | 85 | private final CelZ3TypeSystem typeSystem; |
84 | 86 | private final CelZ3OperatorTranslator operatorTranslator; |
@@ -370,6 +372,10 @@ private TranslatedValue translateMap(CelExpr celExpr, CelAbstractSyntaxTree ast) |
370 | 372 |
|
371 | 373 | private TranslatedValue translateStruct(CelExpr celExpr, CelAbstractSyntaxTree ast) { |
372 | 374 | CelExpr.CelStruct createStruct = celExpr.struct(); |
| 375 | + if (isJsonWkt(createStruct.messageName())) { |
| 376 | + return translateJsonWktStruct(celExpr, createStruct, ast); |
| 377 | + } |
| 378 | + |
373 | 379 | // Bypass SMT when the struct is empty (return the cached SMT default pointer) |
374 | 380 | if (createStruct.entries().isEmpty()) { |
375 | 381 | return TranslatedValue.create( |
@@ -448,6 +454,64 @@ private TranslatedValue translateStruct(CelExpr celExpr, CelAbstractSyntaxTree a |
448 | 454 | return TranslatedValue.propagateStrict(ctx, typeSystem, result, celExpr, elementsTv); |
449 | 455 | } |
450 | 456 |
|
| 457 | + private static boolean isJsonWkt(String messageName) { |
| 458 | + return messageName.equals(CelTypes.VALUE_MESSAGE) |
| 459 | + || messageName.equals(CelTypes.LIST_VALUE_MESSAGE) |
| 460 | + || messageName.equals(CelTypes.STRUCT_MESSAGE); |
| 461 | + } |
| 462 | + |
| 463 | + private BoolExpr createJsonWktTypeConstraint(String messageName, Expr<?> val) { |
| 464 | + if (messageName.equals(CelTypes.VALUE_MESSAGE)) { |
| 465 | + return ctx.mkTrue(); |
| 466 | + } |
| 467 | + if (messageName.equals(CelTypes.LIST_VALUE_MESSAGE)) { |
| 468 | + return typeSystem.isList(val); |
| 469 | + } |
| 470 | + return typeSystem.isMap(val); |
| 471 | + } |
| 472 | + |
| 473 | + // Concretize JSON WKT unwrapping directly into native Z3 primitives to avoid |
| 474 | + // sort incompatibilities (Message == String) and solver performance penalties (quantifiers). |
| 475 | + private TranslatedValue translateJsonWktStruct( |
| 476 | + CelExpr celExpr, CelExpr.CelStruct createStruct, CelAbstractSyntaxTree ast) { |
| 477 | + Expr<?> fallback; |
| 478 | + if (createStruct.messageName().equals(CelTypes.VALUE_MESSAGE)) { |
| 479 | + fallback = typeSystem.mkNull(); |
| 480 | + } else if (createStruct.messageName().equals(CelTypes.LIST_VALUE_MESSAGE)) { |
| 481 | + fallback = getDefaultValueForType(ListType.create(SimpleType.DYN)); |
| 482 | + } else { |
| 483 | + fallback = getDefaultValueForType(MapType.create(SimpleType.STRING, SimpleType.DYN)); |
| 484 | + } |
| 485 | + |
| 486 | + if (createStruct.entries().isEmpty()) { |
| 487 | + return TranslatedValue.create(fallback, celExpr, typeSystem, ctx.mkFalse()); |
| 488 | + } |
| 489 | + |
| 490 | + CelExpr.CelStruct.Entry entry = createStruct.entries().get(0); |
| 491 | + TranslatedValue entryTv; |
| 492 | + |
| 493 | + // null_value is a proto enum represented as integer 0 in the AST. |
| 494 | + // We must explicitly force it to CelNull to match runtime semantics. |
| 495 | + if (createStruct.messageName().equals(CelTypes.VALUE_MESSAGE) |
| 496 | + && entry.fieldKey().equals(NULL_VALUE_FIELD)) { |
| 497 | + entryTv = |
| 498 | + TranslatedValue.create(typeSystem.mkNull(), entry.value(), typeSystem, ctx.mkFalse()); |
| 499 | + } else { |
| 500 | + entryTv = translateExpr(entry.value(), ast); |
| 501 | + } |
| 502 | + |
| 503 | + Expr<?> finalVal = entryTv.z3Expr(); |
| 504 | + |
| 505 | + if (entry.optionalEntry()) { |
| 506 | + Expr<?> optRef = typeSystem.getOptionalRef(finalVal); |
| 507 | + BoolExpr hasValue = typeSystem.optHasValue(optRef); |
| 508 | + finalVal = ctx.mkITE(hasValue, typeSystem.getOptionalValue(optRef), fallback); |
| 509 | + } |
| 510 | + |
| 511 | + return TranslatedValue.propagateStrict( |
| 512 | + ctx, typeSystem, finalVal, celExpr, ImmutableList.of(entryTv)); |
| 513 | + } |
| 514 | + |
451 | 515 | private Expr<?> getDefaultValueForType(CelType type) { |
452 | 516 | if (type instanceof NullableType) { |
453 | 517 | return typeSystem.mkNull(); |
@@ -1244,6 +1308,9 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) { |
1244 | 1308 | return typeSystem.isMap(val); |
1245 | 1309 | } |
1246 | 1310 | if (type.kind() == CelKind.STRUCT) { |
| 1311 | + if (isJsonWkt(type.name())) { |
| 1312 | + return createJsonWktTypeConstraint(type.name(), val); |
| 1313 | + } |
1247 | 1314 | return ctx.mkAnd( |
1248 | 1315 | typeSystem.isMessage(val), |
1249 | 1316 | ctx.mkEq( |
|
0 commit comments