# 0150 — a `void` struct field crashes the compiler (unsized-type SIGTRAP in LLVM) ## Status OPEN — surfaced by Stream B1 (fibers) B1.2: `Future(void)` (needed by `timeout(io, ms) -> Future(void)`) instantiates a struct with a `result: void` field, which hits this bug. Independent of the fibers work (a plain `struct { v: void; }` reproduces it standalone). ## Symptom Declaring or instantiating any struct that has a field of type `void` aborts the compiler with `SIGTRAP` (exit 133/134) — no sx diagnostic. The trap is LLVM's `llvm_unreachable("Cannot getTypeInfo() on a type that is unsized!")`: ``` libLLVM`llvm::DataLayout::getTypeSizeInBits + 912 brk #0x1 (EXC_BREAKPOINT) ``` Reached via `declareFunction` → `toLLVMType(func.ret)` when a function returns such a struct, or directly when laying out the struct. Observed: SIGTRAP, no output, no diagnostic. Expected: either zero-size the `void` field (a `void`/zero-sized field is a legitimate construct — cf. Zig) OR emit a clean type diagnostic ("a struct field may not have type `void`") — never a raw backend crash. ## Reproduction ```sx #import "modules/std.sx"; Holder :: struct { v: void; ok: bool; } main :: () -> i32 { h : Holder = .{ ok = true }; if h.ok { print("ok\n"); } return 0; } ``` `./zig-out/bin/sx run repro.sx` → SIGTRAP (exit 133), no output. Also reproduces through a generic: `Box :: struct($T: Type) { v: T; }` then `Box(void)` — i.e. any monomorphization that binds a struct field to `void`. ## Suspected area - `src/backend/llvm/types.zig` `toLLVMTypeInfo` (struct field loop ~line 111): a `void` field's LLVM type is the unsized `void` type, then `getTypeSizeInBits` on the enclosing struct traps. - The type layout / size code (`src/ir/types.zig` `typeSizeBytes` and the LLVM struct builder) should treat a `void` field as zero-sized (skip it in the LLVM struct, size 0, align 1) — the same way a zero-field struct is handled. ## Investigation prompt (paste into a fresh session) > A `void` struct field crashes the sx compiler with an unsized-type SIGTRAP in > LLVM `getTypeSizeInBits` (no diagnostic). Repro: `issues/0150-...` (run it → > exit 133). Decide the semantics: a `void` field should be ZERO-SIZED (preferred > — it is a legitimate construct, e.g. `Future(void).result`), laid out as > nothing (size 0, align 1) and OMITTED from the LLVM struct body; OR, if > zero-sized fields are out of scope, a clean front-end diagnostic ("a struct > field may not have type `void`, found in field `` of ``") before > emission — NEVER a backend trap. Likely sites: `src/backend/llvm/types.zig` > `toLLVMTypeInfo` (skip `void` fields when building the LLVM struct element > list) + `src/ir/types.zig` size/align (`typeSizeBytes`/align: a `void` field > contributes 0). If choosing the diagnostic route, add it where struct fields > are validated at type-resolution time. Verify: the repro prints `ok` (zero-size > route) or emits the diagnostic + clean exit 1 (diagnostic route); then move the > repro into `examples/` as a regression test. ## Why this matters for B1 (fibers) `Future($R)` with `$R = void` is the natural shape for `timeout(io, ms) -> Future(void)` (B1.2 spec) and for any future-of-no-value. B1.2 deferred `timeout` pending this fix rather than route around it with a substitute non-void shape (which would hide the bug). Once 0150 lands, re-add `timeout` with `Future(void)` (see the saved WIP at `.sx-tmp/b12-wip/io.sx`).