[ty] Refactor detection of import statements for completions

This commit essentially does away of all our old heuristic and piecemeal
code for detecting different kinds of import statements. Instead, we
offer one single state machine that does everything. This on its own
fixes a few bugs. For example, `import collections.abc, unico<CURSOR>`
would previously offer global scope completions instead of module
completions.

For the most part though, this commit is a refactoring that preserves
parity. In the next commit, we'll add support for completions on
relative imports.
This commit is contained in:
Andrew Gallant 2025-11-18 10:52:01 -05:00 committed by Andrew Gallant
parent cdef3f5ab8
commit 553e568624
2 changed files with 600 additions and 484 deletions

File diff suppressed because it is too large Load Diff

View File

@ -91,11 +91,7 @@ impl<'db> SemanticModel<'db> {
} }
/// Returns completions for symbols available in a `from module import <CURSOR>` context. /// Returns completions for symbols available in a `from module import <CURSOR>` context.
pub fn from_import_completions( pub fn from_import_completions(&self, import: &ast::StmtImportFrom) -> Vec<Completion<'db>> {
&self,
import: &ast::StmtImportFrom,
_name: Option<usize>,
) -> Vec<Completion<'db>> {
let module_name = match ModuleName::from_import_statement(self.db, self.file, import) { let module_name = match ModuleName::from_import_statement(self.db, self.file, import) {
Ok(module_name) => module_name, Ok(module_name) => module_name,
Err(err) => { Err(err) => {
@ -110,69 +106,8 @@ impl<'db> SemanticModel<'db> {
self.module_completions(&module_name) self.module_completions(&module_name)
} }
/// Returns completions only for submodules for the module
/// identified by `name` in `import`.
///
/// For example, `import re, os.<CURSOR>, zlib`.
pub fn import_submodule_completions(
&self,
import: &ast::StmtImport,
name: usize,
) -> Vec<Completion<'db>> {
let module_ident = &import.names[name].name;
let Some((parent_ident, _)) = module_ident.rsplit_once('.') else {
return vec![];
};
let module_name =
match ModuleName::from_identifier_parts(self.db, self.file, Some(parent_ident), 0) {
Ok(module_name) => module_name,
Err(err) => {
tracing::debug!(
"Could not extract module name from `{module:?}`: {err:?}",
module = module_ident,
);
return vec![];
}
};
self.import_submodule_completions_for_name(&module_name)
}
/// Returns completions only for submodules for the module
/// used in a `from module import attribute` statement.
///
/// For example, `from os.<CURSOR>`.
pub fn from_import_submodule_completions(
&self,
import: &ast::StmtImportFrom,
) -> Vec<Completion<'db>> {
let level = import.level;
let Some(module_ident) = import.module.as_deref() else {
return vec![];
};
let Some((parent_ident, _)) = module_ident.rsplit_once('.') else {
return vec![];
};
let module_name = match ModuleName::from_identifier_parts(
self.db,
self.file,
Some(parent_ident),
level,
) {
Ok(module_name) => module_name,
Err(err) => {
tracing::debug!(
"Could not extract module name from `{module:?}` with level {level}: {err:?}",
module = import.module,
level = import.level,
);
return vec![];
}
};
self.import_submodule_completions_for_name(&module_name)
}
/// Returns submodule-only completions for the given module. /// Returns submodule-only completions for the given module.
fn import_submodule_completions_for_name( pub fn import_submodule_completions_for_name(
&self, &self,
module_name: &ModuleName, module_name: &ModuleName,
) -> Vec<Completion<'db>> { ) -> Vec<Completion<'db>> {