lowering: close the local-binder module-alias shadowing residual - #18
Merged
Conversation
Any binder named like an imported module alias now forces the aliased import (import ids as _pf_ids): parameters, block lets anywhere in a function (Python hoists locals, so position does not matter), lambda parameters, match-pattern captures at any level including module level, and native-CE binders (a `let! ids` lowers to a `case Ok(ids):` capture inside the generated def). Design: a whole-module binder walk (`collect_binders`, entering nested scopes unlike `scan_scope`) feeds one `binder_names` set consulted by `py_module_ref`. Deliberately an overapproximation - one collision anywhere costs only the alias spelling - which keeps the check independent of the scope machinery the fold pass (P8 rebind check) and closure capture rely on. A first attempt that enriched `fn_local_stack` frames instead made the fold pass conservatively reject the dedupLegs shape; the pinned test caught it. ROADMAP: the module-alias shadowing entry is now fully closed.
This was referenced Jul 30, 2026
Closed
Merged
simontreanor
added a commit
that referenced
this pull request
Jul 31, 2026
Two user-visible additions since 0.2.0 -- the `input` prelude builtin
(`string ->{io} string`, #19) and caller-supplied `extern` keyword slots
(`kw = ...`, #21) -- plus five fixes, four of them from the first program
written in Pyfun in anger:
* imported schemes reused the exporting module's type-variable ids, silently
blocking generalization in the importing module and cascading to its
consumers (#26 / #27)
* a recursive function of arity >= 2 could not perform any effect -- the shape
of every loop in a language with no `while` (#24 / #28)
* a module that only matched an imported `Option` emitted `None_` without
importing it (#25 / #29)
* user identifiers collided with Python keywords, builtins the emitter calls,
and modules an `extern` imports (#23 / #30)
* the local-binder module-alias shadowing residual (#18)
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.
Closes the residual from the module-alias shadowing fix: any binder named like an imported module alias - parameter, block
letanywhere in the function (Python hoists locals, so even one after the qualified call), lambda parameter, match-pattern capture (function-level or module-level), or native-CE binder (let! idsbecomes acase Ok(ids):capture inside the generated def) - now forcesimport ids as _pf_ids, with every qualified reference routed through the alias.Design note: the check is a whole-module binder walk (
collect_binders, which enters nested scopes, unlikescan_scope) feeding onebinder_namesset. That is a deliberate overapproximation: one collision anywhere in the module costs only the alias spelling in the emitted imports, and it keeps the check fully independent of the scope machinery the fold pass and closure capture rely on. The first attempt threaded richer frames throughfn_local_stackand the fold pass''s P8 rebind check immediately became conservative enough to reject the network-rail dedupLegs shape - the pinned differential test caught it, and the whole-module design avoids that class of interaction entirely.Tests: one per-shape regression test covering all six binder kinds end-to-end through Python, plus the existing collided/uncollided tests. Full suite green; clippy clean. ROADMAP entry closed.