BUG: missing-field check on struct literals skipped inside module-declared files
Filer: repoman v0.1.1 implementation
Reef version: 0.5.18
Found: 2026-05-04
Severity: High — silent acceptance of incomplete struct literals; uninitialized fields cause runtime segfaults when read
Suggested ID: BUG-050
Summary
The typechecker's "missing field in struct literal" check fires correctly in single-file programs (reefc run foo.reef) but is silently skipped when the same struct literal is inside a file declared as module X. The build succeeds. At runtime, the missing field's memory is uninitialized — for string-typed fields this means a null/garbage pointer, which segfaults when the field is read.
This bit repoman v0.1.1 hard: we added a new field output: string to the Registry struct and forgot to update two existing Registry { ... } literals inside module config. Build passed, all 11 unit tests passed (because the unchanged code paths didn't read output), and the bug only surfaced during smoke-testing when serialize_registry (called by cmd_new's save) finally read the uninitialized output and segfaulted.
Reproducer
Single-file (correctly errors):
import core.result_generic as rg
type Bar = struct
a: int
b: string
c: int
end Bar
fn make_partial(o: Bar): rg.Result[Bar, string]
return @Result[Bar, string].Ok(Bar {
a: o.a,
c: o.c
})
end make_partial
proc main()
let r = make_partial(Bar { a: 1, b: "two", c: 3 })
if rg.is_ok(r)
println("ok")
end if
end main
reefc run repro_single.reef:
Type Error: Missing field 'b' in struct literal for 'Bar'
at repro_single.reef:line 10, column 35
Module-scoped (silently accepts):
Project layout:
project/
├── reef.toml (source_dirs = ["src"])
├── src/
│ ├── data.reef (defines Outer, returns partial literal)
│ └── main.reef (entry, imports data)
src/data.reef:
module data
import core.result_generic as rg
export
type Outer
fn make(seed: Outer): rg.Result[Outer, string]
end export
type Inner = struct
z: int
end Inner
type Outer = struct
schema: int
output: string
defaults: Inner
projects: [Inner]
end Outer
fn make(seed: Outer): rg.Result[Outer, string]
return @Result[Outer, string].Ok(Outer {
schema: seed.schema,
defaults: seed.defaults,
projects: seed.projects
})
end make
end module
src/main.reef:
import data
proc main()
println("noop")
end main
reefc build:
Building project: modtest
Entry point: src/main.reef
Build complete: build/modtest
No error. The Outer literal is missing output: string and the build succeeds. If the consumer code were extended to actually call make() and read the resulting Outer.output, it would dereference an uninitialized string pointer and segfault.
Repoman impact
repoman v0.1.1's add_project and update_last_sync in src/config.reef returned Registry literals missing the new output field. Build was clean, unit tests passed, smoke test on repoman new <name> segfaulted at the registry-write step. Quoted from our log:
==> incus restart veemarker
[1] 1503002 segmentation fault (core dumped) repoman new veemarker
The crash surfaced because config.save calls serialize_registry which reads reg.output. With uninitialized memory there, the string operation segfaults.
The fix in repoman was a one-character-per-site addition: output: reg.output, in both literals. But the typechecker should have caught this at compile time, not at runtime, and not after passing all unit tests.
We added regression tests to tests/test_config_mutate.reef that read reg.output after add_project and update_last_sync — those would have caught the bug if the typechecker doesn't. But "every codebase that adds a struct field must remember to add a regression test for that field across every literal site" is not a sustainable contract. The typechecker should enforce completeness.
Diagnosis
Two code paths likely diverge between single-file mode and module mode in the typechecker. Maybe:
- The single-file pass walks the AST and runs full struct-literal validation.
- The module pass either skips that validation pass entirely, or runs it before the struct definition is fully resolved (so all fields appear "known but not required").
The bug is path-dependent, not type-dependent — same struct, same literal, different validation behavior depending on whether the file declares a module. So I'd start by looking at any conditional in the typechecker that branches on "is this a module file" or "do we have an export block".
Suggested fix
Whatever validation logic is in the single-file path needs to also run for module-declared files. Likely a one- or two-line patch in the typechecker pass dispatcher (similar shape to BUG-037's "register generics for imported modules before scanning" fix from 0.5.10).
Suggested test
reef-compiler/tests/typecheck/test_struct_missing_field_module.reef — a two-file project (one with module X) where a struct literal in the module file is missing a field. Should fail to compile with Type Error: Missing field 'X' in struct literal for 'Y'.
Filed by the repoman v0.1.1 build flow.