aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2020-10-14 20:40:51 +0100
committerLukas Wirth <[email protected]>2020-10-15 17:31:33 +0100
commitbc11475a2a0f06d5083a7032764a50e5f8ade130 (patch)
treebc9f1fde67ad95fbd8061779469897576f233cc3
parentd983f18df7dd484ec43510111169180d7abe038d (diff)
Properly qualify trait methods in qualify_path assist
-rw-r--r--crates/assists/src/handlers/auto_import.rs2
-rw-r--r--crates/assists/src/handlers/qualify_path.rs124
-rw-r--r--crates/assists/src/tests/generated.rs19
-rw-r--r--crates/assists/src/utils/import_assets.rs40
-rw-r--r--crates/syntax/src/ast/make.rs3
5 files changed, 118 insertions, 70 deletions
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index e595b5b93..13e390a1f 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -45,7 +45,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
45 let group = import_group_message(import_assets.import_candidate()); 45 let group = import_group_message(import_assets.import_candidate());
46 let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?; 46 let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?;
47 let syntax = scope.as_syntax_node(); 47 let syntax = scope.as_syntax_node();
48 for import in proposed_imports { 48 for (import, _) in proposed_imports {
49 acc.add_group( 49 acc.add_group(
50 &group, 50 &group,
51 AssistId("auto_import", AssistKind::QuickFix), 51 AssistId("auto_import", AssistKind::QuickFix),
diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs
index bbbf47bef..479ff498c 100644
--- a/crates/assists/src/handlers/qualify_path.rs
+++ b/crates/assists/src/handlers/qualify_path.rs
@@ -1,6 +1,12 @@
1use std::collections::BTreeSet; 1use std::iter;
2 2
3use syntax::{ast, AstNode, TextRange}; 3use hir::AsName;
4use ide_db::RootDatabase;
5use syntax::{
6 ast,
7 ast::{make, ArgListOwner},
8 AstNode, TextRange,
9};
4use test_utils::mark; 10use test_utils::mark;
5 11
6use crate::{ 12use crate::{
@@ -10,6 +16,8 @@ use crate::{
10 AssistId, AssistKind, GroupLabel, 16 AssistId, AssistKind, GroupLabel,
11}; 17};
12 18
19const ASSIST_ID: AssistId = AssistId("qualify_path", AssistKind::QuickFix);
20
13// Assist: qualify_path 21// Assist: qualify_path
14// 22//
15// If the name is unresolved, provides all possible qualified paths for it. 23// If the name is unresolved, provides all possible qualified paths for it.
@@ -53,30 +61,14 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
53 ImportCandidate::UnqualifiedName(candidate) => { 61 ImportCandidate::UnqualifiedName(candidate) => {
54 qualify_path_unqualified_name(acc, proposed_imports, range, &candidate.name) 62 qualify_path_unqualified_name(acc, proposed_imports, range, &candidate.name)
55 } 63 }
56 ImportCandidate::TraitAssocItem(candidate) => { 64 ImportCandidate::TraitAssocItem(_) => {
57 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; 65 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
58 let (qualifier, segment) = (path.qualifier()?, path.segment()?); 66 let (qualifier, segment) = (path.qualifier()?, path.segment()?);
59 qualify_path_trait_assoc_item( 67 qualify_path_trait_assoc_item(acc, proposed_imports, range, qualifier, segment)
60 acc,
61 proposed_imports,
62 range,
63 qualifier,
64 segment,
65 &candidate.name,
66 )
67 } 68 }
68 ImportCandidate::TraitMethod(candidate) => { 69 ImportCandidate::TraitMethod(_) => {
69 let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?; 70 let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?;
70 let receiver = mcall_expr.receiver()?; 71 qualify_path_trait_method(acc, ctx.sema.db, proposed_imports, range, mcall_expr)?;
71 let name_ref = mcall_expr.name_ref()?;
72 qualify_path_trait_method(
73 acc,
74 proposed_imports,
75 range,
76 receiver,
77 name_ref,
78 &candidate.name,
79 )
80 } 72 }
81 }; 73 };
82 Some(()) 74 Some(())
@@ -85,17 +77,17 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
85// a test that covers this -> `associated_struct_const` 77// a test that covers this -> `associated_struct_const`
86fn qualify_path_qualifier_start( 78fn qualify_path_qualifier_start(
87 acc: &mut Assists, 79 acc: &mut Assists,
88 proposed_imports: BTreeSet<hir::ModPath>, 80 proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>,
89 range: TextRange, 81 range: TextRange,
90 segment: ast::PathSegment, 82 segment: ast::PathSegment,
91 qualifier_start: &str, 83 qualifier_start: &ast::NameRef,
92) { 84) {
93 mark::hit!(qualify_path_qualifier_start); 85 mark::hit!(qualify_path_qualifier_start);
94 let group_label = GroupLabel(format!("Qualify {}", qualifier_start)); 86 let group_label = GroupLabel(format!("Qualify {}", qualifier_start));
95 for import in proposed_imports { 87 for (import, _) in proposed_imports {
96 acc.add_group( 88 acc.add_group(
97 &group_label, 89 &group_label,
98 AssistId("qualify_path", AssistKind::QuickFix), 90 ASSIST_ID,
99 format!("Qualify with `{}`", &import), 91 format!("Qualify with `{}`", &import),
100 range, 92 range,
101 |builder| { 93 |builder| {
@@ -109,16 +101,16 @@ fn qualify_path_qualifier_start(
109// a test that covers this -> `applicable_when_found_an_import_partial` 101// a test that covers this -> `applicable_when_found_an_import_partial`
110fn qualify_path_unqualified_name( 102fn qualify_path_unqualified_name(
111 acc: &mut Assists, 103 acc: &mut Assists,
112 proposed_imports: BTreeSet<hir::ModPath>, 104 proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>,
113 range: TextRange, 105 range: TextRange,
114 name: &str, 106 name: &ast::NameRef,
115) { 107) {
116 mark::hit!(qualify_path_unqualified_name); 108 mark::hit!(qualify_path_unqualified_name);
117 let group_label = GroupLabel(format!("Qualify {}", name)); 109 let group_label = GroupLabel(format!("Qualify {}", name));
118 for import in proposed_imports { 110 for (import, _) in proposed_imports {
119 acc.add_group( 111 acc.add_group(
120 &group_label, 112 &group_label,
121 AssistId("qualify_path", AssistKind::QuickFix), 113 ASSIST_ID,
122 format!("Qualify as `{}`", &import), 114 format!("Qualify as `{}`", &import),
123 range, 115 range,
124 |builder| builder.replace(range, mod_path_to_ast(&import).to_string()), 116 |builder| builder.replace(range, mod_path_to_ast(&import).to_string()),
@@ -129,18 +121,17 @@ fn qualify_path_unqualified_name(
129// a test that covers this -> `associated_trait_const` 121// a test that covers this -> `associated_trait_const`
130fn qualify_path_trait_assoc_item( 122fn qualify_path_trait_assoc_item(
131 acc: &mut Assists, 123 acc: &mut Assists,
132 proposed_imports: BTreeSet<hir::ModPath>, 124 proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>,
133 range: TextRange, 125 range: TextRange,
134 qualifier: ast::Path, 126 qualifier: ast::Path,
135 segment: ast::PathSegment, 127 segment: ast::PathSegment,
136 trait_assoc_item_name: &str,
137) { 128) {
138 mark::hit!(qualify_path_trait_assoc_item); 129 mark::hit!(qualify_path_trait_assoc_item);
139 let group_label = GroupLabel(format!("Qualify {}", trait_assoc_item_name)); 130 let group_label = GroupLabel(format!("Qualify {}", &segment));
140 for import in proposed_imports { 131 for (import, _) in proposed_imports {
141 acc.add_group( 132 acc.add_group(
142 &group_label, 133 &group_label,
143 AssistId("qualify_path", AssistKind::QuickFix), 134 ASSIST_ID,
144 format!("Qualify with cast as `{}`", &import), 135 format!("Qualify with cast as `{}`", &import),
145 range, 136 range,
146 |builder| { 137 |builder| {
@@ -154,33 +145,74 @@ fn qualify_path_trait_assoc_item(
154// a test that covers this -> `trait_method` 145// a test that covers this -> `trait_method`
155fn qualify_path_trait_method( 146fn qualify_path_trait_method(
156 acc: &mut Assists, 147 acc: &mut Assists,
157 proposed_imports: BTreeSet<hir::ModPath>, 148 db: &RootDatabase,
149 proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>,
158 range: TextRange, 150 range: TextRange,
159 receiver: ast::Expr, 151 mcall_expr: ast::MethodCallExpr,
160 name_ref: ast::NameRef, 152) -> Option<()> {
161 trait_method_name: &str,
162) {
163 mark::hit!(qualify_path_trait_method); 153 mark::hit!(qualify_path_trait_method);
154
155 let receiver = mcall_expr.receiver()?;
156 let trait_method_name = mcall_expr.name_ref()?;
157 let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args());
164 let group_label = GroupLabel(format!("Qualify {}", trait_method_name)); 158 let group_label = GroupLabel(format!("Qualify {}", trait_method_name));
165 for import in proposed_imports { 159 let find_method = |item: &hir::AssocItem| {
160 item.name(db).map(|name| name == trait_method_name.as_name()).unwrap_or(false)
161 };
162 for (import, trait_) in proposed_imports.into_iter().filter_map(filter_trait) {
166 acc.add_group( 163 acc.add_group(
167 &group_label, 164 &group_label,
168 AssistId("qualify_path", AssistKind::QuickFix), // < Does this still count as quickfix? 165 ASSIST_ID,
169 format!("Qualify `{}`", &import), 166 format!("Qualify `{}`", &import),
170 range, 167 range,
171 |builder| { 168 |builder| {
172 let import = mod_path_to_ast(&import); 169 let import = mod_path_to_ast(&import);
173 // TODO: check the receiver self type and emit refs accordingly, don't discard other function parameters 170 if let Some(hir::AssocItem::Function(method)) =
174 builder.replace(range, format!("{}::{}(&{})", import, name_ref, receiver)); 171 trait_.items(db).into_iter().find(find_method)
172 {
173 if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
174 let receiver = receiver.clone();
175 let receiver = match self_access {
176 hir::Access::Shared => make::expr_ref(receiver, false),
177 hir::Access::Exclusive => make::expr_ref(receiver, true),
178 hir::Access::Owned => receiver,
179 };
180 builder.replace(
181 range,
182 format!(
183 "{}::{}{}",
184 import,
185 trait_method_name,
186 match arg_list.clone() {
187 Some(args) => make::arg_list(iter::once(receiver).chain(args)),
188 None => make::arg_list(iter::once(receiver)),
189 }
190 ),
191 );
192 }
193 }
175 }, 194 },
176 ); 195 );
177 } 196 }
197 Some(())
198}
199
200fn filter_trait(
201 (import, trait_): (hir::ModPath, hir::ItemInNs),
202) -> Option<(hir::ModPath, hir::Trait)> {
203 if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(trait_.as_module_def_id()?) {
204 Some((import, trait_))
205 } else {
206 None
207 }
178} 208}
179 209
180#[cfg(test)] 210#[cfg(test)]
181mod tests { 211mod tests {
182 use super::*;
183 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; 212 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
213
214 use super::*;
215
184 #[test] 216 #[test]
185 fn applicable_when_found_an_import_partial() { 217 fn applicable_when_found_an_import_partial() {
186 mark::check!(qualify_path_unqualified_name); 218 mark::check!(qualify_path_unqualified_name);
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 41f536574..7d5618263 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -713,6 +713,25 @@ fn handle(action: Action) {
713} 713}
714 714
715#[test] 715#[test]
716fn doctest_qualify_path() {
717 check_doc_test(
718 "qualify_path",
719 r#####"
720fn main() {
721 let map = HashMap<|>::new();
722}
723pub mod std { pub mod collections { pub struct HashMap { } } }
724"#####,
725 r#####"
726fn main() {
727 let map = std::collections::HashMap::new();
728}
729pub mod std { pub mod collections { pub struct HashMap { } } }
730"#####,
731 )
732}
733
734#[test]
716fn doctest_remove_dbg() { 735fn doctest_remove_dbg() {
717 check_doc_test( 736 check_doc_test(
718 "remove_dbg", 737 "remove_dbg",
diff --git a/crates/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs
index 601f51098..23db3a74b 100644
--- a/crates/assists/src/utils/import_assets.rs
+++ b/crates/assists/src/utils/import_assets.rs
@@ -1,6 +1,4 @@
1//! Look up accessible paths for items. 1//! Look up accessible paths for items.
2use std::collections::BTreeSet;
3
4use either::Either; 2use either::Either;
5use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; 3use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics};
6use ide_db::{imports_locator, RootDatabase}; 4use ide_db::{imports_locator, RootDatabase};
@@ -29,12 +27,12 @@ pub(crate) enum ImportCandidate {
29#[derive(Debug)] 27#[derive(Debug)]
30pub(crate) struct TraitImportCandidate { 28pub(crate) struct TraitImportCandidate {
31 pub ty: hir::Type, 29 pub ty: hir::Type,
32 pub name: String, 30 pub name: ast::NameRef,
33} 31}
34 32
35#[derive(Debug)] 33#[derive(Debug)]
36pub(crate) struct PathImportCandidate { 34pub(crate) struct PathImportCandidate {
37 pub name: String, 35 pub name: ast::NameRef,
38} 36}
39 37
40#[derive(Debug)] 38#[derive(Debug)]
@@ -86,9 +84,9 @@ impl ImportAssets {
86 fn get_search_query(&self) -> &str { 84 fn get_search_query(&self) -> &str {
87 match &self.import_candidate { 85 match &self.import_candidate {
88 ImportCandidate::UnqualifiedName(candidate) 86 ImportCandidate::UnqualifiedName(candidate)
89 | ImportCandidate::QualifierStart(candidate) => &candidate.name, 87 | ImportCandidate::QualifierStart(candidate) => candidate.name.text(),
90 ImportCandidate::TraitAssocItem(candidate) 88 ImportCandidate::TraitAssocItem(candidate)
91 | ImportCandidate::TraitMethod(candidate) => &candidate.name, 89 | ImportCandidate::TraitMethod(candidate) => candidate.name.text(),
92 } 90 }
93 } 91 }
94 92
@@ -96,7 +94,7 @@ impl ImportAssets {
96 &self, 94 &self,
97 sema: &Semantics<RootDatabase>, 95 sema: &Semantics<RootDatabase>,
98 config: &InsertUseConfig, 96 config: &InsertUseConfig,
99 ) -> BTreeSet<hir::ModPath> { 97 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
100 let _p = profile::span("import_assists::search_for_imports"); 98 let _p = profile::span("import_assists::search_for_imports");
101 self.search_for(sema, Some(config.prefix_kind)) 99 self.search_for(sema, Some(config.prefix_kind))
102 } 100 }
@@ -106,7 +104,7 @@ impl ImportAssets {
106 pub(crate) fn search_for_relative_paths( 104 pub(crate) fn search_for_relative_paths(
107 &self, 105 &self,
108 sema: &Semantics<RootDatabase>, 106 sema: &Semantics<RootDatabase>,
109 ) -> BTreeSet<hir::ModPath> { 107 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
110 let _p = profile::span("import_assists::search_for_relative_paths"); 108 let _p = profile::span("import_assists::search_for_relative_paths");
111 self.search_for(sema, None) 109 self.search_for(sema, None)
112 } 110 }
@@ -115,7 +113,7 @@ impl ImportAssets {
115 &self, 113 &self,
116 sema: &Semantics<RootDatabase>, 114 sema: &Semantics<RootDatabase>,
117 prefixed: Option<hir::PrefixKind>, 115 prefixed: Option<hir::PrefixKind>,
118 ) -> BTreeSet<hir::ModPath> { 116 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
119 let db = sema.db; 117 let db = sema.db;
120 let mut trait_candidates = FxHashSet::default(); 118 let mut trait_candidates = FxHashSet::default();
121 let current_crate = self.module_with_name_to_import.krate(); 119 let current_crate = self.module_with_name_to_import.krate();
@@ -181,7 +179,7 @@ impl ImportAssets {
181 } 179 }
182 }; 180 };
183 181
184 imports_locator::find_imports(sema, current_crate, &self.get_search_query()) 182 let mut res = imports_locator::find_imports(sema, current_crate, &self.get_search_query())
185 .into_iter() 183 .into_iter()
186 .filter_map(filter) 184 .filter_map(filter)
187 .filter_map(|candidate| { 185 .filter_map(|candidate| {
@@ -191,10 +189,13 @@ impl ImportAssets {
191 } else { 189 } else {
192 self.module_with_name_to_import.find_use_path(db, item) 190 self.module_with_name_to_import.find_use_path(db, item)
193 } 191 }
192 .map(|path| (path, item))
194 }) 193 })
195 .filter(|use_path| !use_path.segments.is_empty()) 194 .filter(|(use_path, _)| !use_path.segments.is_empty())
196 .take(20) 195 .take(20)
197 .collect::<BTreeSet<_>>() 196 .collect::<Vec<_>>();
197 res.sort_by_key(|(path, _)| path.clone());
198 res
198 } 199 }
199 200
200 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> { 201 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> {
@@ -215,7 +216,7 @@ impl ImportCandidate {
215 Some(_) => None, 216 Some(_) => None,
216 None => Some(Self::TraitMethod(TraitImportCandidate { 217 None => Some(Self::TraitMethod(TraitImportCandidate {
217 ty: sema.type_of_expr(&method_call.receiver()?)?, 218 ty: sema.type_of_expr(&method_call.receiver()?)?,
218 name: method_call.name_ref()?.syntax().to_string(), 219 name: method_call.name_ref()?,
219 })), 220 })),
220 } 221 }
221 } 222 }
@@ -243,24 +244,17 @@ impl ImportCandidate {
243 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => { 244 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
244 ImportCandidate::TraitAssocItem(TraitImportCandidate { 245 ImportCandidate::TraitAssocItem(TraitImportCandidate {
245 ty: assoc_item_path.ty(sema.db), 246 ty: assoc_item_path.ty(sema.db),
246 name: segment.syntax().to_string(), 247 name: segment.name_ref()?,
247 }) 248 })
248 } 249 }
249 _ => return None, 250 _ => return None,
250 } 251 }
251 } else { 252 } else {
252 ImportCandidate::QualifierStart(PathImportCandidate { 253 ImportCandidate::QualifierStart(PathImportCandidate { name: qualifier_start })
253 name: qualifier_start.syntax().to_string(),
254 })
255 } 254 }
256 } else { 255 } else {
257 ImportCandidate::UnqualifiedName(PathImportCandidate { 256 ImportCandidate::UnqualifiedName(PathImportCandidate {
258 name: segment 257 name: segment.syntax().descendants().find_map(ast::NameRef::cast)?,
259 .syntax()
260 .descendants()
261 .find_map(ast::NameRef::cast)?
262 .syntax()
263 .to_string(),
264 }) 258 })
265 }; 259 };
266 Some(candidate) 260 Some(candidate)
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 74dbdfaf7..5b06cb767 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -172,6 +172,9 @@ pub fn expr_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr {
172pub fn expr_method_call(receiver: ast::Expr, method: &str, arg_list: ast::ArgList) -> ast::Expr { 172pub fn expr_method_call(receiver: ast::Expr, method: &str, arg_list: ast::ArgList) -> ast::Expr {
173 expr_from_text(&format!("{}.{}{}", receiver, method, arg_list)) 173 expr_from_text(&format!("{}.{}{}", receiver, method, arg_list))
174} 174}
175pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr {
176 expr_from_text(&if exclusive { format!("&mut {}", expr) } else { format!("&{}", expr) })
177}
175fn expr_from_text(text: &str) -> ast::Expr { 178fn expr_from_text(text: &str) -> ast::Expr {
176 ast_from_text(&format!("const C: () = {};", text)) 179 ast_from_text(&format!("const C: () = {};", text))
177} 180}