lowering: decide capture and nested-let renames by liveness - #98
Merged
Conversation
…ntial reuse keeps plain names A capture or nested-block let is renamed only when a reference outside it resolves to a binding that is live across it: the frame root, an enclosing frame, or an arm/block that encloses it. A reference bound by a disjoint arm or block reads its own binding and does not count; one inside a nested closure always does. The census resolves every reference in one walk and identifies binders by AST node address, so case Error why: in three sequential matches keeps its name while every #92/#96 rename survives.
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.
Tightens the #92/#96 renamer to a liveness rule, so a capture name reused across sequential matches keeps its plain spelling.
The rule
A capture (or a nested-block
let)nis renamed to_nonly when some reference tonoutside it resolves, under Pyfun scoping, to a binding that is live across it: a parameter, a root-levellet, a global or builtin, a binding of an enclosing frame, or an arm/nested-block binder whose extent encloses the one being decided. A reference bound by a disjoint arm or block (a sibling arm, an arm of a different match, another block'slet) reads its own binding and does not count. A reference inside a nested closure always counts, since the closure can outlive its arm. A binder's own references never count for it.Mechanism
src/lowering/captures.rsis a one-pass census that resolves everyVarreference and<-target to what it reads at that point (the frame root, a registered arm/nested-block binder, or a nested scope's own slot, which is no occurrence at all) and records whether it sits in a closure. Binders are identified by the address of their AST node (binder_key), which is what the lowering holds when it decides them, and each registered binder records the binders enclosing it, soFrame::must_rename(name, key)can tell an enclosing binder from a disjoint one. A binder the census never saw gets the conservative answer. The oldframe - arm - siblingssubtraction,arm_occurrencesandblock_occurrencesare gone;enter_blocknow just reports whether the block is nested.Effect on emitted Python
where main emits
_v,_why,_v2,_why2. Every #95 rename that protects a real binding survives (the #92 and #96 programs, nested matches reusing a name, an escaping closure, an outer binding read after both matches). No existing snapshot moved; all #95 tests pass unchanged.Tests
e2e_a_capture_reused_across_sequential_matches_keeps_its_name,a_let_reused_across_sequential_branches_keeps_its_name,e2e_an_outer_binding_read_after_both_matches_renames_both_captures,e2e_a_nested_let_reading_the_parameter_it_shadows_is_renamed,e2e_three_sequential_error_arms_emit_the_plain_name,e2e_a_closure_escaping_an_earlier_arm_forces_the_later_capture_to_rename.Docs: DESIGN.md §5 paragraph and the INTERNALS.md section restated for the liveness rule; ROADMAP item 10 notes the tightening.
Closes #97