diff options
Diffstat (limited to 'crates/ra_assists')
-rw-r--r-- | crates/ra_assists/src/handlers/auto_import.rs | 189 |
1 files changed, 93 insertions, 96 deletions
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index c564f5027..86615d659 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs | |||
@@ -36,15 +36,8 @@ use std::collections::BTreeSet; | |||
36 | // # pub mod std { pub mod collections { pub struct HashMap { } } } | 36 | // # pub mod std { pub mod collections { pub struct HashMap { } } } |
37 | // ``` | 37 | // ``` |
38 | pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { | 38 | pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { |
39 | let auto_import_assets = if let Some(path_under_caret) = ctx.find_node_at_offset::<ast::Path>() | 39 | let auto_import_assets = AutoImportAssets::new(&ctx)?; |
40 | { | 40 | let proposed_imports = auto_import_assets.search_for_imports(ctx.db); |
41 | AutoImportAssets::for_regular_path(path_under_caret, &ctx)? | ||
42 | } else { | ||
43 | AutoImportAssets::for_method_call(ctx.find_node_at_offset()?, &ctx)? | ||
44 | }; | ||
45 | |||
46 | let proposed_imports = auto_import_assets | ||
47 | .search_for_imports(ctx.db, auto_import_assets.module_with_name_to_import); | ||
48 | if proposed_imports.is_empty() { | 41 | if proposed_imports.is_empty() { |
49 | return None; | 42 | return None; |
50 | } | 43 | } |
@@ -54,7 +47,6 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { | |||
54 | } else { | 47 | } else { |
55 | auto_import_assets.get_import_group_message() | 48 | auto_import_assets.get_import_group_message() |
56 | }; | 49 | }; |
57 | |||
58 | let mut group = ctx.add_assist_group(assist_group_name); | 50 | let mut group = ctx.add_assist_group(assist_group_name); |
59 | for import in proposed_imports { | 51 | for import in proposed_imports { |
60 | group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { | 52 | group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { |
@@ -77,6 +69,14 @@ struct AutoImportAssets { | |||
77 | } | 69 | } |
78 | 70 | ||
79 | impl AutoImportAssets { | 71 | impl AutoImportAssets { |
72 | fn new(ctx: &AssistCtx) -> Option<Self> { | ||
73 | if let Some(path_under_caret) = ctx.find_node_at_offset::<ast::Path>() { | ||
74 | Self::for_regular_path(path_under_caret, &ctx) | ||
75 | } else { | ||
76 | Self::for_method_call(ctx.find_node_at_offset()?, &ctx) | ||
77 | } | ||
78 | } | ||
79 | |||
80 | fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistCtx) -> Option<Self> { | 80 | fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistCtx) -> Option<Self> { |
81 | let syntax_under_caret = method_call.syntax().to_owned(); | 81 | let syntax_under_caret = method_call.syntax().to_owned(); |
82 | let source_analyzer = ctx.source_analyzer(&syntax_under_caret, None); | 82 | let source_analyzer = ctx.source_analyzer(&syntax_under_caret, None); |
@@ -111,36 +111,33 @@ impl AutoImportAssets { | |||
111 | }) | 111 | }) |
112 | } | 112 | } |
113 | 113 | ||
114 | fn get_search_query(&self) -> String { | 114 | fn get_search_query(&self) -> &str { |
115 | match &self.import_candidate { | 115 | match &self.import_candidate { |
116 | ImportCandidate::UnqualifiedName(name_ref) | 116 | ImportCandidate::UnqualifiedName(name) => name, |
117 | | ImportCandidate::QualifierStart(name_ref) => name_ref.syntax().to_string(), | 117 | ImportCandidate::QualifierStart(qualifier_start) => qualifier_start, |
118 | ImportCandidate::TraitFunction(_, trait_function) => { | 118 | ImportCandidate::TraitFunction(_, trait_function_name) => trait_function_name, |
119 | trait_function.syntax().to_string() | 119 | ImportCandidate::TraitMethod(_, trait_method_name) => trait_method_name, |
120 | } | ||
121 | ImportCandidate::TraitMethod(_, trait_method) => trait_method.syntax().to_string(), | ||
122 | } | 120 | } |
123 | } | 121 | } |
124 | 122 | ||
125 | fn get_import_group_message(&self) -> String { | 123 | fn get_import_group_message(&self) -> String { |
126 | match &self.import_candidate { | 124 | match &self.import_candidate { |
127 | ImportCandidate::UnqualifiedName(name_ref) | 125 | ImportCandidate::UnqualifiedName(name) => format!("Import {}", name), |
128 | | ImportCandidate::QualifierStart(name_ref) => format!("Import {}", name_ref.syntax()), | 126 | ImportCandidate::QualifierStart(qualifier_start) => { |
129 | ImportCandidate::TraitFunction(_, trait_function) => { | 127 | format!("Import {}", qualifier_start) |
130 | format!("Import a trait for function {}", trait_function.syntax()) | ||
131 | } | 128 | } |
132 | ImportCandidate::TraitMethod(_, trait_method) => { | 129 | ImportCandidate::TraitFunction(_, trait_function_name) => { |
133 | format!("Import a trait for method {}", trait_method.syntax()) | 130 | format!("Import a trait for function {}", trait_function_name) |
131 | } | ||
132 | ImportCandidate::TraitMethod(_, trait_method_name) => { | ||
133 | format!("Import a trait for method {}", trait_method_name) | ||
134 | } | 134 | } |
135 | } | 135 | } |
136 | } | 136 | } |
137 | 137 | ||
138 | fn search_for_imports( | 138 | fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { |
139 | &self, | ||
140 | db: &RootDatabase, | ||
141 | module_with_name_to_import: Module, | ||
142 | ) -> BTreeSet<ModPath> { | ||
143 | let _p = profile("auto_import::search_for_imports"); | 139 | let _p = profile("auto_import::search_for_imports"); |
140 | let current_crate = self.module_with_name_to_import.krate(); | ||
144 | ImportsLocator::new(db) | 141 | ImportsLocator::new(db) |
145 | .find_imports(&self.get_search_query()) | 142 | .find_imports(&self.get_search_query()) |
146 | .into_iter() | 143 | .into_iter() |
@@ -148,49 +145,46 @@ impl AutoImportAssets { | |||
148 | ImportCandidate::TraitFunction(function_callee, _) => { | 145 | ImportCandidate::TraitFunction(function_callee, _) => { |
149 | let mut applicable_traits = Vec::new(); | 146 | let mut applicable_traits = Vec::new(); |
150 | if let ModuleDef::Function(located_function) = module_def { | 147 | if let ModuleDef::Function(located_function) = module_def { |
151 | let trait_candidates = Self::get_trait_candidates( | 148 | let trait_candidates: FxHashSet<_> = |
152 | db, | 149 | Self::get_trait_candidates(db, located_function, current_crate) |
153 | located_function, | 150 | .into_iter() |
154 | module_with_name_to_import.krate(), | 151 | .map(|trait_candidate| trait_candidate.into()) |
155 | ) | 152 | .collect(); |
156 | .into_iter() | 153 | if !trait_candidates.is_empty() { |
157 | .map(|trait_candidate| trait_candidate.into()) | 154 | function_callee.iterate_path_candidates( |
158 | .collect(); | 155 | db, |
159 | 156 | current_crate, | |
160 | function_callee.iterate_path_candidates( | 157 | &trait_candidates, |
161 | db, | 158 | None, |
162 | module_with_name_to_import.krate(), | 159 | |_, assoc| { |
163 | &trait_candidates, | 160 | if let AssocContainerId::TraitId(trait_id) = assoc.container(db) |
164 | None, | 161 | { |
165 | |_, assoc| { | 162 | applicable_traits.push( |
166 | if let AssocContainerId::TraitId(trait_id) = assoc.container(db) { | 163 | self.module_with_name_to_import.find_use_path( |
167 | applicable_traits.push( | 164 | db, |
168 | module_with_name_to_import | 165 | ModuleDef::Trait(trait_id.into()), |
169 | .find_use_path(db, ModuleDef::Trait(trait_id.into())), | 166 | ), |
170 | ); | 167 | ); |
171 | }; | 168 | }; |
172 | None::<()> | 169 | None::<()> |
173 | }, | 170 | }, |
174 | ); | 171 | ); |
172 | }; | ||
175 | } | 173 | } |
176 | applicable_traits | 174 | applicable_traits |
177 | } | 175 | } |
178 | ImportCandidate::TraitMethod(function_callee, _) => { | 176 | ImportCandidate::TraitMethod(function_callee, _) => { |
179 | let mut applicable_traits = Vec::new(); | 177 | let mut applicable_traits = Vec::new(); |
180 | if let ModuleDef::Function(located_function) = module_def { | 178 | if let ModuleDef::Function(located_function) = module_def { |
181 | let trait_candidates: FxHashSet<_> = Self::get_trait_candidates( | 179 | let trait_candidates: FxHashSet<_> = |
182 | db, | 180 | Self::get_trait_candidates(db, located_function, current_crate) |
183 | located_function, | 181 | .into_iter() |
184 | module_with_name_to_import.krate(), | 182 | .map(|trait_candidate| trait_candidate.into()) |
185 | ) | 183 | .collect(); |
186 | .into_iter() | ||
187 | .map(|trait_candidate| trait_candidate.into()) | ||
188 | .collect(); | ||
189 | |||
190 | if !trait_candidates.is_empty() { | 184 | if !trait_candidates.is_empty() { |
191 | function_callee.iterate_method_candidates( | 185 | function_callee.iterate_method_candidates( |
192 | db, | 186 | db, |
193 | module_with_name_to_import.krate(), | 187 | current_crate, |
194 | &trait_candidates, | 188 | &trait_candidates, |
195 | None, | 189 | None, |
196 | |_, funciton| { | 190 | |_, funciton| { |
@@ -198,7 +192,7 @@ impl AutoImportAssets { | |||
198 | funciton.container(db) | 192 | funciton.container(db) |
199 | { | 193 | { |
200 | applicable_traits.push( | 194 | applicable_traits.push( |
201 | module_with_name_to_import.find_use_path( | 195 | self.module_with_name_to_import.find_use_path( |
202 | db, | 196 | db, |
203 | ModuleDef::Trait(trait_id.into()), | 197 | ModuleDef::Trait(trait_id.into()), |
204 | ), | 198 | ), |
@@ -211,7 +205,7 @@ impl AutoImportAssets { | |||
211 | } | 205 | } |
212 | applicable_traits | 206 | applicable_traits |
213 | } | 207 | } |
214 | _ => vec![module_with_name_to_import.find_use_path(db, module_def)], | 208 | _ => vec![self.module_with_name_to_import.find_use_path(db, module_def)], |
215 | }) | 209 | }) |
216 | .flatten() | 210 | .flatten() |
217 | .filter_map(std::convert::identity) | 211 | .filter_map(std::convert::identity) |
@@ -235,22 +229,19 @@ impl AutoImportAssets { | |||
235 | crate_def_map | 229 | crate_def_map |
236 | .modules | 230 | .modules |
237 | .iter() | 231 | .iter() |
238 | .map(|(_, module_data)| { | 232 | .map(|(_, module_data)| module_data.scope.declarations()) |
239 | let mut traits = Vec::new(); | 233 | .flatten() |
240 | for module_def_id in module_data.scope.declarations() { | 234 | .filter_map(|module_def_id| match module_def_id.into() { |
241 | if let ModuleDef::Trait(trait_candidate) = module_def_id.into() { | 235 | ModuleDef::Trait(trait_candidate) |
242 | if trait_candidate | 236 | if trait_candidate |
243 | .items(db) | 237 | .items(db) |
244 | .into_iter() | 238 | .into_iter() |
245 | .any(|item| item == AssocItem::Function(called_function)) | 239 | .any(|item| item == AssocItem::Function(called_function)) => |
246 | { | 240 | { |
247 | traits.push(trait_candidate) | 241 | Some(trait_candidate) |
248 | } | ||
249 | } | ||
250 | } | 242 | } |
251 | traits | 243 | _ => None, |
252 | }) | 244 | }) |
253 | .flatten() | ||
254 | .collect::<FxHashSet<_>>() | 245 | .collect::<FxHashSet<_>>() |
255 | }) | 246 | }) |
256 | .flatten() | 247 | .flatten() |
@@ -259,12 +250,20 @@ impl AutoImportAssets { | |||
259 | } | 250 | } |
260 | 251 | ||
261 | #[derive(Debug)] | 252 | #[derive(Debug)] |
262 | // TODO kb rustdocs | ||
263 | enum ImportCandidate { | 253 | enum ImportCandidate { |
264 | UnqualifiedName(ast::NameRef), | 254 | /// Simple name like 'HashMap' |
265 | QualifierStart(ast::NameRef), | 255 | UnqualifiedName(String), |
266 | TraitFunction(Type, ast::PathSegment), | 256 | /// First part of the qualified name. |
267 | TraitMethod(Type, ast::NameRef), | 257 | /// For 'std::collections::HashMap', that will be 'std'. |
258 | QualifierStart(String), | ||
259 | /// A trait function that has no self parameter. | ||
260 | /// For 'test_mod::TestEnum::test_function', `Type` is the `test_mod::TestEnum` expression type | ||
261 | /// and `String`is the `test_function` | ||
262 | TraitFunction(Type, String), | ||
263 | /// A trait method with self parameter. | ||
264 | /// For 'test_enum.test_method()', `Type` is the `test_enum` expression type | ||
265 | /// and `String` is the `test_method` | ||
266 | TraitMethod(Type, String), | ||
268 | } | 267 | } |
269 | 268 | ||
270 | impl ImportCandidate { | 269 | impl ImportCandidate { |
@@ -278,7 +277,7 @@ impl ImportCandidate { | |||
278 | } | 277 | } |
279 | Some(Self::TraitMethod( | 278 | Some(Self::TraitMethod( |
280 | source_analyzer.type_of(db, &method_call.expr()?)?, | 279 | source_analyzer.type_of(db, &method_call.expr()?)?, |
281 | method_call.name_ref()?, | 280 | method_call.name_ref()?.syntax().to_string(), |
282 | )) | 281 | )) |
283 | } | 282 | } |
284 | 283 | ||
@@ -299,36 +298,34 @@ impl ImportCandidate { | |||
299 | if let Some(qualifier_start_resolution) = | 298 | if let Some(qualifier_start_resolution) = |
300 | source_analyzer.resolve_path(db, &qualifier_start_path) | 299 | source_analyzer.resolve_path(db, &qualifier_start_path) |
301 | { | 300 | { |
302 | let qualifier_resolution = if &qualifier_start_path == path_under_caret { | 301 | let qualifier_resolution = if qualifier_start_path == qualifier { |
303 | qualifier_start_resolution | 302 | qualifier_start_resolution |
304 | } else { | 303 | } else { |
305 | source_analyzer.resolve_path(db, &qualifier)? | 304 | source_analyzer.resolve_path(db, &qualifier)? |
306 | }; | 305 | }; |
307 | if let PathResolution::Def(ModuleDef::Adt(function_callee)) = qualifier_resolution { | 306 | if let PathResolution::Def(ModuleDef::Adt(function_callee)) = qualifier_resolution { |
308 | Some(ImportCandidate::TraitFunction(function_callee.ty(db), segment)) | 307 | Some(ImportCandidate::TraitFunction( |
308 | function_callee.ty(db), | ||
309 | segment.syntax().to_string(), | ||
310 | )) | ||
309 | } else { | 311 | } else { |
310 | None | 312 | None |
311 | } | 313 | } |
312 | } else { | 314 | } else { |
313 | Some(ImportCandidate::QualifierStart(qualifier_start)) | 315 | Some(ImportCandidate::QualifierStart(qualifier_start.syntax().to_string())) |
314 | } | 316 | } |
315 | } else { | 317 | } else { |
316 | if source_analyzer.resolve_path(db, path_under_caret).is_none() { | 318 | Some(ImportCandidate::UnqualifiedName( |
317 | Some(ImportCandidate::UnqualifiedName( | 319 | segment.syntax().descendants().find_map(ast::NameRef::cast)?.syntax().to_string(), |
318 | segment.syntax().descendants().find_map(ast::NameRef::cast)?, | 320 | )) |
319 | )) | ||
320 | } else { | ||
321 | None | ||
322 | } | ||
323 | } | 321 | } |
324 | } | 322 | } |
325 | } | 323 | } |
326 | 324 | ||
327 | #[cfg(test)] | 325 | #[cfg(test)] |
328 | mod tests { | 326 | mod tests { |
329 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
330 | |||
331 | use super::*; | 327 | use super::*; |
328 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
332 | 329 | ||
333 | #[test] | 330 | #[test] |
334 | fn applicable_when_found_an_import() { | 331 | fn applicable_when_found_an_import() { |