diff options
author | Lukas Wirth <[email protected]> | 2020-10-14 20:40:51 +0100 |
---|---|---|
committer | Lukas Wirth <[email protected]> | 2020-10-15 17:31:33 +0100 |
commit | bc11475a2a0f06d5083a7032764a50e5f8ade130 (patch) | |
tree | bc9f1fde67ad95fbd8061779469897576f233cc3 /crates/assists/src/handlers | |
parent | d983f18df7dd484ec43510111169180d7abe038d (diff) |
Properly qualify trait methods in qualify_path assist
Diffstat (limited to 'crates/assists/src/handlers')
-rw-r--r-- | crates/assists/src/handlers/auto_import.rs | 2 | ||||
-rw-r--r-- | crates/assists/src/handlers/qualify_path.rs | 124 |
2 files changed, 79 insertions, 47 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 @@ | |||
1 | use std::collections::BTreeSet; | 1 | use std::iter; |
2 | 2 | ||
3 | use syntax::{ast, AstNode, TextRange}; | 3 | use hir::AsName; |
4 | use ide_db::RootDatabase; | ||
5 | use syntax::{ | ||
6 | ast, | ||
7 | ast::{make, ArgListOwner}, | ||
8 | AstNode, TextRange, | ||
9 | }; | ||
4 | use test_utils::mark; | 10 | use test_utils::mark; |
5 | 11 | ||
6 | use crate::{ | 12 | use crate::{ |
@@ -10,6 +16,8 @@ use crate::{ | |||
10 | AssistId, AssistKind, GroupLabel, | 16 | AssistId, AssistKind, GroupLabel, |
11 | }; | 17 | }; |
12 | 18 | ||
19 | const 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` |
86 | fn qualify_path_qualifier_start( | 78 | fn 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` |
110 | fn qualify_path_unqualified_name( | 102 | fn 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` |
130 | fn qualify_path_trait_assoc_item( | 122 | fn 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` |
155 | fn qualify_path_trait_method( | 146 | fn 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 | |||
200 | fn 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)] |
181 | mod tests { | 211 | mod 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); |