Skip to content

Commit 3e450cf

Browse files
bors[bot]Veykrilmatklad
authored
6207: Extract ImportAssets out of auto_import r=matklad a=Veykril See #6172 (comment) I couldn't fully pull out `AssistContext` as `find_node_at_offset_with_descend`: https://github.com/rust-analyzer/rust-analyzer/blob/81fa00c5b5d5ffb559a39c7ff5190a2519a8ea61/crates/assists/src/assist_context.rs#L90-L92 requires the `SourceFile` which is private in it and I don't think making it public just for this is the right call? 6224: ⬆️ salsa r=matklad a=matklad bors r+ 🤖 6226: Add reminder to update lsp-extensions.md r=matklad a=matklad bors r+ 🤖 6227: Reduce bors timeout r=matklad a=matklad bors r+ 🤖 Co-authored-by: Lukas Wirth <[email protected]> Co-authored-by: Aleksey Kladov <[email protected]>
5 parents b62f48f + 01b410c + 41e2639 + d852189 + 74b5f7c commit 3e450cf

File tree

8 files changed

+359
-247
lines changed

8 files changed

+359
-247
lines changed

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bors.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ status = [
66
"TypeScript (windows-latest)",
77
]
88
delete_merged_branches = true
9+
timeout_sec = 1200 # 20 min

crates/assists/src/handlers/auto_import.rs

Lines changed: 28 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,9 @@
1-
use std::collections::BTreeSet;
2-
3-
use either::Either;
4-
use hir::{
5-
AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
6-
Type,
7-
};
8-
use ide_db::{imports_locator, RootDatabase};
9-
use insert_use::ImportScope;
10-
use rustc_hash::FxHashSet;
11-
use syntax::{
12-
ast::{self, AstNode},
13-
SyntaxNode,
14-
};
1+
use syntax::ast;
152

163
use crate::{
17-
utils::insert_use, utils::mod_path_to_ast, AssistContext, AssistId, AssistKind, Assists,
18-
GroupLabel,
4+
utils::import_assets::{ImportAssets, ImportCandidate},
5+
utils::{insert_use, mod_path_to_ast, ImportScope},
6+
AssistContext, AssistId, AssistKind, Assists, GroupLabel,
197
};
208

219
// Assist: auto_import
@@ -38,16 +26,24 @@ use crate::{
3826
// # pub mod std { pub mod collections { pub struct HashMap { } } }
3927
// ```
4028
pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
41-
let auto_import_assets = AutoImportAssets::new(ctx)?;
42-
let proposed_imports = auto_import_assets.search_for_imports(ctx);
29+
let import_assets =
30+
if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
31+
ImportAssets::for_regular_path(path_under_caret, &ctx.sema)
32+
} else if let Some(method_under_caret) =
33+
ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
34+
{
35+
ImportAssets::for_method_call(method_under_caret, &ctx.sema)
36+
} else {
37+
None
38+
}?;
39+
let proposed_imports = import_assets.search_for_imports(&ctx.sema, &ctx.config.insert_use);
4340
if proposed_imports.is_empty() {
4441
return None;
4542
}
4643

