@@ -16,7 +16,7 @@ use std::fmt::Write;
1616use stdx:: { always, format_to, never} ;
1717use syntax:: {
1818 AstNode , SyntaxKind , SyntaxNode , TextRange , TextSize ,
19- ast:: { self , HasArgList , prec:: ExprPrecedence } ,
19+ ast:: { self , AnyHasGenericParams , HasArgList , HasGenericParams , HasName , prec:: ExprPrecedence } ,
2020} ;
2121
2222use ide_db:: text_edit:: TextEdit ;
@@ -79,7 +79,10 @@ pub(crate) fn prepare_rename(
7979 let sema = Semantics :: new ( db) ;
8080 let source_file = sema. parse_guess_edition ( position. file_id ) ;
8181 let syntax = source_file. syntax ( ) ;
82-
82+ if let Some ( lifetime_token) = syntax. token_at_offset ( position. offset ) . find ( |t| t. text ( ) == "'_" )
83+ {
84+ return Ok ( RangeInfo :: new ( lifetime_token. text_range ( ) , ( ) ) ) ;
85+ }
8386 let res = find_definitions ( & sema, syntax, position, & Name :: new_symbol_root ( sym:: underscore) ) ?
8487 . filter ( |( _, _, def, _, _) | def. range_for_rename ( & sema) . is_some ( ) )
8588 . map ( |( frange, kind, _, _, _) | {
@@ -133,6 +136,13 @@ pub(crate) fn rename(
133136
134137 let edition = file_id. edition ( db) ;
135138 let ( new_name, kind) = IdentifierKind :: classify ( edition, new_name) ?;
139+ if kind == IdentifierKind :: Lifetime
140+ && let Some ( lifetime_token) =
141+ syntax. token_at_offset ( position. offset ) . find ( |t| t. text ( ) == "'_" )
142+ {
143+ let new_name_str = new_name. display ( db, edition) . to_string ( ) ;
144+ return rename_elided_lifetime ( position, lifetime_token, & new_name_str) ;
145+ }
136146
137147 let defs = find_definitions ( & sema, syntax, position, & new_name) ?;
138148 let alias_fallback =
@@ -797,6 +807,49 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> O
797807 Some ( TextEdit :: replace ( self_param. syntax ( ) . text_range ( ) , replacement_text) )
798808}
799809
810+ fn rename_elided_lifetime (
811+ position : FilePosition ,
812+ lifetime_token : syntax:: SyntaxToken ,
813+ new_name : & str ,
814+ ) -> RenameResult < SourceChange > {
815+ let mut edit = ide_db:: text_edit:: TextEdit :: builder ( ) ;
816+
817+ // 1. Replace the `'_` with the new lifetime name
818+ edit. replace ( lifetime_token. text_range ( ) , new_name. to_owned ( ) ) ;
819+
820+ // 2. Find the parent fn or impl using AnyHasGenericParams
821+ let parent = lifetime_token. parent ( ) . unwrap ( ) ;
822+ if let Some ( has_generic_params) = parent. ancestors ( ) . find_map ( AnyHasGenericParams :: cast) {
823+ if let Some ( generic_list) = has_generic_params. generic_param_list ( ) {
824+ // Generics list < > already exists, insert inside it
825+ if let Some ( l_angle) = generic_list. l_angle_token ( ) {
826+ let insert_text = if generic_list. generic_params ( ) . count ( ) == 0 {
827+ new_name. to_owned ( )
828+ } else {
829+ format ! ( "{}, " , new_name)
830+ } ;
831+ edit. insert ( l_angle. text_range ( ) . end ( ) , insert_text) ;
832+ }
833+ } else {
834+ // No generics exist yet, we must create the < >
835+ let syntax = has_generic_params. syntax ( ) ;
836+ if let Some ( fn_def) = ast:: Fn :: cast ( syntax. clone ( ) )
837+ && let Some ( name) = fn_def. name ( )
838+ {
839+ edit. insert ( name. syntax ( ) . text_range ( ) . end ( ) , format ! ( "<{}>" , new_name) ) ;
840+ } else if let Some ( impl_def) = ast:: Impl :: cast ( syntax. clone ( ) )
841+ && let Some ( impl_kw) = impl_def. impl_token ( )
842+ {
843+ edit. insert ( impl_kw. text_range ( ) . end ( ) , format ! ( "<{}>" , new_name) ) ;
844+ }
845+ }
846+ }
847+
848+ let mut source_change = SourceChange :: default ( ) ;
849+ source_change. insert_source_edit ( position. file_id , edit. finish ( ) ) ;
850+ Ok ( source_change)
851+ }
852+
800853#[ cfg( test) ]
801854mod tests {
802855 use expect_test:: { Expect , expect} ;
0 commit comments