aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers
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 /crates/assists/src/handlers
parentd983f18df7dd484ec43510111169180d7abe038d (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.rs2
-rw-r--r--crates/assists/src/handlers/qualify_path.rs124
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 @@
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);