The generic-?? wrong-fallback was not in lowerNullCoalesce: coercing ?A -> ?B (differing payload, e.g. the ?i32->?i64 call-arg coercion when instantiating unwrap_or(99, ?i32)) routed through .optional_wrap, which unconditionally unwrapped the source and re-wrapped as ALWAYS-PRESENT, so a null became present-zero everywhere (args, returns, field init, var-decl, ??). Add a CoercionPlan.optional_to_optional (conversions.zig) + a presence-preserving arm in coerceMode (coerce.zig): has_value -> present: unwrap+coerce-child+wrap-present; absent: constNull(dst); merge via a dst_ty block param. lowerVarDecl gains a !src_is_optional guard so an annotated x : ?B = <?A> routes through the same arm (also makes aggregate-payload var-decl ?[3]i64->?[]i64 / ?Concrete->?Protocol work). Alias-optional struct-literal default already works (grouping + 0166); a 1-tuple default ?(i32,) ?? 5 now emits a clean diagnostic instead of an LLVM PHI abort (no implicit scalar->1-tuple coercion per spec). Regressions: optionals/0916 (generic ??), 0917 (alias struct default), 0918 (var-decl optional->optional), diagnostics/1202 (1-tuple default) + a conversions.test.zig unit test. Verified by 3 adversarial reviews, suite 798/0.
60 lines
2.3 KiB
Plaintext
60 lines
2.3 KiB
Plaintext
// Annotated local var-decl `x : ?B = <a ?A value>` preserves the SOURCE
|
|
// optional's presence across a payload coercion.
|
|
//
|
|
// Regression (issue 0180): `lowerVarDecl`'s optional-target branch hand-rolled
|
|
// the optional handling — for a non-null initializer it coerced to the
|
|
// optional's CHILD (classifying `?A → child_B` as an unconditional unwrap → 0
|
|
// for a null source) then re-wrapped with a hardcoded present has-bit. So a
|
|
// null `?A` source became a PRESENT `?B` carrying zero. The fix routes an
|
|
// optional source through the presence-preserving `.optional_to_optional`
|
|
// coercion (the general trailing coerce) instead of the wrap-present path,
|
|
// which now only handles a genuine non-optional `T → ?T` wrap.
|
|
#import "modules/std.sx";
|
|
|
|
Shape :: protocol { area :: (self: *Self) -> i64; }
|
|
Sq :: struct { s: i64; }
|
|
impl Shape for Sq { area :: (self: *Sq) -> i64 { return self.s * self.s; } }
|
|
|
|
main :: () {
|
|
// Widen ?i32 → ?i64: null stays absent, present carries its value.
|
|
a : ?i32 = null;
|
|
b : ?i64 = a;
|
|
if b { print("widen-null: present\n"); } else { print("widen-null: absent\n"); }
|
|
c : ?i32 = 7;
|
|
d : ?i64 = c;
|
|
print("widen-pres: {}\n", d ?? -1);
|
|
|
|
// Narrow ?i64 → ?i32.
|
|
e : ?i64 = null;
|
|
f : ?i32 = e;
|
|
if f { print("narrow-null: present\n"); } else { print("narrow-null: absent\n"); }
|
|
g : ?i64 = 9;
|
|
h : ?i32 = g;
|
|
print("narrow-pres: {}\n", h ?? -1);
|
|
|
|
// int → float ?i32 → ?f64.
|
|
i : ?i32 = null;
|
|
j : ?f64 = i;
|
|
if j { print("i2f-null: present\n"); } else { print("i2f-null: absent\n"); }
|
|
|
|
// A genuine non-optional wrap `T → ?T` stays present.
|
|
k : ?i64 = 5;
|
|
print("wrap: {}\n", k ?? -1);
|
|
|
|
// Aggregate payload: ?[3]i64 → ?[]i64 (presence-preserving array→slice).
|
|
arr : ?[3]i64 = .[10, 20, 30];
|
|
sl : ?[]i64 = arr;
|
|
if s := sl { print("arr2slice-pres: len={} first={}\n", s.len, s[0]); }
|
|
narr : ?[3]i64 = null;
|
|
nsl : ?[]i64 = narr;
|
|
if nsl { print("arr2slice-null: present\n"); } else { print("arr2slice-null: absent\n"); }
|
|
|
|
// Aggregate payload: ?Sq → ?Shape (presence-preserving protocol erasure).
|
|
sq : ?Sq = Sq.{ s = 4 };
|
|
sh : ?Shape = sq;
|
|
if x := sh { print("erase-pres: area={}\n", x.area()); }
|
|
nsq : ?Sq = null;
|
|
nsh : ?Shape = nsq;
|
|
if nsh { print("erase-null: present\n"); } else { print("erase-null: absent\n"); }
|
|
}
|