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

@@ -86,6 +86,7 @@ pub const Node = struct {
function_type_expr: FunctionTypeExpr,
closure_type_expr: ClosureTypeExpr,
tuple_type_expr: TupleTypeExpr,
return_type_expr: ReturnTypeExpr,
tuple_literal: TupleLiteral,
ufcs_alias: UfcsAlias,
c_import_decl: CImportDecl,
@@ -867,6 +868,25 @@ pub const TupleTypeExpr = struct {
field_names: ?[]const []const u8, // null for positional
};
/// A bare-paren MULTI-RETURN signature `(A, B)` / `(x: A, y: B)` / `(A, B, !)`
/// (≥2 value slots, error always the LAST slot). A function with this return
/// returns MULTIPLE VALUES — a DISTINCT thing from one `Tuple(…)` value: it
/// reuses the tuple ABI under the hood (resolves to a `.tuple` TypeId), but is
/// valid ONLY as a function/closure return type (the general type resolver
/// rejects it anywhere else), and its result is consumed only by destructuring
/// (`a, b := f()`), never bound to a single value. Same shape as a tuple type so
/// the resolver can reuse the field-resolution path. The single-value `(T, !)`
/// (one value + error) is NOT this — it is a plain failable, `-> T !`.
pub const ReturnTypeExpr = struct {
field_types: []const *Node,
field_names: ?[]const []const u8, // null for positional
/// Per-slot default value expressions (`(sum: i32 = 0, good: bool)`), 1:1
/// with `field_types`; an entry is null when that slot has no default. null
/// (the whole field) when NO slot has a default. A defaulted named slot is
/// exempt from the must-set rule — the default seeds the slot local.
field_defaults: ?[]const ?*Node = null,
};
pub const TupleLiteral = struct {
elements: []const TupleElement,
// Explicit tuple type for the `Tuple(...).( ... )` typed-construction form