Repository navigation
Preserve annotation, lambda and comprehension source bindings - #900
Open
yangfan-yf-yf wants to merge 3 commits into
Open
yangfan-yf-yf wants to merge 3 commits into
yangfan-yf-yf wants to merge 3 commits into
Conversation
Resolve expression-local binders independently of their structural owner. Keep call value overlays separate from source identities, and refuse ambiguous annotation or unsupported ordinary-parameter edits atomically.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #900 +/- ##
==========================================
- Coverage 95.34% 95.33% -0.01%
==========================================
Files 134 136 +2
Lines 26762 28783 +2021
==========================================
+ Hits 25516 27441 +1925
- Misses 1246 1342 +96 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Exercise closure creation, comprehensions, descriptor calls and annotation namespaces through runtime-preserving edits and source queries. Verify local edit boundaries and atomic refusal of ambiguous bindings, and retain the legacy behavior of independently parsed expression queries.
Verify independent lambda assignment-expression values, atomic refusal of ambiguous conditional type alias edits, and generic bound source locations through public queries and runtime-preserving renames.
3 tasks done
This branch has not been deployed
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.
Description
I found that Rename can confuse a function's annotation names with its parameters, or mistake lambda and comprehension locals for same-named outer variables. For example:
Renaming the module binding to
annotationshould update both annotations while leaving the parameter and its body reference unchanged. Renaming the parameter should leave the annotations bound to the outer name. The runtime result and resolved annotation types should remain the same in both cases.I now resolve these expressions in their name-binding environments while retaining the containing structural scope. Lambda formals and comprehension targets have distinct source identities. Renaming
targetinlambda *, target: targetalso updates the keyword label of a resolved call to that lambda; renaming an outer variable leaves a shadowing comprehension target alone. Completion and error queries use the same identities instead of falling back to an unrelated outer binding.Lambda call values stay separate from canonical attribute and parameter names. A lambda stored on a class can bind a receiver, while a lambda assigned directly to an instance does not. If a class defines
callback = lambda self, item: selfbut its initializer assignsself.callback = lambda item: item, an instance call must use the latter. Rename follows the returned object's field without confusing it with the class callback's receiver. Saved callable values retain the same distinction. This value selection covers direct assignments in the same class body and__init__; it does not infer arbitrary conditional or dynamic attribute installation.Ordinary positional-only and keyword-only parameters remain outside the existing ordinary-function call protocol. Queries return distinct readable names and definition locations with an unknown value. Rename refuses edits to those parameter bindings, and IntroduceParameter refuses to rewrite such a signature, before generating source changes. Renaming an independent outer variable remains available. Unresolved generic annotation bindings are likewise rejected before a potentially affected edit.
The existing
GlobalScope.get_inner_scope_for_offset()still returns the structural scope. The ordinaryArgumentsandPyFunctionprotocols remain unchanged; lambda argument matching is confined to the new expression model. This does not include #898's ordinary parameter-contract changes, #899's unresolved-callee keyword-label fix, or the separate Inline safety guards in #894, #897 and #890.Arbitrary dynamic class attribute installation, explicit string namespaces, complete generic-name resolution, and Inline annotation or initializer timing remain outside this change. Unknown argument expansion or conflicting lambda arguments remain unknown. I have documented the new source queries and their supported boundaries in the library documentation.
Validation
I expanded the regression file to 210 cases covering source identities, annotation environments, lambda calls and receivers, comprehension bindings, queries, and atomic rejection of unsupported edits. The additional tests exercise closure creation values, annotation namespace order, local edit boundaries, public binding-tree traversal, and legacy independently parsed expression queries. Version-specific annotation cases are skipped on the other runtime profile; PEP 695 fixtures require Python 3.12 or newer.
On the master baseline, the selected existing-API tests produced 14 failures, 5 passes and 6 version skips on each local runtime. Twelve failures were actual execution or annotation-result changes; two were incorrect binding or edit-range assertions. Missing new APIs were not counted as baseline failures.
After the final product change, the full suite passed on Python 3.12.3: 2252 passed, 18 skipped, 5 xfailed; and Python 3.14.7: 2256 passed, 14 skipped, 5 xfailed. Both runs collected 2275 cases with no failures or errors.
I then added version guards to the two PEP 695 tests and expanded the behavior contracts, leaving all product sources and the original test bodies unchanged. At that stage, the 205-case regression file passed on Python 3.12.3: 190 passed, 15 skipped; and Python 3.14.7: 188 passed, 17 skipped.
I added five additional boundary tests for lambda assignment-expression locals, conditional class type aliases, and generic class bound queries. Field renames preserve execution while keeping outer and lambda locals separate. Ambiguous conditional alias references remain readable but reject affected edits before changing source; unrelated edits preserve the alias's actual runtime value. The final 210-case file passed on Python 3.12.3: 195 passed, 15 skipped; and Python 3.14.7: 193 passed, 17 skipped. Product sources and the preceding 205 test bodies remain unchanged. A master comparison of these five cases produced four binding or conservative-query assertion failures and one passing bound-query control on each runtime; the failed queries preceded mutation and were not counted as runtime failures.
An additional 21-case selection using existing public APIs produced 18 failures and 3 passing controls on the master baseline on each runtime: 4 actual runtime changes, 6 binding or location errors, 2 completion differences, 4 diagnostic differences, and 2 mutation-boundary differences. The six independent-expression fallback controls passed on both baseline and candidate; their existing unknown-inference limits remain unchanged.
The mixed class/instance callback regression matrix preserved execution in all 48 cases on both runtimes. The preceding implementation failed 8 of those cases with actual
AttributeErrors and preserved the other 40.Black and isort passed for the 10 changed Python files; configured pre-commit checks and the whitespace check passed across the 12-file change. The final test-only update also passed those applicable checks.
GitHub Actions passed all 18 pytest jobs on
f9b4f8eacross Python 3.10, 3.11, 3.12, 3.13, 3.14 and 3.15.0-rc.1 on Ubuntu, Windows and macOS. All 26 reported checks and statuses passed, including Codecov project and patch. Codecov reported 95.33% project coverage, accepted against its adjusted base, and 95.83129% patch coverage.The local results above are Windows results. I have not treated Python 3.10 syntax checks as runtime or multi-platform CI results. The existing dynamic-attribute and unresolved-callee keyword-label limitations remain outside the supported scope described above.
Checklist