fix(ir): validate const-expression typed module-const initializers [F0.7]

Attempt 1 rejected only LITERAL initializers that mismatch a typed module
const's annotation; a const-EXPRESSION initializer escaped, so the same
issue-0088 root remained for `M :: 2; N : string : M + 2` — accepted at exit 0,
folding `[N]s64` to 4 and printing N as an integer.

Root cause: `registerTypedModuleConst` validated only the enumerated literal
node kinds; any other kind fell through to `else => {}`, and pass 0
pre-registers binary_op/unary_op consts as a `.s64` placeholder that was never
reconciled with the annotation.

Fix — validate by TYPE, not by node kind:
- lower.zig: `registerTypedModuleConst` now covers literals AND const-expressions
  (binary_op/unary_op) through one path. `typedConstInitFits` keeps the literal
  arms and routes any non-literal through the new `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 the `type mismatch` diagnostic
  (a const-expression is described by its inferred type, e.g. "an integer
  expression") and evicts the pass-0 placeholder; a match registers the const at
  its resolved annotation type (the same `put` the literal path always did), so a
  const-expression folds and emits at its declared type.
- `literalKindName` → `initializerDescription` (+ `constExprDescription`) so the
  message is accurate for both a literal and a const-expression initializer.

Regression:
- examples/1143: extended with `E : string : M + 2` and `V : string : -M`
  (const-expr mismatches → exit 1, pinned diagnostics).
- examples/0162: extended with `KE : s64 : M + 2` (used as a count + printed) and
  `WE : f32 : M + 2` (over-rejection guard — valid const-exprs still work).
- program_index.test.zig: count-gate test extended with a binary_op value node
  declared `string` (must not fold as a count).

Docs: specs.md §3 + readme.md generalized from "initializer literal" to cover
constant expressions; issues/0088 RESOLVED banner updated.
This commit is contained in:
agra
2026-06-05 07:51:16 +03:00
parent 156edf8e28
commit 454ea06bd4
9 changed files with 188 additions and 88 deletions

View File

@@ -11,31 +11,45 @@
> 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).
>
> **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.
> - `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 '<n>' is declared
> '<ty>' but its initializer is <desc>` 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: 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.
> - `examples/1143-diagnostics-typed-module-const-mismatch.sx` — negative: six
> mismatch shapes — four literal (`int→string`, `string→s64`, `bool→s64`,
> `float→s64`) and two const-expression (`M + 2 → string`, `-M → string`) —
> 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, plus const-expression `s64 : M + 2` used as a count + printed
> and `f32 : M + 2`) compile, fold, and print correctly.
> - `src/ir/program_index.test.zig` — `moduleConstInt gates the fold on the
> declared type, not the initializer node`.
> declared type, not the initializer node` (covers both a literal and a
> binary_op value node declared with a non-numeric type).
# 0088 — Typed module const annotation mismatch is accepted