> **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). > > Both LITERAL initializers (`N : string : 4`) and const-EXPRESSION initializers > (`M :: 2; N : string : M + 2`, `V : string : -M`) are rejected — the validation > is type-based, so a non-literal node kind can no longer escape it (attempt 2). > > **Mixed-numeric escape closed at the type-system root (attempt 3).** The > type-based validation reuses `inferExprType`, which inferred a non-comparison > binary op from its LHS alone — so `BAD : s64 : M + 0.5` (s64 + f64) inferred > `s64` and was accepted+truncated, while `0.5 + M` inferred `f64` and was > rejected: operand-order-dependent. The fix is in the binary-op arm of > `ExprTyper.inferType` (`src/ir/expr_typer.zig`): arithmetic / bitwise / shift > ops now infer the PROMOTED result of `(lhs, rhs)` via `Lowering.arithResultType` > — the same int×float → float rule `lowerBinaryOp` already applied for the > value (extracted from its inline block into a shared helper, so the two can't > diverge). `M + 0.5` now infers `f64` in either operand order, so the typed-const > validation rejects it against an `s64` annotation with no special-casing in the > validation logic itself. This was a pre-existing inference bug broader than > typed consts (it also mis-formatted `print("{}", M + 0.5)` as a truncated int); > the typed-LOCAL `y : s64 = 1.5` → 1 narrowing is a SEPARATE assignment-coercion > bug tracked as issue 0095. > > **Fix per file.** > - `src/ir/lower.zig` — `registerTypedModuleConst` validates the initializer > against the resolved annotation BY TYPE, covering literals AND > const-expressions (binary_op / unary_op) uniformly. `typedConstInitFits` > keeps the literal arms (int → int/float, float → float, bool → bool, > string → string, null → pointer/optional, `---` → any) and routes any > non-literal through `constExprInitFits`, which compares the initializer's > INFERRED type (`inferExprType`, the existing type-inference facility — no > second const evaluator) to the annotation with the same integer/float > compatibility. A mismatch emits `type mismatch: constant '' is declared > '' but its initializer is ` at the initializer span (a literal > names its kind; a const-expression is described by its inferred type, e.g. > "an integer expression"), and does NOT register the const — it evicts the > pass-0 placeholder so a count use can't still fold it. On a MATCH the const is > registered at its resolved annotation type (the same `put` the literal path > always did), so a const-expression folds and emits at its declared type. > - `src/ir/program_index.zig` — `moduleConstInt` / `moduleConstIntFramed` 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 — whether that node is a literal or a > foldable integer expression. Callers in `lower.zig` and `type_bridge.zig` > updated. > > **Regression tests.** > - `examples/1143-diagnostics-typed-module-const-mismatch.sx` — negative: eight > mismatch shapes — four literal (`int→string`, `string→s64`, `bool→s64`, > `float→s64`), two const-expression (`M + 2 → string`, `-M → string`), and two > mixed-numeric (`s64 : M + 0.5` and `s64 : 0.5 + M`, rejected in BOTH operand > orders) — 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, const-expression `s64 : M + 2` used as a count + printed, > `f32 : M + 2`, plus mixed-numeric `f64 : M + 0.5` and `f64 : 0.5 + M` folding > to 2.5 in both orders) compile, fold, and print correctly. > - `examples/0163-types-mixed-numeric-promotion.sx` — positive: pins the > inferExprType promotion DIRECTLY in a value context (`print("{}", n + 0.5)` > formats as the float `2.5`, both operand orders, across `+ - * /` and an f32 > operand; a pure-int expression stays an integer). > - `src/ir/program_index.test.zig` — `moduleConstInt gates the fold on the > declared type, not the initializer node` (covers both a literal and a > binary_op value node declared with a non-numeric type). > - `src/ir/expr_typer.test.zig` — `arithResultType promotes int×float to the > float regardless of operand order` (the shared promotion helper). # 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`.