Files
sx/issues/0197-annotated-assignment-type-mismatch-no-check.md
agra 76689a1ea6 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.
2026-06-27 12:31:23 +03:00

2.7 KiB

0197 — annotated assignment with an incompatible type is unchecked (segfaults)

Symptom — A variable / constant declared with an explicit type annotation and an initializer of an INCOMPATIBLE type is accepted with no diagnostic; the value is passed through unchanged (a .none coercion plan), bit-mangling the slot and segfaulting at run time.

  • Observed: x : i32 = "hi"; compiles, then crashes (Segmentation fault).
  • Expected: a compile-time diagnostic — cannot initialize 'x' of type 'i32' with a value of type 'string' (or similar), exit code 1, no crash.

This is a GENERAL type-checking gap, not specific to any one feature. It was surfaced while reviewing the multi-return feature (a named-return slot default -> (sum: i32 = "hi", …) hit the same path; that site now has its own guard, but the underlying annotated-assignment hole remains).

Reproduction

#import "modules/std.sx";

main :: () -> i64 {
    x : i32 = "hi";        // string initializer for an i32 slot — no diagnostic
    print("{}\n", x);      // garbage, then SIGSEGV
    return 0;
}

./zig-out/bin/sx run repro.sx → prints garbage then Segmentation fault. ./zig-out/bin/sx ir repro.sx does NOT crash (it lowers fine) — the bad coercion blows up only at run time.

Investigation prompt

The annotated var/const-decl lowering stores the initializer into the slot WITHOUT checking that the initializer's type can actually reach the annotated type. The store goes through coerceToTypecoerceMode (src/ir/lower/coerce.zig:596,606), whose classifier (coercionResolver().classify, src/ir/conversions.zig:54) returns .none for an incompatible pair — and coerceMode's .no_op, .none => return val arm (coerce.zig ~614) then passes the value through unchanged, so a 16-byte string lands in a 4-byte i32 slot (and vice-versa), corrupting memory.

The fix likely belongs at the annotated var-decl / const-decl store sites (src/ir/lower/stmt.zig lowerVarDecl ~line 450, and the const-decl path) and anywhere else a value is stored into an explicitly-annotated slot: when classify(src_ty, dst_ty) == .none and src_ty != dst_ty, emit a diagnostic (self.diagnostics.addFmt(.err, span, "...", ...)) instead of silently coercing. (The multi-return default site already does exactly this — see the coercionResolver().classify(...) == .none guard in bindNamedReturnSlots, src/ir/lower/stmt.zig — that pattern can be lifted to a shared helper and reused at the assignment sites.)

Verification: ./zig-out/bin/sx run repro.sx should print a type-mismatch diagnostic and exit non-zero, NOT segfault. Add a examples/diagnostics/ or examples/types/ negative example once fixed.