aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/qualify_path.rs229
1 files changed, 108 insertions, 121 deletions
diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs
index 479ff498c..f436bdbbf 100644
--- a/crates/assists/src/handlers/qualify_path.rs
+++ b/crates/assists/src/handlers/qualify_path.rs
@@ -5,7 +5,7 @@ use ide_db::RootDatabase;
5use syntax::{ 5use syntax::{
6 ast, 6 ast,
7 ast::{make, ArgListOwner}, 7 ast::{make, ArgListOwner},
8 AstNode, TextRange, 8 AstNode,
9}; 9};
10use test_utils::mark; 10use test_utils::mark;
11 11
@@ -16,8 +16,6 @@ use crate::{
16 AssistId, AssistKind, GroupLabel, 16 AssistId, AssistKind, GroupLabel,
17}; 17};
18 18
19const ASSIST_ID: AssistId = AssistId("qualify_path", AssistKind::QuickFix);
20
21// Assist: qualify_path 19// Assist: qualify_path
22// 20//
23// If the name is unresolved, provides all possible qualified paths for it. 21// If the name is unresolved, provides all possible qualified paths for it.
@@ -51,162 +49,151 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
51 return None; 49 return None;
52 } 50 }
53 51
52 let candidate = import_assets.import_candidate();
54 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; 53 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
55 match import_assets.import_candidate() { 54
56 ImportCandidate::QualifierStart(candidate) => { 55 let qualify_candidate = match candidate {
56 ImportCandidate::QualifierStart(_) => {
57 mark::hit!(qualify_path_qualifier_start);
57 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; 58 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
58 let segment = path.segment()?; 59 let segment = path.segment()?;
59 qualify_path_qualifier_start(acc, proposed_imports, range, segment, &candidate.name) 60 QualifyCandidate::QualifierStart(segment)
60 } 61 }
61 ImportCandidate::UnqualifiedName(candidate) => { 62 ImportCandidate::UnqualifiedName(_) => {
62 qualify_path_unqualified_name(acc, proposed_imports, range, &candidate.name) 63 mark::hit!(qualify_path_unqualified_name);
64 QualifyCandidate::UnqualifiedName
63 } 65 }
64 ImportCandidate::TraitAssocItem(_) => { 66 ImportCandidate::TraitAssocItem(_) => {
67 mark::hit!(qualify_path_trait_assoc_item);
65 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; 68 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
66 let (qualifier, segment) = (path.qualifier()?, path.segment()?); 69 let (qualifier, segment) = (path.qualifier()?, path.segment()?);
67 qualify_path_trait_assoc_item(acc, proposed_imports, range, qualifier, segment) 70 QualifyCandidate::TraitAssocItem(qualifier, segment)
68 } 71 }
69 ImportCandidate::TraitMethod(_) => { 72 ImportCandidate::TraitMethod(_) => {
73 mark::hit!(qualify_path_trait_method);
70 let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?; 74 let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?;
71 qualify_path_trait_method(acc, ctx.sema.db, proposed_imports, range, mcall_expr)?; 75 QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr)
72 } 76 }
73 }; 77 };
74 Some(())
75}
76 78
77// a test that covers this -> `associated_struct_const` 79 let group_label = group_label(candidate);
78fn qualify_path_qualifier_start( 80 for (import, item) in proposed_imports {
79 acc: &mut Assists,
80 proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>,
81 range: TextRange,
82 segment: ast::PathSegment,
83 qualifier_start: &ast::NameRef,
84) {
85 mark::hit!(qualify_path_qualifier_start);
86 let group_label = GroupLabel(format!("Qualify {}", qualifier_start));
87 for (import, _) in proposed_imports {
88 acc.add_group( 81 acc.add_group(
89 &group_label, 82 &group_label,
90 ASSIST_ID, 83 AssistId("qualify_path", AssistKind::QuickFix),
91 format!("Qualify with `{}`", &import), 84 label(candidate, &import),
92 range, 85 range,
93 |builder| { 86 |builder| {
94 let import = mod_path_to_ast(&import); 87 qualify_candidate.qualify(
95 builder.replace(range, format!("{}::{}", import, segment)); 88 |replace_with: String| builder.replace(range, replace_with),
89 import,
90 item,
91 )
96 }, 92 },
97 ); 93 );
98 } 94 }
95 Some(())
99} 96}
100 97
101// a test that covers this -> `applicable_when_found_an_import_partial` 98enum QualifyCandidate<'db> {
102fn qualify_path_unqualified_name( 99 QualifierStart(ast::PathSegment),
103 acc: &mut Assists, 100 UnqualifiedName,
104 proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>, 101 TraitAssocItem(ast::Path, ast::PathSegment),
105 range: TextRange, 102 TraitMethod(&'db RootDatabase, ast::MethodCallExpr),
106 name: &ast::NameRef,
107) {
108 mark::hit!(qualify_path_unqualified_name);
109 let group_label = GroupLabel(format!("Qualify {}", name));
110 for (import, _) in proposed_imports {
111 acc.add_group(
112 &group_label,
113 ASSIST_ID,
114 format!("Qualify as `{}`", &import),
115 range,
116 |builder| builder.replace(range, mod_path_to_ast(&import).to_string()),
117 );
118 }
119} 103}
120 104
121// a test that covers this -> `associated_trait_const` 105impl QualifyCandidate<'_> {
122fn qualify_path_trait_assoc_item( 106 fn qualify(&self, mut replacer: impl FnMut(String), import: hir::ModPath, item: hir::ItemInNs) {
123 acc: &mut Assists, 107 match self {
124 proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>, 108 QualifyCandidate::QualifierStart(segment) => {
125 range: TextRange,
126 qualifier: ast::Path,
127 segment: ast::PathSegment,
128) {
129 mark::hit!(qualify_path_trait_assoc_item);
130 let group_label = GroupLabel(format!("Qualify {}", &segment));
131 for (import, _) in proposed_imports {
132 acc.add_group(
133 &group_label,
134 ASSIST_ID,
135 format!("Qualify with cast as `{}`", &import),
136 range,
137 |builder| {
138 let import = mod_path_to_ast(&import); 109 let import = mod_path_to_ast(&import);
139 builder.replace(range, format!("<{} as {}>::{}", qualifier, import, segment)); 110 replacer(format!("{}::{}", import, segment));
140 }, 111 }
141 ); 112 QualifyCandidate::UnqualifiedName => replacer(mod_path_to_ast(&import).to_string()),
113 QualifyCandidate::TraitAssocItem(qualifier, segment) => {
114 let import = mod_path_to_ast(&import);
115 replacer(format!("<{} as {}>::{}", qualifier, import, segment));
116 }
117 &QualifyCandidate::TraitMethod(db, ref mcall_expr) => {
118 Self::qualify_trait_method(db, mcall_expr, replacer, import, item);
119 }
120 }
121 }
122
123 fn qualify_trait_method(
124 db: &RootDatabase,
125 mcall_expr: &ast::MethodCallExpr,
126 mut replacer: impl FnMut(String),
127 import: hir::ModPath,
128 item: hir::ItemInNs,
129 ) -> Option<()> {
130 let receiver = mcall_expr.receiver()?;
131 let trait_method_name = mcall_expr.name_ref()?;
132 let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args());
133 let trait_ = item_as_trait(item)?;
134 let method = find_trait_method(db, trait_, &trait_method_name)?;
135 if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
136 let import = mod_path_to_ast(&import);
137 let receiver = match self_access {
138 hir::Access::Shared => make::expr_ref(receiver, false),
139 hir::Access::Exclusive => make::expr_ref(receiver, true),
140 hir::Access::Owned => receiver,
141 };
142 replacer(format!(
143 "{}::{}{}",
144 import,
145 trait_method_name,
146 match arg_list.clone() {
147 Some(args) => make::arg_list(iter::once(receiver).chain(args)),
148 None => make::arg_list(iter::once(receiver)),
149 }
150 ));
151 }
152 Some(())
142 } 153 }
143} 154}
144 155
145// a test that covers this -> `trait_method` 156fn find_trait_method(
146fn qualify_path_trait_method(
147 acc: &mut Assists,
148 db: &RootDatabase, 157 db: &RootDatabase,
149 proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>, 158 trait_: hir::Trait,
150 range: TextRange, 159 trait_method_name: &ast::NameRef,
151 mcall_expr: ast::MethodCallExpr, 160) -> Option<hir::Function> {
152) -> Option<()> { 161 if let Some(hir::AssocItem::Function(method)) =
153 mark::hit!(qualify_path_trait_method); 162 trait_.items(db).into_iter().find(|item: &hir::AssocItem| {
154 163 item.name(db).map(|name| name == trait_method_name.as_name()).unwrap_or(false)
155 let receiver = mcall_expr.receiver()?; 164 })
156 let trait_method_name = mcall_expr.name_ref()?; 165 {
157 let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args()); 166 Some(method)
158 let group_label = GroupLabel(format!("Qualify {}", trait_method_name)); 167 } else {
159 let find_method = |item: &hir::AssocItem| { 168 None
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) {
163 acc.add_group(
164 &group_label,
165 ASSIST_ID,
166 format!("Qualify `{}`", &import),
167 range,
168 |builder| {
169 let import = mod_path_to_ast(&import);
170 if let Some(hir::AssocItem::Function(method)) =
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 }
194 },
195 );
196 } 169 }
197 Some(())
198} 170}
199 171
200fn filter_trait( 172fn item_as_trait(item: hir::ItemInNs) -> Option<hir::Trait> {
201 (import, trait_): (hir::ModPath, hir::ItemInNs), 173 if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(item.as_module_def_id()?) {
202) -> Option<(hir::ModPath, hir::Trait)> { 174 Some(trait_)
203 if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(trait_.as_module_def_id()?) {
204 Some((import, trait_))
205 } else { 175 } else {
206 None 176 None
207 } 177 }
208} 178}
209 179
180fn group_label(candidate: &ImportCandidate) -> GroupLabel {
181 let name = match candidate {
182 ImportCandidate::UnqualifiedName(it) | ImportCandidate::QualifierStart(it) => &it.name,
183 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
184 };
185 GroupLabel(format!("Qualify {}", name))
186}
187
188fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String {
189 match candidate {
190 ImportCandidate::UnqualifiedName(_) => format!("Qualify as `{}`", &import),
191 ImportCandidate::QualifierStart(_) => format!("Qualify with `{}`", &import),
192 ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import),
193 ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import),
194 }
195}
196
210#[cfg(test)] 197#[cfg(test)]
211mod tests { 198mod tests {
212 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; 199 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};