fix: presence-preserving optional->optional coercion (issue 0180)
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.
This commit is contained in:
@@ -48,6 +48,13 @@ test "conversions: classify covers the built-in coercion ladder" {
|
||||
try std.testing.expectEqual(Plan.optional_unwrap, cr.classify(opt_i64, .i64));
|
||||
try std.testing.expectEqual(Plan.void_to_optional, cr.classify(.void, opt_i64));
|
||||
|
||||
// `?A → ?B` (differing payloads) is a presence-preserving payload coercion,
|
||||
// NOT the always-present unwrap-then-rewrap that the `.optional_wrap` arm
|
||||
// produced (issue 0180). Same-payload optionals are `.no_op`.
|
||||
const opt_i32 = tt.optionalOf(.i32);
|
||||
try std.testing.expectEqual(Plan.optional_to_optional, cr.classify(opt_i32, opt_i64));
|
||||
try std.testing.expectEqual(Plan.no_op, cr.classify(opt_i64, opt_i64));
|
||||
|
||||
// `?T → bool` is NOT an unwrap-then-narrow presence test (issue 0169):
|
||||
// it must reject, never silently produce `false`. But `?bool → bool`
|
||||
// is a genuine unwrap of a bool payload.
|
||||
|
||||
Reference in New Issue
Block a user