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:
@@ -314,7 +314,18 @@ pub fn lowerVarDecl(self: *Lowering, vd: *const ast.VarDecl) void {
|
||||
// fail LLVM verification (issue 0160).
|
||||
if (!ty.isBuiltin()) {
|
||||
const ty_info = self.module.types.get(ty);
|
||||
if (ty_info == .optional and val.data != .null_literal and self.builder.getRefType(ref) != ty) {
|
||||
// Is the initializer value ITSELF an optional (`?A`)? If so the
|
||||
// target `?B` is a presence-PRESERVING coercion (`.optional_to_optional`),
|
||||
// NOT a wrap-present. The manual unwrap-to-child + `optionalWrap(present)`
|
||||
// below classifies `?A → child_B` as an unconditional unwrap (→ 0 for a
|
||||
// null source) then wraps it as always-present, so a null `?A` becomes a
|
||||
// present `?B` carrying zero (issue 0180). Route an optional source through
|
||||
// the trailing general `coerceToType(?A → ?B)` instead, which dispatches to
|
||||
// the presence-preserving arm. The wrap-present path below stays correct for
|
||||
// a non-optional source `T → ?T`.
|
||||
const ref_ty0 = self.builder.getRefType(ref);
|
||||
const src_is_optional = !ref_ty0.isBuiltin() and self.module.types.get(ref_ty0) == .optional;
|
||||
if (ty_info == .optional and val.data != .null_literal and ref_ty0 != ty and !src_is_optional) {
|
||||
// Coerce to the optional's CHILD first (e.g. an array value
|
||||
// into a `?[]T` promotes array→slice), THEN wrap — wrapping
|
||||
// the raw value would store e.g. array bits into the slice
|
||||
|
||||
Reference in New Issue
Block a user