mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 18:58:04 +00:00
[ty] Support import <namespace>
and from <namespace> import module
(#18137)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
This commit is contained in:
parent
7b253100f8
commit
76ab77fe01
16 changed files with 359 additions and 158 deletions
|
@ -19,19 +19,20 @@ impl<'a> Resolver<'a> {
|
|||
pub(crate) fn resolve(&self, import: CollectedImport) -> Option<&'a FilePath> {
|
||||
match import {
|
||||
CollectedImport::Import(import) => {
|
||||
resolve_module(self.db, &import).map(|module| module.file().path(self.db))
|
||||
let module = resolve_module(self.db, &import)?;
|
||||
Some(module.file()?.path(self.db))
|
||||
}
|
||||
CollectedImport::ImportFrom(import) => {
|
||||
// Attempt to resolve the member (e.g., given `from foo import bar`, look for `foo.bar`).
|
||||
let parent = import.parent();
|
||||
|
||||
resolve_module(self.db, &import)
|
||||
.map(|module| module.file().path(self.db))
|
||||
.or_else(|| {
|
||||
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
|
||||
let module = resolve_module(self.db, &import).or_else(|| {
|
||||
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
|
||||
|
||||
resolve_module(self.db, &parent?).map(|module| module.file().path(self.db))
|
||||
})
|
||||
resolve_module(self.db, &parent?)
|
||||
})?;
|
||||
|
||||
Some(module.file()?.path(self.db))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1444,13 +1444,11 @@ mod unix {
|
|||
)
|
||||
.expect("Expected bar.baz to exist in site-packages.");
|
||||
let baz_project = case.project_path("bar/baz.py");
|
||||
let baz_file = baz.file().unwrap();
|
||||
|
||||
assert_eq!(source_text(case.db(), baz_file).as_str(), "def baz(): ...");
|
||||
assert_eq!(
|
||||
source_text(case.db(), baz.file()).as_str(),
|
||||
"def baz(): ..."
|
||||
);
|
||||
assert_eq!(
|
||||
baz.file().path(case.db()).as_system_path(),
|
||||
baz_file.path(case.db()).as_system_path(),
|
||||
Some(&*baz_project)
|
||||
);
|
||||
|
||||
|
@ -1465,7 +1463,7 @@ mod unix {
|
|||
case.apply_changes(changes);
|
||||
|
||||
assert_eq!(
|
||||
source_text(case.db(), baz.file()).as_str(),
|
||||
source_text(case.db(), baz_file).as_str(),
|
||||
"def baz(): print('Version 2')"
|
||||
);
|
||||
|
||||
|
@ -1478,7 +1476,7 @@ mod unix {
|
|||
case.apply_changes(changes);
|
||||
|
||||
assert_eq!(
|
||||
source_text(case.db(), baz.file()).as_str(),
|
||||
source_text(case.db(), baz_file).as_str(),
|
||||
"def baz(): print('Version 3')"
|
||||
);
|
||||
|
||||
|
@ -1524,6 +1522,7 @@ mod unix {
|
|||
&ModuleName::new_static("bar.baz").unwrap(),
|
||||
)
|
||||
.expect("Expected bar.baz to exist in site-packages.");
|
||||
let baz_file = baz.file().unwrap();
|
||||
let bar_baz = case.project_path("bar/baz.py");
|
||||
|
||||
let patched_bar_baz = case.project_path("patched/bar/baz.py");
|
||||
|
@ -1534,11 +1533,8 @@ mod unix {
|
|||
"def baz(): ..."
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
source_text(case.db(), baz.file()).as_str(),
|
||||
"def baz(): ..."
|
||||
);
|
||||
assert_eq!(baz.file().path(case.db()).as_system_path(), Some(&*bar_baz));
|
||||
assert_eq!(source_text(case.db(), baz_file).as_str(), "def baz(): ...");
|
||||
assert_eq!(baz_file.path(case.db()).as_system_path(), Some(&*bar_baz));
|
||||
|
||||
case.assert_indexed_project_files([patched_bar_baz_file]);
|
||||
|
||||
|
@ -1567,7 +1563,7 @@ mod unix {
|
|||
let patched_baz_text = source_text(case.db(), patched_bar_baz_file);
|
||||
let did_update_patched_baz = patched_baz_text.as_str() == "def baz(): print('Version 2')";
|
||||
|
||||
let bar_baz_text = source_text(case.db(), baz.file());
|
||||
let bar_baz_text = source_text(case.db(), baz_file);
|
||||
let did_update_bar_baz = bar_baz_text.as_str() == "def baz(): print('Version 2')";
|
||||
|
||||
assert!(
|
||||
|
@ -1650,7 +1646,7 @@ mod unix {
|
|||
"def baz(): ..."
|
||||
);
|
||||
assert_eq!(
|
||||
baz.file().path(case.db()).as_system_path(),
|
||||
baz.file().unwrap().path(case.db()).as_system_path(),
|
||||
Some(&*baz_original)
|
||||
);
|
||||
|
||||
|
|
|
@ -187,7 +187,10 @@ impl HasNavigationTargets for Type<'_> {
|
|||
|
||||
impl HasNavigationTargets for TypeDefinition<'_> {
|
||||
fn navigation_targets(&self, db: &dyn Db) -> NavigationTargets {
|
||||
let full_range = self.full_range(db.upcast());
|
||||
let Some(full_range) = self.full_range(db.upcast()) else {
|
||||
return NavigationTargets::empty();
|
||||
};
|
||||
|
||||
NavigationTargets::single(NavigationTarget {
|
||||
file: full_range.file(),
|
||||
focus_range: self.focus_range(db.upcast()).unwrap_or(full_range).range(),
|
||||
|
|
|
@ -29,8 +29,10 @@ import parent.child.two
|
|||
`from.py`
|
||||
|
||||
```py
|
||||
# TODO: This should not be an error
|
||||
from parent.child import one, two # error: [unresolved-import]
|
||||
from parent.child import one, two
|
||||
|
||||
reveal_type(one) # revealed: <module 'parent.child.one'>
|
||||
reveal_type(two) # revealed: <module 'parent.child.two'>
|
||||
```
|
||||
|
||||
## Regular package in namespace package
|
||||
|
@ -105,3 +107,42 @@ reveal_type(x) # revealed: Unknown | Literal["module"]
|
|||
|
||||
import foo.bar # error: [unresolved-import]
|
||||
```
|
||||
|
||||
## `from` import with namespace package
|
||||
|
||||
Regression test for <https://github.com/astral-sh/ty/issues/363>
|
||||
|
||||
`google/cloud/pubsub_v1/__init__.py`:
|
||||
|
||||
```py
|
||||
class PublisherClient: ...
|
||||
```
|
||||
|
||||
```py
|
||||
from google.cloud import pubsub_v1
|
||||
|
||||
reveal_type(pubsub_v1.PublisherClient) # revealed: <class 'PublisherClient'>
|
||||
```
|
||||
|
||||
## `from` root importing sub-packages
|
||||
|
||||
Regresssion test for <https://github.com/astral-sh/ty/issues/375>
|
||||
|
||||
`opentelemetry/trace/__init__.py`:
|
||||
|
||||
```py
|
||||
class Trace: ...
|
||||
```
|
||||
|
||||
`opentelemetry/metrics/__init__.py`:
|
||||
|
||||
```py
|
||||
class Metric: ...
|
||||
```
|
||||
|
||||
```py
|
||||
from opentelemetry import trace, metrics
|
||||
|
||||
reveal_type(trace) # revealed: <module 'opentelemetry.trace'>
|
||||
reveal_type(metrics) # revealed: <module 'opentelemetry.metrics'>
|
||||
```
|
||||
|
|
|
@ -109,8 +109,10 @@ impl<'db> DunderAllNamesCollector<'db> {
|
|||
else {
|
||||
return false;
|
||||
};
|
||||
let Some(module_dunder_all_names) =
|
||||
dunder_all_names(self.db, module_literal.module(self.db).file())
|
||||
let Some(module_dunder_all_names) = module_literal
|
||||
.module(self.db)
|
||||
.file()
|
||||
.and_then(|file| dunder_all_names(self.db, file))
|
||||
else {
|
||||
// The module either does not have a `__all__` variable or it is invalid.
|
||||
return false;
|
||||
|
@ -179,7 +181,7 @@ impl<'db> DunderAllNamesCollector<'db> {
|
|||
let module_name =
|
||||
ModuleName::from_import_statement(self.db, self.file, import_from).ok()?;
|
||||
let module = resolve_module(self.db, &module_name)?;
|
||||
dunder_all_names(self.db, module.file())
|
||||
dunder_all_names(self.db, module.file()?)
|
||||
}
|
||||
|
||||
/// Infer the type of a standalone expression.
|
||||
|
|
|
@ -14,15 +14,16 @@ pub struct Module {
|
|||
}
|
||||
|
||||
impl Module {
|
||||
pub(crate) fn new(
|
||||
pub(crate) fn file_module(
|
||||
name: ModuleName,
|
||||
kind: ModuleKind,
|
||||
search_path: SearchPath,
|
||||
file: File,
|
||||
) -> Self {
|
||||
let known = KnownModule::try_from_search_path_and_name(&search_path, &name);
|
||||
|
||||
Self {
|
||||
inner: Arc::new(ModuleInner {
|
||||
inner: Arc::new(ModuleInner::FileModule {
|
||||
name,
|
||||
kind,
|
||||
search_path,
|
||||
|
@ -32,19 +33,36 @@ impl Module {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn namespace_package(name: ModuleName) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(ModuleInner::NamespacePackage { name }),
|
||||
}
|
||||
}
|
||||
|
||||
/// The absolute name of the module (e.g. `foo.bar`)
|
||||
pub fn name(&self) -> &ModuleName {
|
||||
&self.inner.name
|
||||
match &*self.inner {
|
||||
ModuleInner::FileModule { name, .. } => name,
|
||||
ModuleInner::NamespacePackage { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
/// The file to the source code that defines this module
|
||||
pub fn file(&self) -> File {
|
||||
self.inner.file
|
||||
///
|
||||
/// This is `None` for namespace packages.
|
||||
pub fn file(&self) -> Option<File> {
|
||||
match &*self.inner {
|
||||
ModuleInner::FileModule { file, .. } => Some(*file),
|
||||
ModuleInner::NamespacePackage { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this a module that we special-case somehow? If so, which one?
|
||||
pub fn known(&self) -> Option<KnownModule> {
|
||||
self.inner.known
|
||||
match &*self.inner {
|
||||
ModuleInner::FileModule { known, .. } => *known,
|
||||
ModuleInner::NamespacePackage { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Does this module represent the given known module?
|
||||
|
@ -53,13 +71,19 @@ impl Module {
|
|||
}
|
||||
|
||||
/// The search path from which the module was resolved.
|
||||
pub(crate) fn search_path(&self) -> &SearchPath {
|
||||
&self.inner.search_path
|
||||
pub(crate) fn search_path(&self) -> Option<&SearchPath> {
|
||||
match &*self.inner {
|
||||
ModuleInner::FileModule { search_path, .. } => Some(search_path),
|
||||
ModuleInner::NamespacePackage { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine whether this module is a single-file module or a package
|
||||
pub fn kind(&self) -> ModuleKind {
|
||||
self.inner.kind
|
||||
match &*self.inner {
|
||||
ModuleInner::FileModule { kind, .. } => *kind,
|
||||
ModuleInner::NamespacePackage { .. } => ModuleKind::Package,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,17 +94,26 @@ impl std::fmt::Debug for Module {
|
|||
.field("kind", &self.kind())
|
||||
.field("file", &self.file())
|
||||
.field("search_path", &self.search_path())
|
||||
.field("known", &self.known())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
struct ModuleInner {
|
||||
name: ModuleName,
|
||||
kind: ModuleKind,
|
||||
search_path: SearchPath,
|
||||
file: File,
|
||||
known: Option<KnownModule>,
|
||||
enum ModuleInner {
|
||||
/// A module that resolves to a file (`lib.py` or `package/__init__.py`)
|
||||
FileModule {
|
||||
name: ModuleName,
|
||||
kind: ModuleKind,
|
||||
search_path: SearchPath,
|
||||
file: File,
|
||||
known: Option<KnownModule>,
|
||||
},
|
||||
|
||||
/// A namespace package. Namespace packages are special because
|
||||
/// there are multiple possible paths and they have no corresponding
|
||||
/// code file.
|
||||
NamespacePackage { name: ModuleName },
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
|
|
|
@ -143,6 +143,23 @@ impl ModulePath {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn to_system_path(&self) -> Option<SystemPathBuf> {
|
||||
let ModulePath {
|
||||
search_path,
|
||||
relative_path,
|
||||
} = self;
|
||||
match &*search_path.0 {
|
||||
SearchPathInner::Extra(search_path)
|
||||
| SearchPathInner::FirstParty(search_path)
|
||||
| SearchPathInner::SitePackages(search_path)
|
||||
| SearchPathInner::Editable(search_path) => Some(search_path.join(relative_path)),
|
||||
SearchPathInner::StandardLibraryCustom(stdlib_root) => {
|
||||
Some(stdlib_root.join(relative_path))
|
||||
}
|
||||
SearchPathInner::StandardLibraryVendored(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(super) fn to_file(&self, resolver: &ResolverContext) -> Option<File> {
|
||||
let db = resolver.db.upcast();
|
||||
|
|
|
@ -39,22 +39,24 @@ pub(crate) fn resolve_module_query<'db>(
|
|||
let name = module_name.name(db);
|
||||
let _span = tracing::trace_span!("resolve_module", %name).entered();
|
||||
|
||||
let Some((search_path, resolved_module)) = resolve_name(db, name) else {
|
||||
let Some(resolved) = resolve_name(db, name) else {
|
||||
tracing::debug!("Module `{name}` not found in search paths");
|
||||
return None;
|
||||
};
|
||||
|
||||
tracing::trace!(
|
||||
"Resolved module `{name}` to `{path}`",
|
||||
path = resolved_module.file.path(db)
|
||||
);
|
||||
|
||||
let module = Module::new(
|
||||
name.clone(),
|
||||
resolved_module.kind,
|
||||
search_path,
|
||||
resolved_module.file,
|
||||
);
|
||||
let module = match resolved {
|
||||
ResolvedName::FileModule(module) => {
|
||||
tracing::trace!(
|
||||
"Resolved module `{name}` to `{path}`",
|
||||
path = module.file.path(db)
|
||||
);
|
||||
Module::file_module(name.clone(), module.kind, module.search_path, module.file)
|
||||
}
|
||||
ResolvedName::NamespacePackage => {
|
||||
tracing::trace!("Module `{name}` is a namespace package");
|
||||
Module::namespace_package(name.clone())
|
||||
}
|
||||
};
|
||||
|
||||
Some(module)
|
||||
}
|
||||
|
@ -117,8 +119,9 @@ pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module> {
|
|||
// root paths, but that the module corresponding to `path` is in a lower priority search path,
|
||||
// in which case we ignore it.
|
||||
let module = resolve_module(db, &module_name)?;
|
||||
let module_file = module.file()?;
|
||||
|
||||
if file.path(db) == module.file().path(db) {
|
||||
if file.path(db) == module_file.path(db) {
|
||||
Some(module)
|
||||
} else {
|
||||
// This path is for a module with the same name but with a different precedence. For example:
|
||||
|
@ -616,7 +619,7 @@ struct ModuleNameIngredient<'db> {
|
|||
|
||||
/// Given a module name and a list of search paths in which to lookup modules,
|
||||
/// attempt to resolve the module name
|
||||
fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<(SearchPath, ResolvedModule)> {
|
||||
fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<ResolvedName> {
|
||||
let program = Program::get(db);
|
||||
let python_version = program.python_version(db);
|
||||
let resolver_state = ResolverContext::new(db, python_version);
|
||||
|
@ -625,6 +628,7 @@ fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<(SearchPath, ResolvedM
|
|||
|
||||
let name = RelaxedModuleName::new(name);
|
||||
let stub_name = name.to_stub_package();
|
||||
let mut is_namespace_package = false;
|
||||
|
||||
for search_path in search_paths(db) {
|
||||
// When a builtin module is imported, standard module resolution is bypassed:
|
||||
|
@ -636,16 +640,19 @@ fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<(SearchPath, ResolvedM
|
|||
}
|
||||
|
||||
if !search_path.is_standard_library() {
|
||||
match resolve_module_in_search_path(&resolver_state, &stub_name, search_path) {
|
||||
Ok(resolved_module) => {
|
||||
if resolved_module.package_kind.is_root() && resolved_module.kind.is_module() {
|
||||
match resolve_name_in_search_path(&resolver_state, &stub_name, search_path) {
|
||||
Ok((package_kind, ResolvedName::FileModule(module))) => {
|
||||
if package_kind.is_root() && module.kind.is_module() {
|
||||
tracing::trace!(
|
||||
"Search path '{search_path} contains a module named `{stub_name}` but a standalone module isn't a valid stub."
|
||||
);
|
||||
} else {
|
||||
return Some((search_path.clone(), resolved_module));
|
||||
return Some(ResolvedName::FileModule(module));
|
||||
}
|
||||
}
|
||||
Ok((_, ResolvedName::NamespacePackage)) => {
|
||||
is_namespace_package = true;
|
||||
}
|
||||
Err(PackageKind::Root) => {
|
||||
tracing::trace!(
|
||||
"Search path '{search_path}' contains no stub package named `{stub_name}`."
|
||||
|
@ -670,8 +677,13 @@ fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<(SearchPath, ResolvedM
|
|||
}
|
||||
}
|
||||
|
||||
match resolve_module_in_search_path(&resolver_state, &name, search_path) {
|
||||
Ok(resolved_module) => return Some((search_path.clone(), resolved_module)),
|
||||
match resolve_name_in_search_path(&resolver_state, &name, search_path) {
|
||||
Ok((_, ResolvedName::FileModule(module))) => {
|
||||
return Some(ResolvedName::FileModule(module));
|
||||
}
|
||||
Ok((_, ResolvedName::NamespacePackage)) => {
|
||||
is_namespace_package = true;
|
||||
}
|
||||
Err(kind) => match kind {
|
||||
PackageKind::Root => {
|
||||
tracing::trace!(
|
||||
|
@ -693,21 +705,36 @@ fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<(SearchPath, ResolvedM
|
|||
}
|
||||
}
|
||||
|
||||
if is_namespace_package {
|
||||
return Some(ResolvedName::NamespacePackage);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ResolvedModule {
|
||||
enum ResolvedName {
|
||||
/// A module that resolves to a file.
|
||||
FileModule(ResolvedFileModule),
|
||||
|
||||
/// The module name resolved to a namespace package.
|
||||
///
|
||||
/// For example, `from opentelemetry import trace, metrics` where `opentelemetry` is a namespace package (and `trace` and `metrics` are sub packages).
|
||||
NamespacePackage,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ResolvedFileModule {
|
||||
kind: ModuleKind,
|
||||
package_kind: PackageKind,
|
||||
search_path: SearchPath,
|
||||
file: File,
|
||||
}
|
||||
|
||||
fn resolve_module_in_search_path(
|
||||
fn resolve_name_in_search_path(
|
||||
context: &ResolverContext,
|
||||
name: &RelaxedModuleName,
|
||||
search_path: &SearchPath,
|
||||
) -> Result<ResolvedModule, PackageKind> {
|
||||
) -> Result<(PackageKind, ResolvedName), PackageKind> {
|
||||
let mut components = name.components();
|
||||
let module_name = components.next_back().unwrap();
|
||||
|
||||
|
@ -720,22 +747,55 @@ fn resolve_module_in_search_path(
|
|||
// Check for a regular package first (highest priority)
|
||||
package_path.push("__init__");
|
||||
if let Some(regular_package) = resolve_file_module(&package_path, context) {
|
||||
return Ok(ResolvedModule {
|
||||
file: regular_package,
|
||||
kind: ModuleKind::Package,
|
||||
package_kind: resolved_package.kind,
|
||||
});
|
||||
return Ok((
|
||||
resolved_package.kind,
|
||||
ResolvedName::FileModule(ResolvedFileModule {
|
||||
search_path: search_path.clone(),
|
||||
kind: ModuleKind::Package,
|
||||
file: regular_package,
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
// Check for a file module next
|
||||
package_path.pop();
|
||||
|
||||
if let Some(file_module) = resolve_file_module(&package_path, context) {
|
||||
return Ok(ResolvedModule {
|
||||
file: file_module,
|
||||
kind: ModuleKind::Module,
|
||||
package_kind: resolved_package.kind,
|
||||
});
|
||||
return Ok((
|
||||
resolved_package.kind,
|
||||
ResolvedName::FileModule(ResolvedFileModule {
|
||||
file: file_module,
|
||||
kind: ModuleKind::Module,
|
||||
search_path: search_path.clone(),
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
// Last resort, check if a folder with the given name exists.
|
||||
// If so, then this is a namespace package.
|
||||
// We need to skip this check for typeshed because the `resolve_file_module` can also return `None`
|
||||
// if the `__init__.py` exists but isn't available for the current Python version.
|
||||
// Let's assume that the `xml` module is only available on Python 3.11+ and we're resolving for Python 3.10:
|
||||
// * `resolve_file_module("xml/__init__.pyi")` returns `None` even though the file exists but the
|
||||
// module isn't available for the current Python version.
|
||||
// * The check here would now return `true` because the `xml` directory exists, resulting
|
||||
// in a false positive for a namespace package.
|
||||
//
|
||||
// Since typeshed doesn't use any namespace packages today (May 2025), simply skip this
|
||||
// check which also helps performance. If typeshed ever uses namespace packages, ensure that
|
||||
// this check also takes the `VERSIONS` file into consideration.
|
||||
if !search_path.is_standard_library() && package_path.is_directory(context) {
|
||||
if let Some(path) = package_path.to_system_path() {
|
||||
let system = context.db.system();
|
||||
if system.case_sensitivity().is_case_sensitive()
|
||||
|| system.path_exists_case_sensitive(
|
||||
&path,
|
||||
package_path.search_path().as_system_path().unwrap(),
|
||||
)
|
||||
{
|
||||
return Ok((resolved_package.kind, ResolvedName::NamespacePackage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(resolved_package.kind)
|
||||
|
@ -937,11 +997,11 @@ mod tests {
|
|||
);
|
||||
|
||||
assert_eq!("foo", foo_module.name());
|
||||
assert_eq!(&src, foo_module.search_path());
|
||||
assert_eq!(&src, foo_module.search_path().unwrap());
|
||||
assert_eq!(ModuleKind::Module, foo_module.kind());
|
||||
|
||||
let expected_foo_path = src.join("foo.py");
|
||||
assert_eq!(&expected_foo_path, foo_module.file().path(&db));
|
||||
assert_eq!(&expected_foo_path, foo_module.file().unwrap().path(&db));
|
||||
assert_eq!(
|
||||
Some(foo_module),
|
||||
path_to_module(&db, &FilePath::System(expected_foo_path))
|
||||
|
@ -958,7 +1018,10 @@ mod tests {
|
|||
let builtins_module_name = ModuleName::new_static("builtins").unwrap();
|
||||
let builtins = resolve_module(&db, &builtins_module_name).expect("builtins to resolve");
|
||||
|
||||
assert_eq!(builtins.file().path(&db), &stdlib.join("builtins.pyi"));
|
||||
assert_eq!(
|
||||
builtins.file().unwrap().path(&db),
|
||||
&stdlib.join("builtins.pyi")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -979,7 +1042,10 @@ mod tests {
|
|||
let builtins_module_name = ModuleName::new_static("builtins").unwrap();
|
||||
let builtins = resolve_module(&db, &builtins_module_name).expect("builtins to resolve");
|
||||
|
||||
assert_eq!(builtins.file().path(&db), &stdlib.join("builtins.pyi"));
|
||||
assert_eq!(
|
||||
builtins.file().unwrap().path(&db),
|
||||
&stdlib.join("builtins.pyi")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1002,11 +1068,14 @@ mod tests {
|
|||
resolve_module(&db, &functools_module_name).as_ref()
|
||||
);
|
||||
|
||||
assert_eq!(&stdlib, functools_module.search_path());
|
||||
assert_eq!(&stdlib, functools_module.search_path().unwrap());
|
||||
assert_eq!(ModuleKind::Module, functools_module.kind());
|
||||
|
||||
let expected_functools_path = stdlib.join("functools.pyi");
|
||||
assert_eq!(&expected_functools_path, functools_module.file().path(&db));
|
||||
assert_eq!(
|
||||
&expected_functools_path,
|
||||
functools_module.file().unwrap().path(&db)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Some(functools_module),
|
||||
|
@ -1052,7 +1121,7 @@ mod tests {
|
|||
let resolved_module = resolve_module(&db, &module_name).unwrap_or_else(|| {
|
||||
panic!("Expected module {module_name} to exist in the mock stdlib")
|
||||
});
|
||||
let search_path = resolved_module.search_path();
|
||||
let search_path = resolved_module.search_path().unwrap();
|
||||
assert_eq!(
|
||||
&stdlib, search_path,
|
||||
"Search path for {module_name} was unexpectedly {search_path:?}"
|
||||
|
@ -1148,7 +1217,7 @@ mod tests {
|
|||
let resolved_module = resolve_module(&db, &module_name).unwrap_or_else(|| {
|
||||
panic!("Expected module {module_name} to exist in the mock stdlib")
|
||||
});
|
||||
let search_path = resolved_module.search_path();
|
||||
let search_path = resolved_module.search_path().unwrap();
|
||||
assert_eq!(
|
||||
&stdlib, search_path,
|
||||
"Search path for {module_name} was unexpectedly {search_path:?}"
|
||||
|
@ -1209,9 +1278,12 @@ mod tests {
|
|||
Some(&functools_module),
|
||||
resolve_module(&db, &functools_module_name).as_ref()
|
||||
);
|
||||
assert_eq!(&src, functools_module.search_path());
|
||||
assert_eq!(&src, functools_module.search_path().unwrap());
|
||||
assert_eq!(ModuleKind::Module, functools_module.kind());
|
||||
assert_eq!(&src.join("functools.py"), functools_module.file().path(&db));
|
||||
assert_eq!(
|
||||
&src.join("functools.py"),
|
||||
functools_module.file().unwrap().path(&db)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Some(functools_module),
|
||||
|
@ -1230,9 +1302,9 @@ mod tests {
|
|||
let pydoc_data_topics = resolve_module(&db, &pydoc_data_topics_name).unwrap();
|
||||
|
||||
assert_eq!("pydoc_data.topics", pydoc_data_topics.name());
|
||||
assert_eq!(pydoc_data_topics.search_path(), &stdlib);
|
||||
assert_eq!(pydoc_data_topics.search_path().unwrap(), &stdlib);
|
||||
assert_eq!(
|
||||
pydoc_data_topics.file().path(&db),
|
||||
pydoc_data_topics.file().unwrap().path(&db),
|
||||
&stdlib.join("pydoc_data/topics.pyi")
|
||||
);
|
||||
}
|
||||
|
@ -1247,8 +1319,8 @@ mod tests {
|
|||
let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
|
||||
|
||||
assert_eq!("foo", foo_module.name());
|
||||
assert_eq!(&src, foo_module.search_path());
|
||||
assert_eq!(&foo_path, foo_module.file().path(&db));
|
||||
assert_eq!(&src, foo_module.search_path().unwrap());
|
||||
assert_eq!(&foo_path, foo_module.file().unwrap().path(&db));
|
||||
|
||||
assert_eq!(
|
||||
Some(&foo_module),
|
||||
|
@ -1274,8 +1346,8 @@ mod tests {
|
|||
let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
|
||||
let foo_init_path = src.join("foo/__init__.py");
|
||||
|
||||
assert_eq!(&src, foo_module.search_path());
|
||||
assert_eq!(&foo_init_path, foo_module.file().path(&db));
|
||||
assert_eq!(&src, foo_module.search_path().unwrap());
|
||||
assert_eq!(&foo_init_path, foo_module.file().unwrap().path(&db));
|
||||
assert_eq!(ModuleKind::Package, foo_module.kind());
|
||||
|
||||
assert_eq!(
|
||||
|
@ -1297,8 +1369,8 @@ mod tests {
|
|||
let foo = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
|
||||
let foo_stub = src.join("foo.pyi");
|
||||
|
||||
assert_eq!(&src, foo.search_path());
|
||||
assert_eq!(&foo_stub, foo.file().path(&db));
|
||||
assert_eq!(&src, foo.search_path().unwrap());
|
||||
assert_eq!(&foo_stub, foo.file().unwrap().path(&db));
|
||||
|
||||
assert_eq!(Some(foo), path_to_module(&db, &FilePath::System(foo_stub)));
|
||||
assert_eq!(
|
||||
|
@ -1321,8 +1393,8 @@ mod tests {
|
|||
resolve_module(&db, &ModuleName::new_static("foo.bar.baz").unwrap()).unwrap();
|
||||
let baz_path = src.join("foo/bar/baz.py");
|
||||
|
||||
assert_eq!(&src, baz_module.search_path());
|
||||
assert_eq!(&baz_path, baz_module.file().path(&db));
|
||||
assert_eq!(&src, baz_module.search_path().unwrap());
|
||||
assert_eq!(&baz_path, baz_module.file().unwrap().path(&db));
|
||||
|
||||
assert_eq!(
|
||||
Some(baz_module),
|
||||
|
@ -1345,8 +1417,8 @@ mod tests {
|
|||
let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
|
||||
let foo_src_path = src.join("foo.py");
|
||||
|
||||
assert_eq!(&src, foo_module.search_path());
|
||||
assert_eq!(&foo_src_path, foo_module.file().path(&db));
|
||||
assert_eq!(&src, foo_module.search_path().unwrap());
|
||||
assert_eq!(&foo_src_path, foo_module.file().unwrap().path(&db));
|
||||
assert_eq!(
|
||||
Some(foo_module),
|
||||
path_to_module(&db, &FilePath::System(foo_src_path))
|
||||
|
@ -1413,14 +1485,14 @@ mod tests {
|
|||
|
||||
assert_ne!(foo_module, bar_module);
|
||||
|
||||
assert_eq!(&src, foo_module.search_path());
|
||||
assert_eq!(&foo, foo_module.file().path(&db));
|
||||
assert_eq!(&src, foo_module.search_path().unwrap());
|
||||
assert_eq!(&foo, foo_module.file().unwrap().path(&db));
|
||||
|
||||
// `foo` and `bar` shouldn't resolve to the same file
|
||||
|
||||
assert_eq!(&src, bar_module.search_path());
|
||||
assert_eq!(&bar, bar_module.file().path(&db));
|
||||
assert_eq!(&foo, foo_module.file().path(&db));
|
||||
assert_eq!(&src, bar_module.search_path().unwrap());
|
||||
assert_eq!(&bar, bar_module.file().unwrap().path(&db));
|
||||
assert_eq!(&foo, foo_module.file().unwrap().path(&db));
|
||||
|
||||
assert_ne!(&foo_module, &bar_module);
|
||||
|
||||
|
@ -1484,7 +1556,7 @@ mod tests {
|
|||
let foo_file = system_path_to_file(&db, &foo_path).expect("foo.py to exist");
|
||||
|
||||
let foo_module = resolve_module(&db, &foo_module_name).expect("Foo module to resolve");
|
||||
assert_eq!(foo_file, foo_module.file());
|
||||
assert_eq!(foo_file, foo_module.file().unwrap());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1500,7 +1572,7 @@ mod tests {
|
|||
let foo_module = resolve_module(&db, &foo_module_name).expect("foo module to exist");
|
||||
let foo_init_path = src.join("foo/__init__.py");
|
||||
|
||||
assert_eq!(&foo_init_path, foo_module.file().path(&db));
|
||||
assert_eq!(&foo_init_path, foo_module.file().unwrap().path(&db));
|
||||
|
||||
// Delete `foo/__init__.py` and the `foo` folder. `foo` should now resolve to `foo.py`
|
||||
db.memory_file_system().remove_file(&foo_init_path)?;
|
||||
|
@ -1510,7 +1582,7 @@ mod tests {
|
|||
File::sync_path(&mut db, foo_init_path.parent().unwrap());
|
||||
|
||||
let foo_module = resolve_module(&db, &foo_module_name).expect("Foo module to resolve");
|
||||
assert_eq!(&src.join("foo.py"), foo_module.file().path(&db));
|
||||
assert_eq!(&src.join("foo.py"), foo_module.file().unwrap().path(&db));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1536,9 +1608,9 @@ mod tests {
|
|||
let stdlib_functools_path = stdlib.join("functools.pyi");
|
||||
|
||||
let functools_module = resolve_module(&db, &functools_module_name).unwrap();
|
||||
assert_eq!(functools_module.search_path(), &stdlib);
|
||||
assert_eq!(functools_module.search_path().unwrap(), &stdlib);
|
||||
assert_eq!(
|
||||
Ok(functools_module.file()),
|
||||
Ok(functools_module.file().unwrap()),
|
||||
system_path_to_file(&db, &stdlib_functools_path)
|
||||
);
|
||||
|
||||
|
@ -1556,9 +1628,9 @@ mod tests {
|
|||
ModuleNameIngredient::new(&db, functools_module_name),
|
||||
&events,
|
||||
);
|
||||
assert_eq!(functools_module.search_path(), &stdlib);
|
||||
assert_eq!(functools_module.search_path().unwrap(), &stdlib);
|
||||
assert_eq!(
|
||||
Ok(functools_module.file()),
|
||||
Ok(functools_module.file().unwrap()),
|
||||
system_path_to_file(&db, &stdlib_functools_path)
|
||||
);
|
||||
}
|
||||
|
@ -1582,9 +1654,9 @@ mod tests {
|
|||
|
||||
let functools_module_name = ModuleName::new_static("functools").unwrap();
|
||||
let functools_module = resolve_module(&db, &functools_module_name).unwrap();
|
||||
assert_eq!(functools_module.search_path(), &stdlib);
|
||||
assert_eq!(functools_module.search_path().unwrap(), &stdlib);
|
||||
assert_eq!(
|
||||
Ok(functools_module.file()),
|
||||
Ok(functools_module.file().unwrap()),
|
||||
system_path_to_file(&db, stdlib.join("functools.pyi"))
|
||||
);
|
||||
|
||||
|
@ -1593,9 +1665,9 @@ mod tests {
|
|||
let src_functools_path = src.join("functools.py");
|
||||
db.write_file(&src_functools_path, "FOO: int").unwrap();
|
||||
let functools_module = resolve_module(&db, &functools_module_name).unwrap();
|
||||
assert_eq!(functools_module.search_path(), &src);
|
||||
assert_eq!(functools_module.search_path().unwrap(), &src);
|
||||
assert_eq!(
|
||||
Ok(functools_module.file()),
|
||||
Ok(functools_module.file().unwrap()),
|
||||
system_path_to_file(&db, &src_functools_path)
|
||||
);
|
||||
}
|
||||
|
@ -1624,9 +1696,9 @@ mod tests {
|
|||
let src_functools_path = src.join("functools.py");
|
||||
|
||||
let functools_module = resolve_module(&db, &functools_module_name).unwrap();
|
||||
assert_eq!(functools_module.search_path(), &src);
|
||||
assert_eq!(functools_module.search_path().unwrap(), &src);
|
||||
assert_eq!(
|
||||
Ok(functools_module.file()),
|
||||
Ok(functools_module.file().unwrap()),
|
||||
system_path_to_file(&db, &src_functools_path)
|
||||
);
|
||||
|
||||
|
@ -1637,9 +1709,9 @@ mod tests {
|
|||
.unwrap();
|
||||
File::sync_path(&mut db, &src_functools_path);
|
||||
let functools_module = resolve_module(&db, &functools_module_name).unwrap();
|
||||
assert_eq!(functools_module.search_path(), &stdlib);
|
||||
assert_eq!(functools_module.search_path().unwrap(), &stdlib);
|
||||
assert_eq!(
|
||||
Ok(functools_module.file()),
|
||||
Ok(functools_module.file().unwrap()),
|
||||
system_path_to_file(&db, stdlib.join("functools.pyi"))
|
||||
);
|
||||
}
|
||||
|
@ -1662,11 +1734,11 @@ mod tests {
|
|||
let foo_bar_module = resolve_module(&db, &foo_bar_module_name).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
foo_module.file().path(&db),
|
||||
foo_module.file().unwrap().path(&db),
|
||||
&FilePath::system("/x/src/foo/__init__.py")
|
||||
);
|
||||
assert_eq!(
|
||||
foo_bar_module.file().path(&db),
|
||||
foo_bar_module.file().unwrap().path(&db),
|
||||
&FilePath::system("/x/src/foo/bar.py")
|
||||
);
|
||||
}
|
||||
|
@ -1693,7 +1765,7 @@ mod tests {
|
|||
let bar_module_name = ModuleName::new_static("bar").unwrap();
|
||||
let bar_module = resolve_module(&db, &bar_module_name).unwrap();
|
||||
assert_eq!(
|
||||
bar_module.file().path(&db),
|
||||
bar_module.file().unwrap().path(&db),
|
||||
&FilePath::system("/y/src/bar.py")
|
||||
);
|
||||
}
|
||||
|
@ -1713,7 +1785,7 @@ mod tests {
|
|||
let foo_module = resolve_module(&db, &foo_module_name).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
foo_module.file().path(&db),
|
||||
foo_module.file().unwrap().path(&db),
|
||||
&FilePath::system("/x/y/src/foo.pyi")
|
||||
);
|
||||
}
|
||||
|
@ -1764,13 +1836,19 @@ not_a_directory
|
|||
let spam_module = resolve_module(&db, &spam_module_name).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
foo_module.file().path(&db),
|
||||
foo_module.file().unwrap().path(&db),
|
||||
&FilePath::system("/x/y/src/foo.pyi")
|
||||
);
|
||||
assert_eq!(a_module.file().path(&db), &FilePath::system("/a.py"));
|
||||
assert_eq!(b_module.file().path(&db), &FilePath::system("/baz/b.py"));
|
||||
assert_eq!(
|
||||
spam_module.file().path(&db),
|
||||
a_module.file().unwrap().path(&db),
|
||||
&FilePath::system("/a.py")
|
||||
);
|
||||
assert_eq!(
|
||||
b_module.file().unwrap().path(&db),
|
||||
&FilePath::system("/baz/b.py")
|
||||
);
|
||||
assert_eq!(
|
||||
spam_module.file().unwrap().path(&db),
|
||||
&FilePath::System(site_packages.join("spam/spam.py"))
|
||||
);
|
||||
}
|
||||
|
@ -1791,14 +1869,14 @@ not_a_directory
|
|||
|
||||
let foo_module = resolve_module(&db, &foo_module_name).unwrap();
|
||||
assert_eq!(
|
||||
foo_module.file().path(&db),
|
||||
foo_module.file().unwrap().path(&db),
|
||||
&FilePath::system("/x/src/foo.py")
|
||||
);
|
||||
|
||||
db.clear_salsa_events();
|
||||
let bar_module = resolve_module(&db, &bar_module_name).unwrap();
|
||||
assert_eq!(
|
||||
bar_module.file().path(&db),
|
||||
bar_module.file().unwrap().path(&db),
|
||||
&FilePath::system("/y/src/bar.py")
|
||||
);
|
||||
let events = db.take_salsa_events();
|
||||
|
@ -1823,7 +1901,7 @@ not_a_directory
|
|||
let foo_module_name = ModuleName::new_static("foo").unwrap();
|
||||
let foo_module = resolve_module(&db, &foo_module_name).unwrap();
|
||||
assert_eq!(
|
||||
foo_module.file().path(&db),
|
||||
foo_module.file().unwrap().path(&db),
|
||||
&FilePath::system("/x/src/foo.py")
|
||||
);
|
||||
|
||||
|
@ -1851,7 +1929,7 @@ not_a_directory
|
|||
let foo_module = resolve_module(&db, &foo_module_name).unwrap();
|
||||
let src_path = SystemPathBuf::from("/x/src");
|
||||
assert_eq!(
|
||||
foo_module.file().path(&db),
|
||||
foo_module.file().unwrap().path(&db),
|
||||
&FilePath::System(src_path.join("foo.py"))
|
||||
);
|
||||
|
||||
|
@ -1925,7 +2003,10 @@ not_a_directory
|
|||
// take precedence over the second `site-packages` directory...
|
||||
let a_module_name = ModuleName::new_static("a").unwrap();
|
||||
let a_module = resolve_module(&db, &a_module_name).unwrap();
|
||||
assert_eq!(a_module.file().path(&db), &editable_install_location);
|
||||
assert_eq!(
|
||||
a_module.file().unwrap().path(&db),
|
||||
&editable_install_location
|
||||
);
|
||||
|
||||
db.memory_file_system()
|
||||
.remove_file(&site_packages_pth)
|
||||
|
@ -1936,7 +2017,10 @@ not_a_directory
|
|||
// the editable install no longer exists, so the module now resolves to the file in the
|
||||
// second `site-packages` directory
|
||||
let a_module = resolve_module(&db, &a_module_name).unwrap();
|
||||
assert_eq!(a_module.file().path(&db), &system_site_packages_location);
|
||||
assert_eq!(
|
||||
a_module.file().unwrap().path(&db),
|
||||
&system_site_packages_location
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2001,6 +2085,7 @@ not_a_directory
|
|||
assert!(
|
||||
a_module
|
||||
.file()
|
||||
.unwrap()
|
||||
.path(&db)
|
||||
.as_str()
|
||||
.ends_with("src/a/__init__.py"),
|
||||
|
@ -2035,6 +2120,6 @@ not_a_directory
|
|||
|
||||
let foo_module_file = File::new(&db, FilePath::System(installed_foo_module));
|
||||
let module = file_to_module(&db, foo_module_file).unwrap();
|
||||
assert_eq!(module.search_path(), &site_packages);
|
||||
assert_eq!(module.search_path().unwrap(), &site_packages);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1336,7 +1336,9 @@ where
|
|||
continue;
|
||||
};
|
||||
|
||||
let referenced_module = module.file();
|
||||
let Some(referenced_module) = module.file() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// In order to understand the visibility of definitions created by a `*` import,
|
||||
// we need to know the visibility of the global-scope definitions in the
|
||||
|
|
|
@ -244,7 +244,12 @@ impl<'db> Visitor<'db> for ExportFinder<'db> {
|
|||
.ok()
|
||||
.and_then(|module_name| resolve_module(self.db, &module_name))
|
||||
.iter()
|
||||
.flat_map(|module| exported_names(self.db, module.file()))
|
||||
.flat_map(|module| {
|
||||
module
|
||||
.file()
|
||||
.map(|file| exported_names(self.db, file))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
{
|
||||
self.possibly_add_export(export, PossibleExportKind::Normal);
|
||||
}
|
||||
|
|
|
@ -342,19 +342,22 @@ pub(crate) fn imported_symbol<'db>(
|
|||
/// (e.g. `from builtins import int`).
|
||||
pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> SymbolAndQualifiers<'db> {
|
||||
resolve_module(db, &KnownModule::Builtins.name())
|
||||
.map(|module| {
|
||||
symbol_impl(
|
||||
db,
|
||||
global_scope(db, module.file()),
|
||||
symbol,
|
||||
RequiresExplicitReExport::Yes,
|
||||
.and_then(|module| {
|
||||
let file = module.file()?;
|
||||
Some(
|
||||
symbol_impl(
|
||||
db,
|
||||
global_scope(db, file),
|
||||
symbol,
|
||||
RequiresExplicitReExport::Yes,
|
||||
)
|
||||
.or_fall_back_to(db, || {
|
||||
// We're looking up in the builtins namespace and not the module, so we should
|
||||
// do the normal lookup in `types.ModuleType` and not the special one as in
|
||||
// `imported_symbol`.
|
||||
module_type_implicit_global_symbol(db, symbol)
|
||||
}),
|
||||
)
|
||||
.or_fall_back_to(db, || {
|
||||
// We're looking up in the builtins namespace and not the module, so we should
|
||||
// do the normal lookup in `types.ModuleType` and not the special one as in
|
||||
// `imported_symbol`.
|
||||
module_type_implicit_global_symbol(db, symbol)
|
||||
})
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
@ -368,7 +371,10 @@ pub(crate) fn known_module_symbol<'db>(
|
|||
symbol: &str,
|
||||
) -> SymbolAndQualifiers<'db> {
|
||||
resolve_module(db, &known_module.name())
|
||||
.map(|module| imported_symbol(db, module.file(), symbol, None))
|
||||
.and_then(|module| {
|
||||
let file = module.file()?;
|
||||
Some(imported_symbol(db, file, symbol, None))
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
|
@ -403,7 +409,8 @@ pub(crate) fn builtins_module_scope(db: &dyn Db) -> Option<ScopeId<'_>> {
|
|||
///
|
||||
/// Can return `None` if a custom typeshed is used that is missing the core module in question.
|
||||
fn core_module_scope(db: &dyn Db, core_module: KnownModule) -> Option<ScopeId<'_>> {
|
||||
resolve_module(db, &core_module.name()).map(|module| global_scope(db, module.file()))
|
||||
let module = resolve_module(db, &core_module.name())?;
|
||||
Some(global_scope(db, module.file()?))
|
||||
}
|
||||
|
||||
/// Infer the combined type from an iterator of bindings, and return it
|
||||
|
|
|
@ -7980,7 +7980,11 @@ impl<'db> ModuleLiteralType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
imported_symbol(db, self.module(db).file(), name, None).symbol
|
||||
self.module(db)
|
||||
.file()
|
||||
.map(|file| imported_symbol(db, file, name, None))
|
||||
.unwrap_or_default()
|
||||
.symbol
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -611,8 +611,12 @@ impl<'db> Bindings<'db> {
|
|||
if let [Some(ty)] = overload.parameter_types() {
|
||||
overload.set_return_type(match ty {
|
||||
Type::ModuleLiteral(module_literal) => {
|
||||
match dunder_all_names(db, module_literal.module(db).file())
|
||||
{
|
||||
let all_names = module_literal
|
||||
.module(db)
|
||||
.file()
|
||||
.map(|file| dunder_all_names(db, file))
|
||||
.unwrap_or_default();
|
||||
match all_names {
|
||||
Some(names) => {
|
||||
let mut names = names.iter().collect::<Vec<_>>();
|
||||
names.sort();
|
||||
|
|
|
@ -2898,7 +2898,7 @@ mod tests {
|
|||
let class_module = resolve_module(&db, &class.canonical_module(&db).name()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
KnownClass::try_from_file_and_name(&db, class_module.file(), class_name),
|
||||
KnownClass::try_from_file_and_name(&db, class_module.file().unwrap(), class_name),
|
||||
Some(class),
|
||||
"`KnownClass::candidate_from_str` appears to be missing a case for `{class_name}`"
|
||||
);
|
||||
|
|
|
@ -24,16 +24,17 @@ impl TypeDefinition<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn full_range(&self, db: &dyn Db) -> FileRange {
|
||||
pub fn full_range(&self, db: &dyn Db) -> Option<FileRange> {
|
||||
match self {
|
||||
Self::Module(module) => {
|
||||
let source = source_text(db.upcast(), module.file());
|
||||
FileRange::new(module.file(), TextRange::up_to(source.text_len()))
|
||||
let file = module.file()?;
|
||||
let source = source_text(db.upcast(), file);
|
||||
Some(FileRange::new(file, TextRange::up_to(source.text_len())))
|
||||
}
|
||||
Self::Class(definition)
|
||||
| Self::Function(definition)
|
||||
| Self::TypeVar(definition)
|
||||
| Self::TypeAlias(definition) => definition.full_range(db),
|
||||
| Self::TypeAlias(definition) => Some(definition.full_range(db)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4049,7 +4049,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
// (e.g. `from parent import submodule` inside the `parent` module).
|
||||
let import_is_self_referential = module_ty
|
||||
.into_module_literal()
|
||||
.is_some_and(|module| self.file() == module.module(self.db()).file());
|
||||
.is_some_and(|module| Some(self.file()) == module.module(self.db()).file());
|
||||
|
||||
// First try loading the requested attribute from the module.
|
||||
if !import_is_self_referential {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue