fix: diagnose ?(?T) tuple-payload mismatch instead of malformed IR (issue 0165)
In type position (T) is a 1-tuple (specs.md:843), so ?(?i64) is optional(tuple(?i64)); assigning a bare ?i64 had coerceToType classify .none and pass the value through, then optionalWrap built a corrupt insertvalue that aborted the LLVM verifier. After coercing toward an optional's child, verify the coerced type equals the child type (stmt.zig decl-init + coerce.zig .optional_wrap); on mismatch emit a located diagnostic (tuple-specific note only when the child is a tuple). formatTypeName now renders tuples as (x: i64, y: i64). Regressions: optionals/0911 (nested optional via alias, round-trip), diagnostics/1195 (the mismatch diagnostic). Updated diagnostics/1101 + protocols/0414 goldens for the improved tuple type-name rendering. Verified by 3 adversarial reviews. Filed adjacent bug 0171 (?any child not canonicalized).
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// Out-of-range tuple index produces a clear
|
||||
// `error: field 'N' not found on type 'tuple'` diagnostic and exit 1.
|
||||
// `error: field 'N' not found on type '(i64, i64)'` diagnostic and exit 1.
|
||||
|
||||
main :: () -> i32 {
|
||||
t := (10, 20);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// `?(?i64)` is `optional` wrapping the SINGLE-FIELD TUPLE `(?i64)` — in type
|
||||
// position `(T)` is a 1-tuple, not a grouping (specs.md §"Tuple Types"). So
|
||||
// assigning a bare `?i64` value to a `?(?i64)` slot is a type mismatch: the
|
||||
// optional's payload is the tuple `(?i64)`, not `?i64`.
|
||||
//
|
||||
// Regression (issue 0165): this used to silently lower to a malformed
|
||||
// `insertvalue { {{i64,i1}}, i1 }` that aborted the LLVM verifier. It now
|
||||
// produces a clean diagnostic naming the payload type and pointing at the
|
||||
// parens-are-a-tuple gotcha. (To write a genuine nested optional, alias the
|
||||
// inner one: `Opt :: ?i64; x : ?Opt = ...` — see
|
||||
// examples/optionals/0911-nested-optional-via-alias.sx.)
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
inner : ?i64 = 5;
|
||||
outer : ?(?i64) = inner;
|
||||
print("unreachable\n");
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
error: field '42' not found on type 'tuple'
|
||||
error: field '42' not found on type '(i64, i64)'
|
||||
--> examples/diagnostics/1101-diagnostics-err-tuple-oob.sx:6:15
|
||||
|
|
||||
6 | return xx t.42;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: cannot assign a value of type '?i64' to optional '?(?i64)': its payload type is '(?i64)' (note: in type position '(T)' is a single-field tuple, not a grouping — write the inner optional without parentheses)
|
||||
--> examples/diagnostics/1195-diagnostics-err-parenthesized-optional-tuple.sx:16:3
|
||||
|
|
||||
16 | outer : ?(?i64) = inner;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
29
examples/optionals/0911-nested-optional-via-alias.sx
Normal file
29
examples/optionals/0911-nested-optional-via-alias.sx
Normal file
@@ -0,0 +1,29 @@
|
||||
// Nested optional `?(?i64)` written via a type alias — `Opt :: ?i64; ?Opt`.
|
||||
// The outer optional's payload is the inner optional, so the layout is the
|
||||
// well-formed double-wrap `{ {i64,i1}, i1 }`. Assigning an inner `?i64`,
|
||||
// unwrapping the outer to recover the inner `?i64`, and unwrapping that all
|
||||
// round-trip cleanly.
|
||||
//
|
||||
// Regression (issue 0165): the type-table interning always produced the
|
||||
// correct `{ {i64,i1}, i1 }` for a genuine nested optional — this locks that in.
|
||||
#import "modules/std.sx";
|
||||
|
||||
Opt :: ?i64;
|
||||
|
||||
main :: () {
|
||||
inner : ?i64 = 5;
|
||||
outer : ?Opt = inner; // ?(?i64) — payload is the inner optional
|
||||
|
||||
print("outer present: {}\n", outer != null);
|
||||
|
||||
mid := outer!; // unwrap outer -> ?i64
|
||||
print("mid present: {}\n", mid != null);
|
||||
print("value: {}\n", mid!); // unwrap inner -> 5
|
||||
|
||||
// null-coalesce through the recovered inner optional
|
||||
print("coalesced: {}\n", mid ?? 0);
|
||||
|
||||
// a none outer
|
||||
empty : ?Opt = null;
|
||||
print("empty present: {}\n", empty != null);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
outer present: true
|
||||
mid present: true
|
||||
value: 5
|
||||
coalesced: 5
|
||||
empty present: false
|
||||
@@ -1381,7 +1381,7 @@
|
||||
@str.1477 = private unnamed_addr constant [19 x i8] c"*__VL__i64__Vtable\00", align 1
|
||||
@str.1478 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1
|
||||
@str.1479 = private unnamed_addr constant [5 x i8] c"null\00", align 1
|
||||
@str.1480 = private unnamed_addr constant [7 x i8] c"*tuple\00", align 1
|
||||
@str.1480 = private unnamed_addr constant [11 x i8] c"*(VL__i64)\00", align 1
|
||||
@str.1481 = private unnamed_addr constant [4 x i8] c"@0x\00", align 1
|
||||
@str.1482 = private unnamed_addr constant [5 x i8] c"null\00", align 1
|
||||
@str.1483 = private unnamed_addr constant [21 x i8] c"**Combined__i64__i64\00", align 1
|
||||
@@ -16197,7 +16197,7 @@ if.else.1256: ; preds = %entry
|
||||
%loadN = load i64, ptr %allocaN, align 8
|
||||
%call = call { ptr, i64 } @int_to_hex_string(ptr %0, i64 %loadN)
|
||||
%callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.1481, i64 3 }, { ptr, i64 } %call)
|
||||
%callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.1480, i64 6 }, { ptr, i64 } %callN)
|
||||
%callN = call { ptr, i64 } @concat(ptr %0, { ptr, i64 } { ptr @str.1480, i64 10 }, { ptr, i64 } %callN)
|
||||
br label %if.merge.1257
|
||||
|
||||
if.merge.1257: ; preds = %if.else.1256, %if.then.1255
|
||||
|
||||
Reference in New Issue
Block a user