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

@@ -1,21 +1,29 @@
// Valid typed module-level constants compile, fold, and print correctly across
// every initializer/annotation pairing the registrar accepts:
// - integer → integer (`K : s64 : 4`) — usable as an array count too
// - integer → float (`W : f32 : 800`)
// - float → float (`PI : f32 : 3.14159`)
// - string → string (`S : string : "hi"`)
// - null → pointer (`P : *void : null`)
// - integer literal → integer (`K : s64 : 4`) — usable as an array count too
// - integer literal → float (`W : f32 : 800`)
// - float literal → float (`PI : f32 : 3.14159`)
// - string literal → string (`S : string : "hi"`)
// - null → pointer (`P : *void : null`)
// - integer EXPRESSION → integer (`KE : s64 : M + 2`) — usable as a count too
// - integer EXPRESSION → float (`WE : f32 : M + 2`)
//
// Companion to the negative example 1143: the issue-0088 fix rejects a typed
// const whose initializer mismatches its annotation, and these correctly-typed
// consts must keep working (no over-rejection).
// consts must keep working (no over-rejection) — including const-EXPRESSION
// initializers, whose type-based validation (attempt 2) must accept a correctly
// typed expression even though it isn't a literal.
#import "modules/std.sx";
M :: 2;
K : s64 : 4;
W : f32 : 800;
PI : f32 : 3.14159;
S : string : "hi";
P : *void : null;
KE : s64 : M + 2;
WE : f32 : M + 2;
main :: () {
// Integer const: prints AND drives an array dimension (len 4).
@@ -32,4 +40,8 @@ main :: () {
// Null pointer const is null.
print("P_is_null={}\n", P == null);
// Integer const-EXPRESSION: prints AND drives an array dimension (len 4).
b : [KE]s64 = ---;
print("KE={} len={} WE={}\n", KE, b.len, WE);
}

View File

@@ -1,19 +1,26 @@
// A typed module-level constant whose initializer does not match its
// annotation is a compile-time type error — not a silently-accepted const.
// Each declaration below pairs a literal with an incompatible annotation, so
// the compiler must emit a `type mismatch` diagnostic at the initializer and
// Each declaration below pairs an initializer with an incompatible annotation,
// so the compiler must emit a `type mismatch` diagnostic at the initializer and
// abort (exit 1) rather than registering a usable const.
//
// Regression (issue 0088): `N : string : 4` was accepted; `print(N)` then
// segfaulted (an integer emitted as a `string` const → a bogus pointer) and
// `[N]s64` folded `N` to 4. The fix rejects the declaration at the root.
// `[N]s64` folded `N` to 4. The fix rejects the declaration at the root. The
// validation is type-based, so a const-EXPRESSION initializer (`E : string :
// M + 2`, `V : string : -M`) is rejected just like a literal — not skipped
// because its node kind isn't a literal (issue 0088, attempt 2).
#import "modules/std.sx";
N : string : 4; // integer literal where a string is annotated
F : s64 : "x"; // string literal where an integer is annotated
B : s64 : true; // boolean literal where an integer is annotated
G : s64 : 1.5; // float literal where an integer is annotated
M :: 2;
N : string : 4; // integer literal where a string is annotated
F : s64 : "x"; // string literal where an integer is annotated
B : s64 : true; // boolean literal where an integer is annotated
G : s64 : 1.5; // float literal where an integer is annotated
E : string : M + 2; // integer EXPRESSION where a string is annotated
V : string : -M; // integer (unary) expression where a string is annotated
main :: () {
print("unreachable\n");

View File

@@ -2,3 +2,4 @@ K=4 len=4 a0=10 a3=40
W=800.000000 PI=3.141590
S=hi
P_is_null=true
KE=4 len=4 WE=4.000000

View File

@@ -1,23 +1,35 @@
error: type mismatch: constant 'N' is declared 'string' but its initializer is an integer literal
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:13:14
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:18:14
|
13 | N : string : 4; // integer literal where a string is annotated
18 | N : string : 4; // integer literal where a string is annotated
| ^
error: type mismatch: constant 'F' is declared 's64' but its initializer is a string literal
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:14:14
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:19:14
|
14 | F : s64 : "x"; // string literal where an integer is annotated
19 | F : s64 : "x"; // string literal where an integer is annotated
| ^^^
error: type mismatch: constant 'B' is declared 's64' but its initializer is a boolean literal
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:15:14
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:20:14
|
15 | B : s64 : true; // boolean literal where an integer is annotated
20 | B : s64 : true; // boolean literal where an integer is annotated
| ^^^^
error: type mismatch: constant 'G' is declared 's64' but its initializer is a float literal
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:16:14
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:21:14
|
16 | G : s64 : 1.5; // float literal where an integer is annotated
21 | G : s64 : 1.5; // float literal where an integer is annotated
| ^^^
error: type mismatch: constant 'E' is declared 'string' but its initializer is an integer expression
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:22:14
|
22 | E : string : M + 2; // integer EXPRESSION where a string is annotated
| ^^^^^
error: type mismatch: constant 'V' is declared 'string' but its initializer is an integer expression
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:23:14
|
23 | V : string : -M; // integer (unary) expression where a string is annotated
| ^^