Skip to content

Commit bed31e0

Browse files
committed
Add ide-assist: add_explicit_method_call_deref
Insert explicit method call reference and dereferences. Example --- ```rust struct Foo; impl Foo { fn foo(&self) {} } fn test() { Foo$0.$0foo(); } ``` -> ```rust struct Foo; impl Foo { fn foo(&self) {} } fn test() { (&Foo).foo(); } ```
1 parent 4bf516e commit bed31e0

File tree

5 files changed

+255
-0
lines changed

5 files changed

+255
-0
lines changed
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
use hir::{Adjust, Mutability};
2+
use ide_db::assists::AssistId;
3+
use itertools::Itertools;
4+
use syntax::{
5+
AstNode, T,
6+
ast::{self, syntax_factory::SyntaxFactory},
7+
};
8+
9+
use crate::{AssistContext, Assists};
10+
11+
// Assist: add_explicit_method_call_deref
12+
//
13+
// Insert explicit method call reference and dereferences.
14+
//
15+
// ```
16+
// struct Foo;
17+
// impl Foo { fn foo(&self) {} }
18+
// fn test() {
19+
// Foo$0.$0foo();
20+
// }
21+
// ```
22+
// ->
23+
// ```
24+
// struct Foo;
25+
// impl Foo { fn foo(&self) {} }
26+
// fn test() {
27+
// (&Foo).foo();
28+
// }
29+
// ```
30+
pub(crate) fn add_explicit_method_call_deref(
31+
acc: &mut Assists,
32+
ctx: &AssistContext<'_>,
33+
) -> Option<()> {
34+
if ctx.has_empty_selection() {
35+
return None;
36+
}
37+
let dot_token = ctx.find_token_syntax_at_offset(T![.])?;
38+
if ctx.selection_trimmed() != dot_token.text_range() {
39+
return None;
40+
}
41+
let method_call_expr = dot_token.parent().and_then(ast::MethodCallExpr::cast)?;
42+
let receiver = method_call_expr.receiver()?;
43+
44+
let adjustments = ctx.sema.expr_adjustments(&receiver)?;
45+
let adjustments =
46+
adjustments.into_iter().filter_map(|adjust| simple_adjust_kind(adjust.kind)).collect_vec();
47+
if adjustments.is_empty() {
48+
return None;
49+
}
50+
51+
acc.add(
52+
AssistId::refactor_rewrite("add_explicit_method_call_deref"),
53+
"Insert explicit method call derefs",
54+
dot_token.text_range(),
55+
|builder| {
56+
let mut edit = builder.make_editor(method_call_expr.syntax());
57+
let make = SyntaxFactory::without_mappings();
58+
let mut expr = receiver.clone();
59+
60+
for adjust_kind in adjustments {
61+
expr = adjust_kind.wrap_expr(expr, &make);
62+
}
63+
64+
expr = make.expr_paren(expr).into();
65+
edit.replace(receiver.syntax(), expr.syntax());
66+
67+
builder.add_file_edits(ctx.vfs_file_id(), edit);
68+
},
69+
)
70+
}
71+
72+
fn simple_adjust_kind(adjust: Adjust) -> Option<AdjustKind> {
73+
match adjust {
74+
Adjust::NeverToAny | Adjust::Pointer(_) => None,
75+
Adjust::Deref(_) => Some(AdjustKind::Deref),
76+
Adjust::Borrow(hir::AutoBorrow::Ref(mutability)) => Some(AdjustKind::Ref(mutability)),
77+
Adjust::Borrow(hir::AutoBorrow::RawPtr(mutability)) => Some(AdjustKind::RefRaw(mutability)),
78+
}
79+
}
80+
81+
enum AdjustKind {
82+
Deref,
83+
Ref(Mutability),
84+
RefRaw(Mutability),
85+
}
86+
87+
impl AdjustKind {
88+
fn wrap_expr(self, expr: ast::Expr, make: &SyntaxFactory) -> ast::Expr {
89+
match self {
90+
AdjustKind::Deref => make.expr_prefix(T![*], expr).into(),
91+
AdjustKind::Ref(mutability) => make.expr_ref(expr, mutability.is_mut()),
92+
AdjustKind::RefRaw(mutability) => make.expr_raw_ref(expr, mutability.is_mut()),
93+
}
94+
}
95+
}
96+
97+
#[cfg(test)]
98+
mod tests {
99+
use crate::tests::check_assist;
100+
101+
use super::*;
102+
103+
#[test]
104+
fn works_ref() {
105+
check_assist(
106+
add_explicit_method_call_deref,
107+
r#"
108+
struct Foo;
109+
impl Foo { fn foo(&self) {} }
110+
fn test() {
111+
Foo$0.$0foo();
112+
}"#,
113+
r#"
114+
struct Foo;
115+
impl Foo { fn foo(&self) {} }
116+
fn test() {
117+
(&Foo).foo();
118+
}"#,
119+
);
120+
}
121+
122+
#[test]
123+
fn works_ref_mut() {
124+
check_assist(
125+
add_explicit_method_call_deref,
126+
r#"
127+
struct Foo;
128+
impl Foo { fn foo(&mut self) {} }
129+
fn test() {
130+
Foo$0.$0foo();
131+
}"#,
132+
r#"
133+
struct Foo;
134+
impl Foo { fn foo(&mut self) {} }
135+
fn test() {
136+
(&mut Foo).foo();
137+
}"#,
138+
);
139+
}
140+
141+
#[test]
142+
fn works_deref() {
143+
check_assist(
144+
add_explicit_method_call_deref,
145+
r#"
146+
struct Foo;
147+
impl Foo { fn foo(self) {} }
148+
fn test() {
149+
let foo = &Foo;
150+
foo$0.$0foo();
151+
}"#,
152+
r#"
153+
struct Foo;
154+
impl Foo { fn foo(self) {} }
155+
fn test() {
156+
let foo = &Foo;
157+
(*foo).foo();
158+
}"#,
159+
);
160+
}
161+
162+
#[test]
163+
fn works_reborrow() {
164+
check_assist(
165+
add_explicit_method_call_deref,
166+
r#"
167+
struct Foo;
168+
impl Foo { fn foo(&self) {} }
169+
fn test() {
170+
let foo = &mut Foo;
171+
foo$0.$0foo();
172+
}"#,
173+
r#"
174+
struct Foo;
175+
impl Foo { fn foo(&self) {} }
176+
fn test() {
177+
let foo = &mut Foo;
178+
(&*foo).foo();
179+
}"#,
180+
);
181+
}
182+
183+
#[test]
184+
fn works_deref_reborrow() {
185+
check_assist(
186+
add_explicit_method_call_deref,
187+
r#"
188+
//- minicore: deref
189+
struct Foo;
190+
struct Bar;
191+
impl core::ops::Deref for Foo {
192+
type Target = Bar;
193+
fn deref(&self) -> &Self::Target {}
194+
}
195+
impl Bar { fn bar(&self) {} }
196+
fn test() {
197+
let foo = &mut Foo;
198+
foo$0.$0bar();
199+
}"#,
200+
r#"
201+
struct Foo;
202+
struct Bar;
203+
impl core::ops::Deref for Foo {
204+
type Target = Bar;
205+
fn deref(&self) -> &Self::Target {}
206+
}
207+
impl Bar { fn bar(&self) {} }
208+
fn test() {
209+
let foo = &mut Foo;
210+
(&**foo).bar();
211+
}"#,
212+
);
213+
}
214+
}

