Skip to content

Commit d33fa38

Browse files
committed
Auto merge of rust-lang#13825 - WaffleLapkin:ufcs_to_method_call_and_back, r=Veykril
feat: Add `unqualify_method_call` assist ...which is the inverse of `qualify_method_call` assist. ![Peek 2022-12-22 22-47](https://user-images.githubusercontent.com/38225716/209206554-8f067206-6fa6-48f8-849e-f6d36ee2e5a1.gif) Optional future work: - import the trait if needed - remove excess references when auto-ref is possible
2 parents 336608a + c782353 commit d33fa38

File tree

3 files changed

+232
-0
lines changed

3 files changed

+232
-0
lines changed
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
use syntax::{
2+
ast::{self, make, AstNode, HasArgList},
3+
TextRange,
4+
};
5+
6+
use crate::{AssistContext, AssistId, AssistKind, Assists};
7+
8+
// Assist: unqualify_method_call
9+
//
10+
// Transforms universal function call syntax into a method call.
11+
//
12+
// ```
13+
// fn main() {
14+
// std::ops::Add::add$0(1, 2);
15+
// }
16+
// # mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } }
17+
// ```
18+
// ->
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+
// ```
25+
pub(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 l_paren = args.l_paren_token()?;
37+
let mut args_iter = args.args();
38+
let first_arg = args_iter.next()?;
39+
let second_arg = args_iter.next();
40+
41+
_ = path.qualifier()?;
42+
let method_name = path.segment()?.name_ref()?;
43+
44+
let res = ctx.sema.resolve_path(&path)?;
45+
let hir::PathResolution::Def(hir::ModuleDef::Function(fun)) = res else { return None };
46+
if !fun.has_self_param(ctx.sema.db) {
47+
return None;
48+
}
49+
50+
// `core::ops::Add::add(` -> ``
51+
let delete_path =
52+
TextRange::new(path.syntax().text_range().start(), l_paren.text_range().end());
53+
54+
// Parens around `expr` if needed
55+
let parens = needs_parens_as_receiver(&first_arg).then(|| {
56+
let range = first_arg.syntax().text_range();
57+
(range.start(), range.end())
58+
});
59+
60+
// `, ` -> `.add(`
61+
let replace_comma = TextRange::new(
62+
first_arg.syntax().text_range().end(),
63+
second_arg
64+
.map(|a| a.syntax().text_range().start())
65+
.unwrap_or_else(|| first_arg.syntax().text_range().end()),
66+
);
67+
68+
acc.add(
69+
AssistId("unqualify_method_call", AssistKind::RefactorRewrite),
70+
"Unqualify method call",
71+
call.syntax().text_range(),
72+
|edit| {
73+
edit.delete(delete_path);
74+
if let Some((open, close)) = parens {
75+
edit.insert(open, "(");
76+
edit.insert(close, ")");
77+
}
78+
edit.replace(replace_comma, format!(".{method_name}("));
79+
},
80+
)
81+
}
82+
83+
fn needs_parens_as_receiver(expr: &ast::Expr) -> bool {
84+
// Make `(expr).dummy()`
85+
let dummy_call = make::expr_method_call(
86+
make::expr_paren(expr.clone()),
87+
make::name_ref("dummy"),
88+
make::arg_list([]),
89+
);
90+
91+
// Get the `expr` clone with the right parent back
92+
// (unreachable!s are fine since we've just constructed the expression)
93+
let ast::Expr::MethodCallExpr(call) = &dummy_call else { unreachable!() };
94+
let Some(receiver) = call.receiver() else { unreachable!() };
95+
let ast::Expr::ParenExpr(parens) = receiver else { unreachable!() };
96+
let Some(expr) = parens.expr() else { unreachable!() };
97+
98+
expr.needs_parens_in(dummy_call.syntax().clone())
99+
}
100+
101+
#[cfg(test)]
102+
mod tests {
103+
use crate::tests::{check_assist, check_assist_not_applicable};
104+
105+
use super::*;
106+
107+
#[test]
108+
fn unqualify_method_call_simple() {
109+
check_assist(
110+
unqualify_method_call,
111+
r#"
112+
struct S;
113+
impl S { fn f(self, S: S) {} }
114+
fn f() { S::$0f(S, S); }"#,
115+
r#"
116+
struct S;
117+
impl S { fn f(self, S: S) {} }
118+
fn f() { S.f(S); }"#,
119+
);
120+
}
121+
122+
#[test]
123+
fn unqualify_method_call_trait() {
124+
check_assist(
125+
unqualify_method_call,
126+
r#"
127+
//- minicore: add
128+
fn f() { <u32 as core::ops::Add>::$0add(2, 2); }"#,
129+
r#"
130+
fn f() { 2.add(2); }"#,
131+
);
132+
133+
check_assist(
134+
unqualify_method_call,
135+
r#"
136+
//- minicore: add
137+
fn f() { core::ops::Add::$0add(2, 2); }"#,
138+
r#"
139+
fn f() { 2.add(2); }"#,
140+
);
141+
142+
check_assist(
143+
unqualify_method_call,
144+
r#"
145+
//- minicore: add
146+
use core::ops::Add;
147+
fn f() { <_>::$0add(2, 2); }"#,
148+
r#"
149+
use core::ops::Add;
150+
fn f() { 2.add(2); }"#,
151+
);
152+
}
153+
154+
#[test]
155+
fn unqualify_method_call_single_arg() {
156+
check_assist(
157+
unqualify_method_call,
158+
r#"
159+
struct S;
160+
impl S { fn f(self) {} }
161+
fn f() { S::$0f(S); }"#,
162+
r#"
163+
struct S;
164+
impl S { fn f(self) {} }
165+
fn f() { S.f(); }"#,
166+
);
167+
}
168+
169+
#[test]
170+
fn unqualify_method_call_parens() {
171+
check_assist(
172+
unqualify_method_call,
173+
r#"
174+
//- minicore: deref
175+
struct S;
176+
impl core::ops::Deref for S {
177+
type Target = S;
178+
fn deref(&self) -> &S { self }
179+
}
180+
fn f() { core::ops::Deref::$0deref(&S); }"#,
181+
r#"
182+
struct S;
183+
impl core::ops::Deref for S {
184+
type Target = S;
185+
fn deref(&self) -> &S { self }
186+
}
187+
fn f() { (&S).deref(); }"#,
188+
);
189+
}
190+
191+
#[test]
192+
fn unqualify_method_call_doesnt_apply_with_cursor_not_on_path() {
193+
check_assist_not_applicable(
194+
unqualify_method_call,
195+
r#"
196+
//- minicore: add
197+
fn f() { core::ops::Add::add(2,$0 2); }"#,
198+
);
199+
}
200+
201+
#[test]
202+
fn unqualify_method_call_doesnt_apply_with_no_self() {
203+
check_assist_not_applicable(
204+
unqualify_method_call,
205+
r#"
206+
struct S;
207+
impl S { fn assoc(S: S, S: S) {} }
208+
fn f() { S::assoc$0(S, S); }"#,
209+
);
210+
}
211+
}

crates/ide-assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ mod handlers {
202202
mod unnecessary_async;
203203
mod unwrap_block;
204204
mod unwrap_result_return_type;
205+
mod unqualify_method_call;
205206
mod wrap_return_type_in_result;
206207

207208
pub(crate) fn all() -> &'static [Handler] {
@@ -308,6 +309,7 @@ mod handlers {
308309
unwrap_block::unwrap_block,
309310
unwrap_result_return_type::unwrap_result_return_type,
310311
unwrap_tuple::unwrap_tuple,
312+
unqualify_method_call::unqualify_method_call,
311313
wrap_return_type_in_result::wrap_return_type_in_result,
312314
// These are manually sorted for better priorities. By default,
313315
// priority is determined by the size of the target range (smaller

crates/ide-assists/src/tests/generated.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2566,6 +2566,25 @@ pub async fn bar() { foo() }
25662566
)
25672567
}
25682568

2569+
#[test]
2570+
fn doctest_unqualify_method_call() {
2571+
check_doc_test(
2572+
"unqualify_method_call",
2573+
r#####"
2574+
fn main() {
2575+
std::ops::Add::add$0(1, 2);
2576+
}
2577+
mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } }
2578+
"#####,
2579+
r#####"
2580+
fn main() {
2581+
1.add(2);
2582+
}
2583+
mod std { pub mod ops { pub trait Add { fn add(self, _: Self) {} } impl Add for i32 {} } }
2584+
"#####,
2585+
)
2586+
}
2587+
25692588
#[test]
25702589
fn doctest_unwrap_block() {
25712590
check_doc_test(

0 commit comments

Comments
 (0)