33//! This is mostly front-end for [`ide_db::rename`], but it also includes the
44//! tests. This module also implements a couple of magic tricks, like renaming
55//! `self` and to `self` (to switch between associated function and method).
6+
67use hir:: { AsAssocItem , InFile , Semantics } ;
78use ide_db:: {
89 base_db:: FileId ,
910 defs:: { Definition , NameClass , NameRefClass } ,
1011 rename:: { bail, format_err, source_edit_from_references, IdentifierKind } ,
1112 RootDatabase ,
1213} ;
14+ use itertools:: Itertools ;
1315use stdx:: { always, never} ;
1416use syntax:: { ast, AstNode , SyntaxNode } ;
1517
@@ -31,14 +33,33 @@ pub(crate) fn prepare_rename(
3133 let source_file = sema. parse ( position. file_id ) ;
3234 let syntax = source_file. syntax ( ) ;
3335
34- let ( name_like, def) = find_definition ( & sema, syntax, position) ?;
35- if def. range_for_rename ( & sema) . is_none ( ) {
36- bail ! ( "No references found at position" )
37- }
36+ let res = find_definitions ( & sema, syntax, position) ?
37+ . map ( |( name_like, def) | {
38+ // ensure all ranges are valid
3839
39- let frange = sema. original_range ( name_like. syntax ( ) ) ;
40- always ! ( frange. range. contains_inclusive( position. offset) && frange. file_id == position. file_id) ;
41- Ok ( RangeInfo :: new ( frange. range , ( ) ) )
40+ if def. range_for_rename ( & sema) . is_none ( ) {
41+ bail ! ( "No references found at position" )
42+ }
43+ let frange = sema. original_range ( name_like. syntax ( ) ) ;
44+
45+ always ! (
46+ frange. range. contains_inclusive( position. offset)
47+ && frange. file_id == position. file_id
48+ ) ;
49+ Ok ( frange. range )
50+ } )
51+ . reduce ( |acc, cur| match ( acc, cur) {
52+ // ensure all ranges are the same
53+ ( Ok ( acc_inner) , Ok ( cur_inner) ) if acc_inner == cur_inner => Ok ( acc_inner) ,
54+ ( Err ( e) , _) => Err ( e) ,
55+ _ => bail ! ( "inconsistent text range" ) ,
56+ } ) ;
57+
58+ match res {
59+ // ensure at least one definition was found
60+ Some ( res) => res. map ( |range| RangeInfo :: new ( range, ( ) ) ) ,
61+ None => bail ! ( "No references found at position" ) ,
62+ }
4263}
4364
4465// Feature: Rename
@@ -61,20 +82,27 @@ pub(crate) fn rename(
6182 let source_file = sema. parse ( position. file_id ) ;
6283 let syntax = source_file. syntax ( ) ;
6384
64- let ( _name_like , def ) = find_definition ( & sema, syntax, position) ?;
85+ let defs = find_definitions ( & sema, syntax, position) ?;
6586
66- if let Definition :: Local ( local) = def {
67- if let Some ( self_param) = local. as_self_param ( sema. db ) {
68- cov_mark:: hit!( rename_self_to_param) ;
69- return rename_self_to_param ( & sema, local, self_param, new_name) ;
70- }
71- if new_name == "self" {
72- cov_mark:: hit!( rename_to_self) ;
73- return rename_to_self ( & sema, local) ;
74- }
75- }
87+ let ops: RenameResult < Vec < SourceChange > > = defs
88+ . map ( |( _namelike, def) | {
89+ if let Definition :: Local ( local) = def {
90+ if let Some ( self_param) = local. as_self_param ( sema. db ) {
91+ cov_mark:: hit!( rename_self_to_param) ;
92+ return rename_self_to_param ( & sema, local, self_param, new_name) ;
93+ }
94+ if new_name == "self" {
95+ cov_mark:: hit!( rename_to_self) ;
96+ return rename_to_self ( & sema, local) ;
97+ }
98+ }
99+ def. rename ( & sema, new_name)
100+ } )
101+ . collect ( ) ;
76102
77- def. rename ( & sema, new_name)
103+ ops?. into_iter ( )
104+ . reduce ( |acc, elem| acc. merge ( elem) )
105+ . ok_or_else ( || format_err ! ( "No references found at position" ) )
78106}
79107
80108/// Called by the client when it is about to rename a file.
@@ -91,59 +119,86 @@ pub(crate) fn will_rename_file(
91119 Some ( change)
92120}
93121
94- fn find_definition (
122+ fn find_definitions (
95123 sema : & Semantics < RootDatabase > ,
96124 syntax : & SyntaxNode ,
97125 position : FilePosition ,
98- ) -> RenameResult < ( ast:: NameLike , Definition ) > {
99- let name_like = sema
100- . find_node_at_offset_with_descend :: < ast:: NameLike > ( syntax, position. offset )
101- . ok_or_else ( || format_err ! ( "No references found at position" ) ) ?;
102-
103- let def = match & name_like {
104- // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
105- ast:: NameLike :: Name ( name)
106- if name. syntax ( ) . parent ( ) . map_or ( false , |it| ast:: Rename :: can_cast ( it. kind ( ) ) ) =>
107- {
108- bail ! ( "Renaming aliases is currently unsupported" )
109- }
110- ast:: NameLike :: Name ( name) => NameClass :: classify ( sema, name) . map ( |class| match class {
111- NameClass :: Definition ( it) | NameClass :: ConstReference ( it) => it,
112- NameClass :: PatFieldShorthand { local_def, field_ref : _ } => {
113- Definition :: Local ( local_def)
114- }
115- } ) ,
116- ast:: NameLike :: NameRef ( name_ref) => {
117- if let Some ( def) = NameRefClass :: classify ( sema, name_ref) . map ( |class| match class {
118- NameRefClass :: Definition ( def) => def,
119- NameRefClass :: FieldShorthand { local_ref, field_ref : _ } => {
120- Definition :: Local ( local_ref)
126+ ) -> RenameResult < impl Iterator < Item = ( ast:: NameLike , Definition ) > > {
127+ let symbols = sema
128+ . find_nodes_at_offset_with_descend :: < ast:: NameLike > ( syntax, position. offset )
129+ . map ( |name_like| {
130+ let res = match & name_like {
131+ // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
132+ ast:: NameLike :: Name ( name)
133+ if name
134+ . syntax ( )
135+ . parent ( )
136+ . map_or ( false , |it| ast:: Rename :: can_cast ( it. kind ( ) ) ) =>
137+ {
138+ bail ! ( "Renaming aliases is currently unsupported" )
139+ }
140+ ast:: NameLike :: Name ( name) => NameClass :: classify ( sema, name)
141+ . map ( |class| match class {
142+ NameClass :: Definition ( it) | NameClass :: ConstReference ( it) => it,
143+ NameClass :: PatFieldShorthand { local_def, field_ref : _ } => {
144+ Definition :: Local ( local_def)
145+ }
146+ } )
147+ . map ( |def| ( name_like. clone ( ) , def) )
148+ . ok_or_else ( || format_err ! ( "No references found at position" ) ) ,
149+ ast:: NameLike :: NameRef ( name_ref) => {
150+ NameRefClass :: classify ( sema, name_ref)
151+ . map ( |class| match class {
152+ NameRefClass :: Definition ( def) => def,
153+ NameRefClass :: FieldShorthand { local_ref, field_ref : _ } => {
154+ Definition :: Local ( local_ref)
155+ }
156+ } )
157+ . ok_or_else ( || format_err ! ( "No references found at position" ) )
158+ . and_then ( |def| {
159+ // if the name differs from the definitions name it has to be an alias
160+ if def
161+ . name ( sema. db )
162+ . map_or ( false , |it| it. to_string ( ) != name_ref. text ( ) )
163+ {
164+ Err ( format_err ! ( "Renaming aliases is currently unsupported" ) )
165+ } else {
166+ Ok ( ( name_like. clone ( ) , def) )
167+ }
168+ } )
121169 }
122- } ) {
123- // if the name differs from the definitions name it has to be an alias
124- if def. name ( sema. db ) . map_or ( false , |it| it. to_string ( ) != name_ref. text ( ) ) {
125- bail ! ( "Renaming aliases is currently unsupported" ) ;
170+ ast:: NameLike :: Lifetime ( lifetime) => {
171+ NameRefClass :: classify_lifetime ( sema, lifetime)
172+ . and_then ( |class| match class {
173+ NameRefClass :: Definition ( def) => Some ( def) ,
174+ _ => None ,
175+ } )
176+ . or_else ( || {
177+ NameClass :: classify_lifetime ( sema, lifetime) . and_then ( |it| match it {
178+ NameClass :: Definition ( it) => Some ( it) ,
179+ _ => None ,
180+ } )
181+ } )
182+ . map ( |def| ( name_like, def) )
183+ . ok_or_else ( || format_err ! ( "No references found at position" ) )
126184 }
127- Some ( def)
185+ } ;
186+ res
187+ } ) ;
188+
189+ let res: RenameResult < Vec < _ > > = symbols. collect ( ) ;
190+ match res {
191+ Ok ( v) => {
192+ if v. is_empty ( ) {
193+ // FIXME: some semantic duplication between "empty vec" and "Err()"
194+ Err ( format_err ! ( "No references found at position" ) )
128195 } else {
129- None
196+ // remove duplicates, comparing `Definition`s
197+ Ok ( v. into_iter ( ) . unique_by ( |t| t. 1 ) )
130198 }
131199 }
132- ast:: NameLike :: Lifetime ( lifetime) => NameRefClass :: classify_lifetime ( sema, lifetime)
133- . and_then ( |class| match class {
134- NameRefClass :: Definition ( def) => Some ( def) ,
135- _ => None ,
136- } )
137- . or_else ( || {
138- NameClass :: classify_lifetime ( sema, lifetime) . and_then ( |it| match it {
139- NameClass :: Definition ( it) => Some ( it) ,
140- _ => None ,
141- } )
142- } ) ,
200+ Err ( e) => Err ( e) ,
143201 }
144- . ok_or_else ( || format_err ! ( "No references found at position" ) ) ?;
145-
146- Ok ( ( name_like, def) )
147202}
148203
149204fn rename_to_self ( sema : & Semantics < RootDatabase > , local : hir:: Local ) -> RenameResult < SourceChange > {
@@ -515,6 +570,36 @@ fn main() {
515570 ) ;
516571 }
517572
573+ #[ test]
574+ fn test_rename_macro_multiple_occurrences ( ) {
575+ check (
576+ "Baaah" ,
577+ r#"macro_rules! foo {
578+ ($ident:ident) => {
579+ const $ident: () = ();
580+ struct $ident {}
581+ };
582+ }
583+
584+ foo!($0Foo);
585+ const _: () = Foo;
586+ const _: Foo = Foo {};
587+ "# ,
588+ r#"
589+ macro_rules! foo {
590+ ($ident:ident) => {
591+ const $ident: () = ();
592+ struct $ident {}
593+ };
594+ }
595+
596+ foo!(Baaah);
597+ const _: () = Baaah;
598+ const _: Baaah = Baaah {};
599+ "# ,
600+ )
601+ }
602+
518603 #[ test]
519604 fn test_rename_for_macro_args ( ) {
520605 check (
0 commit comments