Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/inline_call.rs')
| -rw-r--r-- | crates/ide-assists/src/handlers/inline_call.rs | 80 |
1 files changed, 59 insertions, 21 deletions
diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs index 08302e6d4c..9273ad4cc5 100644 --- a/crates/ide-assists/src/handlers/inline_call.rs +++ b/crates/ide-assists/src/handlers/inline_call.rs @@ -17,7 +17,7 @@ use ide_db::{ }; use itertools::{Itertools, izip}; use syntax::{ - AstNode, NodeOrToken, SyntaxKind, + AstNode, NodeOrToken, SyntaxKind, TextRange, ast::{ self, HasArgList, HasGenericArgs, Pat, PathExpr, edit::{AstNodeEdit, IndentLevel}, @@ -121,6 +121,18 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) -> // directly inlining into macros may cause errors. .filter(|call_info| !ctx.sema.hir_file_for(call_info.node.syntax()).is_macro()) .collect(); + + // Skip calls nested inside other calls being inlined to avoid overlapping + // edits. Nested calls are implicitly replaced when the outer call is inlined. + let all_ranges: Vec<TextRange> = + call_infos.iter().map(|ci| ci.node.syntax().text_range()).collect(); + let (call_infos, nested_infos): (Vec<_>, Vec<_>) = + call_infos.into_iter().partition(|ci| { + let r = ci.node.syntax().text_range(); + !all_ranges.iter().any(|&other| other != r && other.contains_range(r)) + }); + let nested_count = nested_infos.len(); + let anchor = call_infos .first() .map(|ci| ci.node.syntax().clone()) @@ -138,7 +150,7 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) -> editor.replace(call_info.node.syntax(), replacement.syntax()); }) .count(); - if replaced + use_trees.len() == count { + if replaced + nested_count + use_trees.len() == count { // we replaced all usages in this file, so we can remove the imports for use_tree in &use_trees { remove_use_tree_if_simple(use_tree, editor); @@ -332,7 +344,7 @@ fn inline( CallInfo { node, arguments, generic_arg_list, krate }: &CallInfo, file_editor: &SyntaxEditor, ) -> ast::Expr { - let factory = file_editor.make(); + let make = file_editor.make(); let file_id = sema.hir_file_for(fn_body.syntax()); let body_to_clone = if let Some(macro_file) = file_id.macro_file() { cov_mark::hit!(inline_call_defined_in_macro); @@ -433,7 +445,7 @@ fn inline( "", 1, ); - factory.ty(&stripped).syntax().clone() + editor.make().ty(&stripped).syntax().clone() } else { t.syntax().clone() }; @@ -458,8 +470,12 @@ fn inline( let mut let_stmts: Vec<ast::Stmt> = Vec::new(); - let this_token = - factory.name_ref("this").syntax().first_token().expect("NameRef should have had a token."); + let this_token = editor + .make() + .name_ref("this") + .syntax() + .first_token() + .expect("NameRef should have had a token."); let rewrite_self_to_this = |editor: &SyntaxEditor| { for usage in &self_token_usages { editor.replace(usage.clone(), this_token.clone()); @@ -494,7 +510,7 @@ fn inline( let is_self = param.name(sema.db).is_some_and(|name| name == sym::self_); if is_self { - let mut this_pat = factory.ident_pat(false, false, factory.name("this")); + let mut this_pat = make.ident_pat(false, false, make.name("this")); let mut expr = expr.clone(); if let Pat::IdentPat(pat) = pat { match (pat.ref_token(), pat.mut_token()) { @@ -502,11 +518,11 @@ fn inline( (None, None) => {} // mut self => let mut this = obj (None, Some(_)) => { - this_pat = factory.ident_pat(false, true, factory.name("this")); + this_pat = make.ident_pat(false, true, make.name("this")); } // &self => let this = &obj (Some(_), None) => { - expr = factory.expr_ref(expr, false); + expr = make.expr_ref(expr, false); } // let foo = &mut X; &mut self => let this = &mut obj // let mut foo = X; &mut self => let this = &mut *obj (reborrow) @@ -515,16 +531,16 @@ fn inline( .type_of_expr(&expr) .map(|ty| ty.original.is_mutable_reference()); expr = if let Some(true) = should_reborrow { - factory.expr_reborrow(expr) + make.expr_reborrow(expr) } else { - factory.expr_ref(expr, true) + make.expr_ref(expr, true) }; } } }; - let_stmts.push(factory.let_stmt(this_pat.into(), ty, Some(expr)).into()) + let_stmts.push(make.let_stmt(this_pat.into(), ty, Some(expr)).into()) } else { - let_stmts.push(factory.let_stmt(pat.clone(), ty, Some(expr.clone())).into()); + let_stmts.push(make.let_stmt(pat.clone(), ty, Some(expr.clone())).into()); } }; @@ -545,8 +561,8 @@ fn inline( if let Some(field) = path_expr_as_record_field(usage) { cov_mark::hit!(inline_call_inline_direct_field); let field_name = field.field_name().unwrap(); - let new_field = factory.record_expr_field( - factory.name_ref(&field_name.text()), + let new_field = editor.make().record_expr_field( + editor.make().name_ref(&field_name.text()), Some(replacement.clone()), ); editor.replace(field.syntax(), new_field.syntax()); @@ -562,7 +578,7 @@ fn inline( && usage.syntax().parent().and_then(ast::Expr::cast).is_some() => { cov_mark::hit!(inline_call_inline_closure); - let expr = factory.expr_paren(expr.clone()).into(); + let expr = editor.make().expr_paren(expr.clone()).into(); inline_direct(&editor, usage, &expr); } // inline single use literals @@ -605,18 +621,18 @@ fn inline( let is_async_fn = function.is_async(sema.db); if is_async_fn { cov_mark::hit!(inline_call_async_fn); - body = factory.async_move_block_expr(body.statements(), body.tail_expr()); + body = make.async_move_block_expr(body.statements(), body.tail_expr()); // Arguments should be evaluated outside the async block, and then moved into it. if !let_stmts.is_empty() { cov_mark::hit!(inline_call_async_fn_with_let_stmts); body = body.indent(IndentLevel(1)); - body = factory.block_expr(let_stmts, Some(body.into())); + body = make.block_expr(let_stmts, Some(body.into())); } } else if !let_stmts.is_empty() { // Prepend let statements to the body's existing statements let stmts: Vec<ast::Stmt> = let_stmts.into_iter().chain(body.statements()).collect(); - body = factory.block_expr(stmts, body.tail_expr()); + body = make.block_expr(stmts, body.tail_expr()); } let original_indentation = match node { @@ -628,7 +644,7 @@ fn inline( let no_stmts = body.statements().next().is_none(); match body.tail_expr() { Some(expr) if matches!(expr, ast::Expr::ClosureExpr(_)) && no_stmts => { - factory.expr_paren(expr).into() + make.expr_paren(expr).into() } Some(expr) if !is_async_fn && no_stmts => expr, _ => match node @@ -638,7 +654,7 @@ fn inline( .and_then(|bin_expr| bin_expr.lhs()) { Some(lhs) if lhs.syntax() == node.syntax() => { - factory.expr_paren(ast::Expr::BlockExpr(body)).into() + make.expr_paren(ast::Expr::BlockExpr(body)).into() } _ => ast::Expr::BlockExpr(body), }, @@ -1917,6 +1933,28 @@ fn _hash2(self_: &u64, state: &mut u64) { } #[test] + fn inline_callers_nested_calls() { + check_assist( + inline_into_callers, + r#" +fn id$0(x: u32) -> u32 { x } +fn main() { + let x = id(id(1)); +} +"#, + r#" + +fn main() { + let x = { + let x = id(1); + x + }; +} +"#, + ); + } + + #[test] fn inline_into_callers_in_macros_not_applicable() { check_assist_not_applicable( inline_into_callers, |