Files
sx/issues/0200-named-generic-multi-return-implicit-return.md
agra b322dcfe61 fix: type-safe stores + Any unbox/eq; finish multi-return deferrals
Type-checking gaps (segfault/corruption → compile errors):

- 0197: reject a store into an annotated slot whose value has no modeled
  coercion AND a different byte width (a 16-byte string into a 4-byte i32
  overran the slot and segfaulted). New checkAssignable / noneReinterpretIsUnsafe
  (coerce.zig, width via the LLVM-accurate typeSizeBytes) wired into every store
  site: var/const-decl, single + multi assignment (identifier/field/index/
  element/deref), named-return defaults. Same-width reinterpretations (*T→[*]T,
  i64→isize, fn-ref) and explicit xx/cast stay allowed; cascades suppressed via
  externalErrorsExist. Examples 1205, 1206.
- 0198: an implicit `Any → T` unbox is now a compile error (it blindly
  reinterpreted the boxed payload — silent garbage for a wrong scalar, a segfault
  for an aggregate). xx and compiler-generated match/pack unboxes are unaffected.
  Example 1207.
- 0199: `Any == <concrete>` (one operand Any) aborted the LLVM verifier — the
  comparison arm now fires when either operand is Any, boxing the concrete side
  first. Example 0654.

Multi-return deferrals (PLAN-MULTIRET #6 + named-order + D3 + generic):

- Reorder named return elements by name instead of requiring slot order; error on
  unknown/duplicate/missing (value-only AND full-failable-tuple forms). Examples
  0210, 0214.
- Reject a bare-paren (A, B) multi-return signature in generic-arg position
  (return-position-only). Example 0215.
- Multi-return closure types / lambda literals work via the reused tuple
  machinery (destructure, single-bind+field, lambda arg). Example 0216.
- Generic multi-return: positional works (0217); 0200: the named-slot
  implicit-return form now works for generic free fns + struct methods —
  monomorphizeFunction now calls bindNamedReturnSlots. Example 0218.

readme.md documents the annotated-store coercion rule; CHECKPOINT-MULTIRET.md
updated. Full corpus green (850/0).
2026-06-27 17:28:27 +03:00

79 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
> **RESOLVED** (2026-06-27). Root cause exactly as hypothesized: the generic
> monomorph path `monomorphizeFunction` (`src/ir/lower/generic.zig`) bound params
> and lowered the body via `lowerValueBody`, but NEVER called
> `bindNamedReturnSlots` — so `named_return_names` stayed null and the
> implicit-return synthesis (`lowerValueBody`, stmt.zig) didn't fire. (The
> non-generic decl path `lowerFunctionBodyInto` already called it.) Fix: call
> `bindNamedReturnSlots(fd, ret_ty, &scope)` in `monomorphizeFunction` after
> param-binding, with the same `named_return_names`/`named_return_defaults`
> save/restore. Covers generic free functions AND generic struct methods (the
> instance-method path shares the monomorph), with defaults and the failable
> error channel. Regression test: `examples/types/0218-types-multi-return-generic-named.sx`.
# 0200 — named-return locals don't synthesize the implicit return in a GENERIC multi-return function
**Symptom** — A generic function with a NAMED multi-return (`-> (first: $T, second: $U)`)
that relies on the implicit return (assigns the named slot locals, no explicit
`return`) fails to compile: the named-return-locals synthesis does not fire for
the monomorphized instance, so it reports "body produces no value".
- Observed: `pair :: (a: $T, b: $U) -> (first: T, second: U) { first = a; second = b; }`
`error: function returns '(first: i64, second: bool)' but its body produces
no value — end it with a trailing expression (no ';') or an explicit 'return'`.
- Expected: the named slot locals (`first`, `second`) are bound and the implicit
return is synthesized from them, exactly as for a NON-generic named
multi-return.
Note the diagnostic shows the return type RESOLVED to concrete types
(`(first: i64, second: bool)`) — so binding/return-type resolution ran; only the
named-return-LOCALS path (`bindNamedReturnSlots``self.named_return_names`) did
not take effect for the generic instance.
WORKS (so this is narrow): the POSITIONAL generic multi-return with an explicit
return is fine — `(a: $T, b: $U) -> (T, U) { return a, b; }` and explicit-type
`pair(i32, bool, 7, true)` both run correctly. Only the named-slot IMPLICIT-return
form × generic monomorph is broken. Workaround: use an explicit `return a, b`.
## Reproduction
```sx
#import "modules/std.sx";
pair :: (a: $T, b: $U) -> (first: T, second: U) {
first = a;
second = b; // implicit return from named slots — never synthesized
}
main :: () -> i64 {
x, y := pair(7, true);
print("{} {}\n", x, y);
return 0;
}
```
`./zig-out/bin/sx run repro.sx` → the "produces no value" error, exit 1.
## Investigation prompt
The implicit-return-from-named-slots synthesis (`lowerValueBody` in
`src/ir/lower/stmt.zig` ~line 172: `if (self.named_return_names) |names| { … }`)
only fires when `self.named_return_names` is set by `bindNamedReturnSlots`
(`src/ir/lower/stmt.zig` ~258). That binder is called from `lowerFunctionBodyInto`
(`src/ir/lower/decl.zig:2729`). `bindNamedReturnSlots` early-returns unless
`fd.return_type.?.data == .return_type_expr`.
The likely cause: the generic-FREE-function monomorph lowers the instance with a
SUBSTITUTED return-type node (the `$T`/`$U` resolved into a concrete
`tuple_type_expr` or a resolved TypeId), so `fd.return_type.data` is no longer
`.return_type_expr``bindNamedReturnSlots` early-returns → `named_return_names`
stays null → the implicit return isn't synthesized. Confirm by checking the
generic free-function instantiation path (search `instantiateGeneric` /
`lazyLowerFunction` / the monomorph that rewrites `fd` for free functions): does
it preserve the original `ReturnTypeExpr` AST node (binding via `type_bindings`),
or rewrite it? The fix likely keys `bindNamedReturnSlots` off the ORIGINAL
template `fd.return_type` (which carries `field_names`), or threads the
field-names through the monomorph. Generic STRUCT methods may have the same gap —
test `Box(T)` with a named multi-return method.
Verify: the repro prints `7 true`, exit 0. Add a positive generics example.