Merge branch 'flow/sx-foundation/F0.7' into dist-foundation
This commit is contained in:
58
examples/0162-types-typed-module-const-roundtrip.sx
Normal file
58
examples/0162-types-typed-module-const-roundtrip.sx
Normal file
@@ -0,0 +1,58 @@
|
||||
// Valid typed module-level constants compile, fold, and print correctly across
|
||||
// every initializer/annotation pairing the registrar accepts:
|
||||
// - 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`)
|
||||
// - MIXED int+float EXPRESSION → float (`MF : f64 : M + 0.5`, both operand orders)
|
||||
//
|
||||
// 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) — including const-EXPRESSION
|
||||
// initializers, whose type-based validation (attempt 2) must accept a correctly
|
||||
// typed expression even though it isn't a literal.
|
||||
//
|
||||
// `MF`/`MFR` pin the attempt-3 inferExprType promotion fix: a mixed int+float
|
||||
// arithmetic expression infers as the float result regardless of operand order,
|
||||
// so it matches an `f64` annotation (and folds to 2.5, not a truncated 2).
|
||||
#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;
|
||||
MF : f64 : M + 0.5;
|
||||
MFR : f64 : 0.5 + M;
|
||||
|
||||
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);
|
||||
|
||||
// Integer const-EXPRESSION: prints AND drives an array dimension (len 4).
|
||||
b : [KE]s64 = ---;
|
||||
print("KE={} len={} WE={}\n", KE, b.len, WE);
|
||||
|
||||
// Mixed int+float const-EXPRESSION folds to the promoted float (2.5),
|
||||
// operand-order-independent.
|
||||
print("MF={} MFR={}\n", MF, MFR);
|
||||
}
|
||||
35
examples/0163-types-mixed-numeric-promotion.sx
Normal file
35
examples/0163-types-mixed-numeric-promotion.sx
Normal file
@@ -0,0 +1,35 @@
|
||||
// Mixed int+float arithmetic infers as the FLOAT result, operand-order-independent.
|
||||
//
|
||||
// `print("{}", expr)` selects integer- vs float-formatting from the STATIC type
|
||||
// `inferExprType` reports for the argument (not the lowered value's type), so it
|
||||
// exercises the binary-op inference arm directly — distinct from the typed-const
|
||||
// validation path. Before the attempt-3 fix, binary-op inference was LHS-biased:
|
||||
// `n + 0.5` (int LHS) inferred `s64` and printed a truncated `2`, while `0.5 + n`
|
||||
// (float LHS) inferred `f64` and printed `2.5`. The fix routes both through the
|
||||
// shared promotion rule (`Lowering.arithResultType`, the same one `lowerBinaryOp`
|
||||
// applies for the value), so an int operand with a float operand promotes to the
|
||||
// float in either order.
|
||||
//
|
||||
// Regression (issue 0088, attempt 3 — the inferExprType numeric-promotion root fix).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
n := 2; // runtime s64
|
||||
|
||||
// Addition, both operand orders — both promote to f64 → 2.5.
|
||||
print("add: {} {}\n", n + 0.5, 0.5 + n);
|
||||
|
||||
// Multiplication, both orders — both promote → 3.0.
|
||||
print("mul: {} {}\n", n * 1.5, 1.5 * n);
|
||||
|
||||
// Subtraction / division with the int on the left.
|
||||
print("sub: {} div: {}\n", n - 0.5, n / 4.0);
|
||||
|
||||
// f32 operand promotes too (int LHS, f32 RHS).
|
||||
half : f32 = 0.5;
|
||||
print("f32: {}\n", n + half);
|
||||
|
||||
// A pure-int expression is unaffected — stays s64, prints as an integer.
|
||||
print("int: {}\n", n + 3);
|
||||
}
|
||||
35
examples/1143-diagnostics-typed-module-const-mismatch.sx
Normal file
35
examples/1143-diagnostics-typed-module-const-mismatch.sx
Normal file
@@ -0,0 +1,35 @@
|
||||
// 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 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. 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).
|
||||
//
|
||||
// The mixed-numeric pair (`s64 : M + 0.5`, `s64 : 0.5 + M`) is rejected in BOTH
|
||||
// operand orders: arithmetic binary-op inference now promotes int+float to the
|
||||
// float result (`Lowering.arithResultType`), so an s64 annotation no longer
|
||||
// matches a float-producing initializer regardless of which operand is the
|
||||
// float (issue 0088, attempt 3 — the inferExprType promotion root fix).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
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
|
||||
BAD : s64 : M + 0.5; // mixed int+float (int LHS) → f64, rejected vs s64
|
||||
BAD2 : s64 : 0.5 + M; // mixed float+int (float LHS) → f64, rejected vs s64 — order-independent
|
||||
|
||||
main :: () {
|
||||
print("unreachable\n");
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
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
|
||||
MF=2.500000 MFR=2.500000
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
add: 2.500000 2.500000
|
||||
mul: 3.000000 3.000000
|
||||
sub: 1.500000 div: 0.500000
|
||||
f32: 2.500000
|
||||
int: 5
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,47 @@
|
||||
error: type mismatch: constant 'N' is declared 'string' but its initializer is an integer literal
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:24:15
|
||||
|
|
||||
24 | 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:25:15
|
||||
|
|
||||
25 | 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:26:15
|
||||
|
|
||||
26 | 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:27:15
|
||||
|
|
||||
27 | 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:28:15
|
||||
|
|
||||
28 | 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:29:15
|
||||
|
|
||||
29 | V : string : -M; // integer (unary) expression where a string is annotated
|
||||
| ^^
|
||||
|
||||
error: type mismatch: constant 'BAD' is declared 's64' but its initializer is a floating-point expression
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:30:15
|
||||
|
|
||||
30 | BAD : s64 : M + 0.5; // mixed int+float (int LHS) → f64, rejected vs s64
|
||||
| ^^^^^^^
|
||||
|
||||
error: type mismatch: constant 'BAD2' is declared 's64' but its initializer is a floating-point expression
|
||||
--> examples/1143-diagnostics-typed-module-const-mismatch.sx:31:15
|
||||
|
|
||||
31 | BAD2 : s64 : 0.5 + M; // mixed float+int (float LHS) → f64, rejected vs s64 — order-independent
|
||||
| ^^^^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
131
issues/0088-typed-module-const-annotation-mismatch.md
Normal file
131
issues/0088-typed-module-const-annotation-mismatch.md
Normal file
@@ -0,0 +1,131 @@
|
||||
> **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 '<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: 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`.
|
||||
@@ -114,6 +114,15 @@ y : s32 = 0; // explicit type
|
||||
z : s32 = ---; // uninitialized
|
||||
```
|
||||
|
||||
A typed constant's initializer must be compatible with its annotation — an
|
||||
integer fits any integer or float, a float a float type, a string `string`,
|
||||
`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. Mixed
|
||||
int+float arithmetic promotes to the float in either operand order (`n + 0.5` and
|
||||
`0.5 + n` are both `f64`), so `C : s64 : M + 0.5` is rejected regardless of order
|
||||
while `F : f64 : M + 0.5` folds to `2.5`.
|
||||
|
||||
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
|
||||
|
||||
21
specs.md
21
specs.md
@@ -1458,6 +1458,18 @@ SOME_FUNC :: () => 42; // () -> s32
|
||||
SOME_TYPE :: f64; // type alias
|
||||
```
|
||||
|
||||
With an explicit annotation, the initializer must be compatible with the
|
||||
annotated type, or the declaration is a compile-time `type mismatch` error: an
|
||||
integer fits any integer or float type (`W : f32 : 800`), a float a float type, a
|
||||
boolean `bool`, a string `string`, `null` a pointer or optional, and `---` any
|
||||
type. The check is type-based, so it applies equally to a literal and to a
|
||||
constant expression: both `N : string : 4` and `N : string : M + 2` (with
|
||||
`M :: 2`) are rejected at the declaration — neither registers a usable constant.
|
||||
A constant expression's type is its promoted result type (see
|
||||
[Arithmetic](#arithmetic)), so a mixed int+float initializer is a float in either
|
||||
operand order: `C : s64 : M + 0.5` and `C : s64 : 0.5 + M` are both rejected, and
|
||||
`F : f64 : M + 0.5` is accepted and folds to `2.5`.
|
||||
|
||||
### Variable Binding (mutable)
|
||||
|
||||
```sx
|
||||
@@ -1750,6 +1762,15 @@ x * x
|
||||
x + 2
|
||||
```
|
||||
|
||||
**Numeric promotion.** When the two operands of an arithmetic op have different
|
||||
numeric types, the result is the promoted type: an integer operand combined with
|
||||
a floating-point operand yields the **float**, regardless of operand order
|
||||
(`n + 0.5` and `0.5 + n` both produce an `f64`). This holds for the expression's
|
||||
static type as well as its value, so `print("{}", n + 0.5)` formats a float and a
|
||||
typed binding `x : f64 = n + 0.5` is exact (not truncated). A mixed-numeric
|
||||
expression therefore does not satisfy an integer annotation — `C : s64 : n + 0.5`
|
||||
is a `type mismatch` in either operand order.
|
||||
|
||||
### Chained Comparisons
|
||||
Comparison operators can be chained. Each operand is evaluated exactly once.
|
||||
```sx
|
||||
|
||||
@@ -34,7 +34,7 @@ test "expr_typer: literal shapes" {
|
||||
try std.testing.expectEqual(TypeId.string, l.inferExprType(&str_n));
|
||||
}
|
||||
|
||||
test "expr_typer: binary comparison is bool, arithmetic takes lhs type" {
|
||||
test "expr_typer: binary comparison is bool, int arithmetic stays int" {
|
||||
const alloc = std.testing.allocator;
|
||||
var module = ir_mod.Module.init(alloc);
|
||||
defer module.deinit();
|
||||
@@ -50,6 +50,46 @@ test "expr_typer: binary comparison is bool, arithmetic takes lhs type" {
|
||||
try std.testing.expectEqual(TypeId.s64, l.inferExprType(&add));
|
||||
}
|
||||
|
||||
// issue 0088 (attempt 3): a non-comparison binary op infers the PROMOTED result
|
||||
// of (lhs, rhs), not the LHS alone — so a mixed int+float op types as the float
|
||||
// in EITHER operand order (was LHS-biased: `int + float` → s64 while
|
||||
// `float + int` → f64). This is what feeds the typed-const validation that
|
||||
// rejected `s64 : 0.5 + M` but not `s64 : M + 0.5`.
|
||||
test "expr_typer: mixed int+float arithmetic promotes to float, order-independent" {
|
||||
const alloc = std.testing.allocator;
|
||||
var module = ir_mod.Module.init(alloc);
|
||||
defer module.deinit();
|
||||
var l = Lowering.init(&module);
|
||||
|
||||
var int_n = node(.{ .int_literal = .{ .value = 2 } });
|
||||
var float_n = node(.{ .float_literal = .{ .value = 0.5 } });
|
||||
|
||||
// int LHS, float RHS → f64 (was s64 before the fix).
|
||||
var add_if = node(.{ .binary_op = .{ .op = .add, .lhs = &int_n, .rhs = &float_n } });
|
||||
try std.testing.expectEqual(TypeId.f64, l.inferExprType(&add_if));
|
||||
|
||||
// float LHS, int RHS → f64 (already correct; confirms order-independence).
|
||||
var add_fi = node(.{ .binary_op = .{ .op = .add, .lhs = &float_n, .rhs = &int_n } });
|
||||
try std.testing.expectEqual(TypeId.f64, l.inferExprType(&add_fi));
|
||||
|
||||
// Multiplication promotes the same way.
|
||||
var mul_if = node(.{ .binary_op = .{ .op = .mul, .lhs = &int_n, .rhs = &float_n } });
|
||||
try std.testing.expectEqual(TypeId.f64, l.inferExprType(&mul_if));
|
||||
}
|
||||
|
||||
// The shared promotion helper itself (single source of truth for both
|
||||
// `lowerBinaryOp`'s value type and `inferExprType`): an integer LHS with a
|
||||
// floating-point RHS promotes to the float; every other pairing keeps the LHS.
|
||||
test "arithResultType: int×float promotes to float, else takes lhs" {
|
||||
try std.testing.expectEqual(TypeId.f64, Lowering.arithResultType(.s64, .f64));
|
||||
try std.testing.expectEqual(TypeId.f32, Lowering.arithResultType(.u32, .f32));
|
||||
try std.testing.expectEqual(TypeId.f32, Lowering.arithResultType(.s64, .f32));
|
||||
// Non-promoting pairings keep the LHS type.
|
||||
try std.testing.expectEqual(TypeId.s64, Lowering.arithResultType(.s64, .s64));
|
||||
try std.testing.expectEqual(TypeId.f64, Lowering.arithResultType(.f64, .s64));
|
||||
try std.testing.expectEqual(TypeId.f32, Lowering.arithResultType(.f32, .f64));
|
||||
}
|
||||
|
||||
test "expr_typer: unary not is bool, negate preserves operand type" {
|
||||
const alloc = std.testing.allocator;
|
||||
var module = ir_mod.Module.init(alloc);
|
||||
|
||||
@@ -49,7 +49,12 @@ pub const ExprTyper = struct {
|
||||
break :blk .bool;
|
||||
},
|
||||
.eq, .neq, .lt, .lte, .gt, .gte, .and_op, .in_op => .bool,
|
||||
else => self.l.inferExprType(bop.lhs),
|
||||
// Arithmetic / bitwise / shift ops: infer the PROMOTED result
|
||||
// of (lhs, rhs), not the LHS alone — `Lowering.arithResultType`
|
||||
// is the same rule `lowerBinaryOp` applies, so `M + 0.5` types
|
||||
// as `f64` regardless of operand order (was LHS-biased: `M + 0.5`
|
||||
// → s64 while `0.5 + M` → f64).
|
||||
else => Lowering.arithResultType(self.l.inferExprType(bop.lhs), self.l.inferExprType(bop.rhs)),
|
||||
},
|
||||
.unary_op => |uop| switch (uop.op) {
|
||||
.not => .bool,
|
||||
|
||||
161
src/ir/lower.zig
161
src/ir/lower.zig
@@ -917,13 +917,104 @@ pub const Lowering = struct {
|
||||
/// would otherwise mistype the constant (issue 0070).
|
||||
fn registerTypedModuleConst(self: *Lowering, cd: *const ast.ConstDecl) void {
|
||||
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) {
|
||||
.int_literal, .float_literal, .bool_literal, .string_literal, .undef_literal, .null_literal => {
|
||||
const ty = self.resolveType(ta);
|
||||
self.program_index.module_const_map.put(cd.name, .{ .value = cd.value, .ty = ty }) catch {};
|
||||
},
|
||||
else => {},
|
||||
.int_literal, .float_literal, .bool_literal, .string_literal, .undef_literal, .null_literal, .binary_op, .unary_op => {},
|
||||
else => return,
|
||||
}
|
||||
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
|
||||
/// 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,
|
||||
},
|
||||
// Const-EXPRESSION initializer (binary_op / unary_op — the only
|
||||
// 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 = ---;`).
|
||||
@@ -3172,19 +3263,12 @@ pub const Lowering = struct {
|
||||
const rhs_ref_pointee = self.refCapturePointee(bop.rhs);
|
||||
if (rhs_ref_pointee) |p| rhs = self.builder.load(rhs, p);
|
||||
self.target_type = saved_tt;
|
||||
// Infer result type from LHS operand (covers float, bool, etc.)
|
||||
var ty = lhs_ty;
|
||||
|
||||
// Promote int×float → float (e.g., s64 * f32 → f32)
|
||||
// Only for scalar int LHS — don't affect vectors or structs.
|
||||
{
|
||||
const rhs_inferred = rhs_ref_pointee orelse self.inferExprType(bop.rhs);
|
||||
const l_int = isInt(ty);
|
||||
const r_float = (rhs_inferred == .f32 or rhs_inferred == .f64);
|
||||
if (l_int and r_float) {
|
||||
ty = rhs_inferred;
|
||||
}
|
||||
}
|
||||
// Result type follows the shared promotion rule: an int LHS with a
|
||||
// float RHS promotes to the float (`s64 * f32` → `f32`); vectors /
|
||||
// structs keep the LHS type. `inferExprType` reuses the same helper
|
||||
// so static typing agrees with the value produced here.
|
||||
const rhs_inferred = rhs_ref_pointee orelse self.inferExprType(bop.rhs);
|
||||
var ty = arithResultType(lhs_ty, rhs_inferred);
|
||||
|
||||
// Auto-unwrap optional operands for arithmetic/comparison
|
||||
if (!ty.isBuiltin()) {
|
||||
@@ -11876,7 +11960,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.
|
||||
@@ -14437,6 +14521,20 @@ pub const Lowering = struct {
|
||||
return ty == .f32 or ty == .f64;
|
||||
}
|
||||
|
||||
/// Result type of an arithmetic / bitwise / shift binary op over two
|
||||
/// scalar operand types. This is the single promotion rule shared by the
|
||||
/// value path (`lowerBinaryOp`) and AST-level inference
|
||||
/// (`ExprTyper.inferType`'s binary-op arm), so static typing reports
|
||||
/// exactly the type the lowered value carries. An integer LHS with a
|
||||
/// floating-point RHS promotes to the float (`s64 + f64` → `f64`); every
|
||||
/// other pairing — including vectors / structs, whose `isInt` is false —
|
||||
/// takes the LHS type. Comparison / logical ops never reach here (they
|
||||
/// are `.bool` at both sites).
|
||||
pub fn arithResultType(lhs_ty: TypeId, rhs_ty: TypeId) TypeId {
|
||||
if (isInt(lhs_ty) and isFloat(rhs_ty)) return rhs_ty;
|
||||
return lhs_ty;
|
||||
}
|
||||
|
||||
fn isInt(ty: TypeId) bool {
|
||||
return switch (ty) {
|
||||
.s8, .s16, .s32, .s64, .u8, .u16, .u32, .u64, .usize, .isize => true,
|
||||
@@ -15519,6 +15617,31 @@ pub const Lowering = struct {
|
||||
return self.closureShapeKey(params, self.returnValuePart(ret));
|
||||
}
|
||||
|
||||
/// Human-readable description of a typed module-const initializer, used in
|
||||
/// the issue-0088 type-mismatch diagnostic. A literal names its kind; a
|
||||
/// 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) {
|
||||
.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 => 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 {
|
||||
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,42 @@ 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);
|
||||
|
||||
// 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" {
|
||||
|
||||
@@ -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