Compare commits
11 Commits
micha/ty-p
...
gankra/sub
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4be2aad04 | ||
|
|
a2f9e5a680 | ||
|
|
695f2723d4 | ||
|
|
949684677f | ||
|
|
d4f5e51c23 | ||
|
|
73af08a520 | ||
|
|
b60910c82b | ||
|
|
ac532e81e7 | ||
|
|
dd1ac81f15 | ||
|
|
a10c4e2027 | ||
|
|
d6ce7f0fce |
@@ -2770,9 +2770,9 @@ import foo
|
||||
import baz
|
||||
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(foo.bar) # revealed: Unknown
|
||||
reveal_type(foo.bar) # revealed: <module 'foo.bar'>
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(baz.bar) # revealed: Unknown
|
||||
reveal_type(baz.bar) # revealed: <module 'baz.bar'>
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
@@ -61,7 +61,7 @@ import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -91,7 +91,7 @@ import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
## Absolute `from` Import of Direct Submodule in `__init__`
|
||||
@@ -126,7 +126,7 @@ import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -156,7 +156,7 @@ import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
## Import of Direct Submodule in `__init__`
|
||||
@@ -185,7 +185,7 @@ import mypackage
|
||||
|
||||
# TODO: this could work and would be nice to have?
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -209,7 +209,7 @@ import mypackage
|
||||
|
||||
# TODO: this could work and would be nice to have
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
## Relative `from` Import of Nested Submodule in `__init__`
|
||||
@@ -243,9 +243,9 @@ import mypackage
|
||||
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
reveal_type(mypackage.nested) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
@@ -281,9 +281,9 @@ import mypackage
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# TODO: this would be nice to support
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
reveal_type(mypackage.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
reveal_type(mypackage.nested.X) # revealed: int
|
||||
```
|
||||
@@ -319,9 +319,9 @@ import mypackage
|
||||
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
reveal_type(mypackage.nested) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
@@ -357,9 +357,9 @@ import mypackage
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# TODO: this would be nice to support
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
reveal_type(mypackage.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
reveal_type(mypackage.nested.X) # revealed: int
|
||||
```
|
||||
@@ -394,11 +394,13 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -430,11 +432,13 @@ import mypackage
|
||||
|
||||
# TODO: this would be nice to support
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
```
|
||||
|
||||
## Relative `from` Import of Direct Submodule in `__init__`, Mismatched Alias
|
||||
@@ -461,7 +465,7 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: [unresolved-attribute] "has no member `imported_m`"
|
||||
reveal_type(mypackage.imported_m.X) # revealed: Unknown
|
||||
```
|
||||
@@ -487,7 +491,7 @@ import mypackage
|
||||
|
||||
# TODO: this would be nice to support, as it works at runtime
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
reveal_type(mypackage.imported_m.X) # revealed: int
|
||||
```
|
||||
|
||||
@@ -647,8 +651,8 @@ reveal_type(mypackage.imported.X) # revealed: int
|
||||
|
||||
## `from` Import of Other Package's Submodule
|
||||
|
||||
`from mypackage import submodule` from outside the package is not modeled as a side-effect on
|
||||
`mypackage`, even in the importing file (this could be changed!).
|
||||
`from mypackage import submodule` and `from mypackage.submodule import not_a_submodule` from outside
|
||||
the package are both modeled as a side-effects on `mypackage`.
|
||||
|
||||
### In Stub
|
||||
|
||||
@@ -663,18 +667,28 @@ reveal_type(mypackage.imported.X) # revealed: int
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`package2/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
```
|
||||
|
||||
`package2/submodule.pyi`:
|
||||
|
||||
```pyi
|
||||
not_a_submodule: int
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
import package2
|
||||
from mypackage import imported
|
||||
from package2.submodule import not_a_submodule
|
||||
|
||||
reveal_type(imported.X) # revealed: int
|
||||
|
||||
# TODO: this would be nice to support, but it's dangerous with available_submodule_attributes
|
||||
# for details, see: https://github.com/astral-sh/ty/issues/1488
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
reveal_type(package2.submodule.not_a_submodule) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -690,17 +704,28 @@ reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
X: int = 42
|
||||
```
|
||||
|
||||
`package2/__init__.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`package2/submodule.py`:
|
||||
|
||||
```py
|
||||
not_a_submodule: int
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import mypackage
|
||||
import package2
|
||||
from mypackage import imported
|
||||
from package2.submodule import not_a_submodule
|
||||
|
||||
reveal_type(imported.X) # revealed: int
|
||||
|
||||
# TODO: this would be nice to support, as it works at runtime
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
reveal_type(package2.submodule.not_a_submodule) # revealed: int
|
||||
```
|
||||
|
||||
## `from` Import of Sibling Module
|
||||
@@ -738,7 +763,7 @@ reveal_type(imported.X) # revealed: int
|
||||
# error: [unresolved-attribute] "has no member `fails`"
|
||||
reveal_type(imported.fails.Y) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -771,7 +796,7 @@ from mypackage import imported
|
||||
reveal_type(imported.X) # revealed: int
|
||||
reveal_type(imported.fails.Y) # revealed: int
|
||||
# error: [possibly-missing-attribute] "Submodule `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
## Fractal Re-export Nameclash Problems
|
||||
@@ -859,6 +884,48 @@ from mypackage import funcmod
|
||||
x = funcmod(1)
|
||||
```
|
||||
|
||||
## A Tale of Two Modules
|
||||
|
||||
`from typing import TYPE_CHECKING` has side-effects???
|
||||
|
||||
### In Stub
|
||||
|
||||
`mypackage/__init__.py`:
|
||||
|
||||
```py
|
||||
from .conflicted.b import x
|
||||
```
|
||||
|
||||
`mypackage/conflicted/__init__.py`:
|
||||
|
||||
`mypackage/conflicted/other1/__init__.py`:
|
||||
|
||||
```py
|
||||
x: int = 1
|
||||
```
|
||||
|
||||
`mypackage/conflicted/b/__init__.py`:
|
||||
|
||||
```py
|
||||
x: int = 1
|
||||
```
|
||||
|
||||
`mypackage/conflicted/b/c/__init__.py`:
|
||||
|
||||
```py
|
||||
y: int = 2
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from typing import TYPE_CHECKING
|
||||
from mypackage.conflicted.other1 import x as x1
|
||||
import mypackage.conflicted.b.c
|
||||
|
||||
reveal_type(mypackage.conflicted.b.c.y) # revealed: int
|
||||
```
|
||||
|
||||
## Re-export Nameclash Problems In Functions
|
||||
|
||||
`from` imports in an `__init__.py` at file scope should be visible to functions defined in the file:
|
||||
|
||||
@@ -248,7 +248,7 @@ from . import foo
|
||||
import package
|
||||
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(package.foo.X) # revealed: Unknown
|
||||
reveal_type(package.foo.X) # revealed: int
|
||||
```
|
||||
|
||||
## Relative imports at the top of a search path
|
||||
|
||||
@@ -116,3 +116,43 @@ b = 1
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
## Submodule is loaded in a non-global scope
|
||||
|
||||
We recognise submodules as being available as attributes even if they are loaded in a function
|
||||
scope. The function might never be executed, which means that the submodule might never be loaded;
|
||||
however, we prefer to prioritise avoiding false positives over catching all possible errors here.
|
||||
|
||||
`a/b.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`a/c.py`:
|
||||
|
||||
```py
|
||||
d = 42
|
||||
```
|
||||
|
||||
`a/e/f.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import a
|
||||
|
||||
def f():
|
||||
import a.b
|
||||
from a.c import d
|
||||
from a.e import f
|
||||
|
||||
f()
|
||||
|
||||
reveal_type(a.b) # revealed: <module 'a.b'>
|
||||
reveal_type(a.c) # revealed: <module 'a.c'>
|
||||
reveal_type(a.e) # revealed: <module 'a.e'>
|
||||
reveal_type(a.e.f) # revealed: <module 'a.e.f'>
|
||||
```
|
||||
|
||||
@@ -31,9 +31,9 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md
|
||||
2 | import baz
|
||||
3 |
|
||||
4 | # error: [possibly-missing-attribute]
|
||||
5 | reveal_type(foo.bar) # revealed: Unknown
|
||||
5 | reveal_type(foo.bar) # revealed: <module 'foo.bar'>
|
||||
6 | # error: [possibly-missing-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: Unknown
|
||||
7 | reveal_type(baz.bar) # revealed: <module 'baz.bar'>
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
@@ -43,10 +43,10 @@ warning[possibly-missing-attribute]: Submodule `bar` may not be available as an
|
||||
--> src/main.py:5:13
|
||||
|
|
||||
4 | # error: [possibly-missing-attribute]
|
||||
5 | reveal_type(foo.bar) # revealed: Unknown
|
||||
5 | reveal_type(foo.bar) # revealed: <module 'foo.bar'>
|
||||
| ^^^^^^^
|
||||
6 | # error: [possibly-missing-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: Unknown
|
||||
7 | reveal_type(baz.bar) # revealed: <module 'baz.bar'>
|
||||
|
|
||||
help: Consider explicitly importing `foo.bar`
|
||||
info: rule `possibly-missing-attribute` is enabled by default
|
||||
@@ -57,9 +57,9 @@ info: rule `possibly-missing-attribute` is enabled by default
|
||||
warning[possibly-missing-attribute]: Submodule `bar` may not be available as an attribute on module `baz`
|
||||
--> src/main.py:7:13
|
||||
|
|
||||
5 | reveal_type(foo.bar) # revealed: Unknown
|
||||
5 | reveal_type(foo.bar) # revealed: <module 'foo.bar'>
|
||||
6 | # error: [possibly-missing-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: Unknown
|
||||
7 | reveal_type(baz.bar) # revealed: <module 'baz.bar'>
|
||||
| ^^^^^^^
|
||||
|
|
||||
help: Consider explicitly importing `baz.bar`
|
||||
|
||||
@@ -629,7 +629,7 @@ from module2 import imported as other_imported
|
||||
from ty_extensions import TypeOf, static_assert, is_equivalent_to
|
||||
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(imported.abc) # revealed: Unknown
|
||||
reveal_type(imported.abc) # revealed: <module 'imported.abc'>
|
||||
|
||||
reveal_type(other_imported.abc) # revealed: <module 'imported.abc'>
|
||||
|
||||
|
||||
@@ -79,7 +79,10 @@ pub(crate) fn place_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc<Plac
|
||||
/// See [`ModuleLiteralType::available_submodule_attributes`] for discussion
|
||||
/// of why this analysis is intentionally limited.
|
||||
#[salsa::tracked(returns(deref), heap_size=ruff_memory_usage::heap_size)]
|
||||
pub(crate) fn imported_modules<'db>(db: &'db dyn Db, file: File) -> Arc<FxHashSet<ModuleName>> {
|
||||
pub(crate) fn imported_modules<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
) -> Arc<FxHashMap<ModuleName, ImportKind>> {
|
||||
semantic_index(db, file).imported_modules.clone()
|
||||
}
|
||||
|
||||
@@ -249,7 +252,7 @@ pub(crate) struct SemanticIndex<'db> {
|
||||
ast_ids: IndexVec<FileScopeId, AstIds>,
|
||||
|
||||
/// The set of modules that are imported anywhere within this file.
|
||||
imported_modules: Arc<FxHashSet<ModuleName>>,
|
||||
imported_modules: Arc<FxHashMap<ModuleName, ImportKind>>,
|
||||
|
||||
/// Flags about the global scope (code usage impacting inference)
|
||||
has_future_annotations: bool,
|
||||
@@ -586,6 +589,12 @@ impl<'db> SemanticIndex<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, get_size2::GetSize)]
|
||||
pub(crate) enum ImportKind {
|
||||
Import,
|
||||
ImportFrom,
|
||||
}
|
||||
|
||||
pub(crate) struct AncestorsIter<'a> {
|
||||
scopes: &'a IndexSlice<FileScopeId, Scope>,
|
||||
next_id: Option<FileScopeId>,
|
||||
|
||||
@@ -47,7 +47,7 @@ use crate::semantic_index::symbol::{ScopedSymbolId, Symbol};
|
||||
use crate::semantic_index::use_def::{
|
||||
EnclosingSnapshotKey, FlowSnapshot, ScopedEnclosingSnapshotId, UseDefMapBuilder,
|
||||
};
|
||||
use crate::semantic_index::{ExpressionsScopeMap, SemanticIndex, VisibleAncestorsIter};
|
||||
use crate::semantic_index::{ExpressionsScopeMap, ImportKind, SemanticIndex, VisibleAncestorsIter};
|
||||
use crate::semantic_model::HasTrackedScope;
|
||||
use crate::unpack::{EvaluationMode, Unpack, UnpackKind, UnpackPosition, UnpackValue};
|
||||
use crate::{Db, Program};
|
||||
@@ -110,7 +110,7 @@ pub(super) struct SemanticIndexBuilder<'db, 'ast> {
|
||||
scopes_by_expression: ExpressionsScopeMapBuilder,
|
||||
definitions_by_node: FxHashMap<DefinitionNodeKey, Definitions<'db>>,
|
||||
expressions_by_node: FxHashMap<ExpressionNodeKey, Expression<'db>>,
|
||||
imported_modules: FxHashSet<ModuleName>,
|
||||
imported_modules: FxHashMap<ModuleName, ImportKind>,
|
||||
seen_submodule_imports: FxHashSet<String>,
|
||||
/// Hashset of all [`FileScopeId`]s that correspond to [generator functions].
|
||||
///
|
||||
@@ -150,7 +150,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
||||
expressions_by_node: FxHashMap::default(),
|
||||
|
||||
seen_submodule_imports: FxHashSet::default(),
|
||||
imported_modules: FxHashSet::default(),
|
||||
imported_modules: FxHashMap::default(),
|
||||
generator_functions: FxHashSet::default(),
|
||||
|
||||
enclosing_snapshots: FxHashMap::default(),
|
||||
@@ -1472,7 +1472,11 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||
// Mark the imported module, and all of its parents, as being imported in this
|
||||
// file.
|
||||
if let Some(module_name) = ModuleName::new(&alias.name) {
|
||||
self.imported_modules.extend(module_name.ancestors());
|
||||
self.imported_modules.extend(
|
||||
module_name
|
||||
.ancestors()
|
||||
.zip(std::iter::repeat(ImportKind::Import)),
|
||||
);
|
||||
}
|
||||
|
||||
let (symbol_name, is_reexported) = if let Some(asname) = &alias.asname {
|
||||
@@ -1513,33 +1517,54 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||
// that `x` can be freely overwritten, and that we don't assume that an import
|
||||
// in one function is visible in another function.
|
||||
let mut is_self_import = false;
|
||||
if self.file.is_package(self.db)
|
||||
&& let Ok(module_name) = ModuleName::from_identifier_parts(
|
||||
self.db,
|
||||
self.file,
|
||||
node.module.as_deref(),
|
||||
node.level,
|
||||
)
|
||||
&& let Ok(thispackage) = ModuleName::package_for_file(self.db, self.file)
|
||||
{
|
||||
let is_package = self.file.is_package(self.db);
|
||||
let this_package = ModuleName::package_for_file(self.db, self.file);
|
||||
|
||||
if let Ok(module_name) = ModuleName::from_identifier_parts(
|
||||
self.db,
|
||||
self.file,
|
||||
node.module.as_deref(),
|
||||
node.level,
|
||||
) {
|
||||
// Record whether this is equivalent to `from . import ...`
|
||||
is_self_import = module_name == thispackage;
|
||||
if is_package && let Ok(thispackage) = this_package.as_ref() {
|
||||
is_self_import = &module_name == thispackage;
|
||||
}
|
||||
|
||||
if node.module.is_some()
|
||||
&& let Some(relative_submodule) = module_name.relative_to(&thispackage)
|
||||
&& let Some(direct_submodule) = relative_submodule.components().next()
|
||||
&& !self.seen_submodule_imports.contains(direct_submodule)
|
||||
&& self.current_scope().is_global()
|
||||
{
|
||||
self.seen_submodule_imports
|
||||
.insert(direct_submodule.to_owned());
|
||||
if node.module.is_some() {
|
||||
if is_package
|
||||
&& let Ok(thispackage) = this_package
|
||||
&& self.current_scope().is_global()
|
||||
&& let Some(relative_submodule) = module_name.relative_to(&thispackage)
|
||||
&& let Some(direct_submodule) = relative_submodule.components().next()
|
||||
&& !self.seen_submodule_imports.contains(direct_submodule)
|
||||
{
|
||||
self.seen_submodule_imports
|
||||
.insert(direct_submodule.to_owned());
|
||||
|
||||
let direct_submodule_name = Name::new(direct_submodule);
|
||||
let symbol = self.add_symbol(direct_submodule_name);
|
||||
self.add_definition(
|
||||
symbol.into(),
|
||||
ImportFromSubmoduleDefinitionNodeRef { node },
|
||||
);
|
||||
let direct_submodule_name = Name::new(direct_submodule);
|
||||
let symbol = self.add_symbol(direct_submodule_name);
|
||||
self.add_definition(
|
||||
symbol.into(),
|
||||
ImportFromSubmoduleDefinitionNodeRef { node },
|
||||
);
|
||||
} else {
|
||||
for name in module_name.ancestors() {
|
||||
self.imported_modules
|
||||
.entry(name)
|
||||
.or_insert(ImportKind::ImportFrom);
|
||||
}
|
||||
for name in &node.names {
|
||||
let Some(relative_name) = ModuleName::new(&name.name) else {
|
||||
continue;
|
||||
};
|
||||
let mut full_name = module_name.clone();
|
||||
full_name.extend(&relative_name);
|
||||
self.imported_modules
|
||||
.entry(full_name)
|
||||
.or_insert(ImportKind::ImportFrom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use compact_str::{CompactString, ToCompactString};
|
||||
use infer::nearest_enclosing_class;
|
||||
use itertools::{Either, Itertools};
|
||||
use itertools::Either;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::time::Duration;
|
||||
@@ -40,7 +40,7 @@ use crate::place::{
|
||||
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
||||
use crate::semantic_index::place::ScopedPlaceId;
|
||||
use crate::semantic_index::scope::ScopeId;
|
||||
use crate::semantic_index::{imported_modules, place_table, semantic_index};
|
||||
use crate::semantic_index::{ImportKind, imported_modules, place_table, semantic_index};
|
||||
use crate::suppression::check_suppressions;
|
||||
use crate::types::bound_super::BoundSuperType;
|
||||
use crate::types::call::{Binding, Bindings, CallArguments, CallableBinding};
|
||||
@@ -12241,12 +12241,18 @@ impl<'db> ModuleLiteralType<'db> {
|
||||
///
|
||||
/// We instead prefer handling most other import effects as definitions in the scope of
|
||||
/// the current file (i.e. [`crate::semantic_index::definition::ImportFromDefinitionNodeRef`]).
|
||||
fn available_submodule_attributes(&self, db: &'db dyn Db) -> impl Iterator<Item = Name> {
|
||||
fn available_submodule_attributes(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
) -> impl Iterator<Item = (Name, ImportKind)> {
|
||||
self.importing_file(db)
|
||||
.into_iter()
|
||||
.flat_map(|file| imported_modules(db, file))
|
||||
.filter_map(|submodule_name| submodule_name.relative_to(self.module(db).name(db)))
|
||||
.filter_map(|relative_submodule| relative_submodule.components().next().map(Name::from))
|
||||
.filter_map(|(submodule_name, kind)| {
|
||||
let relative_name = submodule_name.relative_to(self.module(db).name(db))?;
|
||||
let available_attribute = relative_name.components().next()?;
|
||||
Some((Name::from(available_attribute), *kind))
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_submodule(self, db: &'db dyn Db, name: &str) -> Option<Type<'db>> {
|
||||
@@ -12290,33 +12296,55 @@ impl<'db> ModuleLiteralType<'db> {
|
||||
.member(db, "__dict__");
|
||||
}
|
||||
|
||||
// If the file that originally imported the module has also imported a submodule
|
||||
// named `name`, then the result is (usually) that submodule, even if the module
|
||||
// also defines a (non-module) symbol with that name.
|
||||
//
|
||||
// Note that technically, either the submodule or the non-module symbol could take
|
||||
// priority, depending on the ordering of when the submodule is loaded relative to
|
||||
// the parent module's `__init__.py` file being evaluated. That said, we have
|
||||
// chosen to always have the submodule take priority. (This matches pyright's
|
||||
// current behavior, but is the opposite of mypy's current behavior.)
|
||||
if self.available_submodule_attributes(db).contains(name) {
|
||||
if let Some(submodule) = self.resolve_submodule(db, name) {
|
||||
return Place::bound(submodule).into();
|
||||
}
|
||||
let mut submodule_type = None;
|
||||
|
||||
let available_submodule_kind = self
|
||||
.available_submodule_attributes(db)
|
||||
.find_map(|(attr, kind)| (attr == name).then_some(kind));
|
||||
|
||||
if available_submodule_kind.is_some() {
|
||||
submodule_type = self.resolve_submodule(db, name);
|
||||
}
|
||||
|
||||
// if we're in a module `foo` and `foo` contains `import a.b`,
|
||||
// and the package `a` has a submodule `b`, we assume that the
|
||||
// attribute access `a.b` inside `foo` will resolve to the submodule
|
||||
// `a.b` *even if* `a/__init__.py` also defines a symbol `b` (e.g. `b = 42`).
|
||||
// This is a heuristic, but it's almost certainly what will actually happen
|
||||
// at runtime. However, if `foo` only contains `from a.b import <something>,
|
||||
// we prioritise the `b` attribute in `a/__init__.py` over the submodule `a.b`.
|
||||
if available_submodule_kind == Some(ImportKind::Import)
|
||||
&& let Some(submodule) = submodule_type
|
||||
{
|
||||
return Place::bound(submodule).into();
|
||||
}
|
||||
|
||||
let place_and_qualifiers = self
|
||||
.module(db)
|
||||
.file(db)
|
||||
.map(|file| imported_symbol(db, file, name, None))
|
||||
.map(|file| {
|
||||
imported_symbol(db, file, name, None).map_type(|ty| {
|
||||
if let Some(importing) = self.importing_file(db)
|
||||
&& let Type::ModuleLiteral(module) = ty
|
||||
{
|
||||
Type::module_literal(db, importing, module.module(db))
|
||||
} else {
|
||||
ty
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
// If the normal lookup failed, try to call the module's `__getattr__` function
|
||||
if place_and_qualifiers.place.is_undefined() {
|
||||
return self.try_module_getattr(db, name);
|
||||
if !place_and_qualifiers.is_undefined() {
|
||||
return place_and_qualifiers;
|
||||
}
|
||||
|
||||
place_and_qualifiers
|
||||
if let Some(submodule) = submodule_type {
|
||||
return Place::bound(submodule).into();
|
||||
}
|
||||
|
||||
// If the normal lookup failed, try to call the module's `__getattr__` function
|
||||
self.try_module_getattr(db, name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -315,7 +315,7 @@ impl<'db> AllMembers<'db> {
|
||||
|
||||
self.members
|
||||
.extend(literal.available_submodule_attributes(db).filter_map(
|
||||
|submodule_name| {
|
||||
|(submodule_name, _)| {
|
||||
let ty = literal.resolve_submodule(db, &submodule_name)?;
|
||||
let name = submodule_name.clone();
|
||||
Some(Member { name, ty })
|
||||
|
||||
@@ -9148,7 +9148,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
{
|
||||
let mut maybe_submodule_name = module_name.clone();
|
||||
maybe_submodule_name.extend(&relative_submodule);
|
||||
if resolve_module(db, &maybe_submodule_name).is_some() {
|
||||
if let Some(submodule) = resolve_module(db, &maybe_submodule_name) {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&POSSIBLY_MISSING_ATTRIBUTE, attribute)
|
||||
@@ -9161,7 +9161,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
"Consider explicitly importing `{maybe_submodule_name}`"
|
||||
));
|
||||
}
|
||||
return fallback();
|
||||
return TypeAndQualifiers::new(
|
||||
Type::module_literal(db, self.file(), submodule),
|
||||
TypeOrigin::Inferred,
|
||||
TypeQualifiers::empty(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user