A typed module-level constant whose initializer did not match its annotation was silently accepted: `N : string : 4` compiled, then `print(N)` segfaulted (an integer emitted as a `string` const → a bogus pointer) and `[N]s64` folded `N` to 4 as an integer count. Issue 0088. Root cause: `registerTypedModuleConst` stored the annotation type but never validated the initializer literal against it, and `program_index.moduleConstInt` folded a const into a count by inspecting the initializer node alone, ignoring `ModuleConstInfo.ty`. Fix at the declaration (kills both symptoms): - lower.zig: `registerTypedModuleConst` now validates the initializer via `typedConstInitFits` (arms mirror `emitModuleConst`'s faithful-emit precondition: int→int/float, float→float, bool→bool, string→string, null→pointer/optional, `---`→any). A mismatch emits a `type mismatch` diagnostic at the initializer span and does not register the const (also evicting the pass-0 placeholder). Not routed through `coercionResolver().classify`: that runtime-coercion planner is unsound here (null's natural type is void → false-rejects `*T`; bool is 1 bit → false-accepts s64). - program_index.zig: `moduleConstInt` now takes the `TypeTable` and gates the fold on `isCountableConstType(ci.ty)` (integer of any width, or a float), so a non-numeric typed const can never fold into a count off its initializer node. Callers in lower.zig and type_bridge.zig updated. Regression: - examples/1143-diagnostics-typed-module-const-mismatch.sx (negative, exit 1) - examples/0162-types-typed-module-const-roundtrip.sx (positive) - program_index.test.zig: gate-on-declared-type unit test Docs: specs.md §3 Constant Binding + readme.md note the compatibility rule.
4.2 KiB
RESOLVED (F0.7) — A typed module-level constant whose initializer does not match its annotation is now rejected at the declaration with a clear
type mismatchdiagnostic, killing both symptoms (theprint(N)segfault and the[N]s64→ 4 fold).Root cause.
registerTypedModuleConst(src/ir/lower.zig) stored the annotation type on the const but never checked the initializer literal against it, soN : string : 4registered as{value = int 4, ty = string}.emitModuleConstthen stamped theint_literalwith thestringtype (a bogus pointer → segfault at the use site), andprogram_index.moduleConstIntfolded the const into an integer COUNT by inspecting theint_literalnode alone, ignoringModuleConstInfo.ty(so[N]s64folded to 4).Fix per file.
src/ir/lower.zig—registerTypedModuleConstnow validates the initializer against the resolved annotation via the newtypedConstInitFits(arms mirroremitModuleConst's faithful-emit precondition: int → int/float, float → float, bool → bool, string → string, null → pointer/optional,---→ any). A mismatch emitstype mismatch: constant '<n>' is declared '<ty>' but its initializer is <kind>at the initializer span and does NOT register the const (it also evicts the pass-0 placeholder so a count use can't still fold it).literalKindNamenames the literal kind for the message.src/ir/program_index.zig—moduleConstInt/moduleConstIntFramednow take theTypeTableand gate the fold onisCountableConstType(ci.ty)(integer of any width, or a float), so a non-numeric typed const can never be folded into a count off its initializer node. Callers inlower.zigandtype_bridge.zigupdated.Regression tests.
examples/1143-diagnostics-typed-module-const-mismatch.sx— negative: four mismatch shapes (int→string,string→s64,bool→s64,float→s64) each emit atype mismatchdiagnostic, exit 1.examples/0162-types-typed-module-const-roundtrip.sx— positive: valid typed consts (s64as count + printed,f32from int,f32float,string,*voidnull) compile, fold, and print correctly.src/ir/program_index.test.zig—moduleConstInt gates the fold on the declared type, not the initializer node.
0088 — Typed module const annotation mismatch is accepted
Symptom
A module-level typed constant whose initializer does not match its annotation is
accepted. Observed: N : string : 4 compiles; printing N segfaults, and using
N as an array dimension folds it as 4. Expected: the const declaration emits
a type-mismatch diagnostic and no downstream use treats it as a valid string or
integer count.
Reproduction
#import "modules/std.sx";
N : string : 4;
main :: () {
print("N={}\n", N);
}
Related count-surface manifestation:
#import "modules/std.sx";
N : string : 4;
main :: () {
a : [N]s64 = ---;
print("{}\n", a.len);
}
Observed on flow/sx-foundation/F0.4 attempt 10: the first repro segfaults in
the generated program; the second prints 4.
Investigation prompt
Fix issue 0088: typed module constants must validate/coerce their initializer
against the explicit annotation before being registered or used. Suspected area:
src/ir/lower.zig, especially registerTypedModuleConst, lowerExpr's
module-const identifier path, and any const-declaration lowering that stores
ProgramIndex.module_const_map entries. src/ir/program_index.zig's
moduleConstInt currently folds by inspecting the initializer node and ignores
ModuleConstInfo.ty; after the declaration is diagnosed or represented
correctly, a non-integer typed const such as N : string : 4 must not become a
valid count. Likely fix: add a typed-const validation path that emits a clear
diagnostic for incompatible initializer/annotation pairs, and ensure the
module-const count lookup only accepts constants whose declared/inferred type is
numeric and integral-compatible. Verify by running the two repros above: expect
a non-zero compile with a type-mismatch diagnostic for N : string : 4, no
runtime segfault, and no [N] length of 4.