fix(lang): numeric-limit shadow guard covers all 3 value sources [NL.2]
The issue-0092 fix guarded the numeric-limit accessor intercept against
raw value shadowing using only lexical Scope.lookup. The ordinary
identifier field-access path resolves a value through THREE sources
(scope / program_index.global_names / program_index.module_const_map),
so a backtick raw identifier bound at module scope — a global
`` `f64 := Box.{…} `` or a module constant `` `f64 :: Box.{…} `` — still
folded `` `f64.epsilon `` to the numeric limit instead of reading the
value's field (issue 0093, plus the module-const variant: same root
cause, same fix).
Fix: a single shared helper Lowering.identifierBindsValue(name) that
returns true when the name resolves through scope OR global_names OR
module_const_map. Used in BOTH lowerNumericLimit (lower.zig) and the
numeric-limit inference arm (expr_typer.zig) so the two resolvers can't
desync (issue-0083 class). A bare `f64.epsilon` / `s32.max` (a
.type_expr receiver) still folds even when a raw value of the same
spelling is bound — the bare receiver is never value-shadowed.
- examples/0161: extended to exercise all three binding kinds — a
GLOBAL `` `f32 ``, a MODULE-CONST `` `s16 ``, and LOCAL
`` `f64 ``/`` `s32 ``/`` `u8 `` — each reading its field while the
bare spelling still folds.
- src/ir/expr_typer.test.zig: unit test pinning the global +
module-const sources of the shared guard.
- issues/0093: RESOLVED banner (3-source root cause + fix, module-const
variant folded in).
- specs.md / readme.md: numeric-limit shadow note now source-agnostic
(local / global / module-const).
This commit is contained in:
@@ -4904,6 +4904,27 @@ pub const Lowering = struct {
|
||||
return self.lowerFieldAccessOnType(obj, obj_ty, fa.field, span);
|
||||
}
|
||||
|
||||
/// True when an `.identifier` receiver text resolves to an in-scope VALUE
|
||||
/// binding rather than a builtin type. A backtick raw identifier (F0.6) can
|
||||
/// bind a value whose spelling shadows a builtin type name (`` `f64 := … ``);
|
||||
/// such a value is reachable through the same three sources the ordinary
|
||||
/// identifier field-access path consults (see `expr_typer` `.identifier`
|
||||
/// arm): lexical `scope`, program `global_names`, and module value
|
||||
/// constants `module_const_map`. The numeric-limit intercept must defer to
|
||||
/// ordinary field access whenever ANY of the three binds the name, so a
|
||||
/// raw value field read is never hijacked into a numeric-limit fold
|
||||
/// (issues 0092 local / 0093 global + module-const). A single helper used
|
||||
/// by both lowering and inference keeps the two resolvers in lockstep
|
||||
/// (issue-0083 two-resolver defect class).
|
||||
pub fn identifierBindsValue(self: *Lowering, name: []const u8) bool {
|
||||
if (self.scope) |scope| {
|
||||
if (scope.lookup(name) != null) return true;
|
||||
}
|
||||
if (self.program_index.global_names.get(name) != null) return true;
|
||||
if (self.program_index.module_const_map.get(name) != null) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Numeric-limit accessor intercept (`<Type>.min`/`.max`/`.epsilon`/
|
||||
/// `.min_positive`/`.true_min`/`.inf`/`.nan`), a sibling of the `error.X` /
|
||||
/// `Struct.CONST` / pack-arity identifier-receiver intercepts in
|
||||
@@ -4933,13 +4954,10 @@ pub const Lowering = struct {
|
||||
// shadows a builtin type name (`` `f64 := … ``). Field access on that
|
||||
// value is an ordinary field read, not a numeric-limit fold — defer to
|
||||
// the normal field-access path when the receiver identifier resolves to
|
||||
// an in-scope value binding (issue 0092). A `.type_expr` receiver is
|
||||
// unambiguously a type and can never be value-shadowed.
|
||||
if (fa.object.data == .identifier) {
|
||||
if (self.scope) |scope| {
|
||||
if (scope.lookup(name) != null) return null;
|
||||
}
|
||||
}
|
||||
// a value binding through any of scope / globals / module consts
|
||||
// (issues 0092, 0093). A `.type_expr` receiver is unambiguously a type
|
||||
// and can never be value-shadowed.
|
||||
if (fa.object.data == .identifier and self.identifierBindsValue(name)) return null;
|
||||
|
||||
if (TypeResolver.integerLimitFor(name, fa.field)) |value| {
|
||||
return self.builder.constInt(value, ty);
|
||||
|
||||
Reference in New Issue
Block a user