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:
35
examples/0162-types-typed-module-const-roundtrip.sx
Normal file
35
examples/0162-types-typed-module-const-roundtrip.sx
Normal file
@@ -0,0 +1,35 @@
|
||||
// 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`)
|
||||
//
|
||||
// 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).
|
||||
#import "modules/std.sx";
|
||||
|
||||
K : s64 : 4;
|
||||
W : f32 : 800;
|
||||
PI : f32 : 3.14159;
|
||||
S : string : "hi";
|
||||
P : *void : null;
|
||||
|
||||
main :: () {
|
||||
// Integer const: prints AND drives an array dimension (len 4).
|
||||
a : [K]s64 = ---;
|
||||
a[0] = 10;
|
||||
a[3] = 40;
|
||||
print("K={} len={} a0={} a3={}\n", K, a.len, a[0], a[3]);
|
||||
|
||||
// Integer-into-float and float consts print as floats.
|
||||
print("W={} PI={}\n", W, PI);
|
||||
|
||||
// String const prints its text.
|
||||
print("S={}\n", S);
|
||||
|
||||
// Null pointer const is null.
|
||||
print("P_is_null={}\n", P == null);
|
||||
}
|
||||
20
examples/1143-diagnostics-typed-module-const-mismatch.sx
Normal file
20
examples/1143-diagnostics-typed-module-const-mismatch.sx
Normal file
@@ -0,0 +1,20 @@
|
||||
// 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
|
||||
// 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.
|
||||
|
||||
#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
|
||||
|
||||
main :: () {
|
||||
print("unreachable\n");
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
K=4 len=4 a0=10 a3=40
|
||||
W=800.000000 PI=3.141590
|
||||
S=hi
|
||||
P_is_null=true
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,23 @@
|
||||
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
|
||||
|
|
||||
13 | 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
|
||||
|
|
||||
14 | 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
|
||||
|
|
||||
15 | 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
|
||||
|
|
||||
16 | G : s64 : 1.5; // float literal where an integer is annotated
|
||||
| ^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
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`.
|
||||
@@ -114,6 +114,11 @@ y : s32 = 0; // explicit type
|
||||
z : s32 = ---; // uninitialized
|
||||
```
|
||||
|
||||
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
|
||||
`string`, `null` a pointer/optional. A mismatch like `N : string : 4` is a
|
||||
compile-time `type mismatch` error, not a silently-accepted constant.
|
||||
|
||||
Builtin type names (`s2`, `u8`, `bool`, `string`, …) are reserved and a *bare*
|
||||
spelling can't be used as an identifier at a **value-binding or declaration-name**
|
||||
site — a value binding (`:=` / typed local / parameter), a `::` constant or
|
||||
|
||||
7
specs.md
7
specs.md
@@ -1458,6 +1458,13 @@ SOME_FUNC :: () => 42; // () -> s32
|
||||
SOME_TYPE :: f64; // type alias
|
||||
```
|
||||
|
||||
With an explicit annotation, the initializer literal must be compatible with the
|
||||
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
|
||||
a float type, a boolean `bool`, a string literal `string`, `null` a pointer or
|
||||
optional, and `---` any type. A mismatch such as `N : string : 4` is rejected at
|
||||
the declaration — it does not register a usable constant.
|
||||
|
||||
### Variable Binding (mutable)
|
||||
|
||||
```sx
|
||||
|
||||
@@ -920,12 +920,75 @@ pub const Lowering = struct {
|
||||
switch (cd.value.data) {
|
||||
.int_literal, .float_literal, .bool_literal, .string_literal, .undef_literal, .null_literal => {
|
||||
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 (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 => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// True iff a literal initializer of `value`'s kind is faithfully
|
||||
/// representable at the declared `dst_ty` — the precondition
|
||||
/// `emitModuleConst` relies on when it materialises the constant. The arms
|
||||
/// match `emitModuleConst`'s arms exactly, using the same type-kind
|
||||
/// predicates (`isIntEx` / `isFloat` / the `module.types.get` tag) the rest
|
||||
/// of lowering uses.
|
||||
///
|
||||
/// Deliberately NOT routed through `coercionResolver().classify`
|
||||
/// (conversions.zig): that planner judges RUNTIME value coercions and is
|
||||
/// unsound as a compile-time literal-representability oracle here — a `null`
|
||||
/// literal's natural type is `.void`, so `classify(.void, *T)` yields `.none`
|
||||
/// and would reject the valid `P : *void : null`; `bool` is 1 bit wide, so
|
||||
/// `classify(.bool, s64)` yields `.widen` and would accept the bogus
|
||||
/// `B : s64 : true`.
|
||||
fn typedConstInitFits(self: *Lowering, value: *const Node, dst_ty: TypeId) bool {
|
||||
return switch (value.data) {
|
||||
// `---` zero-inits at any type.
|
||||
.undef_literal => true,
|
||||
// Integer literal → any integer (incl. custom widths) or float
|
||||
// (`WIDTH : f32 : 800`).
|
||||
.int_literal => self.isIntEx(dst_ty) or isFloat(dst_ty),
|
||||
// Float literal → a float type only (the float arm emits `constFloat`).
|
||||
.float_literal => isFloat(dst_ty),
|
||||
.bool_literal => dst_ty == .bool,
|
||||
.string_literal => dst_ty == .string,
|
||||
// `null` → a pointer or optional.
|
||||
.null_literal => !dst_ty.isBuiltin() and switch (self.module.types.get(dst_ty)) {
|
||||
.pointer, .many_pointer, .optional => true,
|
||||
else => false,
|
||||
},
|
||||
// Only the literal kinds the caller's switch admits reach here.
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
|
||||
/// Register a top-level mutable global (e.g., `context : Context = ---;`).
|
||||
/// Run AFTER `resolveForwardIdentifierAliases` so a forward identifier alias
|
||||
/// in the type annotation (`A :: B; B :: s32; g : A = 7;`) resolves to its
|
||||
@@ -11876,7 +11939,7 @@ pub const Lowering = struct {
|
||||
// The module-const branch is shared verbatim with the stateless
|
||||
// registration-time resolver (`type_bridge`) so a `[N]T` dimension
|
||||
// resolves to the same length on both paths (issue 0083).
|
||||
return program_index_mod.moduleConstInt(&self.program_index.module_const_map, name);
|
||||
return program_index_mod.moduleConstInt(&self.program_index.module_const_map, &self.module.types, name);
|
||||
}
|
||||
|
||||
/// Resolve a type node, checking type_bindings first for generic type params.
|
||||
@@ -15519,6 +15582,20 @@ pub const Lowering = struct {
|
||||
return self.closureShapeKey(params, self.returnValuePart(ret));
|
||||
}
|
||||
|
||||
/// Human-readable name for a literal initializer kind, used in the typed
|
||||
/// module-const type-mismatch diagnostic.
|
||||
fn literalKindName(node: *const Node) []const u8 {
|
||||
return switch (node.data) {
|
||||
.int_literal => "an integer literal",
|
||||
.float_literal => "a float literal",
|
||||
.bool_literal => "a boolean literal",
|
||||
.string_literal => "a string literal",
|
||||
.null_literal => "null",
|
||||
.undef_literal => "'---'",
|
||||
else => "a value",
|
||||
};
|
||||
}
|
||||
|
||||
fn binOpSymbol(op: ast.BinaryOp.Op) []const u8 {
|
||||
return switch (op) {
|
||||
.add => "+",
|
||||
|
||||
@@ -215,6 +215,8 @@ test "floatToIntExact accepts integral floats, rejects the rest" {
|
||||
}
|
||||
|
||||
test "moduleConstInt folds expression-RHS consts and rejects cycles" {
|
||||
var table = types.TypeTable.init(std.testing.allocator);
|
||||
defer table.deinit();
|
||||
var map = std.StringHashMap(pi.ModuleConstInfo).init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
@@ -236,12 +238,12 @@ test "moduleConstInt folds expression-RHS consts and rejects cycles" {
|
||||
try map.put("F", .{ .value = &f_val, .ty = .f64 });
|
||||
try map.put("G", .{ .value = &g_val, .ty = .f64 });
|
||||
|
||||
try std.testing.expectEqual(@as(?i64, 2), pi.moduleConstInt(&map, "M"));
|
||||
try std.testing.expectEqual(@as(?i64, 3), pi.moduleConstInt(&map, "N"));
|
||||
try std.testing.expectEqual(@as(?i64, 6), pi.moduleConstInt(&map, "P"));
|
||||
try std.testing.expectEqual(@as(?i64, 4), pi.moduleConstInt(&map, "F"));
|
||||
try std.testing.expect(pi.moduleConstInt(&map, "G") == null);
|
||||
try std.testing.expect(pi.moduleConstInt(&map, "absent") == null);
|
||||
try std.testing.expectEqual(@as(?i64, 2), pi.moduleConstInt(&map, &table, "M"));
|
||||
try std.testing.expectEqual(@as(?i64, 3), pi.moduleConstInt(&map, &table, "N"));
|
||||
try std.testing.expectEqual(@as(?i64, 6), pi.moduleConstInt(&map, &table, "P"));
|
||||
try std.testing.expectEqual(@as(?i64, 4), pi.moduleConstInt(&map, &table, "F"));
|
||||
try std.testing.expect(pi.moduleConstInt(&map, &table, "G") == null);
|
||||
try std.testing.expect(pi.moduleConstInt(&map, &table, "absent") == null);
|
||||
|
||||
// A cyclic const has no compile-time integer value, and folding it must not
|
||||
// recurse forever: mutual `A :: B + 0; B :: A + 0` and self `C :: C + 0` all
|
||||
@@ -256,9 +258,29 @@ test "moduleConstInt folds expression-RHS consts and rejects cycles" {
|
||||
try map.put("A", .{ .value = &a_val, .ty = .s64 });
|
||||
try map.put("B", .{ .value = &b_val, .ty = .s64 });
|
||||
try map.put("C", .{ .value = &c_val, .ty = .s64 });
|
||||
try std.testing.expect(pi.moduleConstInt(&map, "A") == null);
|
||||
try std.testing.expect(pi.moduleConstInt(&map, "B") == null);
|
||||
try std.testing.expect(pi.moduleConstInt(&map, "C") == null);
|
||||
try std.testing.expect(pi.moduleConstInt(&map, &table, "A") == null);
|
||||
try std.testing.expect(pi.moduleConstInt(&map, &table, "B") == null);
|
||||
try std.testing.expect(pi.moduleConstInt(&map, &table, "C") == null);
|
||||
}
|
||||
|
||||
test "moduleConstInt gates the fold on the declared type, not the initializer node" {
|
||||
var table = types.TypeTable.init(std.testing.allocator);
|
||||
defer table.deinit();
|
||||
var map = std.StringHashMap(pi.ModuleConstInfo).init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
// An `int_literal` value node folds to an integer ONLY when the declared
|
||||
// type is numeric. A `string`/`bool`-typed const carrying an integer-looking
|
||||
// initializer must never be folded into a count (issue 0088): the count path
|
||||
// consults `ModuleConstInfo.ty`, not just the node shape.
|
||||
var int_val = nLit(4);
|
||||
try map.put("OK", .{ .value = &int_val, .ty = .s64 });
|
||||
try map.put("STR", .{ .value = &int_val, .ty = .string });
|
||||
try map.put("BOOLEAN", .{ .value = &int_val, .ty = .bool });
|
||||
|
||||
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, "BOOLEAN") == null);
|
||||
}
|
||||
|
||||
test "evalConstIntExpr folds an integral float literal, halts on a fractional one" {
|
||||
|
||||
@@ -91,20 +91,40 @@ fn moduleConstFrameContains(frame: ?*const ModuleConstFrame, name: []const u8) b
|
||||
/// scope, so `lookupPackLen` is always null.
|
||||
const ModuleConstCtx = struct {
|
||||
consts: *const std.StringHashMap(ModuleConstInfo),
|
||||
table: *const types.TypeTable,
|
||||
frame: ?*const ModuleConstFrame,
|
||||
pub fn lookupDimName(self: ModuleConstCtx, name: []const u8) ?i64 {
|
||||
return moduleConstIntFramed(self.consts, name, self.frame);
|
||||
return moduleConstIntFramed(self.consts, self.table, name, self.frame);
|
||||
}
|
||||
pub fn lookupPackLen(_: ModuleConstCtx, _: []const u8) ?i64 {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
fn moduleConstIntFramed(consts: *const std.StringHashMap(ModuleConstInfo), name: []const u8, parent: ?*const ModuleConstFrame) ?i64 {
|
||||
/// A module const may serve as an integer COUNT only when its DECLARED type is
|
||||
/// numeric — an integer of any width or a float (an integral float folds to its
|
||||
/// int via `floatToIntExact`). `moduleConstIntFramed` consults this so a count
|
||||
/// is gated on `ModuleConstInfo.ty`, not just the shape of the initializer node:
|
||||
/// a `string`/`bool`/pointer/struct-typed const can never be folded into a count
|
||||
/// off an integer-looking initializer (issue 0088 — the second symptom, where
|
||||
/// `N : string : 4` folded `[N]s64` to 4 by reading the `int_literal` node and
|
||||
/// ignoring the `string` annotation).
|
||||
fn isCountableConstType(table: *const types.TypeTable, ty: TypeId) bool {
|
||||
return switch (ty) {
|
||||
.s8, .s16, .s32, .s64, .u8, .u16, .u32, .u64, .usize, .isize, .f32, .f64 => true,
|
||||
else => if (ty.isBuiltin()) false else switch (table.get(ty)) {
|
||||
.signed, .unsigned => true,
|
||||
else => false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn moduleConstIntFramed(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, parent: ?*const ModuleConstFrame) ?i64 {
|
||||
if (moduleConstFrameContains(parent, name)) return null;
|
||||
const ci = consts.get(name) orelse return null;
|
||||
if (!isCountableConstType(table, ci.ty)) return null;
|
||||
var frame = ModuleConstFrame{ .name = name, .parent = parent };
|
||||
return evalConstIntExpr(ci.value, ModuleConstCtx{ .consts = consts, .frame = &frame });
|
||||
return evalConstIntExpr(ci.value, ModuleConstCtx{ .consts = consts, .table = table, .frame = &frame });
|
||||
}
|
||||
|
||||
/// A name bound to a module-global integer constant → its value, else null.
|
||||
@@ -120,8 +140,8 @@ fn moduleConstIntFramed(consts: *const std.StringHashMap(ModuleConstInfo), name:
|
||||
/// RHS over other consts (`M :: 2; N :: M + 1` → 3) all resolve identically and
|
||||
/// everywhere a count is accepted. Cyclic consts fold to null (see
|
||||
/// `ModuleConstCtx`).
|
||||
pub fn moduleConstInt(consts: *const std.StringHashMap(ModuleConstInfo), name: []const u8) ?i64 {
|
||||
return moduleConstIntFramed(consts, name, null);
|
||||
pub fn moduleConstInt(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8) ?i64 {
|
||||
return moduleConstIntFramed(consts, table, name, null);
|
||||
}
|
||||
|
||||
/// Evaluate a constant integer expression to its value. THE single
|
||||
|
||||
@@ -70,7 +70,7 @@ const StatelessInner = struct {
|
||||
/// operand may legitimately be negative.
|
||||
pub fn lookupDimName(self: StatelessInner, name: []const u8) ?i64 {
|
||||
const consts = self.consts orelse return null;
|
||||
return program_index_mod.moduleConstInt(consts, name);
|
||||
return program_index_mod.moduleConstInt(consts, self.table, name);
|
||||
}
|
||||
/// Pack-length leaf for the shared integer-expression evaluator. The
|
||||
/// registration-time path has no pack-arity information (packs are bound
|
||||
|
||||
Reference in New Issue
Block a user