ide_assists/handlers/
unqualify_method_call.rs

1use hir::AsAssocItem;
2use syntax::{
3    TextRange,
4    ast::{self, AstNode, HasArgList, prec::ExprPrecedence},
5};
6
7use crate::{AssistContext, AssistId, Assists};
8
9// Assist: unqualify_method_call
10//
11// Transforms universal function call syntax into a method call.
12//
13// ```
14// fn main() {
15//     std::ops::Add::add$0(1, 2);
16// }
17// # mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } }
18// ```
19// ->
20// ```
21// use std::ops::Add;
22//
23// fn main() {
24//     1.add(2);
25// }
26// # mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } }
27// ```
28pub(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    // `core::ops::Add::add(` -> ``
55    let delete_path =
56        TextRange::new(path.syntax().text_range().start(), l_paren.text_range().end());
57
58    // Parens around `expr` if needed
59    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    // `, ` -> `.add(`
65    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                // Only add an import for trait methods that are not already imported.
89                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        // for `<i32 as std::ops::Add>`
102        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        // in case for `<_>`
115        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}