mirror of https://github.com/astral-sh/ruff
Consider `from thispackage import y` to re-export `y` in `__init__.pyi`
Fixes https://github.com/astral-sh/ty/issues/1487
This commit is contained in:
parent
018febf444
commit
f47b9f22f5
|
|
@ -29,7 +29,8 @@ defining symbols *at all* and re-exporting them.
|
||||||
|
|
||||||
## Relative `from` Import of Direct Submodule in `__init__`
|
## Relative `from` Import of Direct Submodule in `__init__`
|
||||||
|
|
||||||
We consider the `from . import submodule` idiom in an `__init__.pyi` an explicit re-export.
|
We consider the `from . import submodule` idiom in an `__init__.pyi` an explicit re-export. This
|
||||||
|
pattern is observed in the wild with various stub packages.
|
||||||
|
|
||||||
### In Stub
|
### In Stub
|
||||||
|
|
||||||
|
|
@ -94,8 +95,7 @@ reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||||
## Absolute `from` Import of Direct Submodule in `__init__`
|
## Absolute `from` Import of Direct Submodule in `__init__`
|
||||||
|
|
||||||
If an absolute `from...import` happens to import a submodule (i.e. it's equivalent to
|
If an absolute `from...import` happens to import a submodule (i.e. it's equivalent to
|
||||||
`from . import y`) we do not treat it as a re-export. We could, but we don't. (This is an arbitrary
|
`from . import y`) we also treat it as a re-export.
|
||||||
decision and can be changed!)
|
|
||||||
|
|
||||||
### In Stub
|
### In Stub
|
||||||
|
|
||||||
|
|
@ -122,9 +122,7 @@ Y: int = 47
|
||||||
```py
|
```py
|
||||||
import mypackage
|
import mypackage
|
||||||
|
|
||||||
# TODO: this could work and would be nice to have?
|
reveal_type(mypackage.imported.X) # revealed: int
|
||||||
# error: "has no member `imported`"
|
|
||||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
|
||||||
# error: "has no member `fails`"
|
# error: "has no member `fails`"
|
||||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1465,9 +1465,8 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||||
// reasons but it works well for most practical purposes. In particular it's nice
|
// reasons but it works well for most practical purposes. In particular it's nice
|
||||||
// that `x` can be freely overwritten, and that we don't assume that an import
|
// that `x` can be freely overwritten, and that we don't assume that an import
|
||||||
// in one function is visible in another function.
|
// in one function is visible in another function.
|
||||||
if node.module.is_some()
|
let mut is_self_import = false;
|
||||||
&& self.current_scope().is_global()
|
if self.file.is_package(self.db)
|
||||||
&& self.file.is_package(self.db)
|
|
||||||
&& let Ok(module_name) = ModuleName::from_identifier_parts(
|
&& let Ok(module_name) = ModuleName::from_identifier_parts(
|
||||||
self.db,
|
self.db,
|
||||||
self.file,
|
self.file,
|
||||||
|
|
@ -1475,19 +1474,26 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||||
node.level,
|
node.level,
|
||||||
)
|
)
|
||||||
&& let Ok(thispackage) = ModuleName::package_for_file(self.db, self.file)
|
&& let Ok(thispackage) = ModuleName::package_for_file(self.db, self.file)
|
||||||
&& 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
|
// Record whether this is equivalent to `from . import ...`
|
||||||
.insert(direct_submodule.to_owned());
|
is_self_import = module_name == thispackage;
|
||||||
|
|
||||||
let direct_submodule_name = Name::new(direct_submodule);
|
if node.module.is_some()
|
||||||
let symbol = self.add_symbol(direct_submodule_name);
|
&& let Some(relative_submodule) = module_name.relative_to(&thispackage)
|
||||||
self.add_definition(
|
&& let Some(direct_submodule) = relative_submodule.components().next()
|
||||||
symbol.into(),
|
&& !self.seen_submodule_imports.contains(direct_submodule)
|
||||||
ImportFromSubmoduleDefinitionNodeRef { node },
|
&& self.current_scope().is_global()
|
||||||
);
|
{
|
||||||
|
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 mut found_star = false;
|
let mut found_star = false;
|
||||||
|
|
@ -1599,13 +1605,10 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||||
// It's re-exported if it's `from ... import x as x`
|
// It's re-exported if it's `from ... import x as x`
|
||||||
(&asname.id, asname.id == alias.name.id)
|
(&asname.id, asname.id == alias.name.id)
|
||||||
} else {
|
} else {
|
||||||
// It's re-exported if it's `from . import x` in an `__init__.pyi`
|
// As a non-standard rule to handle stubs in the wild, we consider
|
||||||
(
|
// `from . import x` and `from whatever.thispackage import x` in an
|
||||||
&alias.name.id,
|
// `__init__.pyi` to re-export `x` (as long as it wasn't renamed)
|
||||||
node.level == 1
|
(&alias.name.id, is_self_import)
|
||||||
&& node.module.is_none()
|
|
||||||
&& self.file.is_package(self.db),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Look for imports `from __future__ import annotations`, ignore `as ...`
|
// Look for imports `from __future__ import annotations`, ignore `as ...`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue