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:
33
specs.md
33
specs.md
@@ -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 }`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user