Skip to content

Commit d308f17

Browse files
committed
Inline parameters in inline_call if possible
1 parent 14e18bf commit d308f17

File tree

3 files changed

+111
-56
lines changed

3 files changed

+111
-56
lines changed

crates/ide_assists/src/handlers/inline_call.rs

Lines changed: 96 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use ast::make;
22
use hir::{HasSource, PathResolution};
3+
use ide_db::{defs::Definition, search::FileReference};
4+
use itertools::izip;
35
use syntax::{
46
ast::{self, edit::AstNodeEdit, ArgListOwner},
57
ted, AstNode,
@@ -69,23 +71,25 @@ pub(crate) fn inline_(
6971
arg_list: Vec<ast::Expr>,
7072
expr: ast::Expr,
7173
) -> Option<()> {
72-
let hir::InFile { value: function_source, .. } = function.source(ctx.db())?;
74+
let hir::InFile { value: function_source, file_id } = function.source(ctx.db())?;
7375
let param_list = function_source.param_list()?;
76+
let mut assoc_fn_params = function.assoc_fn_params(ctx.sema.db).into_iter();
7477

7578
let mut params = Vec::new();
7679
if let Some(self_param) = param_list.self_param() {
7780
// FIXME this should depend on the receiver as well as the self_param
78-
params.push(
81+
params.push((
7982
make::ident_pat(
8083
self_param.amp_token().is_some(),
8184
self_param.mut_token().is_some(),
8285
make::name("this"),
8386
)
8487
.into(),
85-
);
88+
assoc_fn_params.next()?,
89+
));
8690
}
8791
for param in param_list.params() {
88-
params.push(param.pat()?);
92+
params.push((param.pat()?, assoc_fn_params.next()?));
8993
}
9094

9195
if arg_list.len() != params.len() {
@@ -95,41 +99,95 @@ pub(crate) fn inline_(
9599
return None;
96100
}
97101

98-
let new_bindings = params.into_iter().zip(arg_list);
99-
100102
let body = function_source.body()?;
101103

102104
acc.add(
103105
AssistId("inline_call", AssistKind::RefactorInline),
104106
label,
105107
expr.syntax().text_range(),
106108
|builder| {
107-
// FIXME: emit type ascriptions when a coercion happens?
108-
// FIXME: dont create locals when its not required
109-
let statements = new_bindings
110-
.map(|(pattern, value)| make::let_stmt(pattern, Some(value)).into())
111-
.chain(body.statements());
112-
113-
let original_indentation = expr.indent_level();
114-
let mut replacement = make::block_expr(statements, body.tail_expr())
115-
.reset_indent()
116-
.indent(original_indentation);
117-
109+
let body = body.clone_for_update();
110+
111+
let file_id = file_id.original_file(ctx.sema.db);
112+
let usages_for_locals = |local| {
113+
Definition::Local(local)
114+
.usages(&ctx.sema)
115+
.all()
116+
.references
117+
.remove(&file_id)
118+
.unwrap_or_default()
119+
.into_iter()
120+
};
121+
// Contains the nodes of usages of parameters.
122+
// If the inner Vec for a parameter is empty it either means there are no usages or that the parameter
123+
// has a pattern that does not allow inlining
124+
let param_use_nodes: Vec<Vec<_>> = params
125+
.iter()
126+
.map(|(pat, param)| {
127+
if !matches!(pat, ast::Pat::IdentPat(pat) if pat.is_simple_ident()) {
128+
return Vec::new();
129+
}
130+
usages_for_locals(param.as_local(ctx.sema.db))
131+
.map(|FileReference { name, range, .. }| match name {
132+
ast::NameLike::NameRef(_) => body
133+
.syntax()
134+
.covering_element(range)
135+
.ancestors()
136+
.nth(3)
137+
.filter(|it| ast::PathExpr::can_cast(it.kind())),
138+
_ => None,
139+
})
140+
.collect::<Option<Vec<_>>>()
141+
.unwrap_or_default()
142+
})
143+
.collect();
144+
145+
// Rewrite `self` to `this`
118146
if param_list.self_param().is_some() {
119-
replacement = replacement.clone_for_update();
120147
let this = make::name_ref("this").syntax().clone_for_update();
121-
// FIXME dont look into descendant methods
122-
replacement
123-
.syntax()
124-
.descendants()
125-
.filter_map(ast::NameRef::cast)
126-
.filter(|n| n.self_token().is_some())
127-
.collect::<Vec<_>>()
128-
.into_iter()
129-
.rev()
130-
.for_each(|self_ref| ted::replace(self_ref.syntax(), &this));
148+
usages_for_locals(params[0].1.as_local(ctx.sema.db))
149+
.flat_map(|FileReference { name, range, .. }| match name {
150+
ast::NameLike::NameRef(_) => Some(body.syntax().covering_element(range)),
151+
_ => None,
152+
})
153+
.for_each(|it| {
154+
ted::replace(it, &this);
155+
})
156+
}
157+
158+
// Inline parameter expressions or generate `let` statements depending on whether inlining works or not.
159+
for ((pat, _), usages, expr) in izip!(params, param_use_nodes, arg_list).rev() {
160+
match &*usages {
161+
// inline single use parameters
162+
[usage] => {
163+
ted::replace(usage, expr.syntax().clone_for_update());
164+
}
165+
// inline parameters whose expression is a simple local reference
166+
[_, ..]
167+
if matches!(&expr,
168+
ast::Expr::PathExpr(expr)
169+
if expr.path().and_then(|path| path.as_single_name_ref()).is_some()
170+
) =>
171+
{
172+
let expr = expr.syntax().clone_for_update();
173+
usages.into_iter().for_each(|usage| {
174+
ted::replace(usage, &expr);
175+
});
176+
}
177+
// cant inline, emit a let statement
178+
// FIXME: emit type ascriptions when a coercion happens?
179+
_ => body.push_front(make::let_stmt(pat, Some(expr)).clone_for_update().into()),
180+
}
131181
}
132-
builder.replace_ast(expr, ast::Expr::BlockExpr(replacement));
182+
183+
let original_indentation = expr.indent_level();
184+
let replacement = body.reset_indent().indent(original_indentation);
185+
186+
let replacement = match replacement.tail_expr() {
187+
Some(expr) if replacement.statements().next().is_none() => expr,
188+
_ => ast::Expr::BlockExpr(replacement),
189+
};
190+
builder.replace_ast(expr, replacement);
133191
},
134192
)
135193
}
@@ -153,9 +211,7 @@ fn main() {
153211
r#"
154212
fn foo() { println!("Hello, World!"); }
155213
fn main() {
156-
{
157-
println!("Hello, World!");
158-
};
214+
{ println!("Hello, World!"); };
159215
}
160216
"#,
161217
);
@@ -174,10 +230,7 @@ fn main() {
174230
r#"
175231
fn foo(name: String) { println!("Hello, {}!", name); }
176232
fn main() {
177-
{
178-
let name = String::from("Michael");
179-
println!("Hello, {}!", name);
180-
};
233+
{ let name = String::from("Michael"); println!("Hello, {}!", name); };
181234
}
182235
"#,
183236
);
@@ -218,10 +271,8 @@ fn foo(a: u32, b: u32) -> u32 {
218271
}
219272
220273
fn main() {
221-
let x = {
222-
let a = 1;
223-
let b = 2;
224-
let x = a + b;
274+
let x = { let b = 2;
275+
let x = 1 + b;
225276
let y = x - b;
226277
x * y
227278
};
@@ -257,11 +308,7 @@ impl Foo {
257308
}
258309
259310
fn main() {
260-
let x = {
261-
let this = Foo(3);
262-
let a = 2;
263-
Foo(this.0 + a)
264-
};
311+
let x = Foo(Foo(3).0 + 2);
265312
}
266313
"#,
267314
);
@@ -294,11 +341,7 @@ impl Foo {
294341
}
295342
296343
fn main() {
297-
let x = {
298-
let this = Foo(3);
299-
let a = 2;
300-
Foo(this.0 + a)
301-
};
344+
let x = Foo(Foo(3).0 + 2);
302345
}
303346
"#,
304347
);
@@ -331,10 +374,8 @@ impl Foo {
331374
}
332375
333376
fn main() {
334-
let x = {
335-
let ref this = Foo(3);
336-
let a = 2;
337-
Foo(this.0 + a)
377+
let x = { let ref this = Foo(3);
378+
Foo(this.0 + 2)
338379
};
339380
}
340381
"#,
@@ -370,8 +411,7 @@ impl Foo {
370411
371412
fn main() {
372413
let mut foo = Foo(3);
373-
{
374-
let ref mut this = foo;
414+
{ let ref mut this = foo;
375415
this.0 = 0;
376416
};
377417
}

crates/syntax/src/ast/edit_in_place.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,12 @@ impl ast::RecordExprFieldList {
431431
}
432432
}
433433

434+
impl ast::BlockExpr {
435+
pub fn push_front(&self, statement: ast::Stmt) {
436+
ted::insert(Position::after(self.l_curly_token().unwrap()), statement.syntax());
437+
}
438+
}
439+
434440
fn normalize_ws_between_braces(node: &SyntaxNode) -> Option<()> {
435441
let l = node
436442
.children_with_tokens()

crates/syntax/src/ast/node_ext.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,15 @@ impl ast::SlicePat {
641641
}
642642
}
643643

644+
impl ast::IdentPat {
645+
pub fn is_simple_ident(&self) -> bool {
646+
self.at_token().is_none()
647+
&& self.mut_token().is_none()
648+
&& self.ref_token().is_none()
649+
&& self.pat().is_none()
650+
}
651+
}
652+
644653
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
645654
pub enum SelfParamKind {
646655
/// self

0 commit comments

Comments
 (0)