forked from astral-sh/ruff
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bind top-most parent when importing nested module (astral-sh#14946)
When importing a nested module, we were correctly creating a binding for the top-most parent, but we were binding that to the nested module, not to that parent module. Moreover, we weren't treating those submodules as members of their containing parents. This PR addresses both issues, so that nested imports work as expected. As discussed in ~Slack~ whatever chat app I find myself in these days :smile:, this requires keeping track of which modules have been imported within the current file, so that when we resolve member access on a module reference, we can see if that member has been imported as a submodule. If so, we return the submodule reference immediately, instead of checking whether the parent module's definition defines the symbol. This is currently done in a flow insensitive manner. The `SemanticIndex` now tracks all of the modules that are imported (via `import`, not via `from...import`). The member access logic mentioned above currently only considers module imports in the file containing the attribute expression. --------- Co-authored-by: Carl Meyer <[email protected]>
- Loading branch information
Showing
10 changed files
with
308 additions
and
28 deletions.
There are no files selected for viewing
This file contains 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
100 changes: 100 additions & 0 deletions
100
crates/red_knot_python_semantic/resources/mdtest/import/tracking.md
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# Tracking imported modules | ||
|
||
These tests depend on how we track which modules have been imported. There are currently two | ||
characteristics of our module tracking that can lead to inaccuracies: | ||
|
||
- Imports are tracked on a per-file basis. At runtime, importing a submodule in one file makes that | ||
submodule globally available via any reference to the containing package. We will flag an error | ||
if a file tries to access a submodule without there being an import of that submodule _in that | ||
same file_. | ||
|
||
This is a purposeful decision, and not one we plan to change. If a module wants to re-export some | ||
other module that it imports, there are ways to do that (tested below) that are blessed by the | ||
typing spec and that are visible to our file-scoped import tracking. | ||
|
||
- Imports are tracked flow-insensitively: submodule accesses are allowed and resolved if that | ||
submodule is imported _anywhere in the file_. This handles the common case where all imports are | ||
grouped at the top of the file, and is easiest to implement. We might revisit this decision and | ||
track submodule imports flow-sensitively, in which case we will have to update the assertions in | ||
some of these tests. | ||
|
||
## Import submodule later in file | ||
|
||
This test highlights our flow-insensitive analysis, since we access the `a.b` submodule before it | ||
has been imported. | ||
|
||
```py | ||
import a | ||
|
||
# Would be an error with flow-sensitive tracking | ||
reveal_type(a.b.C) # revealed: Literal[C] | ||
|
||
import a.b | ||
``` | ||
|
||
```py path=a/__init__.py | ||
``` | ||
|
||
```py path=a/b.py | ||
class C: ... | ||
``` | ||
|
||
## Rename a re-export | ||
|
||
This test highlights how import tracking is local to each file, but specifically to the file where a | ||
containing module is first referenced. This allows the main module to see that `q.a` contains a | ||
submodule `b`, even though `a.b` is never imported in the main module. | ||
|
||
```py | ||
from q import a, b | ||
|
||
reveal_type(b) # revealed: <module 'a.b'> | ||
reveal_type(b.C) # revealed: Literal[C] | ||
|
||
reveal_type(a.b) # revealed: <module 'a.b'> | ||
reveal_type(a.b.C) # revealed: Literal[C] | ||
``` | ||
|
||
```py path=a/__init__.py | ||
``` | ||
|
||
```py path=a/b.py | ||
class C: ... | ||
``` | ||
|
||
```py path=q.py | ||
import a as a | ||
import a.b as b | ||
``` | ||
|
||
## Attribute overrides submodule | ||
|
||
Technically, either a submodule or a non-module attribute could shadow the other, depending on the | ||
ordering of when the submodule is loaded relative to the parent module's `__init__.py` file being | ||
evaluated. We have chosen to always have the submodule take priority. (This matches pyright's | ||
current behavior, and opposite of mypy's current behavior.) | ||
|
||
```py | ||
import sub.b | ||
import attr.b | ||
|
||
# In the Python interpreter, `attr.b` is Literal[1] | ||
reveal_type(sub.b) # revealed: <module 'sub.b'> | ||
reveal_type(attr.b) # revealed: <module 'attr.b'> | ||
``` | ||
|
||
```py path=sub/__init__.py | ||
b = 1 | ||
``` | ||
|
||
```py path=sub/b.py | ||
``` | ||
|
||
```py path=attr/__init__.py | ||
from . import b as _ | ||
|
||
b = 1 | ||
``` | ||
|
||
```py path=attr/b.py | ||
``` |
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Oops, something went wrong.