aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_db
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_db')
-rw-r--r--crates/ide_db/src/defs.rs45
-rw-r--r--crates/ide_db/src/helpers.rs1
-rw-r--r--crates/ide_db/src/helpers/import_assets.rs267
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs18
-rw-r--r--crates/ide_db/src/imports_locator.rs6
-rw-r--r--crates/ide_db/src/search.rs2
-rw-r--r--crates/ide_db/src/source_change.rs56
7 files changed, 358 insertions, 37 deletions
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs
index d68fe42b0..231e886a9 100644
--- a/crates/ide_db/src/defs.rs
+++ b/crates/ide_db/src/defs.rs
@@ -10,7 +10,7 @@ use hir::{
10 Module, ModuleDef, Name, PathResolution, Semantics, Visibility, 10 Module, ModuleDef, Name, PathResolution, Semantics, Visibility,
11}; 11};
12use syntax::{ 12use syntax::{
13 ast::{self, AstNode}, 13 ast::{self, AstNode, PathSegmentKind},
14 match_ast, SyntaxKind, SyntaxNode, 14 match_ast, SyntaxKind, SyntaxNode,
15}; 15};
16 16
@@ -117,6 +117,13 @@ impl NameClass {
117 } 117 }
118 } 118 }
119 119
120 pub fn classify_self_param(
121 sema: &Semantics<RootDatabase>,
122 self_param: &ast::SelfParam,
123 ) -> Option<NameClass> {
124 sema.to_def(self_param).map(Definition::Local).map(NameClass::Definition)
125 }
126
120 pub fn classify(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option<NameClass> { 127 pub fn classify(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option<NameClass> {
121 let _p = profile::span("classify_name"); 128 let _p = profile::span("classify_name");
122 129
@@ -135,24 +142,26 @@ impl NameClass {
135 let path = use_tree.path()?; 142 let path = use_tree.path()?;
136 let path_segment = path.segment()?; 143 let path_segment = path.segment()?;
137 let name_ref_class = path_segment 144 let name_ref_class = path_segment
138 .name_ref() 145 .kind()
139 // The rename might be from a `self` token, so fallback to the name higher 146 .and_then(|kind| {
140 // in the use tree. 147 match kind {
141 .or_else(||{ 148 // The rename might be from a `self` token, so fallback to the name higher
142 if path_segment.self_token().is_none() { 149 // in the use tree.
143 return None; 150 PathSegmentKind::SelfKw => {
151 let use_tree = use_tree
152 .syntax()
153 .parent()
154 .as_ref()
155 // Skip over UseTreeList
156 .and_then(SyntaxNode::parent)
157 .and_then(ast::UseTree::cast)?;
158 let path = use_tree.path()?;
159 let path_segment = path.segment()?;
160 path_segment.name_ref()
161 },
162 PathSegmentKind::Name(name_ref) => Some(name_ref),
163 _ => return None,
144 } 164 }
145
146 let use_tree = use_tree
147 .syntax()
148 .parent()
149 .as_ref()
150 // Skip over UseTreeList
151 .and_then(SyntaxNode::parent)
152 .and_then(ast::UseTree::cast)?;
153 let path = use_tree.path()?;
154 let path_segment = path.segment()?;
155 path_segment.name_ref()
156 }) 165 })
157 .and_then(|name_ref| NameRefClass::classify(sema, &name_ref))?; 166 .and_then(|name_ref| NameRefClass::classify(sema, &name_ref))?;
158 167
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index c6763ae36..0dcc4dd29 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -1,5 +1,6 @@
1//! A module with ide helpers for high-level ide features. 1//! A module with ide helpers for high-level ide features.
2pub mod insert_use; 2pub mod insert_use;
3pub mod import_assets;
3 4
4use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait}; 5use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait};
5use syntax::ast::{self, make}; 6use syntax::ast::{self, make};
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
new file mode 100644
index 000000000..edc3da318
--- /dev/null
+++ b/crates/ide_db/src/helpers/import_assets.rs
@@ -0,0 +1,267 @@
1//! Look up accessible paths for items.
2use either::Either;
3use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics};
4use rustc_hash::FxHashSet;
5use syntax::{ast, AstNode, SyntaxNode};
6
7use crate::{imports_locator, RootDatabase};
8
9use super::insert_use::InsertUseConfig;
10
11#[derive(Debug)]
12pub enum ImportCandidate {
13 // A path, qualified (`std::collections::HashMap`) or not (`HashMap`).
14 Path(PathImportCandidate),
15 /// A trait associated function (with no self parameter) or associated constant.
16 /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type
17 /// and `name` is the `test_function`
18 TraitAssocItem(TraitImportCandidate),
19 /// A trait method with self parameter.
20 /// For 'test_enum.test_method()', `ty` is the `test_enum` expression type
21 /// and `name` is the `test_method`
22 TraitMethod(TraitImportCandidate),
23}
24
25#[derive(Debug)]
26pub struct TraitImportCandidate {
27 pub ty: hir::Type,
28 pub name: ast::NameRef,
29}
30
31#[derive(Debug)]
32pub struct PathImportCandidate {
33 pub qualifier: Option<ast::Path>,
34 pub name: ast::NameRef,
35}
36
37#[derive(Debug)]
38pub struct ImportAssets {
39 import_candidate: ImportCandidate,
40 module_with_name_to_import: hir::Module,
41 syntax_under_caret: SyntaxNode,
42}
43
44impl ImportAssets {
45 pub fn for_method_call(
46 method_call: ast::MethodCallExpr,
47 sema: &Semantics<RootDatabase>,
48 ) -> 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 {
52 import_candidate: ImportCandidate::for_method_call(sema, &method_call)?,
53 module_with_name_to_import,
54 syntax_under_caret,
55 })
56 }
57
58 pub fn for_regular_path(
59 path_under_caret: ast::Path,
60 sema: &Semantics<RootDatabase>,
61 ) -> Option<Self> {
62 let syntax_under_caret = path_under_caret.syntax().to_owned();
63 if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
64 return None;
65 }
66
67 let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
68 Some(Self {
69 import_candidate: ImportCandidate::for_regular_path(sema, &path_under_caret)?,
70 module_with_name_to_import,
71 syntax_under_caret,
72 })
73 }
74
75 pub fn syntax_under_caret(&self) -> &SyntaxNode {
76 &self.syntax_under_caret
77 }
78
79 pub fn import_candidate(&self) -> &ImportCandidate {
80 &self.import_candidate
81 }
82
83 fn get_search_query(&self) -> &str {
84 match &self.import_candidate {
85 ImportCandidate::Path(candidate) => candidate.name.text(),
86 ImportCandidate::TraitAssocItem(candidate)
87 | ImportCandidate::TraitMethod(candidate) => candidate.name.text(),
88 }
89 }
90
91 pub fn search_for_imports(
92 &self,
93 sema: &Semantics<RootDatabase>,
94 config: &InsertUseConfig,
95 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
96 let _p = profile::span("import_assists::search_for_imports");
97 self.search_for(sema, Some(config.prefix_kind))
98 }
99
100 /// 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(
103 &self,
104 sema: &Semantics<RootDatabase>,
105 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
106 let _p = profile::span("import_assists::search_for_relative_paths");
107 self.search_for(sema, None)
108 }
109
110 fn search_for(
111 &self,
112 sema: &Semantics<RootDatabase>,
113 prefixed: Option<hir::PrefixKind>,
114 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
115 let db = sema.db;
116 let mut trait_candidates = FxHashSet::default();
117 let current_crate = self.module_with_name_to_import.krate();
118
119 let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| {
120 trait_candidates.clear();
121 match &self.import_candidate {
122 ImportCandidate::TraitAssocItem(trait_candidate) => {
123 let located_assoc_item = match candidate {
124 Either::Left(ModuleDef::Function(located_function)) => {
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,
131 }
132 .map(|assoc| assoc.container(db))
133 .and_then(Self::assoc_to_trait)?;
134
135 trait_candidates.insert(located_assoc_item.into());
136
137 trait_candidate
138 .ty
139 .iterate_path_candidates(
140 db,
141 current_crate,
142 &trait_candidates,
143 None,
144 |_, assoc| Self::assoc_to_trait(assoc.container(db)),
145 )
146 .map(ModuleDef::from)
147 .map(Either::Left)
148 }
149 ImportCandidate::TraitMethod(trait_candidate) => {
150 let located_assoc_item =
151 if let Either::Left(ModuleDef::Function(located_function)) = candidate {
152 located_function
153 .as_assoc_item(db)
154 .map(|assoc| assoc.container(db))
155 .and_then(Self::assoc_to_trait)
156 } else {
157 None
158 }?;
159
160 trait_candidates.insert(located_assoc_item.into());
161
162 trait_candidate
163 .ty
164 .iterate_method_candidates(
165 db,
166 current_crate,
167 &trait_candidates,
168 None,
169 |_, function| {
170 Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
171 },
172 )
173 .map(ModuleDef::from)
174 .map(Either::Left)
175 }
176 _ => Some(candidate),
177 }
178 };
179
180 let mut res = imports_locator::find_exact_imports(
181 sema,
182 current_crate,
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 }
193 .map(|path| (path, item))
194 })
195 .filter(|(use_path, _)| use_path.len() > 1)
196 .take(20)
197 .collect::<Vec<_>>();
198 res.sort_by_key(|(path, _)| path.clone());
199 res
200 }
201
202 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> {
203 if let AssocItemContainer::Trait(extracted_trait) = assoc {
204 Some(extracted_trait)
205 } else {
206 None
207 }
208 }
209}
210
211impl ImportCandidate {
212 fn for_method_call(
213 sema: &Semantics<RootDatabase>,
214 method_call: &ast::MethodCallExpr,
215 ) -> Option<Self> {
216 match sema.resolve_method_call(method_call) {
217 Some(_) => None,
218 None => Some(Self::TraitMethod(TraitImportCandidate {
219 ty: sema.type_of_expr(&method_call.receiver()?)?,
220 name: method_call.name_ref()?,
221 })),
222 }
223 }
224
225 fn for_regular_path(
226 sema: &Semantics<RootDatabase>,
227 path_under_caret: &ast::Path,
228 ) -> Option<Self> {
229 if sema.resolve_path(path_under_caret).is_some() {
230 return None;
231 }
232
233 let segment = path_under_caret.segment()?;
234 let candidate = if let Some(qualifier) = path_under_caret.qualifier() {
235 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
236 let qualifier_start_path =
237 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
238 if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) {
239 let qualifier_resolution = if qualifier_start_path == qualifier {
240 qualifier_start_resolution
241 } else {
242 sema.resolve_path(&qualifier)?
243 };
244 match qualifier_resolution {
245 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
246 ImportCandidate::TraitAssocItem(TraitImportCandidate {
247 ty: assoc_item_path.ty(sema.db),
248 name: segment.name_ref()?,
249 })
250 }
251 _ => return None,
252 }
253 } else {
254 ImportCandidate::Path(PathImportCandidate {
255 qualifier: Some(qualifier),
256 name: qualifier_start,
257 })
258 }
259 } else {
260 ImportCandidate::Path(PathImportCandidate {
261 qualifier: None,
262 name: segment.syntax().descendants().find_map(ast::NameRef::cast)?,
263 })
264 };
265 Some(candidate)
266 }
267}
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs
index d6b498be3..877d4f1c7 100644
--- a/crates/ide_db/src/helpers/insert_use.rs
+++ b/crates/ide_db/src/helpers/insert_use.rs
@@ -15,6 +15,12 @@ use syntax::{
15}; 15};
16use test_utils::mark; 16use test_utils::mark;
17 17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct InsertUseConfig {
20 pub merge: Option<MergeBehavior>,
21 pub prefix_kind: hir::PrefixKind,
22}
23
18#[derive(Debug, Clone)] 24#[derive(Debug, Clone)]
19pub enum ImportScope { 25pub enum ImportScope {
20 File(ast::SourceFile), 26 File(ast::SourceFile),
@@ -97,7 +103,7 @@ pub fn insert_use<'a>(
97) -> SyntaxRewriter<'a> { 103) -> SyntaxRewriter<'a> {
98 let _p = profile::span("insert_use"); 104 let _p = profile::span("insert_use");
99 let mut rewriter = SyntaxRewriter::default(); 105 let mut rewriter = SyntaxRewriter::default();
100 let use_item = make::use_(make::use_tree(path.clone(), None, None, false)); 106 let use_item = make::use_(None, make::use_tree(path.clone(), None, None, false));
101 // merge into existing imports if possible 107 // merge into existing imports if possible
102 if let Some(mb) = merge { 108 if let Some(mb) = merge {
103 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) {
@@ -444,8 +450,14 @@ fn use_tree_path_cmp(a: &ast::Path, a_has_tl: bool, b: &ast::Path, b_has_tl: boo
444} 450}
445 451
446fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering { 452fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
447 let a = a.name_ref(); 453 let a = a.kind().and_then(|kind| match kind {
448 let b = b.name_ref(); 454 PathSegmentKind::Name(name_ref) => Some(name_ref),
455 _ => None,
456 });
457 let b = b.kind().and_then(|kind| match kind {
458 PathSegmentKind::Name(name_ref) => Some(name_ref),
459 _ => None,
460 });
449 a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text)) 461 a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text))
450} 462}
451 463
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs
index e9f23adf8..d111fba92 100644
--- a/crates/ide_db/src/imports_locator.rs
+++ b/crates/ide_db/src/imports_locator.rs
@@ -12,6 +12,8 @@ use crate::{
12use either::Either; 12use either::Either;
13use rustc_hash::FxHashSet; 13use rustc_hash::FxHashSet;
14 14
15const QUERY_SEARCH_LIMIT: usize = 40;
16
15pub fn find_exact_imports<'a>( 17pub fn find_exact_imports<'a>(
16 sema: &Semantics<'a, RootDatabase>, 18 sema: &Semantics<'a, RootDatabase>,
17 krate: Crate, 19 krate: Crate,
@@ -24,11 +26,11 @@ pub fn find_exact_imports<'a>(
24 { 26 {
25 let mut local_query = symbol_index::Query::new(name_to_import.clone()); 27 let mut local_query = symbol_index::Query::new(name_to_import.clone());
26 local_query.exact(); 28 local_query.exact();
27 local_query.limit(40); 29 local_query.limit(QUERY_SEARCH_LIMIT);
28 local_query 30 local_query
29 }, 31 },
30 import_map::Query::new(name_to_import) 32 import_map::Query::new(name_to_import)
31 .limit(40) 33 .limit(QUERY_SEARCH_LIMIT)
32 .name_only() 34 .name_only()
33 .search_mode(import_map::SearchMode::Equals) 35 .search_mode(import_map::SearchMode::Equals)
34 .case_sensitive(), 36 .case_sensitive(),
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index b5fa46642..0ecb13a64 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -65,7 +65,7 @@ pub enum ReferenceKind {
65 FieldShorthandForLocal, 65 FieldShorthandForLocal,
66 StructLiteral, 66 StructLiteral,
67 RecordFieldExprOrPat, 67 RecordFieldExprOrPat,
68 SelfKw, 68 SelfParam,
69 EnumLiteral, 69 EnumLiteral,
70 Lifetime, 70 Lifetime,
71 Other, 71 Other,
diff --git a/crates/ide_db/src/source_change.rs b/crates/ide_db/src/source_change.rs
index 10c0abdac..b1f87731b 100644
--- a/crates/ide_db/src/source_change.rs
+++ b/crates/ide_db/src/source_change.rs
@@ -3,12 +3,19 @@
3//! 3//!
4//! It can be viewed as a dual for `AnalysisChange`. 4//! It can be viewed as a dual for `AnalysisChange`.
5 5
6use std::{
7 collections::hash_map::Entry,
8 iter::{self, FromIterator},
9};
10
6use base_db::{AnchoredPathBuf, FileId}; 11use base_db::{AnchoredPathBuf, FileId};
12use rustc_hash::FxHashMap;
13use stdx::assert_never;
7use text_edit::TextEdit; 14use text_edit::TextEdit;
8 15
9#[derive(Default, Debug, Clone)] 16#[derive(Default, Debug, Clone)]
10pub struct SourceChange { 17pub struct SourceChange {
11 pub source_file_edits: Vec<SourceFileEdit>, 18 pub source_file_edits: FxHashMap<FileId, TextEdit>,
12 pub file_system_edits: Vec<FileSystemEdit>, 19 pub file_system_edits: Vec<FileSystemEdit>,
13 pub is_snippet: bool, 20 pub is_snippet: bool,
14} 21}
@@ -17,27 +24,50 @@ impl SourceChange {
17 /// Creates a new SourceChange with the given label 24 /// Creates a new SourceChange with the given label
18 /// from the edits. 25 /// from the edits.
19 pub fn from_edits( 26 pub fn from_edits(
20 source_file_edits: Vec<SourceFileEdit>, 27 source_file_edits: FxHashMap<FileId, TextEdit>,
21 file_system_edits: Vec<FileSystemEdit>, 28 file_system_edits: Vec<FileSystemEdit>,
22 ) -> Self { 29 ) -> Self {
23 SourceChange { source_file_edits, file_system_edits, is_snippet: false } 30 SourceChange { source_file_edits, file_system_edits, is_snippet: false }
24 } 31 }
25}
26 32
27#[derive(Debug, Clone)] 33 pub fn from_text_edit(file_id: FileId, edit: TextEdit) -> Self {
28pub struct SourceFileEdit { 34 SourceChange {
29 pub file_id: FileId, 35 source_file_edits: FxHashMap::from_iter(iter::once((file_id, edit))),
30 pub edit: TextEdit, 36 ..Default::default()
37 }
38 }
39
40 pub fn insert_source_edit(&mut self, file_id: FileId, edit: TextEdit) {
41 match self.source_file_edits.entry(file_id) {
42 Entry::Occupied(mut entry) => {
43 assert_never!(
44 entry.get_mut().union(edit).is_err(),
45 "overlapping edits for same file"
46 );
47 }
48 Entry::Vacant(entry) => {
49 entry.insert(edit);
50 }
51 }
52 }
53
54 pub fn push_file_system_edit(&mut self, edit: FileSystemEdit) {
55 self.file_system_edits.push(edit);
56 }
57
58 pub fn get_source_edit(&self, file_id: FileId) -> Option<&TextEdit> {
59 self.source_file_edits.get(&file_id)
60 }
31} 61}
32 62
33impl From<SourceFileEdit> for SourceChange { 63impl Extend<(FileId, TextEdit)> for SourceChange {
34 fn from(edit: SourceFileEdit) -> SourceChange { 64 fn extend<T: IntoIterator<Item = (FileId, TextEdit)>>(&mut self, iter: T) {
35 vec![edit].into() 65 iter.into_iter().for_each(|(file_id, edit)| self.insert_source_edit(file_id, edit));
36 } 66 }
37} 67}
38 68
39impl From<Vec<SourceFileEdit>> for SourceChange { 69impl From<FxHashMap<FileId, TextEdit>> for SourceChange {
40 fn from(source_file_edits: Vec<SourceFileEdit>) -> SourceChange { 70 fn from(source_file_edits: FxHashMap<FileId, TextEdit>) -> SourceChange {
41 SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false } 71 SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false }
42 } 72 }
43} 73}
@@ -51,7 +81,7 @@ pub enum FileSystemEdit {
51impl From<FileSystemEdit> for SourceChange { 81impl From<FileSystemEdit> for SourceChange {
52 fn from(edit: FileSystemEdit) -> SourceChange { 82 fn from(edit: FileSystemEdit) -> SourceChange {
53 SourceChange { 83 SourceChange {
54 source_file_edits: Vec::new(), 84 source_file_edits: Default::default(),
55 file_system_edits: vec![edit], 85 file_system_edits: vec![edit],
56 is_snippet: false, 86 is_snippet: false,
57 } 87 }