fix(ir): reject typed module const whose initializer mismatches annotation [F0.7]
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.
This commit is contained in:
93
issues/0088-typed-module-const-annotation-mismatch.md
Normal file
93
issues/0088-typed-module-const-annotation-mismatch.md
Normal file
@@ -0,0 +1,93 @@
|
||||
> **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 mismatch` diagnostic, killing both symptoms (the `print(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, so `N : string : 4` registered as `{value = int 4, ty = string}`.
|
||||
> `emitModuleConst` then stamped the `int_literal` with the `string` type (a
|
||||
> bogus pointer → segfault at the use site), and `program_index.moduleConstInt`
|
||||
> folded the const into an integer COUNT by inspecting the `int_literal` node
|
||||
> alone, ignoring `ModuleConstInfo.ty` (so `[N]s64` folded to 4).
|
||||
>
|
||||
> **Fix per file.**
|
||||
> - `src/ir/lower.zig` — `registerTypedModuleConst` now validates the
|
||||
> initializer against the resolved annotation via the new `typedConstInitFits`
|
||||
> (arms mirror `emitModuleConst`'s faithful-emit precondition: int → int/float,
|
||||
> float → float, bool → bool, string → string, null → pointer/optional,
|
||||
> `---` → any). A mismatch emits `type 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). `literalKindName` names the literal kind for the
|
||||
> message.
|
||||
> - `src/ir/program_index.zig` — `moduleConstInt` / `moduleConstIntFramed` now
|
||||
> take the `TypeTable` and gate the fold on `isCountableConstType(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 in `lower.zig` and
|
||||
> `type_bridge.zig` updated.
|
||||
>
|
||||
> **Regression tests.**
|
||||
> - `examples/1143-diagnostics-typed-module-const-mismatch.sx` — negative: four
|
||||
> mismatch shapes (`int→string`, `string→s64`, `bool→s64`, `float→s64`) each
|
||||
> emit a `type mismatch` diagnostic, exit 1.
|
||||
> - `examples/0162-types-typed-module-const-roundtrip.sx` — positive: valid
|
||||
> typed consts (`s64` as count + printed, `f32` from int, `f32` float,
|
||||
> `string`, `*void` null) 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
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
N : string : 4;
|
||||
|
||||
main :: () {
|
||||
print("N={}\n", N);
|
||||
}
|
||||
```
|
||||
|
||||
Related count-surface manifestation:
|
||||
|
||||
```sx
|
||||
#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`.
|
||||
Reference in New Issue
Block a user