Close the two remaining shadowing holes (§13.25, §13.18) - #180
Merged
Merged
Conversation
evaluateIdentifier consulted the library registries before the
environment, so §9.1's two global names beat every binding in the
language - a parameter, a loop variable, a name bound in a block, an
`import ... as`. The binding was made and then never readable.
function render(type) { return type }
render("warning") // was: library function {type}
Nothing raised, which makes this worse than the shadowing §13.22
described: there the failure was at least a fault at some later call.
Here a string parameter silently is a function object and gets stored as
one - `new Cmd("edit").type` wrote the library function into an instance
- which is the silent wrong answer §3 sets out to make impossible. And
`type` is an ordinary word for an ordinary parameter: a command type, an
event type, a token type.
§9.1 grants these two global standing "the same standing print/type-
checking primitives have in other scripting languages", where a local
shadows them without ceremony. The behavior contradicted the rationale
offered for it.
The two lookups are swapped: environment first, registries as the
fallback when nothing is bound. Shadowing becomes the ordinary rule and a
global is what a name means where nothing else claims it. The optimizer's
LibraryBinding marking is unchanged and still correct, now saving an
undefined name two map lookups rather than saving every ordinary variable
two - which the new order already does. Allocation is identical on the
benchmarks and wall time is within noise.
Found by sweeping the scoping surface after §14 decision 12 rather than
by the Chisel/Studio report - the mirror image of §13.22, where a member
hid an outer name and here an outer name hid everything.
The SPEC entry for this lands with the next commit, which touches the
same sections.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGsNMRiKwWmvoizxD619zA
Closes the residual the previous pass recorded rather than fixed: the
in-body check could not see a field here colliding with a method that
arrived from a superclass or a used trait, so `class C extends P { label
= 7 }` against `P.label()` kept the silent duality §13.18 is about -
`c.label` answering 7 and `c.label()` answering the method.
checkInheritedCollisions runs once the class body is complete, which is
the earliest either declaration path could see the other: `use` may sit
anywhere in the body, so when a member is declared the other body may not
have been consulted yet. Both directions are caught - a field here
against an inherited method, and a method here against an inherited
field - through a superclass, a grandparent, or a trait.
Only field-against-method counts. A name arriving twice as two fields, or
twice as two methods, is overriding, which is the point of `extends` and
`use`; five cases pin that it stays legal.
The fault is reported at the declaration in this body and names where the
other one came from, that being the half a reader cannot see from here.
object.Field gained the token it was declared at so a field can be
reported precisely after the fact, and declaredMethods reads method names
and tokens back off the class body for the same reason.
Also carries the SPEC for both this and §13.25 from the previous commit,
since they touch the same sections: §13.25 is new, §13.18 records the
residual as closed, §9.1 states that the two globals are shadowable, and
§15 lists §13.25 among the closed items with a note that it came from a
sweep rather than from the report.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGsNMRiKwWmvoizxD619zA
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
The two items left over from the "does this resolve all shadow/scoping issues?" sweep after #179. One is a new callout that report never found; the other closes a residual #179 recorded rather than fixed.
§13.25 — a library global could not be shadowed by anything
evaluateIdentifierconsulted the library registries before the environment, so §9.1's two global names beat every binding in the language — a parameter, a loop variable, a name bound in a block, animport ... as. The binding was made and then never readable.Nothing raised, which makes it worse than the shadowing §13.22 described: there the failure was at least a fault at some later call. Here a
stringparameter silently is a function object and gets stored as one — the silent wrong answer §3 sets out to make impossible. Andtypeis an ordinary word for an ordinary parameter: a command type, an event type, a token type.§9.1 grants these two global standing "the same standing print/type-checking primitives have in other scripting languages" — where a local shadows them without ceremony (Python's
typeis a builtin any local shadows). The behaviour contradicted the rationale offered for it.Fix: the two lookups are swapped — environment first, registries as the fallback when nothing is bound. Shadowing becomes the ordinary rule; a global is what a name means where nothing else claims it. The optimizer's
LibraryBindingmarking is unchanged and still correct: it now saves an undefined name two map lookups, rather than saving every ordinary variable two, which the new order already does.Cost: none measurable. Allocation is identical on
evaluator/benchmark_test.goand wall time is within run-to-run noise — a global now walks the scope chain before reaching the registry, which is the walk every other name already pays.§13.18 — the cross-body collision
#179's note recorded this as a residual with the shape of the fix; this closes it. The in-body check couldn't see a field here colliding with a method that arrived from a superclass or a used trait:
checkInheritedCollisionsruns once the class body is complete — the earliest either declaration path could see the other, sinceusemay sit anywhere in the body. Both directions are caught (a field here against an inherited method, and a method here against an inherited field), through a superclass, a grandparent, or a trait.Only field-against-method counts. A name arriving twice as two fields, or twice as two methods, is overriding — the point of
extendsanduse— and five cases pin that it stays legal.The fault is reported at the declaration in this body and names where the other one came from, that being the half a reader can't see from here.
object.Fieldgained the token it was declared at;declaredMethodsreads method names and tokens back off the class body.Verification
evaluator/globals_test.go: seven binding forms shadowing a global, an aliased import doing the same, the globals still resolving unshadowed (including inside a function and a method), and a shadow not escaping its scope.evaluator/class_members_test.go: five cross-body collisions and the five overriding shapes that must stay legal, alongside the existing in-body cases.examples/unchanged (mud.gsexcepted as before — a non-terminating interactive loop, byte-identical for 2.9 MB, differing only in frame count inside the timeout).go build ./...,go vet ./...,gofmt -l .,go test ./...clean.Note on §15
§13.25 came from sweeping the scoping surface after §14 decision 12, not from the Chisel/Studio report — and it outranked most of what was still open. §15 now records that, because ranking a report is worth doing and is not the same as knowing what is wrong.
With this, §15's next item is #5 (§13.16) — the export surface, and the only ranked item left that isn't small. It needs §14 decision 10 settled before code.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EGsNMRiKwWmvoizxD619zA
Generated by Claude Code