aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_db/src/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_db/src/helpers')
-rw-r--r--crates/ide_db/src/helpers/import_assets.rs578
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs24
-rw-r--r--crates/ide_db/src/helpers/insert_use/tests.rs44
3 files changed, 465 insertions, 181 deletions
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
index 517abbb4b..e03ccd351 100644
--- a/crates/ide_db/src/helpers/import_assets.rs
+++ b/crates/ide_db/src/helpers/import_assets.rs
@@ -1,19 +1,29 @@
1//! Look up accessible paths for items. 1//! Look up accessible paths for items.
2use either::Either; 2use hir::{
3use hir::{AsAssocItem, AssocItem, Crate, MacroDef, Module, ModuleDef, PrefixKind, Semantics}; 3 AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, MacroDef, ModPath, Module,
4 ModuleDef, PathResolution, PrefixKind, ScopeDef, Semantics, Type,
5};
6use itertools::Itertools;
4use rustc_hash::FxHashSet; 7use rustc_hash::FxHashSet;
5use syntax::{ast, AstNode}; 8use syntax::{ast, utils::path_to_string_stripping_turbo_fish, AstNode, SyntaxNode};
6 9
7use crate::{ 10use crate::{
8 imports_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT}, 11 items_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT},
9 RootDatabase, 12 RootDatabase,
10}; 13};
11 14
15use super::item_name;
16
17/// A candidate for import, derived during various IDE activities:
18/// * completion with imports on the fly proposals
19/// * completion edit resolve requests
20/// * assists
21/// * etc.
12#[derive(Debug)] 22#[derive(Debug)]
13pub enum ImportCandidate { 23pub enum ImportCandidate {
14 // A path, qualified (`std::collections::HashMap`) or not (`HashMap`). 24 /// A path, qualified (`std::collections::HashMap`) or not (`HashMap`).
15 Path(PathImportCandidate), 25 Path(PathImportCandidate),
16 /// A trait associated function (with no self parameter) or associated constant. 26 /// A trait associated function (with no self parameter) or an associated constant.
17 /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type 27 /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type
18 /// and `name` is the `test_function` 28 /// and `name` is the `test_function`
19 TraitAssocItem(TraitImportCandidate), 29 TraitAssocItem(TraitImportCandidate),
@@ -23,21 +33,40 @@ pub enum ImportCandidate {
23 TraitMethod(TraitImportCandidate), 33 TraitMethod(TraitImportCandidate),
24} 34}
25 35
36/// A trait import needed for a given associated item access.
37/// For `some::path::SomeStruct::ASSOC_`, contains the
38/// type of `some::path::SomeStruct` and `ASSOC_` as the item name.
26#[derive(Debug)] 39#[derive(Debug)]
27pub struct TraitImportCandidate { 40pub struct TraitImportCandidate {
28 pub receiver_ty: hir::Type, 41 /// A type of the item that has the associated item accessed at.
29 pub name: NameToImport, 42 pub receiver_ty: Type,
43 /// The associated item name that the trait to import should contain.
44 pub assoc_item_name: NameToImport,
30} 45}
31 46
47/// Path import for a given name, qualified or not.
32#[derive(Debug)] 48#[derive(Debug)]
33pub struct PathImportCandidate { 49pub struct PathImportCandidate {
34 pub qualifier: Option<ast::Path>, 50 /// Optional qualifier before name.
51 pub qualifier: Option<FirstSegmentUnresolved>,
52 /// The name the item (struct, trait, enum, etc.) should have.
35 pub name: NameToImport, 53 pub name: NameToImport,
36} 54}
37 55
56/// A qualifier that has a first segment and it's unresolved.
57#[derive(Debug)]
58pub struct FirstSegmentUnresolved {
59 fist_segment: ast::NameRef,
60 full_qualifier: ast::Path,
61}
62
63/// A name that will be used during item lookups.
38#[derive(Debug)] 64#[derive(Debug)]
39pub enum NameToImport { 65pub enum NameToImport {
66 /// Requires items with names that exactly match the given string, case-sensitive.
40 Exact(String), 67 Exact(String),
68 /// Requires items with names that case-insensitively contain all letters from the string,
69 /// in the same order, but not necessary adjacent.
41 Fuzzy(String), 70 Fuzzy(String),
42} 71}
43 72
@@ -50,10 +79,12 @@ impl NameToImport {
50 } 79 }
51} 80}
52 81
82/// A struct to find imports in the project, given a certain name (or its part) and the context.
53#[derive(Debug)] 83#[derive(Debug)]
54pub struct ImportAssets { 84pub struct ImportAssets {
55 import_candidate: ImportCandidate, 85 import_candidate: ImportCandidate,
56 module_with_candidate: hir::Module, 86 candidate_node: SyntaxNode,
87 module_with_candidate: Module,
57} 88}
58 89
59impl ImportAssets { 90impl ImportAssets {
@@ -61,9 +92,11 @@ impl ImportAssets {
61 method_call: &ast::MethodCallExpr, 92 method_call: &ast::MethodCallExpr,
62 sema: &Semantics<RootDatabase>, 93 sema: &Semantics<RootDatabase>,
63 ) -> Option<Self> { 94 ) -> Option<Self> {
95 let candidate_node = method_call.syntax().clone();
64 Some(Self { 96 Some(Self {
65 import_candidate: ImportCandidate::for_method_call(sema, method_call)?, 97 import_candidate: ImportCandidate::for_method_call(sema, method_call)?,
66 module_with_candidate: sema.scope(method_call.syntax()).module()?, 98 module_with_candidate: sema.scope(&candidate_node).module()?,
99 candidate_node,
67 }) 100 })
68 } 101 }
69 102
@@ -71,94 +104,94 @@ impl ImportAssets {
71 fully_qualified_path: &ast::Path, 104 fully_qualified_path: &ast::Path,
72 sema: &Semantics<RootDatabase>, 105 sema: &Semantics<RootDatabase>,
73 ) -> Option<Self> { 106 ) -> Option<Self> {
74 let syntax_under_caret = fully_qualified_path.syntax(); 107 let candidate_node = fully_qualified_path.syntax().clone();
75 if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() { 108 if candidate_node.ancestors().find_map(ast::Use::cast).is_some() {
76 return None; 109 return None;
77 } 110 }
78 Some(Self { 111 Some(Self {
79 import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?, 112 import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?,
80 module_with_candidate: sema.scope(syntax_under_caret).module()?, 113 module_with_candidate: sema.scope(&candidate_node).module()?,
114 candidate_node,
81 }) 115 })
82 } 116 }
83 117
84 pub fn for_fuzzy_path( 118 pub fn for_fuzzy_path(
85 module_with_path: Module, 119 module_with_candidate: Module,
86 qualifier: Option<ast::Path>, 120 qualifier: Option<ast::Path>,
87 fuzzy_name: String, 121 fuzzy_name: String,
88 sema: &Semantics<RootDatabase>, 122 sema: &Semantics<RootDatabase>,
123 candidate_node: SyntaxNode,
89 ) -> Option<Self> { 124 ) -> Option<Self> {
90 Some(match qualifier { 125 Some(Self {
91 Some(qualifier) => { 126 import_candidate: ImportCandidate::for_fuzzy_path(qualifier, fuzzy_name, sema)?,
92 let qualifier_resolution = sema.resolve_path(&qualifier)?; 127 module_with_candidate,
93 match qualifier_resolution { 128 candidate_node,
94 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => Self {
95 import_candidate: ImportCandidate::TraitAssocItem(TraitImportCandidate {
96 receiver_ty: assoc_item_path.ty(sema.db),
97 name: NameToImport::Fuzzy(fuzzy_name),
98 }),
99 module_with_candidate: module_with_path,
100 },
101 _ => Self {
102 import_candidate: ImportCandidate::Path(PathImportCandidate {
103 qualifier: Some(qualifier),
104 name: NameToImport::Fuzzy(fuzzy_name),
105 }),
106 module_with_candidate: module_with_path,
107 },
108 }
109 }
110 None => Self {
111 import_candidate: ImportCandidate::Path(PathImportCandidate {
112 qualifier: None,
113 name: NameToImport::Fuzzy(fuzzy_name),
114 }),
115 module_with_candidate: module_with_path,
116 },
117 }) 129 })
118 } 130 }
119 131
120 pub fn for_fuzzy_method_call( 132 pub fn for_fuzzy_method_call(
121 module_with_method_call: Module, 133 module_with_method_call: Module,
122 receiver_ty: hir::Type, 134 receiver_ty: Type,
123 fuzzy_method_name: String, 135 fuzzy_method_name: String,
136 candidate_node: SyntaxNode,
124 ) -> Option<Self> { 137 ) -> Option<Self> {
125 Some(Self { 138 Some(Self {
126 import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate { 139 import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
127 receiver_ty, 140 receiver_ty,
128 name: NameToImport::Fuzzy(fuzzy_method_name), 141 assoc_item_name: NameToImport::Fuzzy(fuzzy_method_name),
129 }), 142 }),
130 module_with_candidate: module_with_method_call, 143 module_with_candidate: module_with_method_call,
144 candidate_node,
131 }) 145 })
132 } 146 }
133} 147}
134 148
149/// An import (not necessary the only one) that corresponds a certain given [`PathImportCandidate`].
150/// (the structure is not entirely correct, since there can be situations requiring two imports, see FIXME below for the details)
151#[derive(Debug, Clone, PartialEq, Eq, Hash)]
152pub struct LocatedImport {
153 /// The path to use in the `use` statement for a given candidate to be imported.
154 pub import_path: ModPath,
155 /// An item that will be imported with the import path given.
156 pub item_to_import: ItemInNs,
157 /// The path import candidate, resolved.
158 ///
159 /// Not necessary matches the import:
160 /// For any associated constant from the trait, we try to access as `some::path::SomeStruct::ASSOC_`
161 /// the original item is the associated constant, but the import has to be a trait that
162 /// defines this constant.
163 pub original_item: ItemInNs,
164 /// A path of the original item.
165 pub original_path: Option<ModPath>,
166}
167
168impl LocatedImport {
169 pub fn new(
170 import_path: ModPath,
171 item_to_import: ItemInNs,
172 original_item: ItemInNs,
173 original_path: Option<ModPath>,
174 ) -> Self {
175 Self { import_path, item_to_import, original_item, original_path }
176 }
177}
178
135impl ImportAssets { 179impl ImportAssets {
136 pub fn import_candidate(&self) -> &ImportCandidate { 180 pub fn import_candidate(&self) -> &ImportCandidate {
137 &self.import_candidate 181 &self.import_candidate
138 } 182 }
139 183
140 fn name_to_import(&self) -> &NameToImport {
141 match &self.import_candidate {
142 ImportCandidate::Path(candidate) => &candidate.name,
143 ImportCandidate::TraitAssocItem(candidate)
144 | ImportCandidate::TraitMethod(candidate) => &candidate.name,
145 }
146 }
147
148 pub fn search_for_imports( 184 pub fn search_for_imports(
149 &self, 185 &self,
150 sema: &Semantics<RootDatabase>, 186 sema: &Semantics<RootDatabase>,
151 prefix_kind: PrefixKind, 187 prefix_kind: PrefixKind,
152 ) -> Vec<(hir::ModPath, hir::ItemInNs)> { 188 ) -> Vec<LocatedImport> {
153 let _p = profile::span("import_assets::search_for_imports"); 189 let _p = profile::span("import_assets::search_for_imports");
154 self.search_for(sema, Some(prefix_kind)) 190 self.search_for(sema, Some(prefix_kind))
155 } 191 }
156 192
157 /// This may return non-absolute paths if a part of the returned path is already imported into scope. 193 /// This may return non-absolute paths if a part of the returned path is already imported into scope.
158 pub fn search_for_relative_paths( 194 pub fn search_for_relative_paths(&self, sema: &Semantics<RootDatabase>) -> Vec<LocatedImport> {
159 &self,
160 sema: &Semantics<RootDatabase>,
161 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
162 let _p = profile::span("import_assets::search_for_relative_paths"); 195 let _p = profile::span("import_assets::search_for_relative_paths");
163 self.search_for(sema, None) 196 self.search_for(sema, None)
164 } 197 }
@@ -166,29 +199,29 @@ impl ImportAssets {
166 fn search_for( 199 fn search_for(
167 &self, 200 &self,
168 sema: &Semantics<RootDatabase>, 201 sema: &Semantics<RootDatabase>,
169 prefixed: Option<hir::PrefixKind>, 202 prefixed: Option<PrefixKind>,
170 ) -> Vec<(hir::ModPath, hir::ItemInNs)> { 203 ) -> Vec<LocatedImport> {
171 let current_crate = self.module_with_candidate.krate(); 204 let items_with_candidate_name = match self.name_to_import() {
172 205 NameToImport::Exact(exact_name) => items_locator::with_exact_name(
173 let unfiltered_imports = match self.name_to_import() { 206 sema,
174 NameToImport::Exact(exact_name) => { 207 self.module_with_candidate.krate(),
175 imports_locator::find_exact_imports(sema, current_crate, exact_name.clone()) 208 exact_name.clone(),
176 } 209 ),
177 // FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items: 210 // FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items:
178 // instead, we need to look up all trait impls for a certain struct and search through them only 211 // instead, we need to look up all trait impls for a certain struct and search through them only
179 // see https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761585032 212 // see https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761585032
180 // and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup 213 // and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup
181 // for the details 214 // for the details
182 NameToImport::Fuzzy(fuzzy_name) => { 215 NameToImport::Fuzzy(fuzzy_name) => {
183 let (assoc_item_search, limit) = match self.import_candidate { 216 let (assoc_item_search, limit) = if self.import_candidate.is_trait_candidate() {
184 ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => { 217 (AssocItemSearch::AssocItemsOnly, None)
185 (AssocItemSearch::AssocItemsOnly, None) 218 } else {
186 } 219 (AssocItemSearch::Include, Some(DEFAULT_QUERY_SEARCH_LIMIT))
187 _ => (AssocItemSearch::Exclude, Some(DEFAULT_QUERY_SEARCH_LIMIT)),
188 }; 220 };
189 imports_locator::find_similar_imports( 221
222 items_locator::with_similar_name(
190 sema, 223 sema,
191 current_crate, 224 self.module_with_candidate.krate(),
192 fuzzy_name.clone(), 225 fuzzy_name.clone(),
193 assoc_item_search, 226 assoc_item_search,
194 limit, 227 limit,
@@ -196,63 +229,224 @@ impl ImportAssets {
196 } 229 }
197 }; 230 };
198 231
199 let db = sema.db; 232 let scope_definitions = self.scope_definitions(sema);
200 let mut res = 233 self.applicable_defs(sema.db, prefixed, items_with_candidate_name)
201 applicable_defs(self.import_candidate(), current_crate, db, unfiltered_imports) 234 .into_iter()
202 .filter_map(|candidate| { 235 .filter(|import| import.import_path.len() > 1)
203 let item: hir::ItemInNs = candidate.clone().either(Into::into, Into::into); 236 .filter(|import| !scope_definitions.contains(&ScopeDef::from(import.item_to_import)))
204 237 .sorted_by_key(|import| import.import_path.clone())
205 let item_to_search = match self.import_candidate { 238 .collect()
206 ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => { 239 }
207 let canidate_trait = match candidate { 240
208 Either::Left(module_def) => { 241 fn scope_definitions(&self, sema: &Semantics<RootDatabase>) -> FxHashSet<ScopeDef> {
209 module_def.as_assoc_item(db)?.containing_trait(db) 242 let mut scope_definitions = FxHashSet::default();
210 } 243 sema.scope(&self.candidate_node).process_all_names(&mut |_, scope_def| {
211 _ => None, 244 scope_definitions.insert(scope_def);
212 }?; 245 });
213 ModuleDef::from(canidate_trait).into() 246 scope_definitions
214 } 247 }
215 _ => item, 248
216 }; 249 fn name_to_import(&self) -> &NameToImport {
217 250 match &self.import_candidate {
218 if let Some(prefix_kind) = prefixed { 251 ImportCandidate::Path(candidate) => &candidate.name,
219 self.module_with_candidate.find_use_path_prefixed( 252 ImportCandidate::TraitAssocItem(candidate)
220 db, 253 | ImportCandidate::TraitMethod(candidate) => &candidate.assoc_item_name,
221 item_to_search, 254 }
222 prefix_kind, 255 }
223 ) 256
224 } else { 257 fn applicable_defs(
225 self.module_with_candidate.find_use_path(db, item_to_search) 258 &self,
226 } 259 db: &RootDatabase,
227 .map(|path| (path, item)) 260 prefixed: Option<PrefixKind>,
228 }) 261 items_with_candidate_name: FxHashSet<ItemInNs>,
229 .filter(|(use_path, _)| use_path.len() > 1) 262 ) -> FxHashSet<LocatedImport> {
230 .collect::<Vec<_>>(); 263 let _p = profile::span("import_assets::applicable_defs");
231 res.sort_by_cached_key(|(path, _)| path.clone()); 264 let current_crate = self.module_with_candidate.krate();
232 res 265
266 let mod_path = |item| {
267 get_mod_path(db, item_for_path_search(db, item)?, &self.module_with_candidate, prefixed)
268 };
269
270 match &self.import_candidate {
271 ImportCandidate::Path(path_candidate) => {
272 path_applicable_imports(db, path_candidate, mod_path, items_with_candidate_name)
273 }
274 ImportCandidate::TraitAssocItem(trait_candidate) => trait_applicable_items(
275 db,
276 current_crate,
277 trait_candidate,
278 true,
279 mod_path,
280 items_with_candidate_name,
281 ),
282 ImportCandidate::TraitMethod(trait_candidate) => trait_applicable_items(
283 db,
284 current_crate,
285 trait_candidate,
286 false,
287 mod_path,
288 items_with_candidate_name,
289 ),
290 }
233 } 291 }
234} 292}
235 293
236fn applicable_defs<'a>( 294fn path_applicable_imports(
237 import_candidate: &ImportCandidate,
238 current_crate: Crate,
239 db: &RootDatabase, 295 db: &RootDatabase,
240 unfiltered_imports: Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a>, 296 path_candidate: &PathImportCandidate,
241) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> { 297 mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy,
242 let receiver_ty = match import_candidate { 298 items_with_candidate_name: FxHashSet<ItemInNs>,
243 ImportCandidate::Path(_) => return unfiltered_imports, 299) -> FxHashSet<LocatedImport> {
244 ImportCandidate::TraitAssocItem(candidate) | ImportCandidate::TraitMethod(candidate) => { 300 let _p = profile::span("import_assets::path_applicable_imports");
245 &candidate.receiver_ty 301
302 let (unresolved_first_segment, unresolved_qualifier) = match &path_candidate.qualifier {
303 None => {
304 return items_with_candidate_name
305 .into_iter()
306 .filter_map(|item| {
307 Some(LocatedImport::new(mod_path(item)?, item, item, mod_path(item)))
308 })
309 .collect();
246 } 310 }
311 Some(first_segment_unresolved) => (
312 first_segment_unresolved.fist_segment.to_string(),
313 path_to_string_stripping_turbo_fish(&first_segment_unresolved.full_qualifier),
314 ),
247 }; 315 };
248 316
317 items_with_candidate_name
318 .into_iter()
319 .filter_map(|item| {
320 import_for_item(db, mod_path, &unresolved_first_segment, &unresolved_qualifier, item)
321 })
322 .collect()
323}
324
325fn import_for_item(
326 db: &RootDatabase,
327 mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
328 unresolved_first_segment: &str,
329 unresolved_qualifier: &str,
330 original_item: ItemInNs,
331) -> Option<LocatedImport> {
332 let _p = profile::span("import_assets::import_for_item");
333
334 let original_item_candidate = item_for_path_search(db, original_item)?;
335 let import_path_candidate = mod_path(original_item_candidate)?;
336 let import_path_string = import_path_candidate.to_string();
337
338 let expected_import_end = if item_as_assoc(db, original_item).is_some() {
339 unresolved_qualifier.to_string()
340 } else {
341 format!("{}::{}", unresolved_qualifier, item_name(db, original_item)?)
342 };
343 if !import_path_string.contains(unresolved_first_segment)
344 || !import_path_string.ends_with(&expected_import_end)
345 {
346 return None;
347 }
348
349 let segment_import =
350 find_import_for_segment(db, original_item_candidate, &unresolved_first_segment)?;
351 let trait_item_to_import = item_as_assoc(db, original_item)
352 .and_then(|assoc| assoc.containing_trait(db))
353 .map(|trait_| ItemInNs::from(ModuleDef::from(trait_)));
354 Some(match (segment_import == original_item_candidate, trait_item_to_import) {
355 (true, Some(_)) => {
356 // FIXME we should be able to import both the trait and the segment,
357 // but it's unclear what to do with overlapping edits (merge imports?)
358 // especially in case of lazy completion edit resolutions.
359 return None;
360 }
361 (false, Some(trait_to_import)) => LocatedImport::new(
362 mod_path(trait_to_import)?,
363 trait_to_import,
364 original_item,
365 mod_path(original_item),
366 ),
367 (true, None) => LocatedImport::new(
368 import_path_candidate,
369 original_item_candidate,
370 original_item,
371 mod_path(original_item),
372 ),
373 (false, None) => LocatedImport::new(
374 mod_path(segment_import)?,
375 segment_import,
376 original_item,
377 mod_path(original_item),
378 ),
379 })
380}
381
382fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
383 Some(match item {
384 ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
385 Some(assoc_item) => match assoc_item.container(db) {
386 AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
387 AssocItemContainer::Impl(impl_) => {
388 ItemInNs::from(ModuleDef::from(impl_.target_ty(db).as_adt()?))
389 }
390 },
391 None => item,
392 },
393 ItemInNs::Macros(_) => item,
394 })
395}
396
397fn find_import_for_segment(
398 db: &RootDatabase,
399 original_item: ItemInNs,
400 unresolved_first_segment: &str,
401) -> Option<ItemInNs> {
402 let segment_is_name = item_name(db, original_item)
403 .map(|name| name.to_string() == unresolved_first_segment)
404 .unwrap_or(false);
405
406 Some(if segment_is_name {
407 original_item
408 } else {
409 let matching_module =
410 module_with_segment_name(db, &unresolved_first_segment, original_item)?;
411 ItemInNs::from(ModuleDef::from(matching_module))
412 })
413}
414
415fn module_with_segment_name(
416 db: &RootDatabase,
417 segment_name: &str,
418 candidate: ItemInNs,
419) -> Option<Module> {
420 let mut current_module = match candidate {
421 ItemInNs::Types(module_def_id) => ModuleDef::from(module_def_id).module(db),
422 ItemInNs::Values(module_def_id) => ModuleDef::from(module_def_id).module(db),
423 ItemInNs::Macros(macro_def_id) => MacroDef::from(macro_def_id).module(db),
424 };
425 while let Some(module) = current_module {
426 if let Some(module_name) = module.name(db) {
427 if module_name.to_string() == segment_name {
428 return Some(module);
429 }
430 }
431 current_module = module.parent(db);
432 }
433 None
434}
435
436fn trait_applicable_items(
437 db: &RootDatabase,
438 current_crate: Crate,
439 trait_candidate: &TraitImportCandidate,
440 trait_assoc_item: bool,
441 mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
442 items_with_candidate_name: FxHashSet<ItemInNs>,
443) -> FxHashSet<LocatedImport> {
444 let _p = profile::span("import_assets::trait_applicable_items");
249 let mut required_assoc_items = FxHashSet::default(); 445 let mut required_assoc_items = FxHashSet::default();
250 446
251 let trait_candidates = unfiltered_imports 447 let trait_candidates = items_with_candidate_name
252 .filter_map(|input| match input { 448 .into_iter()
253 Either::Left(module_def) => module_def.as_assoc_item(db), 449 .filter_map(|input| item_as_assoc(db, input))
254 _ => None,
255 })
256 .filter_map(|assoc| { 450 .filter_map(|assoc| {
257 let assoc_item_trait = assoc.containing_trait(db)?; 451 let assoc_item_trait = assoc.containing_trait(db)?;
258 required_assoc_items.insert(assoc); 452 required_assoc_items.insert(assoc);
@@ -260,11 +454,10 @@ fn applicable_defs<'a>(
260 }) 454 })
261 .collect(); 455 .collect();
262 456
263 let mut applicable_defs = FxHashSet::default(); 457 let mut located_imports = FxHashSet::default();
264 458
265 match import_candidate { 459 if trait_assoc_item {
266 ImportCandidate::Path(_) => unreachable!(), 460 trait_candidate.receiver_ty.iterate_path_candidates(
267 ImportCandidate::TraitAssocItem(_) => receiver_ty.iterate_path_candidates(
268 db, 461 db,
269 current_crate, 462 current_crate,
270 &trait_candidates, 463 &trait_candidates,
@@ -276,12 +469,21 @@ fn applicable_defs<'a>(
276 return None; 469 return None;
277 } 470 }
278 } 471 }
279 applicable_defs.insert(Either::Left(assoc_to_module_def(assoc))); 472
473 let item = ItemInNs::from(ModuleDef::from(assoc.containing_trait(db)?));
474 let original_item = assoc_to_item(assoc);
475 located_imports.insert(LocatedImport::new(
476 mod_path(item)?,
477 item,
478 original_item,
479 mod_path(original_item),
480 ));
280 } 481 }
281 None::<()> 482 None::<()>
282 }, 483 },
283 ), 484 )
284 ImportCandidate::TraitMethod(_) => receiver_ty.iterate_method_candidates( 485 } else {
486 trait_candidate.receiver_ty.iterate_method_candidates(
285 db, 487 db,
286 current_crate, 488 current_crate,
287 &trait_candidates, 489 &trait_candidates,
@@ -289,21 +491,41 @@ fn applicable_defs<'a>(
289 |_, function| { 491 |_, function| {
290 let assoc = function.as_assoc_item(db)?; 492 let assoc = function.as_assoc_item(db)?;
291 if required_assoc_items.contains(&assoc) { 493 if required_assoc_items.contains(&assoc) {
292 applicable_defs.insert(Either::Left(assoc_to_module_def(assoc))); 494 let item = ItemInNs::from(ModuleDef::from(assoc.containing_trait(db)?));
495 let original_item = assoc_to_item(assoc);
496 located_imports.insert(LocatedImport::new(
497 mod_path(item)?,
498 item,
499 original_item,
500 mod_path(original_item),
501 ));
293 } 502 }
294 None::<()> 503 None::<()>
295 }, 504 },
296 ), 505 )
297 }; 506 };
298 507
299 Box::new(applicable_defs.into_iter()) 508 located_imports
300} 509}
301 510
302fn assoc_to_module_def(assoc: AssocItem) -> ModuleDef { 511fn assoc_to_item(assoc: AssocItem) -> ItemInNs {
303 match assoc { 512 match assoc {
304 AssocItem::Function(f) => f.into(), 513 AssocItem::Function(f) => ItemInNs::from(ModuleDef::from(f)),
305 AssocItem::Const(c) => c.into(), 514 AssocItem::Const(c) => ItemInNs::from(ModuleDef::from(c)),
306 AssocItem::TypeAlias(t) => t.into(), 515 AssocItem::TypeAlias(t) => ItemInNs::from(ModuleDef::from(t)),
516 }
517}
518
519fn get_mod_path(
520 db: &RootDatabase,
521 item_to_search: ItemInNs,
522 module_with_candidate: &Module,
523 prefixed: Option<PrefixKind>,
524) -> Option<ModPath> {
525 if let Some(prefix_kind) = prefixed {
526 module_with_candidate.find_use_path_prefixed(db, item_to_search, prefix_kind)
527 } else {
528 module_with_candidate.find_use_path(db, item_to_search)
307 } 529 }
308} 530}
309 531
@@ -316,7 +538,7 @@ impl ImportCandidate {
316 Some(_) => None, 538 Some(_) => None,
317 None => Some(Self::TraitMethod(TraitImportCandidate { 539 None => Some(Self::TraitMethod(TraitImportCandidate {
318 receiver_ty: sema.type_of_expr(&method_call.receiver()?)?, 540 receiver_ty: sema.type_of_expr(&method_call.receiver()?)?,
319 name: NameToImport::Exact(method_call.name_ref()?.to_string()), 541 assoc_item_name: NameToImport::Exact(method_call.name_ref()?.to_string()),
320 })), 542 })),
321 } 543 }
322 } 544 }
@@ -325,41 +547,63 @@ impl ImportCandidate {
325 if sema.resolve_path(path).is_some() { 547 if sema.resolve_path(path).is_some() {
326 return None; 548 return None;
327 } 549 }
550 path_import_candidate(
551 sema,
552 path.qualifier(),
553 NameToImport::Exact(path.segment()?.name_ref()?.to_string()),
554 )
555 }
328 556
329 let segment = path.segment()?; 557 fn for_fuzzy_path(
330 let candidate = if let Some(qualifier) = path.qualifier() { 558 qualifier: Option<ast::Path>,
331 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?; 559 fuzzy_name: String,
332 let qualifier_start_path = 560 sema: &Semantics<RootDatabase>,
333 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; 561 ) -> Option<Self> {
334 if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) { 562 path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name))
335 let qualifier_resolution = if qualifier_start_path == qualifier { 563 }
336 qualifier_start_resolution 564
565 fn is_trait_candidate(&self) -> bool {
566 matches!(self, ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_))
567 }
568}
569
570fn path_import_candidate(
571 sema: &Semantics<RootDatabase>,
572 qualifier: Option<ast::Path>,
573 name: NameToImport,
574) -> Option<ImportCandidate> {
575 Some(match qualifier {
576 Some(qualifier) => match sema.resolve_path(&qualifier) {
577 None => {
578 let qualifier_start =
579 qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
580 let qualifier_start_path =
581 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
582 if sema.resolve_path(&qualifier_start_path).is_none() {
583 ImportCandidate::Path(PathImportCandidate {
584 qualifier: Some(FirstSegmentUnresolved {
585 fist_segment: qualifier_start,
586 full_qualifier: qualifier,
587 }),
588 name,
589 })
337 } else { 590 } else {
338 sema.resolve_path(&qualifier)? 591 return None;
339 };
340 match qualifier_resolution {
341 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
342 ImportCandidate::TraitAssocItem(TraitImportCandidate {
343 receiver_ty: assoc_item_path.ty(sema.db),
344 name: NameToImport::Exact(segment.name_ref()?.to_string()),
345 })
346 }
347 _ => return None,
348 } 592 }
349 } else { 593 }
350 ImportCandidate::Path(PathImportCandidate { 594 Some(PathResolution::Def(ModuleDef::Adt(assoc_item_path))) => {
351 qualifier: Some(qualifier), 595 ImportCandidate::TraitAssocItem(TraitImportCandidate {
352 name: NameToImport::Exact(qualifier_start.to_string()), 596 receiver_ty: assoc_item_path.ty(sema.db),
597 assoc_item_name: name,
353 }) 598 })
354 } 599 }
355 } else { 600 Some(_) => return None,
356 ImportCandidate::Path(PathImportCandidate { 601 },
357 qualifier: None, 602 None => ImportCandidate::Path(PathImportCandidate { qualifier: None, name }),
358 name: NameToImport::Exact( 603 })
359 segment.syntax().descendants().find_map(ast::NameRef::cast)?.to_string(), 604}
360 ), 605
361 }) 606fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option<AssocItem> {
362 }; 607 item.as_module_def_id()
363 Some(candidate) 608 .and_then(|module_def_id| ModuleDef::from(module_def_id).as_assoc_item(db))
364 }
365} 609}
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs
index fd4035198..df66d8ea0 100644
--- a/crates/ide_db/src/helpers/insert_use.rs
+++ b/crates/ide_db/src/helpers/insert_use.rs
@@ -13,12 +13,12 @@ use syntax::{
13 }, 13 },
14 AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken, 14 AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
15}; 15};
16use test_utils::mark;
17 16
18#[derive(Clone, Copy, Debug, PartialEq, Eq)] 17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct InsertUseConfig { 18pub struct InsertUseConfig {
20 pub merge: Option<MergeBehavior>, 19 pub merge: Option<MergeBehavior>,
21 pub prefix_kind: hir::PrefixKind, 20 pub prefix_kind: hir::PrefixKind,
21 pub group: bool,
22} 22}
23 23
24#[derive(Debug, Clone)] 24#[derive(Debug, Clone)]
@@ -99,13 +99,13 @@ fn is_inner_comment(token: SyntaxToken) -> bool {
99pub fn insert_use<'a>( 99pub fn insert_use<'a>(
100 scope: &ImportScope, 100 scope: &ImportScope,
101 path: ast::Path, 101 path: ast::Path,
102 merge: Option<MergeBehavior>, 102 cfg: InsertUseConfig,
103) -> SyntaxRewriter<'a> { 103) -> SyntaxRewriter<'a> {
104 let _p = profile::span("insert_use"); 104 let _p = profile::span("insert_use");
105 let mut rewriter = SyntaxRewriter::default(); 105 let mut rewriter = SyntaxRewriter::default();
106 let use_item = make::use_(None, make::use_tree(path.clone(), None, None, false)); 106 let use_item = make::use_(None, make::use_tree(path.clone(), None, None, false));
107 // merge into existing imports if possible 107 // merge into existing imports if possible
108 if let Some(mb) = merge { 108 if let Some(mb) = cfg.merge {
109 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { 109 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
110 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { 110 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
111 rewriter.replace(existing_use.syntax(), merged.syntax()); 111 rewriter.replace(existing_use.syntax(), merged.syntax());
@@ -116,7 +116,7 @@ pub fn insert_use<'a>(
116 116
117 // either we weren't allowed to merge or there is no import that fits the merge conditions 117 // either we weren't allowed to merge or there is no import that fits the merge conditions
118 // so look for the place we have to insert to 118 // so look for the place we have to insert to
119 let (insert_position, add_blank) = find_insert_position(scope, path); 119 let (insert_position, add_blank) = find_insert_position(scope, path, cfg.group);
120 120
121 let indent = if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize { 121 let indent = if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize {
122 Some(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into()) 122 Some(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into())
@@ -137,7 +137,7 @@ pub fn insert_use<'a>(
137 137
138 if add_blank.has_before() { 138 if add_blank.has_before() {
139 if let Some(indent) = indent.clone() { 139 if let Some(indent) = indent.clone() {
140 mark::hit!(insert_use_indent_before); 140 cov_mark::hit!(insert_use_indent_before);
141 buf.push(indent); 141 buf.push(indent);
142 } 142 }
143 } 143 }
@@ -155,11 +155,11 @@ pub fn insert_use<'a>(
155 // only add indentation *after* our stuff if there's another node directly after it 155 // only add indentation *after* our stuff if there's another node directly after it
156 if add_blank.has_after() && matches!(insert_position, InsertPosition::Before(_)) { 156 if add_blank.has_after() && matches!(insert_position, InsertPosition::Before(_)) {
157 if let Some(indent) = indent { 157 if let Some(indent) = indent {
158 mark::hit!(insert_use_indent_after); 158 cov_mark::hit!(insert_use_indent_after);
159 buf.push(indent); 159 buf.push(indent);
160 } 160 }
161 } else if add_blank.has_after() && matches!(insert_position, InsertPosition::After(_)) { 161 } else if add_blank.has_after() && matches!(insert_position, InsertPosition::After(_)) {
162 mark::hit!(insert_use_no_indent_after); 162 cov_mark::hit!(insert_use_no_indent_after);
163 } 163 }
164 164
165 buf 165 buf
@@ -538,6 +538,7 @@ impl AddBlankLine {
538fn find_insert_position( 538fn find_insert_position(
539 scope: &ImportScope, 539 scope: &ImportScope,
540 insert_path: ast::Path, 540 insert_path: ast::Path,
541 group_imports: bool,
541) -> (InsertPosition<SyntaxElement>, AddBlankLine) { 542) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
542 let group = ImportGroup::new(&insert_path); 543 let group = ImportGroup::new(&insert_path);
543 let path_node_iter = scope 544 let path_node_iter = scope
@@ -550,6 +551,14 @@ fn find_insert_position(
550 let has_tl = tree.use_tree_list().is_some(); 551 let has_tl = tree.use_tree_list().is_some();
551 Some((path, has_tl, node)) 552 Some((path, has_tl, node))
552 }); 553 });
554
555 if !group_imports {
556 if let Some((_, _, node)) = path_node_iter.last() {
557 return (InsertPosition::After(node.into()), AddBlankLine::Before);
558 }
559 return (InsertPosition::First, AddBlankLine::AfterTwice);
560 }
561
553 // Iterator that discards anything thats not in the required grouping 562 // Iterator that discards anything thats not in the required grouping
554 // This implementation allows the user to rearrange their import groups as this only takes the first group that fits 563 // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
555 let group_iter = path_node_iter 564 let group_iter = path_node_iter
@@ -565,6 +574,7 @@ fn find_insert_position(
565 use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater 574 use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater
566 }, 575 },
567 ); 576 );
577
568 match post_insert { 578 match post_insert {
569 // insert our import before that element 579 // insert our import before that element
570 Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After), 580 Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs
index 4bbe66f1f..3d151e629 100644
--- a/crates/ide_db/src/helpers/insert_use/tests.rs
+++ b/crates/ide_db/src/helpers/insert_use/tests.rs
@@ -1,8 +1,32 @@
1use super::*; 1use super::*;
2 2
3use hir::PrefixKind;
3use test_utils::assert_eq_text; 4use test_utils::assert_eq_text;
4 5
5#[test] 6#[test]
7fn insert_not_group() {
8 check(
9 "use external_crate2::bar::A",
10 r"
11use std::bar::B;
12use external_crate::bar::A;
13use crate::bar::A;
14use self::bar::A;
15use super::bar::A;",
16 r"
17use std::bar::B;
18use external_crate::bar::A;
19use crate::bar::A;
20use self::bar::A;
21use super::bar::A;
22use external_crate2::bar::A;",
23 None,
24 false,
25 false,
26 );
27}
28
29#[test]
6fn insert_existing() { 30fn insert_existing() {
7 check_full("std::fs", "use std::fs;", "use std::fs;") 31 check_full("std::fs", "use std::fs;", "use std::fs;")
8} 32}
@@ -27,7 +51,7 @@ use std::bar::G;",
27 51
28#[test] 52#[test]
29fn insert_start_indent() { 53fn insert_start_indent() {
30 mark::check!(insert_use_indent_after); 54 cov_mark::check!(insert_use_indent_after);
31 check_none( 55 check_none(
32 "std::bar::AA", 56 "std::bar::AA",
33 r" 57 r"
@@ -96,7 +120,7 @@ use std::bar::ZZ;",
96 120
97#[test] 121#[test]
98fn insert_end_indent() { 122fn insert_end_indent() {
99 mark::check!(insert_use_indent_before); 123 cov_mark::check!(insert_use_indent_before);
100 check_none( 124 check_none(
101 "std::bar::ZZ", 125 "std::bar::ZZ",
102 r" 126 r"
@@ -231,7 +255,7 @@ fn insert_empty_file() {
231 255
232#[test] 256#[test]
233fn insert_empty_module() { 257fn insert_empty_module() {
234 mark::check!(insert_use_no_indent_after); 258 cov_mark::check!(insert_use_no_indent_after);
235 check( 259 check(
236 "foo::bar", 260 "foo::bar",
237 "mod x {}", 261 "mod x {}",
@@ -240,6 +264,7 @@ fn insert_empty_module() {
240}", 264}",
241 None, 265 None,
242 true, 266 true,
267 true,
243 ) 268 )
244} 269}
245 270
@@ -584,6 +609,7 @@ fn check(
584 ra_fixture_after: &str, 609 ra_fixture_after: &str,
585 mb: Option<MergeBehavior>, 610 mb: Option<MergeBehavior>,
586 module: bool, 611 module: bool,
612 group: bool,
587) { 613) {
588 let mut syntax = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(); 614 let mut syntax = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone();
589 if module { 615 if module {
@@ -597,21 +623,25 @@ fn check(
597 .find_map(ast::Path::cast) 623 .find_map(ast::Path::cast)
598 .unwrap(); 624 .unwrap();
599 625
600 let rewriter = insert_use(&file, path, mb); 626 let rewriter = insert_use(
627 &file,
628 path,
629 InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group },
630 );
601 let result = rewriter.rewrite(file.as_syntax_node()).to_string(); 631 let result = rewriter.rewrite(file.as_syntax_node()).to_string();
602 assert_eq_text!(ra_fixture_after, &result); 632 assert_eq_text!(ra_fixture_after, &result);
603} 633}
604 634
605fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 635fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
606 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Full), false) 636 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Full), false, true)
607} 637}
608 638
609fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 639fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
610 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Last), false) 640 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Last), false, true)
611} 641}
612 642
613fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 643fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
614 check(path, ra_fixture_before, ra_fixture_after, None, false) 644 check(path, ra_fixture_before, ra_fixture_after, None, false, true)
615} 645}
616 646
617fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) { 647fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) {