mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ty] Add a new abstraction for adding imports to a module
This is somewhat inspired by a similar abstraction in `ruff_linter`. The main idea is to create an importer once for a module that you want to add imports to. And then call `import` to generate an edit for each symbol you want to add. I haven't done any performance profiling here yet. I don't know if it will be a bottleneck. In particular, I do expect `Importer::import` (but not `Importer::new`) to get called many times for a single completion request when auto-import is enabled. Particularly in projects with a lot of unimported symbols. Because I don't know the perf impact, I didn't do any premature optimization here. But there are surely some low hanging fruit if this does prove to be a problem. New tests make up a big portion of the diff here. I tried to think of a bunch of different cases, although I'm sure there are more.
This commit is contained in:
parent
bcc8d6910b
commit
b6a29592e7
3 changed files with 1812 additions and 32 deletions
|
@ -3,6 +3,7 @@ use ruff_db::source::line_index;
|
|||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Expr, ExprRef, HasNodeIndex, name::Name};
|
||||
use ruff_source_file::LineIndex;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::Db;
|
||||
use crate::module_name::ModuleName;
|
||||
|
@ -37,6 +38,37 @@ impl<'db> SemanticModel<'db> {
|
|||
line_index(self.db, self.file)
|
||||
}
|
||||
|
||||
/// Returns a map from symbol name to that symbol's
|
||||
/// type and definition site (if available).
|
||||
///
|
||||
/// The symbols are the symbols in scope at the given
|
||||
/// AST node.
|
||||
pub fn members_in_scope_at(
|
||||
&self,
|
||||
node: ast::AnyNodeRef<'_>,
|
||||
) -> FxHashMap<Name, MemberDefinition<'db>> {
|
||||
let index = semantic_index(self.db, self.file);
|
||||
let mut members = FxHashMap::default();
|
||||
let Some(file_scope) = self.scope(node) else {
|
||||
return members;
|
||||
};
|
||||
|
||||
for (file_scope, _) in index.ancestor_scopes(file_scope) {
|
||||
for memberdef in
|
||||
all_declarations_and_bindings(self.db, file_scope.to_scope_id(self.db, self.file))
|
||||
{
|
||||
members.insert(
|
||||
memberdef.member.name,
|
||||
MemberDefinition {
|
||||
ty: memberdef.member.ty,
|
||||
definition: memberdef.definition,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
members
|
||||
}
|
||||
|
||||
pub fn resolve_module(&self, module_name: &ModuleName) -> Option<Module<'_>> {
|
||||
resolve_module(self.db, module_name)
|
||||
}
|
||||
|
@ -212,20 +244,7 @@ impl<'db> SemanticModel<'db> {
|
|||
pub fn scoped_completions(&self, node: ast::AnyNodeRef<'_>) -> Vec<Completion<'db>> {
|
||||
let index = semantic_index(self.db, self.file);
|
||||
|
||||
// TODO: We currently use `try_expression_scope_id` here as a hotfix for [1].
|
||||
// Revert this to use `expression_scope_id` once a proper fix is in place.
|
||||
//
|
||||
// [1] https://github.com/astral-sh/ty/issues/572
|
||||
let Some(file_scope) = (match node {
|
||||
ast::AnyNodeRef::Identifier(identifier) => index.try_expression_scope_id(identifier),
|
||||
node => match node.as_expr_ref() {
|
||||
// If we couldn't identify a specific
|
||||
// expression that we're in, then just
|
||||
// fall back to the global scope.
|
||||
None => Some(FileScopeId::global()),
|
||||
Some(expr) => index.try_expression_scope_id(&expr),
|
||||
},
|
||||
}) else {
|
||||
let Some(file_scope) = self.scope(node) else {
|
||||
return vec![];
|
||||
};
|
||||
let mut completions = vec![];
|
||||
|
@ -244,6 +263,32 @@ impl<'db> SemanticModel<'db> {
|
|||
completions.extend(self.module_completions(&builtins));
|
||||
completions
|
||||
}
|
||||
|
||||
fn scope(&self, node: ast::AnyNodeRef<'_>) -> Option<FileScopeId> {
|
||||
let index = semantic_index(self.db, self.file);
|
||||
|
||||
// TODO: We currently use `try_expression_scope_id` here as a hotfix for [1].
|
||||
// Revert this to use `expression_scope_id` once a proper fix is in place.
|
||||
//
|
||||
// [1] https://github.com/astral-sh/ty/issues/572
|
||||
match node {
|
||||
ast::AnyNodeRef::Identifier(identifier) => index.try_expression_scope_id(identifier),
|
||||
node => match node.as_expr_ref() {
|
||||
// If we couldn't identify a specific
|
||||
// expression that we're in, then just
|
||||
// fall back to the global scope.
|
||||
None => Some(FileScopeId::global()),
|
||||
Some(expr) => index.try_expression_scope_id(&expr),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type and definition (if available) of a symbol.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MemberDefinition<'db> {
|
||||
pub ty: Type<'db>,
|
||||
pub definition: Option<Definition<'db>>,
|
||||
}
|
||||
|
||||
/// A classification of symbol names.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue