Skip to content

Commit afc57c7

Browse files
committed
internal: address round 2 review for inline_call SyntaxEditor migration
- file-keyed editor map in inline_into_callers (one editor per file) - inline() takes the file editor as a parameter - SyntaxFactory gains async_move_block_expr and expr_reborrow - Self type generic stripping uses replacen, no sub-editor - factory used for record_expr_field, expr_paren, expr_ref, ident_pat, let_stmt, ty - self-token rewriting hoisted into a shared closure
1 parent af16dc1 commit afc57c7

2 files changed

Lines changed: 110 additions & 63 deletions

File tree

crates/ide-assists/src/handlers/inline_call.rs

Lines changed: 58 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use hir::{
77
sym,
88
};
99
use ide_db::{
10-
EditionedFileId, RootDatabase,
10+
EditionedFileId, FileId, FxHashMap, RootDatabase,
1111
base_db::Crate,
1212
defs::Definition,
1313
imports::insert_use::remove_use_tree_if_simple,
@@ -107,6 +107,7 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) ->
107107
let mut usages = usages.all();
108108
let current_file_usage = usages.references.remove(&def_file);
109109

110+
let mut file_editors: FxHashMap<FileId, SyntaxEditor> = FxHashMap::default();
110111
let mut remove_def = true;
111112
let mut inline_refs_for_file = |file_id: EditionedFileId, refs: Vec<FileReference>| {
112113
let file_id = file_id.file_id(ctx.db());
@@ -126,25 +127,26 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) ->
126127
.map(|ci| ci.node.syntax().clone())
127128
.or_else(|| use_trees.first().map(|ut| ut.syntax().clone()));
128129
if let Some(anchor) = anchor {
129-
let mut editor = builder.make_editor(&anchor);
130+
let editor =
131+
file_editors.entry(file_id).or_insert_with(|| builder.make_editor(&anchor));
130132
let replaced = call_infos
131133
.into_iter()
132134
.map(|call_info| {
133135
let replacement = inline(
134136
&ctx.sema, def_file, function, &func_body, &params, &call_info,
137+
editor,
135138
);
136139
editor.replace(call_info.node.syntax(), replacement.syntax());
137140
})
138141
.count();
139142
if replaced + use_trees.len() == count {
140143
// we replaced all usages in this file, so we can remove the imports
141144
for use_tree in &use_trees {
142-
remove_use_tree_if_simple(use_tree, &mut editor);
145+
remove_use_tree_if_simple(use_tree, editor);
143146
}
144147
} else {
145148
remove_def = false;
146149
}
147-
builder.add_file_edits(file_id, editor);
148150
} else if use_trees.len() != count {
149151
remove_def = false;
150152
}
@@ -157,9 +159,13 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) ->
157159
None => builder.edit_file(vfs_def_file),
158160
}
159161
if remove_def {
160-
let mut editor = builder.make_editor(ast_func.syntax());
162+
let editor = file_editors
163+
.entry(vfs_def_file)
164+
.or_insert_with(|| builder.make_editor(ast_func.syntax()));
161165
editor.delete(ast_func.syntax());
162-
builder.add_file_edits(vfs_def_file, editor);
166+
}
167+
for (file_id, editor) in file_editors {
168+
builder.add_file_edits(file_id, editor);
163169
}
164170
},
165171
)
@@ -245,14 +251,11 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
245251

246252
let syntax = call_info.node.syntax().clone();
247253
acc.add(AssistId::refactor_inline("inline_call"), label, syntax.text_range(), |builder| {
248-
let replacement = inline(&ctx.sema, file_id, function, &fn_body, &params, &call_info);
249-
builder.replace_ast(
250-
match call_info.node {
251-
ast::CallableExpr::Call(it) => ast::Expr::CallExpr(it),
252-
ast::CallableExpr::MethodCall(it) => ast::Expr::MethodCallExpr(it),
253-
},
254-
replacement,
255-
);
254+
let mut editor = builder.make_editor(call_info.node.syntax());
255+
let replacement =
256+
inline(&ctx.sema, file_id, function, &fn_body, &params, &call_info, &mut editor);
257+
editor.replace(call_info.node.syntax(), replacement.syntax());
258+
builder.add_file_edits(ctx.vfs_file_id(), editor);
256259
})
257260
}
258261

@@ -328,7 +331,9 @@ fn inline(
328331
fn_body: &ast::BlockExpr,
329332
params: &[(ast::Pat, Option<ast::Type>, hir::Param<'_>)],
330333
CallInfo { node, arguments, generic_arg_list, krate }: &CallInfo,
334+
file_editor: &mut SyntaxEditor,
331335
) -> ast::Expr {
336+
let factory = SyntaxFactory::with_mappings();
332337
let file_id = sema.hir_file_for(fn_body.syntax());
333338
let body_to_clone = if let Some(macro_file) = file_id.macro_file() {
334339
cov_mark::hit!(inline_call_defined_in_macro);
@@ -340,15 +345,8 @@ fn inline(
340345
fn_body.clone()
341346
};
342347

343-
// Capture indent level before SyntaxEditor re-roots via clone_subtree.
344-
// For non-macro bodies, body_to_clone still has its parent, so from_node
345-
// finds the whitespace before `{` and returns the correct source indent level.
346-
// For macro/prettified bodies (already re-rooted at 0), from_node returns 0,
347-
// which is also correct since prettify_macro_expansion rebuilds indent from 0.
348+
// Capture before `with_ast_node` re-roots and loses the source-relative position.
348349
let original_body_indent = IndentLevel::from_node(body_to_clone.syntax());
349-
350-
// The original body's start offset — needed to adjust FileReference ranges
351-
// since SyntaxEditor::with_ast_node re-roots the tree to offset 0
352350
let body_offset = body_to_clone.syntax().text_range().start();
353351
let (mut editor, body) = SyntaxEditor::with_ast_node(&body_to_clone);
354352

@@ -424,20 +422,22 @@ fn inline(
424422
.filter(|tok| tok.kind() == SyntaxKind::SELF_TYPE_KW)
425423
.collect();
426424
for self_tok in self_tokens {
427-
let mut replace_with = t.syntax().clone_subtree();
428-
if !is_in_type_path(&self_tok)
429-
&& let Some(ty) = ast::Type::cast(replace_with.clone())
430-
&& let Some(generic_arg_list) = ty.generic_arg_list()
425+
let replace_with = if !is_in_type_path(&self_tok)
426+
&& let Some(generic_arg_list) = t.generic_arg_list()
431427
{
432-
// Build replacement without generics using a sub-editor
433-
let (mut sub_editor, sub_root) = SyntaxEditor::new(replace_with.clone());
434-
let generic_node = sub_root
435-
.descendants()
436-
.find(|n| n.text_range() == generic_arg_list.syntax().text_range())
437-
.unwrap();
438-
sub_editor.delete(generic_node);
439-
replace_with = sub_editor.finish().new_root().clone();
440-
}
428+
// Strip the outer generic arg list and reparse, since turbofish-less
429+
// generics aren't valid in expression position. The outermost
430+
// `GenericArgList` text is unique within `t`'s text (any inner generics
431+
// are nested inside it), so `replacen(.., 1)` is safe.
432+
let stripped = t.syntax().text().to_string().replacen(
433+
&generic_arg_list.syntax().text().to_string(),
434+
"",
435+
1,
436+
);
437+
factory.ty(&stripped).syntax().clone()
438+
} else {
439+
t.syntax().clone()
440+
};
441441
editor.replace(self_tok, replace_with);
442442
}
443443
}
@@ -459,6 +459,14 @@ fn inline(
459459

460460
let mut let_stmts: Vec<ast::Stmt> = Vec::new();
461461

462+
let this_token =
463+
factory.name_ref("this").syntax().first_token().expect("NameRef should have had a token.");
464+
let rewrite_self_to_this = |editor: &mut SyntaxEditor| {
465+
for usage in &self_token_usages {
466+
editor.replace(usage.clone(), this_token.clone());
467+
}
468+
};
469+
462470
// Inline parameter expressions or generate `let` statements depending on whether inlining works or not.
463471
for ((pat, param_ty, param), usages, expr) in izip!(params, param_use_nodes, arguments) {
464472
// izip confuses RA due to our lack of hygiene info currently losing us type info causing incorrect errors
@@ -487,19 +495,19 @@ fn inline(
487495
let is_self = param.name(sema.db).is_some_and(|name| name == sym::self_);
488496

489497
if is_self {
490-
let mut this_pat = make::ident_pat(false, false, make::name("this"));
498+
let mut this_pat = factory.ident_pat(false, false, factory.name("this"));
491499
let mut expr = expr.clone();
492500
if let Pat::IdentPat(pat) = pat {
493501
match (pat.ref_token(), pat.mut_token()) {
494502
// self => let this = obj
495503
(None, None) => {}
496504
// mut self => let mut this = obj
497505
(None, Some(_)) => {
498-
this_pat = make::ident_pat(false, true, make::name("this"));
506+
this_pat = factory.ident_pat(false, true, factory.name("this"));
499507
}
500508
// &self => let this = &obj
501509
(Some(_), None) => {
502-
expr = make::expr_ref(expr, false);
510+
expr = factory.expr_ref(expr, false);
503511
}
504512
// let foo = &mut X; &mut self => let this = &mut obj
505513
// let mut foo = X; &mut self => let this = &mut *obj (reborrow)
@@ -508,16 +516,16 @@ fn inline(
508516
.type_of_expr(&expr)
509517
.map(|ty| ty.original.is_mutable_reference());
510518
expr = if let Some(true) = should_reborrow {
511-
make::expr_reborrow(expr)
519+
factory.expr_reborrow(expr)
512520
} else {
513-
make::expr_ref(expr, true)
521+
factory.expr_ref(expr, true)
514522
};
515523
}
516524
}
517525
};
518-
let_stmts.push(make::let_stmt(this_pat.into(), ty, Some(expr)).into())
526+
let_stmts.push(factory.let_stmt(this_pat.into(), ty, Some(expr)).into())
519527
} else {
520-
let_stmts.push(make::let_stmt(pat.clone(), ty, Some(expr.clone())).into());
528+
let_stmts.push(factory.let_stmt(pat.clone(), ty, Some(expr.clone())).into());
521529
}
522530
};
523531

@@ -528,13 +536,7 @@ fn inline(
528536
// if it does then emit a let statement and continue
529537
if func_let_vars.contains(&expr.syntax().text().to_string()) {
530538
if is_self_param {
531-
for usage in &self_token_usages {
532-
let this_token = make::name_ref("this")
533-
.syntax()
534-
.first_token()
535-
.expect("NameRef should have had a token.");
536-
editor.replace(usage.clone(), this_token);
537-
}
539+
rewrite_self_to_this(&mut editor);
538540
}
539541
insert_let_stmt();
540542
continue;
@@ -545,7 +547,6 @@ fn inline(
545547
if let Some(field) = path_expr_as_record_field(usage) {
546548
cov_mark::hit!(inline_call_inline_direct_field);
547549
let field_name = field.field_name().unwrap();
548-
let factory = SyntaxFactory::without_mappings();
549550
let new_field = factory.record_expr_field(
550551
factory.name_ref(&field_name.text()),
551552
Some(replacement.clone()),
@@ -563,7 +564,7 @@ fn inline(
563564
&& usage.syntax().parent().and_then(ast::Expr::cast).is_some() =>
564565
{
565566
cov_mark::hit!(inline_call_inline_closure);
566-
let expr = make::expr_paren(expr.clone()).into();
567+
let expr = factory.expr_paren(expr.clone()).into();
567568
inline_direct(&mut editor, usage, &expr);
568569
}
569570
// inline single use literals
@@ -580,14 +581,7 @@ fn inline(
580581
_ => {
581582
if is_self_param {
582583
// Rename all `self` tokens to `this` so the let binding matches.
583-
for usage in &self_token_usages {
584-
let this_token = make::name_ref("this")
585-
.syntax()
586-
.clone_subtree()
587-
.first_token()
588-
.expect("NameRef should have had a token.");
589-
editor.replace(usage.clone(), this_token);
590-
}
584+
rewrite_self_to_this(&mut editor);
591585
}
592586
insert_let_stmt();
593587
}
@@ -610,11 +604,10 @@ fn inline(
610604
body = new_body;
611605
}
612606

613-
let factory = SyntaxFactory::without_mappings();
614607
let is_async_fn = function.is_async(sema.db);
615608
if is_async_fn {
616609
cov_mark::hit!(inline_call_async_fn);
617-
body = make::async_move_block_expr(body.statements(), body.tail_expr());
610+
body = factory.async_move_block_expr(body.statements(), body.tail_expr());
618611

619612
// Arguments should be evaluated outside the async block, and then moved into it.
620613
if !let_stmts.is_empty() {
@@ -635,7 +628,7 @@ fn inline(
635628
body = body.dedent(original_body_indent).indent(original_indentation);
636629

637630
let no_stmts = body.statements().next().is_none();
638-
match body.tail_expr() {
631+
let result = match body.tail_expr() {
639632
Some(expr) if matches!(expr, ast::Expr::ClosureExpr(_)) && no_stmts => {
640633
factory.expr_paren(expr).into()
641634
}
@@ -651,7 +644,9 @@ fn inline(
651644
}
652645
_ => ast::Expr::BlockExpr(body),
653646
},
654-
}
647+
};
648+
file_editor.add_mappings(factory.finish_with_mappings());
649+
result
655650
}
656651

657652
fn is_in_type_path(self_tok: &syntax::SyntaxToken) -> bool {

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,37 @@ impl SyntaxFactory {
878878
ast
879879
}
880880

881+
pub fn async_move_block_expr(
882+
&self,
883+
statements: impl IntoIterator<Item = ast::Stmt>,
884+
tail_expr: Option<ast::Expr>,
885+
) -> ast::BlockExpr {
886+
let (statements, mut input) = iterator_input(statements);
887+
888+
let ast = make::async_move_block_expr(statements, tail_expr.clone()).clone_for_update();
889+
890+
if let Some(mut mapping) = self.mappings() {
891+
let stmt_list = ast.stmt_list().unwrap();
892+
let mut builder = SyntaxMappingBuilder::new(stmt_list.syntax().clone());
893+
894+
if let Some(input) = tail_expr {
895+
builder.map_node(
896+
input.syntax().clone(),
897+
stmt_list.tail_expr().unwrap().syntax().clone(),
898+
);
899+
} else if let Some(ast_tail) = stmt_list.tail_expr() {
900+
let last_stmt = input.pop().unwrap();
901+
builder.map_node(last_stmt, ast_tail.syntax().clone());
902+
}
903+
904+
builder.map_children(input, stmt_list.statements().map(|it| it.syntax().clone()));
905+
906+
builder.finish(&mut mapping);
907+
}
908+
909+
ast
910+
}
911+
881912
pub fn expr_empty_block(&self) -> ast::BlockExpr {
882913
make::expr_empty_block().clone_for_update()
883914
}
@@ -1028,6 +1059,27 @@ impl SyntaxFactory {
10281059
ast.into()
10291060
}
10301061

1062+
pub fn expr_reborrow(&self, expr: ast::Expr) -> ast::Expr {
1063+
let ast::Expr::RefExpr(ast) = make::expr_reborrow(expr.clone()).clone_for_update() else {
1064+
unreachable!()
1065+
};
1066+
1067+
if let Some(mut mapping) = self.mappings() {
1068+
// Layout: RefExpr(&mut, PrefixExpr(*, expr)). Map `expr` to the
1069+
// inner expr inside the synthesized PrefixExpr.
1070+
let prefix = match ast.expr() {
1071+
Some(ast::Expr::PrefixExpr(p)) => p,
1072+
_ => unreachable!("expr_reborrow always produces `&mut *expr`"),
1073+
};
1074+
let inner = prefix.expr().unwrap();
1075+
let mut builder = SyntaxMappingBuilder::new(prefix.syntax().clone());
1076+
builder.map_node(expr.syntax().clone(), inner.syntax().clone());
1077+
builder.finish(&mut mapping);
1078+
}
1079+
1080+
ast.into()
1081+
}
1082+
10311083
pub fn expr_raw_ref(&self, expr: ast::Expr, exclusive: bool) -> ast::Expr {
10321084
let ast::Expr::RefExpr(ast) =
10331085
make::expr_raw_ref(expr.clone(), exclusive).clone_for_update()

0 commit comments

Comments
 (0)