aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/utils/import_assets.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src/utils/import_assets.rs')
-rw-r--r--crates/assists/src/utils/import_assets.rs254
1 files changed, 254 insertions, 0 deletions
diff --git a/crates/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs
new file mode 100644
index 000000000..ce5986db7
--- /dev/null
+++ b/crates/assists/src/utils/import_assets.rs
@@ -0,0 +1,254 @@
1//! Look up accessible paths for items.
2use std::collections::BTreeSet;
3
4use either::Either;
5use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics};
6use ide_db::{imports_locator, RootDatabase};
7use rustc_hash::FxHashSet;
8use syntax::{ast, AstNode, SyntaxNode};
9
10use crate::assist_config::InsertUseConfig;
11
12#[derive(Debug)]
13pub(crate) struct ImportAssets {
14 import_candidate: ImportCandidate,
15 module_with_name_to_import: hir::Module,
16 syntax_under_caret: SyntaxNode,
17}
18
19impl ImportAssets {
20 pub(crate) fn new(ctx: &crate::assist_context::AssistContext) -> Option<Self> {
21 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
22 Self::for_regular_path(path_under_caret, &ctx.sema)
23 } else {
24 Self::for_method_call(ctx.find_node_at_offset_with_descend()?, &ctx.sema)
25 }
26 }
27
28 pub(crate) fn syntax_under_caret(&self) -> &SyntaxNode {
29 &self.syntax_under_caret
30 }
31
32 pub(crate) fn import_candidate(&self) -> &ImportCandidate {
33 &self.import_candidate
34 }
35
36 fn for_method_call(
37 method_call: ast::MethodCallExpr,
38 sema: &Semantics<RootDatabase>,
39 ) -> Option<Self> {
40 let syntax_under_caret = method_call.syntax().to_owned();
41 let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
42 Some(Self {
43 import_candidate: ImportCandidate::for_method_call(sema, &method_call)?,
44 module_with_name_to_import,
45 syntax_under_caret,
46 })
47 }
48
49 fn for_regular_path(
50 path_under_caret: ast::Path,
51 sema: &Semantics<RootDatabase>,
52 ) -> Option<Self> {
53 let syntax_under_caret = path_under_caret.syntax().to_owned();
54 if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
55 return None;
56 }
57
58 let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
59 Some(Self {
60 import_candidate: ImportCandidate::for_regular_path(sema, &path_under_caret)?,
61 module_with_name_to_import,
62 syntax_under_caret,
63 })
64 }
65
66 fn get_search_query(&self) -> &str {
67 match &self.import_candidate {
68 ImportCandidate::UnqualifiedName(name) => name,
69 ImportCandidate::QualifierStart(qualifier_start) => qualifier_start,
70 ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => trait_assoc_item_name,
71 ImportCandidate::TraitMethod(_, trait_method_name) => trait_method_name,
72 }
73 }
74
75 pub(crate) fn search_for_imports(
76 &self,
77 sema: &Semantics<RootDatabase>,
78 config: &InsertUseConfig,
79 ) -> BTreeSet<hir::ModPath> {
80 let _p = profile::span("import_assists::search_for_imports");
81 self.search_for(sema, Some(config.prefix_kind))
82 }
83
84 /// This may return non-absolute paths if a part of the returned path is already imported into scope.
85 #[allow(dead_code)]
86 pub(crate) fn search_for_relative_paths(
87 &self,
88 sema: &Semantics<RootDatabase>,
89 ) -> BTreeSet<hir::ModPath> {
90 let _p = profile::span("import_assists::search_for_relative_paths");
91 self.search_for(sema, None)
92 }
93
94 fn search_for(
95 &self,
96 sema: &Semantics<RootDatabase>,
97 prefixed: Option<hir::PrefixKind>,
98 ) -> BTreeSet<hir::ModPath> {
99 let db = sema.db;
100 let current_crate = self.module_with_name_to_import.krate();
101 imports_locator::find_imports(sema, current_crate, &self.get_search_query())
102 .into_iter()
103 .filter_map(|candidate| match &self.import_candidate {
104 ImportCandidate::TraitAssocItem(assoc_item_type, _) => {
105 let located_assoc_item = match candidate {
106 Either::Left(ModuleDef::Function(located_function)) => located_function
107 .as_assoc_item(db)
108 .map(|assoc| assoc.container(db))
109 .and_then(Self::assoc_to_trait),
110 Either::Left(ModuleDef::Const(located_const)) => located_const
111 .as_assoc_item(db)
112 .map(|assoc| assoc.container(db))
113 .and_then(Self::assoc_to_trait),
114 _ => None,
115 }?;
116
117 let mut trait_candidates = FxHashSet::default();
118 trait_candidates.insert(located_assoc_item.into());
119
120 assoc_item_type
121 .iterate_path_candidates(
122 db,
123 current_crate,
124 &trait_candidates,
125 None,
126 |_, assoc| Self::assoc_to_trait(assoc.container(db)),
127 )
128 .map(ModuleDef::from)
129 .map(Either::Left)
130 }
131 ImportCandidate::TraitMethod(function_callee, _) => {
132 let located_assoc_item =
133 if let Either::Left(ModuleDef::Function(located_function)) = candidate {
134 located_function
135 .as_assoc_item(db)
136 .map(|assoc| assoc.container(db))
137 .and_then(Self::assoc_to_trait)
138 } else {
139 None
140 }?;
141
142 let mut trait_candidates = FxHashSet::default();
143 trait_candidates.insert(located_assoc_item.into());
144
145 function_callee
146 .iterate_method_candidates(
147 db,
148 current_crate,
149 &trait_candidates,
150 None,
151 |_, function| {
152 Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
153 },
154 )
155 .map(ModuleDef::from)
156 .map(Either::Left)
157 }
158 _ => Some(candidate),
159 })
160 .filter_map(|candidate| {
161 let item: hir::ItemInNs = match candidate {
162 Either::Left(module_def) => module_def.into(),
163 Either::Right(macro_def) => macro_def.into(),
164 };
165 if let Some(prefix_kind) = prefixed {
166 self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind)
167 } else {
168 self.module_with_name_to_import.find_use_path(db, item)
169 }
170 })
171 .filter(|use_path| !use_path.segments.is_empty())
172 .take(20)
173 .collect::<BTreeSet<_>>()
174 }
175
176 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> {
177 if let AssocItemContainer::Trait(extracted_trait) = assoc {
178 Some(extracted_trait)
179 } else {
180 None
181 }
182 }
183}
184
185#[derive(Debug)]
186pub(crate) enum ImportCandidate {
187 /// Simple name like 'HashMap'
188 UnqualifiedName(String),
189 /// First part of the qualified name.
190 /// For 'std::collections::HashMap', that will be 'std'.
191 QualifierStart(String),
192 /// A trait associated function (with no self parameter) or associated constant.
193 /// For 'test_mod::TestEnum::test_function', `Type` is the `test_mod::TestEnum` expression type
194 /// and `String` is the `test_function`
195 TraitAssocItem(hir::Type, String),
196 /// A trait method with self parameter.
197 /// For 'test_enum.test_method()', `Type` is the `test_enum` expression type
198 /// and `String` is the `test_method`
199 TraitMethod(hir::Type, String),
200}
201
202impl ImportCandidate {
203 pub(crate) fn for_method_call(
204 sema: &Semantics<RootDatabase>,
205 method_call: &ast::MethodCallExpr,
206 ) -> Option<Self> {
207 if sema.resolve_method_call(method_call).is_some() {
208 return None;
209 }
210 Some(Self::TraitMethod(
211 sema.type_of_expr(&method_call.receiver()?)?,
212 method_call.name_ref()?.syntax().to_string(),
213 ))
214 }
215
216 pub(crate) fn for_regular_path(
217 sema: &Semantics<RootDatabase>,
218 path_under_caret: &ast::Path,
219 ) -> Option<Self> {
220 if sema.resolve_path(path_under_caret).is_some() {
221 return None;
222 }
223
224 let segment = path_under_caret.segment()?;
225 if let Some(qualifier) = path_under_caret.qualifier() {
226 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
227 let qualifier_start_path =
228 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
229 if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) {
230 let qualifier_resolution = if qualifier_start_path == qualifier {
231 qualifier_start_resolution
232 } else {
233 sema.resolve_path(&qualifier)?
234 };
235 if let hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) =
236 qualifier_resolution
237 {
238 Some(ImportCandidate::TraitAssocItem(
239 assoc_item_path.ty(sema.db),
240 segment.syntax().to_string(),
241 ))
242 } else {
243 None
244 }
245 } else {
246 Some(ImportCandidate::QualifierStart(qualifier_start.syntax().to_string()))
247 }
248 } else {
249 Some(ImportCandidate::UnqualifiedName(
250 segment.syntax().descendants().find_map(ast::NameRef::cast)?.syntax().to_string(),
251 ))
252 }
253 }
254}