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:
agra
2026-06-04 23:59:11 +03:00
parent 463557990f
commit b0cc22a8c0
10 changed files with 217 additions and 3 deletions

View File

@@ -340,6 +340,14 @@ qn := f64.nan; // a quiet NaN
`true_min = 0x0000000000000001`, `inf = 0x7FF0000000000000`; the `f32` set is
`0x7F7FFFFF` / `0xFF7FFFFF` / `0x34000000` / `0x00800000` / `0x00000001` /
`0x7F800000`.
- **Type receiver vs. a shadowing value binding.** A numeric-limit access folds
only when the receiver is a builtin numeric **type name** (`f64.epsilon`,
`s32.max`, `u8.max`). A backtick raw identifier that binds a *value* whose
spelling shadows a type name (`` `f64 := … ``, F0.6) is an ordinary value:
`` `f64.epsilon `` reads that value's `epsilon` field — it does **not** fold to
the limit. The two never collide, even in the same scope — a bare builtin name
in expression position is always a type, and only the raw `` `` `` spelling
can bind a value under it.
### Enum Types
User-defined sum types with named variants. Variants may optionally carry typed data (tagged unions). Internally, payload-less enums are represented as `i64` (variant index). Enums with payloads are represented as `{ i64, [max_payload_size x i8] }` (tag + data).