@@ -7,7 +7,7 @@ use hir::{
77 sym,
88} ;
99use 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
657652fn is_in_type_path ( self_tok : & syntax:: SyntaxToken ) -> bool {
0 commit comments