feat: multiple return values — bare-paren signatures, named returns, must-set, defaults

A function may return multiple values via a bare-paren return signature:
`-> (A, B)` / `-> (x: A, y: B)` / `-> (A, B, !)` (error always the last slot),
and `-> ()` is `void`. This is DISTINCT from a `Tuple(…)` value — return-position
only (a dedicated `ReturnTypeExpr` AST node resolving to a reused `.tuple`
TypeId); a parameter / field / variable annotation `x: (A, B)` is rejected. A
single-value `-> (T, !)` stays a plain failable (= `-> T !`).

Returns use the bare comma form `return a, b` / `return x = a, y = b` (no `.( … )`
literal). Consume by destructuring (`a, b := f()`) or single-bind + field access
(`c := f(); c.sum`); a failable bound value holds only the value slots (the error
stays on the `!` channel).

Named return slots are in-scope assignable locals; with no explicit `return` the
implicit return is synthesized from them. Path-sensitive definite-assignment
enforces the must-set rule, and a slot may carry a default that exempts it.
Validation rejects arity mismatches, out-of-slot-order named elements, a
slot/parameter name collision, a comma list from a single-value function, and a
multi-return signature used as a value type.

Examples 0202-0213; readme + specs updated. issues/0197 files a pre-existing
annotated-assignment type-check gap (`x: i32 = "hi"` segfaults) surfaced by the
adversarial review.
This commit is contained in:
agra
2026-06-27 12:31:23 +03:00
parent c94f878e7e
commit 76689a1ea6
65 changed files with 1236 additions and 48 deletions

View File

@@ -893,6 +893,39 @@ s := swap(1, 2); // s.0 = 2, s.1 = 1
t := wrap(42); // t.0 = 42
```
#### Multiple Return Values (bare-paren return signature)
A function may return **multiple values** with a bare-paren return signature
(≥2 value slots) — positional `-> (A, B)` or named `-> (x: A, y: B)`, with an
optional trailing `!` error channel as the **last** slot (`-> (A, B, !)`). The
empty `-> ()` is `void`. A multi-return is a DISTINCT construct from a `Tuple(…)`
VALUE: it is represented internally by a reused tuple TypeId (same ABI), but it
is valid ONLY in a function/closure return position — a parameter, field, or
variable annotation `x: (A, B)` is rejected (use `Tuple(…)` for a tuple value).
A single-value `-> (T, !)` (one value + error) is NOT a multi-return; it is
exactly the failable `-> T !`.
```sx
divmod :: (a: i64, b: i64) -> (i64, i64) { return a / b, a % b; }
stats :: (a: i32, b: i32) -> (sum: i32, big: bool) { return sum = a + b, big = a > b; }
```
- **`return` forms.** Bare comma list — `return a, b` (positional) or
`return x = a, y = b` (named, **in slot order**); no `.( … )` literal. A single
positional `return v` is the ordinary single-value return. The list arity must
match the value-slot count, and named elements must agree with the slots; a
mismatch is a compile error (never a silent wrong result).
- **Consumption.** Destructure (`q, r := divmod(…)`) or single-bind + field
access (`c := stats(…); c.sum`). For a failable multi-return the error rides
the separate `!` channel — a bound value (`c := f() catch … `) holds only the
value slots, never the error.
- **Named returns as locals.** Named slots are in-scope assignable locals;
assigning them all *is* the implicit return (no explicit `return` needed). A
slot may carry a default (`(sum: i32 = 0, good: bool)`), which seeds the local
and exempts it from the must-set rule. A slot that is not assigned on every
non-diverging path and has no default is a compile error (definite assignment),
and a slot name may not collide with a parameter name.
#### Representation
Tuples are represented as anonymous LLVM struct types (same layout as named structs). A tuple `Tuple(i64, i64)` has LLVM type `{ i64, i64 }`.