Skip to content
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

[red-knot] fix eager nested scopes handling #16916

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

mtshiba
Copy link
Contributor

@mtshiba mtshiba commented Mar 22, 2025

Summary

From #16861, and the continuation of #16915.

This PR fixes the incorrect behavior of TypeInferenceBuilder::infer_name_load in eager nested scopes.

And this PR closes #16341.

Test Plan

New test cases are added in annotations/deferred.md.

Copy link
Contributor

github-actions bot commented Mar 22, 2025

mypy_primer results

Changes were detected when running on open source projects
packaging (https://github.com/pypa/packaging)
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_version.py:669:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_version.py:675:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_version.py:684:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_version.py:690:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_version.py:696:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_version.py:711:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_version.py:717:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_version.py:723:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_version.py:732:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_version.py:738:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_specifiers.py:240:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_specifiers.py:264:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_specifiers.py:834:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tests/test_specifiers.py:854:25: Name `operator` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tasks/check.py:71:47: Name `_parse_version` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tasks/check.py:80:35: Name `_parse_version` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tasks/check.py:94:54: Name `_parse_version` used when not defined
+ warning[lint:unresolved-reference] /tmp/mypy_primer/projects/packaging/tasks/check.py:104:47: Name `_parse_version` used when not defined
- Found 137 diagnostics
+ Found 155 diagnostics

@MichaReiser MichaReiser added the red-knot Multi-file analysis & type inference label Mar 22, 2025
Copy link
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! I think there are some adjustments needed.

Comment on lines 4185 to 4201
// If the current scope is an eager scope within the class definition,
// the class name is invisible (unless it is deferred) because it has not yet been registered.
if !self.is_deferred() && scope.is_eager(db) {
if let Some(class_ty) = symbol
.symbol
.ignore_possibly_unbound()
.and_then(Type::into_class_literal)
{
for (ancestor_scope, _) in self.index.ancestor_scopes(file_scope_id) {
let ancestor_scope = ancestor_scope.to_scope_id(db, self.file());
if ancestor_scope == class_ty.body_scope(db) {
symbol = Symbol::Unbound.into();
break;
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is implemented as a special-case patch fix for one very specific case (references to classes inside the class definition), when what we need is a more general fix to the semantics implemented for eager nested scopes. Here is one example of a case that we currently handle wrongly, and is not fixed by this patch:

# this line should be an unresolved-reference, but we don't error on it:
[x for _ in [1]]
x = 1

We should add this to the test suite. The fix should be implemented more like I described in my comment on your prior PR: when snapshotting visible bindings for names in eager nested scopes in semantic indexing, we also need to snapshot the fact that there are no visible bindings, and then respect that when we check eager_bindings above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that we should add undefined symbols to the symbol table of the enclosing scope and register snapshots of empty bindings in eager_bindings?
Should we also register the builtin symbols as undefined ones?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should add the symbol to any outer scope. It may be that we don't actually need any change in semantic index building? We already will store no bindings in eager_bindings if there are no bindings for a name in a scope. It may be that all we need is a bit more sophisticated logic here in infer_name_load, where we need to track as we iterate enclosing scopes if we are still in "eager" context (that is, every scope we've traversed is eager), and if we find no eager binding in a scope while still in eager context, we need to just continue to the next scope rather than falling back to normal non-eager symbol resolution. Once we are out of "eager" context (that is, we have hit a non-eager scope), then eager bindings are no longer relevant and we should check normally for non-eager bindings in each scope. (So I don't think we need any special handling for builtin symbols.)

We should have tests for a case like this, too:

x = 1
def f():
    class C:
        y = [x for _ in [1]]
        x = 2
    reveal_type(C.y)  # should reveal [1]

And the same should be true if x = 1 occurs after the definition of f instead of before it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dcreager would you be able to take a couple minutes to double-check my logic here and make sure I'm suggesting a sensible direction, since you wrote the initial eager-nested-scopes handling?

@sharkdp sharkdp removed their request for review March 24, 2025 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
red-knot Multi-file analysis & type inference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[red-knot] Non-deferred self-references in annotations
3 participants