aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists')
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs189
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// ```
38pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { 38pub(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
79impl AutoImportAssets { 71impl 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
263enum ImportCandidate { 253enum 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
270impl ImportCandidate { 269impl 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)]
328mod tests { 326mod 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() {