aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_db/src/helpers/import_assets.rs
diff options
context:
space:
mode:
authorKirill Bulatov <[email protected]>2021-01-05 08:34:03 +0000
committerKirill Bulatov <[email protected]>2021-01-16 18:44:12 +0000
commitdb335a1bbf1d1bea2c761f67efb4b49831738e31 (patch)
tree910963c004c460d2f0c322a0e643947aaf7132b8 /crates/ide_db/src/helpers/import_assets.rs
parent9a349f280ff1c6d0b57df80aa3d6720474e4b00a (diff)
Add flyimport completion for trait assoc items
Diffstat (limited to 'crates/ide_db/src/helpers/import_assets.rs')
-rw-r--r--crates/ide_db/src/helpers/import_assets.rs286
1 files changed, 186 insertions, 100 deletions
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
index edc3da318..e284220c1 100644
--- a/crates/ide_db/src/helpers/import_assets.rs
+++ b/crates/ide_db/src/helpers/import_assets.rs
@@ -1,12 +1,13 @@
1//! Look up accessible paths for items. 1//! Look up accessible paths for items.
2use either::Either; 2use either::Either;
3use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; 3use hir::{AsAssocItem, AssocItem, Module, ModuleDef, PrefixKind, Semantics};
4use rustc_hash::FxHashSet; 4use rustc_hash::FxHashSet;
5use syntax::{ast, AstNode, SyntaxNode}; 5use syntax::{ast, AstNode};
6 6
7use crate::{imports_locator, RootDatabase}; 7use crate::{
8 8 imports_locator::{self, AssocItemSearch},
9use super::insert_use::InsertUseConfig; 9 RootDatabase,
10};
10 11
11#[derive(Debug)] 12#[derive(Debug)]
12pub enum ImportCandidate { 13pub enum ImportCandidate {
@@ -24,86 +25,141 @@ pub enum ImportCandidate {
24 25
25#[derive(Debug)] 26#[derive(Debug)]
26pub struct TraitImportCandidate { 27pub struct TraitImportCandidate {
27 pub ty: hir::Type, 28 pub receiver_ty: hir::Type,
28 pub name: ast::NameRef, 29 pub name: NameToImport,
29} 30}
30 31
31#[derive(Debug)] 32#[derive(Debug)]
32pub struct PathImportCandidate { 33pub struct PathImportCandidate {
33 pub qualifier: Option<ast::Path>, 34 pub qualifier: Option<ast::Path>,
34 pub name: ast::NameRef, 35 pub name: NameToImport,
36}
37
38#[derive(Debug)]
39pub enum NameToImport {
40 Exact(String),
41 Fuzzy(String),
42}
43
44impl NameToImport {
45 pub fn text(&self) -> &str {
46 match self {
47 NameToImport::Exact(text) => text.as_str(),
48 NameToImport::Fuzzy(text) => text.as_str(),
49 }
50 }
35} 51}
36 52
37#[derive(Debug)] 53#[derive(Debug)]
38pub struct ImportAssets { 54pub struct ImportAssets {
39 import_candidate: ImportCandidate, 55 import_candidate: ImportCandidate,
40 module_with_name_to_import: hir::Module, 56 module_with_candidate: hir::Module,
41 syntax_under_caret: SyntaxNode,
42} 57}
43 58
44impl ImportAssets { 59impl ImportAssets {
45 pub fn for_method_call( 60 pub fn for_method_call(
46 method_call: ast::MethodCallExpr, 61 method_call: &ast::MethodCallExpr,
47 sema: &Semantics<RootDatabase>, 62 sema: &Semantics<RootDatabase>,
48 ) -> Option<Self> { 63 ) -> Option<Self> {
49 let syntax_under_caret = method_call.syntax().to_owned();
50 let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
51 Some(Self { 64 Some(Self {
52 import_candidate: ImportCandidate::for_method_call(sema, &method_call)?, 65 import_candidate: ImportCandidate::for_method_call(sema, method_call)?,
53 module_with_name_to_import, 66 module_with_candidate: sema.scope(method_call.syntax()).module()?,
54 syntax_under_caret,
55 }) 67 })
56 } 68 }
57 69
58 pub fn for_regular_path( 70 pub fn for_exact_path(
59 path_under_caret: ast::Path, 71 fully_qualified_path: &ast::Path,
60 sema: &Semantics<RootDatabase>, 72 sema: &Semantics<RootDatabase>,
61 ) -> Option<Self> { 73 ) -> Option<Self> {
62 let syntax_under_caret = path_under_caret.syntax().to_owned(); 74 let syntax_under_caret = fully_qualified_path.syntax();
63 if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() { 75 if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
64 return None; 76 return None;
65 } 77 }
66
67 let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
68 Some(Self { 78 Some(Self {
69 import_candidate: ImportCandidate::for_regular_path(sema, &path_under_caret)?, 79 import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?,
70 module_with_name_to_import, 80 module_with_candidate: sema.scope(syntax_under_caret).module()?,
71 syntax_under_caret, 81 })
82 }
83
84 pub fn for_fuzzy_path(
85 module_with_path: Module,
86 qualifier: Option<ast::Path>,
87 fuzzy_name: String,
88 sema: &Semantics<RootDatabase>,
89 ) -> Option<Self> {
90 Some(match qualifier {
91 Some(qualifier) => {
92 let qualifier_resolution = sema.resolve_path(&qualifier)?;
93 match qualifier_resolution {
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 },
72 }) 117 })
73 } 118 }
74 119
75 pub fn syntax_under_caret(&self) -> &SyntaxNode { 120 pub fn for_fuzzy_method_call(
76 &self.syntax_under_caret 121 module_with_method_call: Module,
122 receiver_ty: hir::Type,
123 fuzzy_method_name: String,
124 ) -> Option<Self> {
125 Some(Self {
126 import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
127 receiver_ty,
128 name: NameToImport::Fuzzy(fuzzy_method_name),
129 }),
130 module_with_candidate: module_with_method_call,
131 })
77 } 132 }
133}
78 134
135impl ImportAssets {
79 pub fn import_candidate(&self) -> &ImportCandidate { 136 pub fn import_candidate(&self) -> &ImportCandidate {
80 &self.import_candidate 137 &self.import_candidate
81 } 138 }
82 139
83 fn get_search_query(&self) -> &str { 140 fn name_to_import(&self) -> &NameToImport {
84 match &self.import_candidate { 141 match &self.import_candidate {
85 ImportCandidate::Path(candidate) => candidate.name.text(), 142 ImportCandidate::Path(candidate) => &candidate.name,
86 ImportCandidate::TraitAssocItem(candidate) 143 ImportCandidate::TraitAssocItem(candidate)
87 | ImportCandidate::TraitMethod(candidate) => candidate.name.text(), 144 | ImportCandidate::TraitMethod(candidate) => &candidate.name,
88 } 145 }
89 } 146 }
90 147
91 pub fn search_for_imports( 148 pub fn search_for_imports(
92 &self, 149 &self,
93 sema: &Semantics<RootDatabase>, 150 sema: &Semantics<RootDatabase>,
94 config: &InsertUseConfig, 151 prefix_kind: PrefixKind,
95 ) -> Vec<(hir::ModPath, hir::ItemInNs)> { 152 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
96 let _p = profile::span("import_assists::search_for_imports"); 153 let _p = profile::span("import_assets::search_for_imports");
97 self.search_for(sema, Some(config.prefix_kind)) 154 self.search_for(sema, Some(prefix_kind))
98 } 155 }
99 156
100 /// This may return non-absolute paths if a part of the returned path is already imported into scope. 157 /// This may return non-absolute paths if a part of the returned path is already imported into scope.
101 #[allow(dead_code)]
102 pub fn search_for_relative_paths( 158 pub fn search_for_relative_paths(
103 &self, 159 &self,
104 sema: &Semantics<RootDatabase>, 160 sema: &Semantics<RootDatabase>,
105 ) -> Vec<(hir::ModPath, hir::ItemInNs)> { 161 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
106 let _p = profile::span("import_assists::search_for_relative_paths"); 162 let _p = profile::span("import_assets::search_for_relative_paths");
107 self.search_for(sema, None) 163 self.search_for(sema, None)
108 } 164 }
109 165
@@ -114,60 +170,56 @@ impl ImportAssets {
114 ) -> Vec<(hir::ModPath, hir::ItemInNs)> { 170 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
115 let db = sema.db; 171 let db = sema.db;
116 let mut trait_candidates = FxHashSet::default(); 172 let mut trait_candidates = FxHashSet::default();
117 let current_crate = self.module_with_name_to_import.krate(); 173 let current_crate = self.module_with_candidate.krate();
118 174
119 let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| { 175 let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| {
120 trait_candidates.clear(); 176 trait_candidates.clear();
121 match &self.import_candidate { 177 match &self.import_candidate {
122 ImportCandidate::TraitAssocItem(trait_candidate) => { 178 ImportCandidate::TraitAssocItem(trait_candidate) => {
123 let located_assoc_item = match candidate { 179 let canidate_assoc_item = match candidate {
124 Either::Left(ModuleDef::Function(located_function)) => { 180 Either::Left(module_def) => module_def.as_assoc_item(db),
125 located_function.as_assoc_item(db)
126 }
127 Either::Left(ModuleDef::Const(located_const)) => {
128 located_const.as_assoc_item(db)
129 }
130 _ => None, 181 _ => None,
131 } 182 }?;
132 .map(|assoc| assoc.container(db)) 183 trait_candidates.insert(canidate_assoc_item.containing_trait(db)?.into());
133 .and_then(Self::assoc_to_trait)?;
134
135 trait_candidates.insert(located_assoc_item.into());
136 184
137 trait_candidate 185 trait_candidate
138 .ty 186 .receiver_ty
139 .iterate_path_candidates( 187 .iterate_path_candidates(
140 db, 188 db,
141 current_crate, 189 current_crate,
142 &trait_candidates, 190 &trait_candidates,
143 None, 191 None,
144 |_, assoc| Self::assoc_to_trait(assoc.container(db)), 192 |_, assoc| {
193 if canidate_assoc_item == assoc {
194 Some(assoc_to_module_def(assoc))
195 } else {
196 None
197 }
198 },
145 ) 199 )
146 .map(ModuleDef::from)
147 .map(Either::Left) 200 .map(Either::Left)
148 } 201 }
149 ImportCandidate::TraitMethod(trait_candidate) => { 202 ImportCandidate::TraitMethod(trait_candidate) => {
150 let located_assoc_item = 203 let canidate_assoc_item = match candidate {
151 if let Either::Left(ModuleDef::Function(located_function)) = candidate { 204 Either::Left(module_def) => module_def.as_assoc_item(db),
152 located_function 205 _ => None,
153 .as_assoc_item(db) 206 }?;
154 .map(|assoc| assoc.container(db)) 207 trait_candidates.insert(canidate_assoc_item.containing_trait(db)?.into());
155 .and_then(Self::assoc_to_trait)
156 } else {
157 None
158 }?;
159
160 trait_candidates.insert(located_assoc_item.into());
161 208
162 trait_candidate 209 trait_candidate
163 .ty 210 .receiver_ty
164 .iterate_method_candidates( 211 .iterate_method_candidates(
165 db, 212 db,
166 current_crate, 213 current_crate,
167 &trait_candidates, 214 &trait_candidates,
168 None, 215 None,
169 |_, function| { 216 |_, function| {
170 Self::assoc_to_trait(function.as_assoc_item(db)?.container(db)) 217 let assoc = function.as_assoc_item(db)?;
218 if canidate_assoc_item == assoc {
219 Some(assoc_to_module_def(assoc))
220 } else {
221 None
222 }
171 }, 223 },
172 ) 224 )
173 .map(ModuleDef::from) 225 .map(ModuleDef::from)
@@ -177,34 +229,69 @@ impl ImportAssets {
177 } 229 }
178 }; 230 };
179 231
180 let mut res = imports_locator::find_exact_imports( 232 let unfiltered_imports = match self.name_to_import() {
181 sema, 233 NameToImport::Exact(exact_name) => {
182 current_crate, 234 imports_locator::find_exact_imports(sema, current_crate, exact_name.clone())
183 self.get_search_query().to_string(),
184 )
185 .filter_map(filter)
186 .filter_map(|candidate| {
187 let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
188 if let Some(prefix_kind) = prefixed {
189 self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind)
190 } else {
191 self.module_with_name_to_import.find_use_path(db, item)
192 } 235 }
193 .map(|path| (path, item)) 236 // FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items:
194 }) 237 // instead, we need to look up all trait impls for a certain struct and search through them only
195 .filter(|(use_path, _)| use_path.len() > 1) 238 // see https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761585032
196 .take(20) 239 // and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup
197 .collect::<Vec<_>>(); 240 // for the details
198 res.sort_by_key(|(path, _)| path.clone()); 241 NameToImport::Fuzzy(fuzzy_name) => imports_locator::find_similar_imports(
242 sema,
243 current_crate,
244 fuzzy_name.clone(),
245 match self.import_candidate {
246 ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => {
247 AssocItemSearch::AssocItemsOnly
248 }
249 _ => AssocItemSearch::Exclude,
250 },
251 ),
252 };
253
254 let mut res = unfiltered_imports
255 .filter_map(filter)
256 .filter_map(|candidate| {
257 let item: hir::ItemInNs = candidate.clone().either(Into::into, Into::into);
258
259 let item_to_search = match self.import_candidate {
260 ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => {
261 let canidate_trait = match candidate {
262 Either::Left(module_def) => {
263 module_def.as_assoc_item(db)?.containing_trait(db)
264 }
265 _ => None,
266 }?;
267 ModuleDef::from(canidate_trait).into()
268 }
269 _ => item,
270 };
271
272 if let Some(prefix_kind) = prefixed {
273 self.module_with_candidate.find_use_path_prefixed(
274 db,
275 item_to_search,
276 prefix_kind,
277 )
278 } else {
279 self.module_with_candidate.find_use_path(db, item_to_search)
280 }
281 .map(|path| (path, item))
282 })
283 .filter(|(use_path, _)| use_path.len() > 1)
284 .collect::<Vec<_>>();
285 res.sort_by_cached_key(|(path, _)| path.clone());
199 res 286 res
200 } 287 }
288}
201 289
202 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> { 290fn assoc_to_module_def(assoc: AssocItem) -> ModuleDef {
203 if let AssocItemContainer::Trait(extracted_trait) = assoc { 291 match assoc {
204 Some(extracted_trait) 292 AssocItem::Function(f) => f.into(),
205 } else { 293 AssocItem::Const(c) => c.into(),
206 None 294 AssocItem::TypeAlias(t) => t.into(),
207 }
208 } 295 }
209} 296}
210 297
@@ -216,22 +303,19 @@ impl ImportCandidate {
216 match sema.resolve_method_call(method_call) { 303 match sema.resolve_method_call(method_call) {
217 Some(_) => None, 304 Some(_) => None,
218 None => Some(Self::TraitMethod(TraitImportCandidate { 305 None => Some(Self::TraitMethod(TraitImportCandidate {
219 ty: sema.type_of_expr(&method_call.receiver()?)?, 306 receiver_ty: sema.type_of_expr(&method_call.receiver()?)?,
220 name: method_call.name_ref()?, 307 name: NameToImport::Exact(method_call.name_ref()?.to_string()),
221 })), 308 })),
222 } 309 }
223 } 310 }
224 311
225 fn for_regular_path( 312 fn for_regular_path(sema: &Semantics<RootDatabase>, path: &ast::Path) -> Option<Self> {
226 sema: &Semantics<RootDatabase>, 313 if sema.resolve_path(path).is_some() {
227 path_under_caret: &ast::Path,
228 ) -> Option<Self> {
229 if sema.resolve_path(path_under_caret).is_some() {
230 return None; 314 return None;
231 } 315 }
232 316
233 let segment = path_under_caret.segment()?; 317 let segment = path.segment()?;
234 let candidate = if let Some(qualifier) = path_under_caret.qualifier() { 318 let candidate = if let Some(qualifier) = path.qualifier() {
235 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?; 319 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
236 let qualifier_start_path = 320 let qualifier_start_path =
237 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; 321 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
@@ -244,8 +328,8 @@ impl ImportCandidate {
244 match qualifier_resolution { 328 match qualifier_resolution {
245 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => { 329 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
246 ImportCandidate::TraitAssocItem(TraitImportCandidate { 330 ImportCandidate::TraitAssocItem(TraitImportCandidate {
247 ty: assoc_item_path.ty(sema.db), 331 receiver_ty: assoc_item_path.ty(sema.db),
248 name: segment.name_ref()?, 332 name: NameToImport::Exact(segment.name_ref()?.to_string()),
249 }) 333 })
250 } 334 }
251 _ => return None, 335 _ => return None,
@@ -253,13 +337,15 @@ impl ImportCandidate {
253 } else { 337 } else {
254 ImportCandidate::Path(PathImportCandidate { 338 ImportCandidate::Path(PathImportCandidate {
255 qualifier: Some(qualifier), 339 qualifier: Some(qualifier),
256 name: qualifier_start, 340 name: NameToImport::Exact(qualifier_start.to_string()),
257 }) 341 })
258 } 342 }
259 } else { 343 } else {
260 ImportCandidate::Path(PathImportCandidate { 344 ImportCandidate::Path(PathImportCandidate {
261 qualifier: None, 345 qualifier: None,
262 name: segment.syntax().descendants().find_map(ast::NameRef::cast)?, 346 name: NameToImport::Exact(
347 segment.syntax().descendants().find_map(ast::NameRef::cast)?.to_string(),
348 ),
263 }) 349 })
264 }; 350 };
265 Some(candidate) 351 Some(candidate)