diff options
Diffstat (limited to 'crates/ide_db/src/helpers')
-rw-r--r-- | crates/ide_db/src/helpers/import_assets.rs | 578 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/insert_use.rs | 24 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/insert_use/tests.rs | 44 |
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. |
2 | use either::Either; | 2 | use hir::{ |
3 | use 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 | }; | ||
6 | use itertools::Itertools; | ||
4 | use rustc_hash::FxHashSet; | 7 | use rustc_hash::FxHashSet; |
5 | use syntax::{ast, AstNode}; | 8 | use syntax::{ast, utils::path_to_string_stripping_turbo_fish, AstNode, SyntaxNode}; |
6 | 9 | ||
7 | use crate::{ | 10 | use 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 | ||
15 | use 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)] |
13 | pub enum ImportCandidate { | 23 | pub 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)] |
27 | pub struct TraitImportCandidate { | 40 | pub 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)] |
33 | pub struct PathImportCandidate { | 49 | pub 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)] | ||
58 | pub 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)] |
39 | pub enum NameToImport { | 65 | pub 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)] |
54 | pub struct ImportAssets { | 84 | pub 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 | ||
59 | impl ImportAssets { | 90 | impl 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)] | ||
152 | pub 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 | |||
168 | impl 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 | |||
135 | impl ImportAssets { | 179 | impl 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 | ||
236 | fn applicable_defs<'a>( | 294 | fn 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 | |||
325 | fn 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 | |||
382 | fn 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 | |||
397 | fn 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 | |||
415 | fn 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 | |||
436 | fn 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 | ||
302 | fn assoc_to_module_def(assoc: AssocItem) -> ModuleDef { | 511 | fn 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 | |||
519 | fn 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 | |||
570 | fn 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 | }) | 606 | fn 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 | }; |
16 | use test_utils::mark; | ||
17 | 16 | ||
18 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | 17 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
19 | pub struct InsertUseConfig { | 18 | pub 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 { | |||
99 | pub fn insert_use<'a>( | 99 | pub 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 { | |||
538 | fn find_insert_position( | 538 | fn 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 @@ | |||
1 | use super::*; | 1 | use super::*; |
2 | 2 | ||
3 | use hir::PrefixKind; | ||
3 | use test_utils::assert_eq_text; | 4 | use test_utils::assert_eq_text; |
4 | 5 | ||
5 | #[test] | 6 | #[test] |
7 | fn insert_not_group() { | ||
8 | check( | ||
9 | "use external_crate2::bar::A", | ||
10 | r" | ||
11 | use std::bar::B; | ||
12 | use external_crate::bar::A; | ||
13 | use crate::bar::A; | ||
14 | use self::bar::A; | ||
15 | use super::bar::A;", | ||
16 | r" | ||
17 | use std::bar::B; | ||
18 | use external_crate::bar::A; | ||
19 | use crate::bar::A; | ||
20 | use self::bar::A; | ||
21 | use super::bar::A; | ||
22 | use external_crate2::bar::A;", | ||
23 | None, | ||
24 | false, | ||
25 | false, | ||
26 | ); | ||
27 | } | ||
28 | |||
29 | #[test] | ||
6 | fn insert_existing() { | 30 | fn 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] |
29 | fn insert_start_indent() { | 53 | fn 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] |
98 | fn insert_end_indent() { | 122 | fn 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] |
233 | fn insert_empty_module() { | 257 | fn 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 | ||
605 | fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 635 | fn 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 | ||
609 | fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 639 | fn 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 | ||
613 | fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 643 | fn 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 | ||
617 | fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) { | 647 | fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) { |