ide_assists/handlers/
unqualify_method_call.rs1use hir::AsAssocItem;
2use syntax::{
3 TextRange,
4 ast::{self, AstNode, HasArgList, prec::ExprPrecedence},
5};
6
7use crate::{AssistContext, AssistId, Assists};
8
9pub(crate) fn unqualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
29 let call = ctx.find_node_at_offset::<ast::CallExpr>()?;
30 let ast::Expr::PathExpr(path_expr) = call.expr()? else { return None };
31 let path = path_expr.path()?;
32
33 let cursor_in_range = path.syntax().text_range().contains_range(ctx.selection_trimmed());
34 if !cursor_in_range {
35 return None;
36 }
37
38 let args = call.arg_list()?;
39 let l_paren = args.l_paren_token()?;
40 let mut args_iter = args.args();
41 let first_arg = args_iter.next()?;
42 let second_arg = args_iter.next();
43
44 let qualifier = path.qualifier()?;
45 let method_name = path.segment()?.name_ref()?;
46
47 let scope = ctx.sema.scope(path.syntax())?;
48 let res = ctx.sema.resolve_path(&path)?;
49 let hir::PathResolution::Def(hir::ModuleDef::Function(fun)) = res else { return None };
50 if !fun.has_self_param(ctx.sema.db) {
51 return None;
52 }
53
54 let delete_path =
56 TextRange::new(path.syntax().text_range().start(), l_paren.text_range().end());
57
58 let parens = first_arg.precedence().needs_parentheses_in(ExprPrecedence::Postfix).then(|| {
60 let range = first_arg.syntax().text_range();
61 (range.start(), range.end())
62 });
63
64 let replace_comma = TextRange::new(
66 first_arg.syntax().text_range().end(),
67 second_arg
68 .map(|a| a.syntax().text_range().start())
69 .unwrap_or_else(|| first_arg.syntax().text_range().end()),
70 );
71
72 acc.add(
73 AssistId::refactor_rewrite("unqualify_method_call"),
74 "Unqualify method call",
75 call.syntax().text_range(),
76 |edit| {
77 edit.delete(delete_path);
78 if let Some((open, close)) = parens {
79 edit.insert(open, "(");
80 edit.insert(close, ")");
81 }
82 edit.replace(replace_comma, format!(".{method_name}("));
83
84 if let Some(fun) = fun.as_assoc_item(ctx.db())
85 && let Some(trait_) = fun.container_or_implemented_trait(ctx.db())
86 && !scope.can_use_trait_methods(trait_)
87 {
88 add_import(qualifier, ctx, edit);
90 }
91 },
92 )
93}
94
95fn add_import(
96 qualifier: ast::Path,
97 ctx: &AssistContext<'_>,
98 edit: &mut ide_db::source_change::SourceChangeBuilder,
99) {
100 if let Some(path_segment) = qualifier.segment() {
101 let path_type = path_segment.qualifying_trait();
103 let import = match path_type {
104 Some(it) => {
105 if let Some(path) = it.path() {
106 path
107 } else {
108 return;
109 }
110 }
111 None => qualifier,
112 };
113
114 if import.coloncolon_token().is_none() {
116 return;
117 }
118
119 let scope = ide_db::imports::insert_use::ImportScope::find_insert_use_container(
120 import.syntax(),
121 &ctx.sema,
122 );
123
124 if let Some(scope) = scope {
125 let scope = edit.make_import_scope_mut(scope);
126 ide_db::imports::insert_use::insert_use(&scope, import, &ctx.config.insert_use);
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use crate::tests::{check_assist, check_assist_not_applicable};
134
135 use super::*;
136
137 #[test]
138 fn unqualify_method_call_simple() {
139 check_assist(
140 unqualify_method_call,
141 r#"
142struct S;
143impl S { fn f(self, S: S) {} }
144fn f() { S::$0f(S, S); }"#,
145 r#"
146struct S;
147impl S { fn f(self, S: S) {} }
148fn f() { S.f(S); }"#,
149 );
150 }
151
152 #[test]
153 fn unqualify_method_call_trait() {
154 check_assist(
155 unqualify_method_call,
156 r#"
157//- minicore: add
158fn f() { <u32 as core::ops::Add>::$0add(2, 2); }"#,
159 r#"
160use core::ops::Add;
161
162fn f() { 2.add(2); }"#,
163 );
164
165 check_assist(
166 unqualify_method_call,
167 r#"
168//- minicore: add
169fn f() { core::ops::Add::$0add(2, 2); }"#,
170 r#"
171use core::ops::Add;
172
173fn f() { 2.add(2); }"#,
174 );
175
176 check_assist(
177 unqualify_method_call,
178 r#"
179//- minicore: add
180use core::ops::Add;
181fn f() { <_>::$0add(2, 2); }"#,
182 r#"
183use core::ops::Add;
184fn f() { 2.add(2); }"#,
185 );
186 }
187
188 #[test]
189 fn unqualify_method_call_single_arg() {
190 check_assist(
191 unqualify_method_call,
192 r#"
193 struct S;
194 impl S { fn f(self) {} }
195 fn f() { S::$0f(S); }"#,
196 r#"
197 struct S;
198 impl S { fn f(self) {} }
199 fn f() { S.f(); }"#,
200 );
201 }
202
203 #[test]
204 fn unqualify_method_call_parens() {
205 check_assist(
206 unqualify_method_call,
207 r#"
208//- minicore: deref
209struct S;
210impl core::ops::Deref for S {
211 type Target = S;
212 fn deref(&self) -> &S { self }
213}
214fn f() { core::ops::Deref::$0deref(&S); }"#,
215 r#"
216use core::ops::Deref;
217
218struct S;
219impl core::ops::Deref for S {
220 type Target = S;
221 fn deref(&self) -> &S { self }
222}
223fn f() { (&S).deref(); }"#,
224 );
225 }
226
227 #[test]
228 fn unqualify_method_call_doesnt_apply_with_cursor_not_on_path() {
229 check_assist_not_applicable(
230 unqualify_method_call,
231 r#"
232//- minicore: add
233fn f() { core::ops::Add::add(2,$0 2); }"#,
234 );
235 }
236
237 #[test]
238 fn unqualify_method_call_doesnt_apply_with_no_self() {
239 check_assist_not_applicable(
240 unqualify_method_call,
241 r#"
242struct S;
243impl S { fn assoc(S: S, S: S) {} }
244fn f() { S::assoc$0(S, S); }"#,
245 );
246 }
247
248 #[test]
249 fn inherent_method() {
250 check_assist(
251 unqualify_method_call,
252 r#"
253mod foo {
254 pub struct Bar;
255 impl Bar {
256 pub fn bar(self) {}
257 }
258}
259
260fn baz() {
261 foo::Bar::b$0ar(foo::Bar);
262}
263 "#,
264 r#"
265mod foo {
266 pub struct Bar;
267 impl Bar {
268 pub fn bar(self) {}
269 }
270}
271
272fn baz() {
273 foo::Bar.bar();
274}
275 "#,
276 );
277 }
278
279 #[test]
280 fn trait_method_in_impl() {
281 check_assist(
282 unqualify_method_call,
283 r#"
284mod foo {
285 pub trait Bar {
286 pub fn bar(self) {}
287 }
288}
289
290struct Baz;
291impl foo::Bar for Baz {
292 fn bar(self) {
293 foo::Bar::b$0ar(Baz);
294 }
295}
296 "#,
297 r#"
298mod foo {
299 pub trait Bar {
300 pub fn bar(self) {}
301 }
302}
303
304struct Baz;
305impl foo::Bar for Baz {
306 fn bar(self) {
307 Baz.bar();
308 }
309}
310 "#,
311 );
312 }
313
314 #[test]
315 fn trait_method_already_imported() {
316 check_assist(
317 unqualify_method_call,
318 r#"
319mod foo {
320 pub struct Foo;
321 pub trait Bar {
322 pub fn bar(self) {}
323 }
324 impl Bar for Foo {
325 pub fn bar(self) {}
326 }
327}
328
329use foo::Bar;
330
331fn baz() {
332 foo::Bar::b$0ar(foo::Foo);
333}
334 "#,
335 r#"
336mod foo {
337 pub struct Foo;
338 pub trait Bar {
339 pub fn bar(self) {}
340 }
341 impl Bar for Foo {
342 pub fn bar(self) {}
343 }
344}
345
346use foo::Bar;
347
348fn baz() {
349 foo::Foo.bar();
350}
351 "#,
352 );
353 }
354}