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:
@@ -1,21 +1,29 @@
|
|||||||
// Valid typed module-level constants compile, fold, and print correctly across
|
// Valid typed module-level constants compile, fold, and print correctly across
|
||||||
// every initializer/annotation pairing the registrar accepts:
|
// every initializer/annotation pairing the registrar accepts:
|
||||||
// - integer → integer (`K : s64 : 4`) — usable as an array count too
|
// - integer literal → integer (`K : s64 : 4`) — usable as an array count too
|
||||||
// - integer → float (`W : f32 : 800`)
|
// - integer literal → float (`W : f32 : 800`)
|
||||||
// - float → float (`PI : f32 : 3.14159`)
|
// - float literal → float (`PI : f32 : 3.14159`)
|
||||||
// - string → string (`S : string : "hi"`)
|
// - string literal → string (`S : string : "hi"`)
|
||||||
// - null → pointer (`P : *void : null`)
|
// - 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
|
// Companion to the negative example 1143: the issue-0088 fix rejects a typed
|
||||||
// const whose initializer mismatches its annotation, and these correctly-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";
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
M :: 2;
|
||||||
|
|
||||||
K : s64 : 4;
|
K : s64 : 4;
|
||||||
W : f32 : 800;
|
W : f32 : 800;
|
||||||
PI : f32 : 3.14159;
|
PI : f32 : 3.14159;
|
||||||
S : string : "hi";
|
S : string : "hi";
|
||||||
P : *void : null;
|
P : *void : null;
|
||||||
|
KE : s64 : M + 2;
|
||||||
|
WE : f32 : M + 2;
|
||||||
|
|
||||||
main :: () {
|
main :: () {
|
||||||
// Integer const: prints AND drives an array dimension (len 4).
|
// Integer const: prints AND drives an array dimension (len 4).
|
||||||
@@ -32,4 +40,8 @@ main :: () {
|
|||||||
|
|
||||||
// Null pointer const is null.
|
// Null pointer const is null.
|
||||||
print("P_is_null={}\n", P == 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
// A typed module-level constant whose initializer does not match its
|
// A typed module-level constant whose initializer does not match its
|
||||||
// annotation is a compile-time type error — not a silently-accepted const.
|
// annotation is a compile-time type error — not a silently-accepted const.
|
||||||
// Each declaration below pairs a literal with an incompatible annotation, so
|
// Each declaration below pairs an initializer with an incompatible annotation,
|
||||||
// the compiler must emit a `type mismatch` diagnostic at the initializer and
|
// so the compiler must emit a `type mismatch` diagnostic at the initializer and
|
||||||
// abort (exit 1) rather than registering a usable const.
|
// abort (exit 1) rather than registering a usable const.
|
||||||
//
|
//
|
||||||
// Regression (issue 0088): `N : string : 4` was accepted; `print(N)` then
|
// Regression (issue 0088): `N : string : 4` was accepted; `print(N)` then
|
||||||
// segfaulted (an integer emitted as a `string` const → a bogus pointer) and
|
// 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";
|
#import "modules/std.sx";
|
||||||
|
|
||||||
N : string : 4; // integer literal where a string is annotated
|
M :: 2;
|
||||||
F : s64 : "x"; // string literal where an integer is annotated
|
|
||||||
B : s64 : true; // boolean literal where an integer is annotated
|
N : string : 4; // integer literal where a string is annotated
|
||||||
G : s64 : 1.5; // float literal where an integer 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 :: () {
|
main :: () {
|
||||||
print("unreachable\n");
|
print("unreachable\n");
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ K=4 len=4 a0=10 a3=40
|
|||||||
W=800.000000 PI=3.141590
|
W=800.000000 PI=3.141590
|
||||||
S=hi
|
S=hi
|
||||||
P_is_null=true
|
P_is_null=true
|
||||||
|
KE=4 len=4 WE=4.000000
|
||||||
|
|||||||
@@ -1,23 +1,35 @@
|
|||||||
error: type mismatch: constant 'N' is declared 'string' but its initializer is an integer literal
|
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
|
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
|
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
|
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
|
||||||
|
| ^^
|
||||||
|
|||||||
@@ -11,31 +11,45 @@
|
|||||||
> folded the const into an integer COUNT by inspecting the `int_literal` node
|
> folded the const into an integer COUNT by inspecting the `int_literal` node
|
||||||
> alone, ignoring `ModuleConstInfo.ty` (so `[N]s64` folded to 4).
|
> 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.**
|
> **Fix per file.**
|
||||||
> - `src/ir/lower.zig` — `registerTypedModuleConst` now validates the
|
> - `src/ir/lower.zig` — `registerTypedModuleConst` validates the initializer
|
||||||
> initializer against the resolved annotation via the new `typedConstInitFits`
|
> against the resolved annotation BY TYPE, covering literals AND
|
||||||
> (arms mirror `emitModuleConst`'s faithful-emit precondition: int → int/float,
|
> const-expressions (binary_op / unary_op) uniformly. `typedConstInitFits`
|
||||||
> float → float, bool → bool, string → string, null → pointer/optional,
|
> keeps the literal arms (int → int/float, float → float, bool → bool,
|
||||||
> `---` → any). A mismatch emits `type mismatch: constant '<n>' is declared
|
> string → string, null → pointer/optional, `---` → any) and routes any
|
||||||
> '<ty>' but its initializer is <kind>` at the initializer span and does NOT
|
> non-literal through `constExprInitFits`, which compares the initializer's
|
||||||
> register the const (it also evicts the pass-0 placeholder so a count use
|
> INFERRED type (`inferExprType`, the existing type-inference facility — no
|
||||||
> can't still fold it). `literalKindName` names the literal kind for the
|
> second const evaluator) to the annotation with the same integer/float
|
||||||
> message.
|
> compatibility. A mismatch emits `type mismatch: constant '<n>' is declared
|
||||||
> - `src/ir/program_index.zig` — `moduleConstInt` / `moduleConstIntFramed` now
|
> '<ty>' but its initializer is <desc>` at the initializer span (a literal
|
||||||
> take the `TypeTable` and gate the fold on `isCountableConstType(ci.ty)`
|
> names its kind; a const-expression is described by its inferred type, e.g.
|
||||||
> (integer of any width, or a float), so a non-numeric typed const can never be
|
> "an integer expression"), and does NOT register the const — it evicts the
|
||||||
> folded into a count off its initializer node. Callers in `lower.zig` and
|
> pass-0 placeholder so a count use can't still fold it. On a MATCH the const is
|
||||||
> `type_bridge.zig` updated.
|
> 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.**
|
> **Regression tests.**
|
||||||
> - `examples/1143-diagnostics-typed-module-const-mismatch.sx` — negative: four
|
> - `examples/1143-diagnostics-typed-module-const-mismatch.sx` — negative: six
|
||||||
> mismatch shapes (`int→string`, `string→s64`, `bool→s64`, `float→s64`) each
|
> mismatch shapes — four literal (`int→string`, `string→s64`, `bool→s64`,
|
||||||
> emit a `type mismatch` diagnostic, exit 1.
|
> `float→s64`) and two const-expression (`M + 2 → string`, `-M → string`) —
|
||||||
> - `examples/0162-types-typed-module-const-roundtrip.sx` — positive: valid
|
> each emit a `type mismatch` diagnostic, exit 1.
|
||||||
> typed consts (`s64` as count + printed, `f32` from int, `f32` float,
|
> - `examples/0162-types-typed-module-const-roundtrip.sx` — positive: valid typed
|
||||||
> `string`, `*void` null) compile, fold, and print correctly.
|
> 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
|
> - `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
|
# 0088 — Typed module const annotation mismatch is accepted
|
||||||
|
|
||||||
|
|||||||
@@ -115,8 +115,9 @@ z : s32 = ---; // uninitialized
|
|||||||
```
|
```
|
||||||
|
|
||||||
A typed constant's initializer must be compatible with its annotation — an
|
A typed constant's initializer must be compatible with its annotation — an
|
||||||
integer literal fits any integer or float, a float a float type, a string
|
integer fits any integer or float, a float a float type, a string `string`,
|
||||||
`string`, `null` a pointer/optional. A mismatch like `N : string : 4` is a
|
`null` a pointer/optional. The check is type-based, so it covers a literal and a
|
||||||
|
constant expression alike: both `N : string : 4` and `N : string : M + 2` are a
|
||||||
compile-time `type mismatch` error, not a silently-accepted constant.
|
compile-time `type mismatch` error, not a silently-accepted constant.
|
||||||
|
|
||||||
Builtin type names (`s2`, `u8`, `bool`, `string`, …) are reserved and a *bare*
|
Builtin type names (`s2`, `u8`, `bool`, `string`, …) are reserved and a *bare*
|
||||||
|
|||||||
11
specs.md
11
specs.md
@@ -1458,12 +1458,13 @@ SOME_FUNC :: () => 42; // () -> s32
|
|||||||
SOME_TYPE :: f64; // type alias
|
SOME_TYPE :: f64; // type alias
|
||||||
```
|
```
|
||||||
|
|
||||||
With an explicit annotation, the initializer literal must be compatible with the
|
With an explicit annotation, the initializer must be compatible with the
|
||||||
annotated type, or the declaration is a compile-time `type mismatch` error: an
|
annotated type, or the declaration is a compile-time `type mismatch` error: an
|
||||||
integer literal fits any integer or float type (`W : f32 : 800`), a float literal
|
integer fits any integer or float type (`W : f32 : 800`), a float a float type, a
|
||||||
a float type, a boolean `bool`, a string literal `string`, `null` a pointer or
|
boolean `bool`, a string `string`, `null` a pointer or optional, and `---` any
|
||||||
optional, and `---` any type. A mismatch such as `N : string : 4` is rejected at
|
type. The check is type-based, so it applies equally to a literal and to a
|
||||||
the declaration — it does not register a usable constant.
|
constant expression: both `N : string : 4` and `N : string : M + 2` (with
|
||||||
|
`M :: 2`) are rejected at the declaration — neither registers a usable constant.
|
||||||
|
|
||||||
### Variable Binding (mutable)
|
### Variable Binding (mutable)
|
||||||
|
|
||||||
|
|||||||
117
src/ir/lower.zig
117
src/ir/lower.zig
@@ -917,41 +917,47 @@ pub const Lowering = struct {
|
|||||||
/// would otherwise mistype the constant (issue 0070).
|
/// would otherwise mistype the constant (issue 0070).
|
||||||
fn registerTypedModuleConst(self: *Lowering, cd: *const ast.ConstDecl) void {
|
fn registerTypedModuleConst(self: *Lowering, cd: *const ast.ConstDecl) void {
|
||||||
const ta = cd.type_annotation orelse return;
|
const ta = cd.type_annotation orelse return;
|
||||||
|
// Only initializer shapes that pass 0 (binary_op / unary_op → placeholder
|
||||||
|
// `.s64`) or the literal path register as a USABLE module const need
|
||||||
|
// reconciling against the annotation. Every other shape (call,
|
||||||
|
// struct/array literal, bare identifier) is never registered as a
|
||||||
|
// foldable / emittable const, so it cannot manifest the issue-0088
|
||||||
|
// wrong-type fold/emit; a use-site diagnostic covers it.
|
||||||
switch (cd.value.data) {
|
switch (cd.value.data) {
|
||||||
.int_literal, .float_literal, .bool_literal, .string_literal, .undef_literal, .null_literal => {
|
.int_literal, .float_literal, .bool_literal, .string_literal, .undef_literal, .null_literal, .binary_op, .unary_op => {},
|
||||||
const ty = self.resolveType(ta);
|
else => return,
|
||||||
// An unresolvable annotation is already diagnosed by the type
|
|
||||||
// resolver; don't pile a bogus type-mismatch on top, and don't
|
|
||||||
// leave the pass-0 placeholder (registered off the initializer
|
|
||||||
// literal) behind as a usable const.
|
|
||||||
if (ty == .unresolved) {
|
|
||||||
_ = self.program_index.module_const_map.remove(cd.name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Validate the initializer literal against the explicit
|
|
||||||
// annotation. A mismatch (`N : string : 4`) is a type error, not
|
|
||||||
// a silently-accepted const — registering it would let
|
|
||||||
// `emitModuleConst` stamp the literal with the wrong IR type
|
|
||||||
// (an int emitted as a `string` const → a bogus pointer that
|
|
||||||
// segfaults at the use site) and let the count path fold it
|
|
||||||
// (`[N]s64` → 4). Issue 0088.
|
|
||||||
if (!self.typedConstInitFits(cd.value, ty)) {
|
|
||||||
if (self.diagnostics) |d| {
|
|
||||||
d.addFmt(.err, cd.value.span, "type mismatch: constant '{s}' is declared '{s}' but its initializer is {s}", .{
|
|
||||||
cd.name, self.formatTypeName(ty), literalKindName(cd.value),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Evict the pass-0 placeholder (`N : string : 4` was
|
|
||||||
// pre-registered as `.s64` off its `int_literal` in
|
|
||||||
// scanDecls pass 0); leaving it would let a count use still
|
|
||||||
// fold `N` to 4.
|
|
||||||
_ = self.program_index.module_const_map.remove(cd.name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = ty }) catch {};
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
}
|
||||||
|
const ty = self.resolveType(ta);
|
||||||
|
// An unresolvable annotation is already diagnosed by the type resolver;
|
||||||
|
// don't pile a bogus type-mismatch on top, and don't leave the pass-0
|
||||||
|
// placeholder behind as a usable const.
|
||||||
|
if (ty == .unresolved) {
|
||||||
|
_ = self.program_index.module_const_map.remove(cd.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Validate the initializer against the explicit annotation BY TYPE, so a
|
||||||
|
// const-EXPRESSION initializer (`N : string : M + 2`) is checked exactly
|
||||||
|
// like a literal rather than skipped. A mismatch is a type error, not a
|
||||||
|
// silently-accepted const — registering it would let `emitModuleConst`
|
||||||
|
// stamp the value with the wrong IR type (an int emitted as a `string`
|
||||||
|
// const → a bogus pointer that segfaults at the use site) and let the
|
||||||
|
// count path fold it (`[N]s64` → 4). Issue 0088.
|
||||||
|
if (!self.typedConstInitFits(cd.value, ty)) {
|
||||||
|
if (self.diagnostics) |d| {
|
||||||
|
d.addFmt(.err, cd.value.span, "type mismatch: constant '{s}' is declared '{s}' but its initializer is {s}", .{
|
||||||
|
cd.name, self.formatTypeName(ty), self.initializerDescription(cd.value),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Evict the pass-0 placeholder (`N : string : 4` and
|
||||||
|
// `N : string : M + 2` are both pre-registered as `.s64` in scanDecls
|
||||||
|
// pass 0); leaving it would let a count use still fold `N`.
|
||||||
|
_ = self.program_index.module_const_map.remove(cd.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Reconcile the registration with the resolved annotation (pass 0 stored
|
||||||
|
// a literal/expression placeholder type), so the const folds and emits at
|
||||||
|
// its declared type — the same `put` the literal path always did.
|
||||||
|
self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = ty }) catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True iff a literal initializer of `value`'s kind is faithfully
|
/// True iff a literal initializer of `value`'s kind is faithfully
|
||||||
@@ -984,11 +990,33 @@ pub const Lowering = struct {
|
|||||||
.pointer, .many_pointer, .optional => true,
|
.pointer, .many_pointer, .optional => true,
|
||||||
else => false,
|
else => false,
|
||||||
},
|
},
|
||||||
// Only the literal kinds the caller's switch admits reach here.
|
// Const-EXPRESSION initializer (binary_op / unary_op — the only
|
||||||
else => true,
|
// non-literal kinds the caller admits): validate by the initializer's
|
||||||
|
// INFERRED type so coverage is type-based, not a per-node-kind
|
||||||
|
// allowlist where an unenumerated kind silently escapes (issue 0088,
|
||||||
|
// attempt 2). The integer/float fit mirrors the literal arms above.
|
||||||
|
else => self.constExprInitFits(self.inferExprType(value), dst_ty),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True iff a const-expression initializer of inferred type `init_ty` is
|
||||||
|
/// faithfully representable at the declared `dst_ty`. Type-based so it covers
|
||||||
|
/// every const-expression shape (binary_op, unary_op, …) through one check
|
||||||
|
/// rather than per-node-kind arms. The integer/float arms mirror the
|
||||||
|
/// int/float literal arms of `typedConstInitFits` (an integer expression fits
|
||||||
|
/// an integer or float annotation; a float expression fits a float).
|
||||||
|
fn constExprInitFits(self: *Lowering, init_ty: TypeId, dst_ty: TypeId) bool {
|
||||||
|
// An initializer whose type we couldn't infer is left for the use-site /
|
||||||
|
// emission diagnostic rather than rejected here (no over-rejection).
|
||||||
|
if (init_ty == .unresolved) return true;
|
||||||
|
if (self.isIntEx(init_ty)) return self.isIntEx(dst_ty) or isFloat(dst_ty);
|
||||||
|
if (isFloat(init_ty)) return isFloat(dst_ty);
|
||||||
|
if (init_ty == .bool) return dst_ty == .bool;
|
||||||
|
if (init_ty == .string) return dst_ty == .string;
|
||||||
|
// Any other concrete initializer type must match the annotation exactly.
|
||||||
|
return init_ty == dst_ty;
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a top-level mutable global (e.g., `context : Context = ---;`).
|
/// Register a top-level mutable global (e.g., `context : Context = ---;`).
|
||||||
/// Run AFTER `resolveForwardIdentifierAliases` so a forward identifier alias
|
/// Run AFTER `resolveForwardIdentifierAliases` so a forward identifier alias
|
||||||
/// in the type annotation (`A :: B; B :: s32; g : A = 7;`) resolves to its
|
/// in the type annotation (`A :: B; B :: s32; g : A = 7;`) resolves to its
|
||||||
@@ -15582,9 +15610,12 @@ pub const Lowering = struct {
|
|||||||
return self.closureShapeKey(params, self.returnValuePart(ret));
|
return self.closureShapeKey(params, self.returnValuePart(ret));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Human-readable name for a literal initializer kind, used in the typed
|
/// Human-readable description of a typed module-const initializer, used in
|
||||||
/// module-const type-mismatch diagnostic.
|
/// the issue-0088 type-mismatch diagnostic. A literal names its kind; a
|
||||||
fn literalKindName(node: *const Node) []const u8 {
|
/// const-expression is described by its inferred type category, so the
|
||||||
|
/// message is accurate for `N : string : M + 2` ("an integer expression")
|
||||||
|
/// as well as for `N : string : 4` ("an integer literal").
|
||||||
|
fn initializerDescription(self: *Lowering, node: *const Node) []const u8 {
|
||||||
return switch (node.data) {
|
return switch (node.data) {
|
||||||
.int_literal => "an integer literal",
|
.int_literal => "an integer literal",
|
||||||
.float_literal => "a float literal",
|
.float_literal => "a float literal",
|
||||||
@@ -15592,10 +15623,18 @@ pub const Lowering = struct {
|
|||||||
.string_literal => "a string literal",
|
.string_literal => "a string literal",
|
||||||
.null_literal => "null",
|
.null_literal => "null",
|
||||||
.undef_literal => "'---'",
|
.undef_literal => "'---'",
|
||||||
else => "a value",
|
else => self.constExprDescription(self.inferExprType(node)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn constExprDescription(self: *Lowering, init_ty: TypeId) []const u8 {
|
||||||
|
if (self.isIntEx(init_ty)) return "an integer expression";
|
||||||
|
if (isFloat(init_ty)) return "a floating-point expression";
|
||||||
|
if (init_ty == .bool) return "a boolean expression";
|
||||||
|
if (init_ty == .string) return "a string expression";
|
||||||
|
return "an expression of an incompatible type";
|
||||||
|
}
|
||||||
|
|
||||||
fn binOpSymbol(op: ast.BinaryOp.Op) []const u8 {
|
fn binOpSymbol(op: ast.BinaryOp.Op) []const u8 {
|
||||||
return switch (op) {
|
return switch (op) {
|
||||||
.add => "+",
|
.add => "+",
|
||||||
|
|||||||
@@ -281,6 +281,19 @@ test "moduleConstInt gates the fold on the declared type, not the initializer no
|
|||||||
try std.testing.expectEqual(@as(?i64, 4), pi.moduleConstInt(&map, &table, "OK"));
|
try std.testing.expectEqual(@as(?i64, 4), pi.moduleConstInt(&map, &table, "OK"));
|
||||||
try std.testing.expect(pi.moduleConstInt(&map, &table, "STR") == null);
|
try std.testing.expect(pi.moduleConstInt(&map, &table, "STR") == null);
|
||||||
try std.testing.expect(pi.moduleConstInt(&map, &table, "BOOLEAN") == null);
|
try std.testing.expect(pi.moduleConstInt(&map, &table, "BOOLEAN") == null);
|
||||||
|
|
||||||
|
// The same gate holds for a const-EXPRESSION value node (`M + 2`), not just
|
||||||
|
// a bare literal: a `string`-typed const whose initializer is a foldable
|
||||||
|
// integer expression must still never fold as a count (issue 0088 attempt 2 —
|
||||||
|
// the const-expression leak). `KEXPR : s64 : M + 2` (numeric type) folds; the
|
||||||
|
// same expression declared `string` does not.
|
||||||
|
var m_lit = nLit(2);
|
||||||
|
var add2 = nLit(2);
|
||||||
|
var expr_val = nBin(.add, &m_lit, &add2);
|
||||||
|
try map.put("KEXPR", .{ .value = &expr_val, .ty = .s64 });
|
||||||
|
try map.put("STREXPR", .{ .value = &expr_val, .ty = .string });
|
||||||
|
try std.testing.expectEqual(@as(?i64, 4), pi.moduleConstInt(&map, &table, "KEXPR"));
|
||||||
|
try std.testing.expect(pi.moduleConstInt(&map, &table, "STREXPR") == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "evalConstIntExpr folds an integral float literal, halts on a fractional one" {
|
test "evalConstIntExpr folds an integral float literal, halts on a fractional one" {
|
||||||
|
|||||||
Reference in New Issue
Block a user