crates/ide-assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ mod handlers {
105105
pub(crate) type Handler = fn(&mut Assists, &AssistContext<'_>) -> Option<()>;
106106

107107
mod add_braces;
108+
mod add_explicit_dot_deref;
108109
mod add_explicit_enum_discriminant;
109110
mod add_explicit_type;
110111
mod add_label_to_loop;
@@ -241,6 +242,7 @@ mod handlers {
241242
&[
242243
// These are alphabetic for the foolish consistency
243244
add_braces::add_braces,
245+
add_explicit_dot_deref::add_explicit_method_call_deref,
244246
add_explicit_enum_discriminant::add_explicit_enum_discriminant,
245247
add_explicit_type::add_explicit_type,
246248
add_label_to_loop::add_label_to_loop,

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,27 @@ enum TheEnum {
6969
)
7070
}
7171

72+
#[test]
73+
fn doctest_add_explicit_method_call_deref() {
74+
check_doc_test(
75+
"add_explicit_method_call_deref",
76+
r#####"
77+
struct Foo;
78+
impl Foo { fn foo(&self) {} }
79+
fn test() {
80+
Foo$0.$0foo();
81+
}
82+
"#####,
83+
r#####"
84+
struct Foo;
85+
impl Foo { fn foo(&self) {} }
86+
fn test() {
87+
(&Foo).foo();
88+
}
89+
"#####,
90+
)
91+
}
92+
7293
#[test]
7394
fn doctest_add_explicit_type() {
7495
check_doc_test(

crates/syntax/src/ast/make.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,9 @@ pub fn expr_macro(path: ast::Path, tt: ast::TokenTree) -> ast::MacroExpr {
690690
pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr {
691691
expr_from_text(&if exclusive { format!("&mut {expr}") } else { format!("&{expr}") })
692692
}
693+
pub fn expr_raw_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr {
694+
expr_from_text(&if exclusive { format!("&raw mut {expr}") } else { format!("&raw const {expr}") })
695+
}
693696
pub fn expr_reborrow(expr: ast::Expr) -> ast::Expr {
694697
expr_from_text(&format!("&mut *{expr}"))
695698
}

crates/syntax/src/ast/syntax_factory/constructors.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,21 @@ impl SyntaxFactory {
566566
ast.into()
567567
}
568568

569+
pub fn expr_raw_ref(&self, expr: ast::Expr, exclusive: bool) -> ast::Expr {
570+
let ast::Expr::RefExpr(ast) = make::expr_raw_ref(expr.clone(), exclusive).clone_for_update()
571+
else {
572+
unreachable!()
573+
};
574+
575+
if let Some(mut mapping) = self.mappings() {
576+
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
577+
builder.map_node(expr.syntax().clone(), ast.expr().unwrap().syntax().clone());
578+
builder.finish(&mut mapping);
579+
}
580+
581+
ast.into()
582+
}
583+
569584
pub fn expr_closure(
570585
&self,
571586
pats: impl IntoIterator<Item = ast::Param>,

0 commit comments

Comments
 (0)