Skip to main content

ide_assists/handlers/
unqualify_method_call.rs

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