fix(lang): numeric-limit intercept no longer shadows raw value bindings [NL.2]
The numeric-limit accessor intercept (NL.1 integer `.min`/`.max`, NL.2 float `.epsilon`/`.min_positive`/`.true_min`/`.inf`/`.nan`) treated ANY receiver whose text matched a builtin numeric type name as a TYPE receiver, without first checking for an in-scope VALUE binding. An F0.6 backtick raw identifier (`` `f64 := … ``) binds a local under the stripped name `f64`; field access on it (`` `f64.epsilon ``) parses as an `.identifier` receiver, which the intercept silently folded to the type's numeric limit — a silent-wrong-value bug (issue 0092). Fix: for `.identifier` receivers, prefer an in-scope value binding (`Scope.lookup`) over the fold — defer to ordinary field lowering when the identifier resolves to a value. `.type_expr` receivers are unambiguous types and are never shadowed, so a bare `f64.epsilon`/`s32.max` still folds even in a scope where `` `f64 `` is bound (the parser classifies a bare builtin name as a `.type_expr`). Mirrored in expr_typer.zig so inference matches lowering (avoids the issue-0083 two-resolver desync). Float-only-on-int and non-numeric-receiver errors are unchanged. - src/ir/lower.zig: value-binding guard in lowerNumericLimit. - src/ir/expr_typer.zig: same guard in the numeric-limit inference arm. - src/ir/expr_typer.test.zig: unit test pinning the two-resolver agreement. - examples/0161-types-numeric-limit-value-shadow.sx: regression — raw `` `f64 ``/`` `s32 ``/`` `u8 `` value reads coexisting with bare folds. - issues/0092: RESOLVED banner. - specs.md / readme.md: receiver-vs-shadowing-value-binding note.
This commit is contained in:
77
issues/0092-raw-reserved-value-numeric-limit-shadow.md
Normal file
77
issues/0092-raw-reserved-value-numeric-limit-shadow.md
Normal file
@@ -0,0 +1,77 @@
|
||||
> **RESOLVED** (NL.2 attempt 3). Root cause: the numeric-limit accessor
|
||||
> intercept treated ANY receiver whose text matched a builtin numeric type
|
||||
> name as a TYPE receiver, without first checking whether that identifier
|
||||
> resolved to an in-scope VALUE binding. An F0.6 backtick raw identifier
|
||||
> (`` `f64 := … ``) binds a local under the stripped name `f64`; field access
|
||||
> on it (`` `f64.epsilon ``) parses as an `.identifier` receiver, which the
|
||||
> intercept silently folded to the type's numeric limit — a silent-wrong-value
|
||||
> bug.
|
||||
>
|
||||
> Fix (value-binding precedence for `.identifier` receivers; `.type_expr`
|
||||
> receivers are unambiguous types and never shadowed):
|
||||
> - `src/ir/lower.zig` — `lowerNumericLimit`: after confirming the receiver is
|
||||
> a builtin numeric type name and the field is a limit accessor, return null
|
||||
> (defer to ordinary field lowering) when `fa.object` is an `.identifier`
|
||||
> that `Scope.lookup` resolves to a value binding.
|
||||
> - `src/ir/expr_typer.zig` — numeric-limit inference arm: mirror the same
|
||||
> guard so inferred types match lowering (avoids the issue-0083 two-resolver
|
||||
> desync).
|
||||
>
|
||||
> Bare `f64.epsilon` / `s32.max` (no shadowing binding) still fold — the parser
|
||||
> classifies a bare builtin name as a `.type_expr` (parser.zig:2743), so the
|
||||
> bare receiver is never value-shadowed even in a scope where `` `f64 `` is
|
||||
> bound. Float-only-on-int and non-numeric-receiver errors are unchanged.
|
||||
>
|
||||
> Regression: `examples/0161-types-numeric-limit-value-shadow.sx` (raw
|
||||
> `` `f64 ``/`` `s32 ``/`` `u8 `` value reads coexisting with bare folds) +
|
||||
> unit test in `src/ir/expr_typer.test.zig`. NL.1 (`examples/0148`) / NL.2
|
||||
> (`examples/0159`, `examples/0160`) unregressed.
|
||||
|
||||
# 0092 — numeric-limit intercept hijacks raw reserved-spelled value receivers
|
||||
|
||||
## Symptom
|
||||
|
||||
Field access on a raw reserved-spelled value binding is interpreted as a builtin
|
||||
type numeric-limit access instead of an ordinary value field access. Observed:
|
||||
the repro prints `0.000000 2147483647` (`f64.epsilon` / `s32.max`). Expected:
|
||||
it prints `12 78` from the `Box` fields.
|
||||
|
||||
## Reproduction
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: struct { epsilon: s64; max: s64; }
|
||||
|
||||
main :: () -> s32 {
|
||||
`f64 := Box.{ epsilon = 12, max = 34 };
|
||||
`s32 := Box.{ epsilon = 56, max = 78 };
|
||||
print("{} {}\n", `f64.epsilon, `s32.max);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Investigation prompt
|
||||
|
||||
Investigate issue 0092: raw reserved-spelled value receivers are being captured
|
||||
by the numeric-limit accessor intercept. In `src/ir/lower.zig`, start at
|
||||
`Lowering.lowerFieldAccess` and `Lowering.lowerNumericLimit` (currently around
|
||||
`lower.zig:4826` / `lower.zig:4923`). The intercept treats any
|
||||
`.identifier`/`.type_expr` receiver whose text is a builtin type name as a type
|
||||
receiver, without first checking whether the identifier resolves to a value
|
||||
binding in the current lexical scope. Mirror the fix in `src/ir/expr_typer.zig`
|
||||
around the numeric-limit inference arm so inferred types match lowering.
|
||||
|
||||
Likely fix: for `.identifier` receivers, prefer an in-scope value binding
|
||||
(`Scope.lookup`) over the builtin-type numeric-limit intercept; keep the
|
||||
intercept for actual type receivers (`.type_expr`, and bare reserved integer
|
||||
names with no value binding). If raw provenance is available in the AST, using it
|
||||
to disambiguate is also acceptable, but the observable rule must be that
|
||||
`` `f64.epsilon `` reads the value field when `` `f64 `` is a value binding,
|
||||
while bare `f64.epsilon` / `s32.max` still fold as numeric limits.
|
||||
|
||||
Verification: pin a regression test from the repro above. It should print
|
||||
`12 78`. Also verify the existing numeric-limit examples still pass:
|
||||
`examples/0148-types-int-numeric-limits.sx`,
|
||||
`examples/0159-types-float-numeric-limits.sx`, and the negative diagnostics in
|
||||
`examples/0160-types-float-numeric-limits-errors.sx`.
|
||||
Reference in New Issue
Block a user