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:
@@ -312,6 +312,16 @@ pub const Lowering = struct {
|
||||
/// Cleared per function body (the `Ref` space is per-function).
|
||||
narrowed_refs: std.AutoHashMap(Ref, void) = undefined,
|
||||
force_block_value: bool = false, // set by lowerBlockValue to extract if-else values
|
||||
// Set while lowering a NAMED multi-return function body (`-> (x: A, y: B)`):
|
||||
// the slot names (1:1 with the return tuple's fields; a trailing "!" marks
|
||||
// the failable error slot). The slots are bound as in-scope assignable locals;
|
||||
// at end-of-body with no explicit `return`, `lowerValueBody` synthesizes the
|
||||
// implicit return from them (must-set rule: an unset, undefaulted slot errors).
|
||||
named_return_names: ?[]const []const u8 = null,
|
||||
// Per-slot default exprs (1:1 with the return tuple's fields; null where the
|
||||
// slot has none). A defaulted named-return slot is seeded with its default
|
||||
// and exempt from the must-set rule.
|
||||
named_return_defaults: ?[]const ?*const ast.Node = null,
|
||||
block_terminated: bool = false, // set when constant-folded if emits a return/br into current block
|
||||
in_lambda_body: bool = false, // true while lowering a closure-literal body; sharpens the `raise`-not-failable diagnostic (ERR E5.1: tell the user to annotate `-> (T, !)`)
|
||||
defer_stack: std.ArrayList(CleanupEntry) = std.ArrayList(CleanupEntry).empty, // block-scoped defer + onfail cleanup stack
|
||||
@@ -646,6 +656,12 @@ pub const Lowering = struct {
|
||||
|
||||
pub fn resolveReturnType(self: *Lowering, fd: *const ast.FnDecl) TypeId {
|
||||
if (fd.return_type) |rt| {
|
||||
// A bare-paren multi-return signature `(A, B)` is valid HERE (return
|
||||
// position); it resolves to its reused tuple TypeId. Misuse as a VALUE
|
||||
// type (a param / field / var annotation) is rejected at those sites
|
||||
// (`resolveParamType` et al.), not in the common resolver — return
|
||||
// types are re-resolved in many places (call-result typing, protocol
|
||||
// impls) that a central reject would wrongly trip.
|
||||
return self.resolveTypeWithBindings(rt);
|
||||
}
|
||||
// No explicit annotation — the type is inferred from the body, which
|
||||
@@ -715,6 +731,19 @@ pub const Lowering = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// A bare-paren `(A, B)` multi-return SIGNATURE is valid only as a
|
||||
/// function/closure return type — never as a VALUE type (a parameter /
|
||||
/// variable / field annotation), where a tuple value uses `Tuple(…)`. Emits a
|
||||
/// diagnostic and returns true when `node` is a `ReturnTypeExpr`. (`what` names
|
||||
/// the offending position, e.g. "parameter" / "variable" / "field".)
|
||||
pub fn rejectMultiReturnValueType(self: *Lowering, node: *const ast.Node, what: []const u8) bool {
|
||||
if (node.data != .return_type_expr) return false;
|
||||
if (self.diagnostics) |d| {
|
||||
d.addFmt(.err, node.span, "a bare-paren `(A, B)` is a multi-return signature, valid only as a return type; a tuple-valued {s} uses `Tuple(…)`", .{what});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn resolveParamType(self: *Lowering, p: *const ast.Param) TypeId {
|
||||
// A plain value param with no annotation can only be typed from
|
||||
// context (a lambda's target closure signature). When `resolveParamType`
|
||||
@@ -728,6 +757,9 @@ pub const Lowering = struct {
|
||||
}
|
||||
return .unresolved;
|
||||
}
|
||||
// A bare-paren `(A, B)` is a MULTI-RETURN signature, valid only as a
|
||||
// return type — not a parameter value type (use `Tuple(…)`).
|
||||
if (self.rejectMultiReturnValueType(p.type_expr, "parameter")) return .unresolved;
|
||||
const declared_ty = self.resolveTypeWithBindings(p.type_expr);
|
||||
if (p.is_variadic) {
|
||||
// Two surface forms:
|
||||
@@ -1834,6 +1866,9 @@ pub const Lowering = struct {
|
||||
pub const lowerInlineBranch = lower_stmt.lowerInlineBranch;
|
||||
pub const lowerBlockValue = lower_stmt.lowerBlockValue;
|
||||
pub const lowerValueBody = lower_stmt.lowerValueBody;
|
||||
pub const bindNamedReturnSlots = lower_stmt.bindNamedReturnSlots;
|
||||
pub const synthesizeNamedReturn = lower_stmt.synthesizeNamedReturn;
|
||||
pub const validateMultiReturn = lower_stmt.validateMultiReturn;
|
||||
pub const tryLowerAsExpr = lower_stmt.tryLowerAsExpr;
|
||||
pub const lowerStmt = lower_stmt.lowerStmt;
|
||||
pub const lowerVarDecl = lower_stmt.lowerVarDecl;
|
||||
|
||||
Reference in New Issue
Block a user