47-
let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
48-
let group = auto_import_assets.get_import_group_message();
49-
let scope =
50-
ImportScope::find_insert_use_container(&auto_import_assets.syntax_under_caret, ctx)?;
44+
let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
45+
let group = import_group_message(import_assets.import_candidate());
46+
let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?;
5147
let syntax = scope.as_syntax_node();
5248
for import in proposed_imports {
5349
acc.add_group(
@@ -65,227 +61,18 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
6561
Some(())
6662
}
6763

68-
#[derive(Debug)]
69-
struct AutoImportAssets {
70-
import_candidate: ImportCandidate,
71-
module_with_name_to_import: Module,
72-
syntax_under_caret: SyntaxNode,
73-
}
74-
75-
impl AutoImportAssets {
76-
fn new(ctx: &AssistContext) -> Option<Self> {
77-
if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
78-
Self::for_regular_path(path_under_caret, &ctx)
79-
} else {
80-
Self::for_method_call(ctx.find_node_at_offset_with_descend()?, &ctx)
81-
}
82-
}
83-
84-
fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option<Self> {
85-
let syntax_under_caret = method_call.syntax().to_owned();
86-
let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?;
87-
Some(Self {
88-
import_candidate: ImportCandidate::for_method_call(&ctx.sema, &method_call)?,
89-
module_with_name_to_import,
90-
syntax_under_caret,
91-
})
92-
}
93-
94-
fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> {
95-
let syntax_under_caret = path_under_caret.syntax().to_owned();
96-
if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
97-
return None;
98-
}
99-
100-
let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?;
101-
Some(Self {
102-
import_candidate: ImportCandidate::for_regular_path(&ctx.sema, &path_under_caret)?,
103-
module_with_name_to_import,
104-
syntax_under_caret,
105-
})
106-
}
107-
108-
fn get_search_query(&self) -> &str {
109-
match &self.import_candidate {
110-
ImportCandidate::UnqualifiedName(name) => name,
111-
ImportCandidate::QualifierStart(qualifier_start) => qualifier_start,
112-
ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => trait_assoc_item_name,
113-
ImportCandidate::TraitMethod(_, trait_method_name) => trait_method_name,
114-
}
115-
}
116-
117-
fn get_import_group_message(&self) -> GroupLabel {
118-
let name = match &self.import_candidate {
119-
ImportCandidate::UnqualifiedName(name) => format!("Import {}", name),
120-
ImportCandidate::QualifierStart(qualifier_start) => {
121-
format!("Import {}", qualifier_start)
122-
}
123-
ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => {
124-
format!("Import a trait for item {}", trait_assoc_item_name)
125-
}
126-
ImportCandidate::TraitMethod(_, trait_method_name) => {
127-
format!("Import a trait for method {}", trait_method_name)
128-
}
129-
};
130-
GroupLabel(name)
131-
}
132-
133-
fn search_for_imports(&self, ctx: &AssistContext) -> BTreeSet<ModPath> {
134-
let _p = profile::span("auto_import::search_for_imports");
135-
let db = ctx.db();
136-
let current_crate = self.module_with_name_to_import.krate();
137-
imports_locator::find_imports(&ctx.sema, current_crate, &self.get_search_query())
138-
.into_iter()
139-
.filter_map(|candidate| match &self.import_candidate {
140-
ImportCandidate::TraitAssocItem(assoc_item_type, _) => {
141-
let located_assoc_item = match candidate {
142-
Either::Left(ModuleDef::Function(located_function)) => located_function
143-
.as_assoc_item(db)
144-
.map(|assoc| assoc.container(db))
145-
.and_then(Self::assoc_to_trait),
146-
Either::Left(ModuleDef::Const(located_const)) => located_const
147-
.as_assoc_item(db)
148-
.map(|assoc| assoc.container(db))
149-
.and_then(Self::assoc_to_trait),
150-
_ => None,
151-
}?;
152-
153-
let mut trait_candidates = FxHashSet::default();
154-
trait_candidates.insert(located_assoc_item.into());
155-
156-
assoc_item_type
157-
.iterate_path_candidates(
158-
db,
159-
current_crate,
160-
&trait_candidates,
161-
None,
162-
|_, assoc| Self::assoc_to_trait(assoc.container(db)),
163-
)
164-
.map(ModuleDef::from)
165-
.map(Either::Left)
166-
}
167-
ImportCandidate::TraitMethod(function_callee, _) => {
168-
let located_assoc_item =
169-
if let Either::Left(ModuleDef::Function(located_function)) = candidate {
170-
located_function
171-
.as_assoc_item(db)
172-
.map(|assoc| assoc.container(db))
173-
.and_then(Self::assoc_to_trait)
174-
} else {
175-
None
176-
}?;
177-
178-
let mut trait_candidates = FxHashSet::default();
179-
trait_candidates.insert(located_assoc_item.into());
180-
181-
function_callee
182-
.iterate_method_candidates(
183-
db,
184-
current_crate,
185-
&trait_candidates,
186-
None,
187-
|_, function| {
188-
Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
189-
},
190-
)
191-
.map(ModuleDef::from)
192-
.map(Either::Left)
193-
}
194-
_ => Some(candidate),
195-
})
196-
.filter_map(|candidate| match candidate {
197-
Either::Left(module_def) => self.module_with_name_to_import.find_use_path_prefixed(
198-
db,
199-
module_def,
200-
ctx.config.insert_use.prefix_kind,
201-
),
202-
Either::Right(macro_def) => self.module_with_name_to_import.find_use_path_prefixed(
203-
db,
204-
macro_def,
205-
ctx.config.insert_use.prefix_kind,
206-
),
207-
})
208-
.filter(|use_path| !use_path.segments.is_empty())
209-
.take(20)
210-
.collect::<BTreeSet<_>>()
211-
}
212-
213-
fn assoc_to_trait(assoc: AssocItemContainer) -> Option<Trait> {
214-
if let AssocItemContainer::Trait(extracted_trait) = assoc {
215-
Some(extracted_trait)
216-
} else {
217-
None
218-
}
219-
}
220-
}
221-
222-
#[derive(Debug)]
223-
enum ImportCandidate {
224-
/// Simple name like 'HashMap'
225-
UnqualifiedName(String),
226-
/// First part of the qualified name.
227-
/// For 'std::collections::HashMap', that will be 'std'.
228-
QualifierStart(String),
229-
/// A trait associated function (with no self parameter) or associated constant.
230-
/// For 'test_mod::TestEnum::test_function', `Type` is the `test_mod::TestEnum` expression type
231-
/// and `String` is the `test_function`
232-
TraitAssocItem(Type, String),
233-
/// A trait method with self parameter.
234-
/// For 'test_enum.test_method()', `Type` is the `test_enum` expression type
235-
/// and `String` is the `test_method`
236-
TraitMethod(Type, String),
237-
}
238-
239-
impl ImportCandidate {
240-
fn for_method_call(
241-
sema: &Semantics<RootDatabase>,
242-
method_call: &ast::MethodCallExpr,
243-
) -> Option<Self> {
244-
if sema.resolve_method_call(method_call).is_some() {
245-
return None;
246-
}
247-
Some(Self::TraitMethod(
248-
sema.type_of_expr(&method_call.receiver()?)?,
249-
method_call.name_ref()?.syntax().to_string(),
250-
))
251-
}
252-
253-
fn for_regular_path(
254-
sema: &Semantics<RootDatabase>,
255-
path_under_caret: &ast::Path,
256-
) -> Option<Self> {
257-
if sema.resolve_path(path_under_caret).is_some() {
258-
return None;
64+
fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
65+
let name = match import_candidate {
66+
ImportCandidate::UnqualifiedName(candidate)
67+
| ImportCandidate::QualifierStart(candidate) => format!("Import {}", &candidate.name),
68+
ImportCandidate::TraitAssocItem(candidate) => {
69+
format!("Import a trait for item {}", &candidate.name)
25970
}
260-
261-
let segment = path_under_caret.segment()?;
262-
if let Some(qualifier) = path_under_caret.qualifier() {
263-
let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
264-
let qualifier_start_path =
265-
qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
266-
if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) {
267-
let qualifier_resolution = if qualifier_start_path == qualifier {
268-
qualifier_start_resolution
269-
} else {
270-
sema.resolve_path(&qualifier)?
271-
};
272-
if let PathResolution::Def(ModuleDef::Adt(assoc_item_path)) = qualifier_resolution {
273-
Some(ImportCandidate::TraitAssocItem(
274-
assoc_item_path.ty(sema.db),
275-
segment.syntax().to_string(),
276-
))
277-
} else {
278-
None
279-
}
280-
} else {
281-
Some(ImportCandidate::QualifierStart(qualifier_start.syntax().to_string()))
282-
}
283-
} else {
284-
Some(ImportCandidate::UnqualifiedName(
285-
segment.syntax().descendants().find_map(ast::NameRef::cast)?.syntax().to_string(),
286-
))
71+
ImportCandidate::TraitMethod(candidate) => {
72+
format!("Import a trait for method {}", &candidate.name)
28773
}
288-
}
74+
};
75+
GroupLabel(name)
28976
}
29077

29178
#[cfg(test)]

crates/assists/src/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Assorted functions shared by several assists.
22
pub(crate) mod insert_use;
3+
pub(crate) mod import_assets;
34

45
use std::{iter, ops};
56

0 commit comments

Comments
 (0)