Compare commits
2 Commits
micha/rele
...
gankra/imp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ef86c9637 | ||
|
|
793d0d0dd4 |
@@ -459,6 +459,12 @@ impl File {
|
||||
self.source_type(db).is_stub()
|
||||
}
|
||||
|
||||
/// Returns `true` if the file is an `__init__.py(i)`
|
||||
pub fn is_init(self, db: &dyn Db) -> bool {
|
||||
let path = self.path(db).as_str();
|
||||
path.ends_with("__init__.py") || path.ends_with("__init__.pyi")
|
||||
}
|
||||
|
||||
pub fn source_type(self, db: &dyn Db) -> PySourceType {
|
||||
match self.path(db) {
|
||||
FilePath::System(path) => path
|
||||
|
||||
@@ -74,25 +74,52 @@ from typing import Any as Any, Literal as Literal
|
||||
|
||||
Here, none of the symbols are being re-exported in the stub file.
|
||||
|
||||
In this case the symbols shouldn't be available as imports or attributes.
|
||||
|
||||
```py
|
||||
# error: 15 [unresolved-import] "Module `b` has no member `foo`"
|
||||
# error: 20 [unresolved-import] "Module `b` has no member `Any`"
|
||||
# error: 25 [unresolved-import] "Module `b` has no member `Literal`"
|
||||
from b import foo, Any, Literal
|
||||
from a import b
|
||||
|
||||
# error: [unresolved-attribute] "no attribute `Any`"
|
||||
reveal_type(b.Any) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "no attribute `Literal`"
|
||||
reveal_type(b.Literal) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "no attribute `foo`"
|
||||
reveal_type(b.foo) # revealed: Unknown
|
||||
# error: [unresolved-attribute] "no attribute `bar`"
|
||||
reveal_type(b.bar) # revealed: Unknown
|
||||
|
||||
# error: [unresolved-import] "Module `a.b` has no member `foo`"
|
||||
# error: [unresolved-import] "Module `a.b` has no member `bar`"
|
||||
# error: [unresolved-import] "Module `a.b` has no member `Any`"
|
||||
# error: [unresolved-import] "Module `a.b` has no member `Literal`"
|
||||
from a.b import foo, bar, Any, Literal
|
||||
|
||||
reveal_type(Any) # revealed: Unknown
|
||||
reveal_type(Literal) # revealed: Unknown
|
||||
reveal_type(foo) # revealed: Unknown
|
||||
reveal_type(bar) # revealed: Unknown
|
||||
```
|
||||
|
||||
`b.pyi`:
|
||||
`a/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
import foo
|
||||
```
|
||||
|
||||
`a/b.pyi`:
|
||||
|
||||
```pyi
|
||||
import a.foo
|
||||
from . import bar
|
||||
from typing import Any, Literal
|
||||
```
|
||||
|
||||
`foo.pyi`:
|
||||
`a/foo.pyi`:
|
||||
|
||||
```pyi
|
||||
|
||||
```
|
||||
|
||||
`a/bar.pyi`:
|
||||
|
||||
```pyi
|
||||
|
||||
@@ -261,39 +288,93 @@ reveal_type(Foo) # revealed: Unknown
|
||||
|
||||
## Re-exports in `__init__.pyi`
|
||||
|
||||
Similarly, for an `__init__.pyi` (stub) file, importing a non-exported name should raise an error
|
||||
but the inference would be `Unknown`.
|
||||
Within `__init__.pyi` relative imports (`from . import xyz` or `from .pub import xyz`) are also
|
||||
treated as a re-exports.
|
||||
|
||||
We check the both the members of the module and the imports of the module as you _should_ be able to
|
||||
do `from a import priv` but the attribute `a.priv` _should not_ exist.
|
||||
|
||||
The most subtle detail here is whether `from .semipriv import Pub` should make the `a.semipriv`
|
||||
attribute exist or not. We do not currently do this, although perhaps we should.
|
||||
|
||||
```py
|
||||
# error: 15 "Module `a` has no member `Foo`"
|
||||
# error: 20 "Module `a` has no member `c`"
|
||||
from a import Foo, c, foo
|
||||
import a
|
||||
|
||||
reveal_type(Foo) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(foo) # revealed: <module 'a.foo'>
|
||||
reveal_type(a.Pub) # revealed: <class 'Pub'>
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(a.Priv) # revealed: Unknown
|
||||
reveal_type(a.pub) # revealed: <module 'a.pub'>
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(a.priv) # revealed: Unknown
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(a.semipriv) # revealed: Unknown
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(a.sub) # revealed: Unknown
|
||||
reveal_type(a.subpub) # revealed: <module 'a.sub.subpub'>
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(a.subpriv) # revealed: Unknown
|
||||
|
||||
# error: [unresolved-import] "Priv"
|
||||
from a import Pub, Priv
|
||||
|
||||
# error: [unresolved-import] "subpriv"
|
||||
from a import pub, priv, semipriv, sub, subpub, subpriv
|
||||
|
||||
reveal_type(Pub) # revealed: <class 'Pub'>
|
||||
reveal_type(Priv) # revealed: Unknown
|
||||
reveal_type(pub) # revealed: <module 'a.pub'>
|
||||
reveal_type(priv) # revealed: <module 'a.priv'>
|
||||
reveal_type(semipriv) # revealed: <module 'a.semipriv'>
|
||||
reveal_type(sub) # revealed: <module 'a.sub'>
|
||||
reveal_type(subpub) # revealed: <module 'a.sub.subpub'>
|
||||
reveal_type(subpriv) # revealed: Unknown
|
||||
```
|
||||
|
||||
`a/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
from .b import c
|
||||
from .foo import Foo
|
||||
# re-exported because they're relative
|
||||
from .sub import subpub
|
||||
from .semipriv import Pub
|
||||
from . import pub
|
||||
|
||||
# not re-exported because they're absolute
|
||||
from a.sub import subpriv
|
||||
from a.semipriv import Priv
|
||||
from a import priv
|
||||
```
|
||||
|
||||
`a/foo.pyi`:
|
||||
`a/pub.pyi`:
|
||||
|
||||
```pyi
|
||||
class Foo: ...
|
||||
```
|
||||
|
||||
`a/b/__init__.pyi`:
|
||||
`a/priv.pyi`:
|
||||
|
||||
```pyi
|
||||
```
|
||||
|
||||
`a/semipriv.pyi`:
|
||||
|
||||
```pyi
|
||||
class Pub: ...
|
||||
|
||||
class Priv: ...
|
||||
```
|
||||
|
||||
`a/sub/__init__.pyi`:
|
||||
|
||||
```pyi
|
||||
|
||||
```
|
||||
|
||||
`a/b/c.pyi`:
|
||||
`a/sub/subpub.pyi`:
|
||||
|
||||
```pyi
|
||||
|
||||
```
|
||||
|
||||
`a/sub/subpriv.pyi`:
|
||||
|
||||
```pyi
|
||||
|
||||
|
||||
@@ -331,7 +331,11 @@ pub(crate) fn imported_symbol<'db>(
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
let requires_explicit_reexport = requires_explicit_reexport.unwrap_or_else(|| {
|
||||
if file.is_stub(db) {
|
||||
RequiresExplicitReExport::Yes
|
||||
if file.is_init(db) {
|
||||
RequiresExplicitReExport::YesButInitIdiomAllowed
|
||||
} else {
|
||||
RequiresExplicitReExport::Yes
|
||||
}
|
||||
} else {
|
||||
RequiresExplicitReExport::No
|
||||
}
|
||||
@@ -932,7 +936,8 @@ fn place_from_bindings_impl<'db>(
|
||||
let mut bindings_with_constraints = bindings_with_constraints.peekable();
|
||||
|
||||
let is_non_exported = |binding: Definition<'db>| {
|
||||
requires_explicit_reexport.is_yes() && !is_reexported(db, binding)
|
||||
requires_explicit_reexport.is_yes()
|
||||
&& !requires_explicit_reexport.is_satisfied(is_reexported(db, binding))
|
||||
};
|
||||
|
||||
let unbound_reachability_constraint = match bindings_with_constraints.peek() {
|
||||
@@ -1209,7 +1214,8 @@ fn place_from_declarations_impl<'db>(
|
||||
let mut exactly_one_declaration = false;
|
||||
|
||||
let is_non_exported = |declaration: Definition<'db>| {
|
||||
requires_explicit_reexport.is_yes() && !is_reexported(db, declaration)
|
||||
requires_explicit_reexport.is_yes()
|
||||
&& !requires_explicit_reexport.is_satisfied(is_reexported(db, declaration))
|
||||
};
|
||||
|
||||
let undeclared_reachability = match declarations.peek() {
|
||||
@@ -1320,21 +1326,26 @@ fn place_from_declarations_impl<'db>(
|
||||
// This will first check if the definition is using the "redundant alias" pattern like `import foo
|
||||
// as foo` or `from foo import bar as bar`. If it's not, it will check whether the symbol is being
|
||||
// exported via `__all__`.
|
||||
fn is_reexported(db: &dyn Db, definition: Definition<'_>) -> bool {
|
||||
fn is_reexported(db: &dyn Db, definition: Definition<'_>) -> ReExportKind {
|
||||
// This information is computed by the semantic index builder.
|
||||
if definition.is_reexported(db) {
|
||||
return true;
|
||||
let reexported = definition.is_reexported(db);
|
||||
if reexported != ReExportKind::No {
|
||||
return reexported;
|
||||
}
|
||||
// At this point, the definition should either be an `import` or `from ... import` statement.
|
||||
// This is because the default value of `is_reexported` is `true` for any other kind of
|
||||
// definition.
|
||||
let Some(all_names) = dunder_all_names(db, definition.file(db)) else {
|
||||
return false;
|
||||
return ReExportKind::No;
|
||||
};
|
||||
let table = place_table(db, definition.scope(db));
|
||||
let symbol_id = definition.place(db).expect_symbol();
|
||||
let symbol_name = table.symbol(symbol_id).name();
|
||||
all_names.contains(symbol_name)
|
||||
if all_names.contains(symbol_name) {
|
||||
ReExportKind::Yes
|
||||
} else {
|
||||
ReExportKind::No
|
||||
}
|
||||
}
|
||||
|
||||
mod implicit_globals {
|
||||
@@ -1500,13 +1511,35 @@ mod implicit_globals {
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum RequiresExplicitReExport {
|
||||
Yes,
|
||||
No,
|
||||
/// This is an `__init__.pyi` and `from . import b` is considered a re-export
|
||||
YesButInitIdiomAllowed,
|
||||
Yes,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ReExportKind {
|
||||
No,
|
||||
/// `b` in `from . import b`
|
||||
InitIdiom,
|
||||
Yes,
|
||||
}
|
||||
|
||||
impl get_size2::GetSize for ReExportKind {}
|
||||
|
||||
impl RequiresExplicitReExport {
|
||||
/// Whether re-exports are necessary at all (this is really "is not No")
|
||||
const fn is_yes(self) -> bool {
|
||||
matches!(self, RequiresExplicitReExport::Yes)
|
||||
!matches!(self, RequiresExplicitReExport::No)
|
||||
}
|
||||
|
||||
/// Whether the style of re-export is sufficient for the context
|
||||
fn is_satisfied(self, reexport: ReExportKind) -> bool {
|
||||
match self {
|
||||
RequiresExplicitReExport::No => true,
|
||||
RequiresExplicitReExport::YesButInitIdiomAllowed => reexport != ReExportKind::No,
|
||||
RequiresExplicitReExport::Yes => reexport == ReExportKind::Yes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::resolve_module;
|
||||
use crate::node_key::NodeKey;
|
||||
use crate::place::ReExportKind;
|
||||
use crate::semantic_index::ast_ids::AstIdsBuilder;
|
||||
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
|
||||
use crate::semantic_index::definition::{
|
||||
@@ -1436,6 +1437,12 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||
(Name::new(alias.name.id.split('.').next().unwrap()), false)
|
||||
};
|
||||
|
||||
let is_reexported = if is_reexported {
|
||||
ReExportKind::Yes
|
||||
} else {
|
||||
ReExportKind::No
|
||||
};
|
||||
|
||||
let symbol = self.add_symbol(symbol_name);
|
||||
self.add_definition(
|
||||
symbol.into(),
|
||||
@@ -1562,6 +1569,15 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||
(&alias.name.id, false)
|
||||
};
|
||||
|
||||
let is_reexported = if is_reexported {
|
||||
ReExportKind::Yes
|
||||
} else if node.level == 1 {
|
||||
// `from . import a`
|
||||
ReExportKind::InitIdiom
|
||||
} else {
|
||||
ReExportKind::No
|
||||
};
|
||||
|
||||
// Look for imports `from __future__ import annotations`, ignore `as ...`
|
||||
// We intentionally don't enforce the rules about location of `__future__`
|
||||
// imports here, we assume the user's intent was to apply the `__future__`
|
||||
|
||||
@@ -8,6 +8,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
use crate::Db;
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::node_key::NodeKey;
|
||||
use crate::place::ReExportKind;
|
||||
use crate::semantic_index::place::ScopedPlaceId;
|
||||
use crate::semantic_index::scope::{FileScopeId, ScopeId};
|
||||
use crate::semantic_index::symbol::ScopedSymbolId;
|
||||
@@ -41,7 +42,7 @@ pub struct Definition<'db> {
|
||||
pub kind: DefinitionKind<'db>,
|
||||
|
||||
/// This is a dedicated field to avoid accessing `kind` to compute this value.
|
||||
pub(crate) is_reexported: bool,
|
||||
pub(crate) is_reexported: ReExportKind,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
@@ -337,7 +338,7 @@ impl<'ast> From<StarImportDefinitionNodeRef<'ast>> for DefinitionNodeRef<'ast, '
|
||||
pub(crate) struct ImportDefinitionNodeRef<'ast> {
|
||||
pub(crate) node: &'ast ast::StmtImport,
|
||||
pub(crate) alias_index: usize,
|
||||
pub(crate) is_reexported: bool,
|
||||
pub(crate) is_reexported: ReExportKind,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@@ -350,7 +351,7 @@ pub(crate) struct StarImportDefinitionNodeRef<'ast> {
|
||||
pub(crate) struct ImportFromDefinitionNodeRef<'ast> {
|
||||
pub(crate) node: &'ast ast::StmtImportFrom,
|
||||
pub(crate) alias_index: usize,
|
||||
pub(crate) is_reexported: bool,
|
||||
pub(crate) is_reexported: ReExportKind,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@@ -678,11 +679,11 @@ pub enum DefinitionKind<'db> {
|
||||
}
|
||||
|
||||
impl DefinitionKind<'_> {
|
||||
pub(crate) fn is_reexported(&self) -> bool {
|
||||
pub(crate) fn is_reexported(&self) -> ReExportKind {
|
||||
match self {
|
||||
DefinitionKind::Import(import) => import.is_reexported(),
|
||||
DefinitionKind::ImportFrom(import) => import.is_reexported(),
|
||||
_ => true,
|
||||
_ => ReExportKind::Yes,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -956,7 +957,7 @@ impl<'db> ComprehensionDefinitionKind<'db> {
|
||||
pub struct ImportDefinitionKind {
|
||||
node: AstNodeRef<ast::StmtImport>,
|
||||
alias_index: usize,
|
||||
is_reexported: bool,
|
||||
is_reexported: ReExportKind,
|
||||
}
|
||||
|
||||
impl ImportDefinitionKind {
|
||||
@@ -968,7 +969,7 @@ impl ImportDefinitionKind {
|
||||
&self.node.node(module).names[self.alias_index]
|
||||
}
|
||||
|
||||
pub(crate) fn is_reexported(&self) -> bool {
|
||||
pub(crate) fn is_reexported(&self) -> ReExportKind {
|
||||
self.is_reexported
|
||||
}
|
||||
}
|
||||
@@ -977,7 +978,7 @@ impl ImportDefinitionKind {
|
||||
pub struct ImportFromDefinitionKind {
|
||||
node: AstNodeRef<ast::StmtImportFrom>,
|
||||
alias_index: usize,
|
||||
is_reexported: bool,
|
||||
is_reexported: ReExportKind,
|
||||
}
|
||||
|
||||
impl ImportFromDefinitionKind {
|
||||
@@ -989,7 +990,7 @@ impl ImportFromDefinitionKind {
|
||||
&self.node.node(module).names[self.alias_index]
|
||||
}
|
||||
|
||||
pub(crate) fn is_reexported(&self) -> bool {
|
||||
pub(crate) fn is_reexported(&self) -> ReExportKind {
|
||||
self.is_reexported
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user