-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] improve lazy scope place lookup #19321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
96967f3 to
6fb1747
Compare
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
possibly-unbound-attribute |
0 | 123 | 27 |
unresolved-attribute |
101 | 2 | 6 |
call-non-callable |
0 | 47 | 0 |
invalid-argument-type |
0 | 20 | 13 |
unsupported-operator |
0 | 5 | 20 |
unused-ignore-comment |
3 | 0 | 0 |
invalid-assignment |
0 | 2 | 0 |
invalid-return-type |
0 | 2 | 0 |
missing-argument |
0 | 1 | 0 |
no-matching-overload |
0 | 1 | 0 |
redundant-cast |
1 | 0 | 0 |
too-many-positional-arguments |
1 | 0 | 0 |
unresolved-reference |
0 | 1 | 0 |
| Total | 106 | 204 | 66 |
|
The changes in this PR seem to generate new false positive warnings like this: def f(cond: bool):
if cond:
foo = 1
def g():
if cond:
# warning: [possibly-unresolved-reference]
fooThis may be a separate work, but it shows the need for a "proper" boundness analysis of nonlocal symbols (we currently assume the boundness of |
sharkdp
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks really great, thank you!
I would appreciate if someone with a better understanding of lazy/eager scopes could review the semantic index changes here.
Did you look more closely at the ecosystem impact? I understand that there might be quite a few new (true and false) positives because we infer a less-permissive type in many cases now (since we don't union with Unknown anymore), which probably adds a lot of noise to the ecosystem results. It might still be worth going over some of them to see if there are any unexpected things popping up.
crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md
Outdated
Show resolved
Hide resolved
…/nested.md Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
6ac318c to
508823f
Compare
A typical error added by this PR is something like Other undesirable errors (warnings) are caused by the lack of proper boundness analysis of nonlocal symbols, as mentioned above. One thing that bothers me is that the ecosystem-analyzer results report a |
The mypy-primer comment is truncated if it becomes too long. This new error does show up in mypy-primer, at https://github.com/astral-sh/ruff/actions/runs/16325751556/job/46115311715?pr=19321#step:6:1477
I think assuming a nonlocal symbol (accessed from a lazy nested scope) is always bound is probably the right thing to do, at least for now. If we do that, it shouldn't cause false positive
This should just be replacing a previous |
028325e to
fc11d9c
Compare
I see. I checked and it seems that the new error correctly captures the mistake in the code.
Okay. For now, I've reverted the behavior to treat all nonlocal symbols as bound.
Yes, I checked that all the newly added |
Co-authored-by: Carl Meyer <carl@oddbird.net>
4d72a9c to
34c21c4
Compare
… the presence of attribute or subscript assignments
carljm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me. I think as a follow-up we will want to further improve this so that it looks for reassignments after the definition of the nested scope rather than any reassignments at all. Then we would be able to handle the case described in astral-sh/ty#559
Also, @oconnor663 is working on some changes to the semantic index so that it calculates target scopes for nonlocal references (as a fixup pass); I think that may allow simplifying some of the code here. (We decided to do this for now instead of the bigger change to deferred walking of nested scopes.)
I would like to leave it up to @MichaReiser whether to merge this before or after he merges #19497, since that's a much bigger PR and more prone to accumulating merge conflicts.
crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md
Outdated
Show resolved
Hide resolved
crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md
Outdated
Show resolved
Hide resolved
|
Either way is fine with me. The change in |
|
I'll go ahead and merge this. |
Co-authored-by: David Peter <sharkdp@users.noreply.github.com> Co-authored-by: Carl Meyer <carl@oddbird.net> Co-authored-by: Micha Reiser <micha@reiser.io>
| let key = EnclosingSnapshotKey { | ||
| enclosing_scope, | ||
| enclosing_place: place_id, | ||
| nested_scope, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this maybe intended to be nested_scope: ancestor_scope_id instead? I think as written the loop body does the same lookup repeatedly, instead of doing a different lookup for each ancestor, but I don't understand this code well enough yet to know what sort of test case would demonstrate the difference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider the following situation: there is a nested scope (child scope) and a further nested scope (grandchild scope) within an enclosing scope. If both scopes (child/grandchild) are lazy scopes, the snapshots of the enclosing scope's state for the two scopes should be exactly the same (or, if narrowing is performed within the child scope, the snapshot of the enclosing scope seen by the grandchild will be more detailed). Therefore, the snapshot only needs to be taken once in the innermost lazy scope. In other words, the operation to obtain the snapshot can be performed outside the loop, but since it is not a particularly expensive operation, leaving it in the loop should not cause any significant performance issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The need for the loop here is because if no snapshot is found and there is a lazy scope between the enclosing scope and the nested scope, EnclosingSnapshotResult::NoLongerInEagerContext must be returned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, thanks for clarifying.
) ## Summary This is a follow-up to #19321. If we try to access a class variable before it is defined, the variable is looked up in the global scope, rather than in any enclosing scopes. Closes astral-sh/ty#875. ## Test Plan New tests in `narrow/conditionals/nested.md`.
## Summary This is a follow-up to #19321. Narrowing constraints introduced in a class scope were not applied even when they can be applied in lazy nested scopes. This PR fixes so that they are now applied. Conversely, there were cases where narrowing constraints were being applied in places where they should not, so it is also fixed. ## Test Plan Some TODOs in `narrow/conditionals/nested.md` are now work correctly.
## Summary This is a follow-up to #19321. Now lazy snapshots are updated to take into account new bindings on every symbol reassignment. ```python def outer(x: A | None): if x is None: x = A() reveal_type(x) # revealed: A def inner() -> None: # lazy snapshot: {x: A} reveal_type(x) # revealed: A inner() def outer() -> None: x = None x = 1 def inner() -> None: # lazy snapshot: {x: Literal[1]} -> {x: Literal[1, 2]} reveal_type(x) # revealed: Literal[1, 2] inner() x = 2 ``` Closes astral-sh/ty#559. ## Test Plan Some TODOs in `public_types.md` now work properly. --------- Co-authored-by: Carl Meyer <carl@astral.sh>
## Summary This is a follow-up to astral-sh#19321. Now lazy snapshots are updated to take into account new bindings on every symbol reassignment. ```python def outer(x: A | None): if x is None: x = A() reveal_type(x) # revealed: A def inner() -> None: # lazy snapshot: {x: A} reveal_type(x) # revealed: A inner() def outer() -> None: x = None x = 1 def inner() -> None: # lazy snapshot: {x: Literal[1]} -> {x: Literal[1, 2]} reveal_type(x) # revealed: Literal[1, 2] inner() x = 2 ``` Closes astral-sh/ty#559. ## Test Plan Some TODOs in `public_types.md` now work properly. --------- Co-authored-by: Carl Meyer <carl@astral.sh>
Summary
This PR improves place (symbol) lookup in lazy scopes.
This PR has two main changes:
Unknown([ty] Infer nonlocal types as unions of all reachable bindings #18750), but if the defined scope is private (e.g., function scope), we can excludeUnknown(Unknownis only necessary when the symbol is writable from the outside), so we will do so.closes astral-sh/ty#259
closes astral-sh/ty#659
Test Plan
New tests in
narrow/conditionals/nested.md.