3
3
//! This is mostly front-end for [`ide_db::rename`], but it also includes the
4
4
//! tests. This module also implements a couple of magic tricks, like renaming
5
5
//! `self` and to `self` (to switch between associated function and method).
6
+
6
7
use hir:: { AsAssocItem , InFile , Semantics } ;
7
8
use ide_db:: {
8
9
base_db:: FileId ,
9
10
defs:: { Definition , NameClass , NameRefClass } ,
10
11
rename:: { bail, format_err, source_edit_from_references, IdentifierKind } ,
11
12
RootDatabase ,
12
13
} ;
14
+ use itertools:: Itertools ;
13
15
use stdx:: { always, never} ;
14
16
use syntax:: { ast, AstNode , SyntaxNode } ;
15
17
@@ -31,14 +33,33 @@ pub(crate) fn prepare_rename(
31
33
let source_file = sema. parse ( position. file_id ) ;
32
34
let syntax = source_file. syntax ( ) ;
33
35
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
38
39
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
+ }
42
63
}
43
64
44
65
// Feature: Rename
@@ -61,20 +82,27 @@ pub(crate) fn rename(
61
82
let source_file = sema. parse ( position. file_id ) ;
62
83
let syntax = source_file. syntax ( ) ;
63
84
64
- let ( _name_like , def ) = find_definition ( & sema, syntax, position) ?;
85
+ let defs = find_definitions ( & sema, syntax, position) ?;
65
86
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 ( ) ;
76
102
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" ) )
78
106
}
79
107
80
108
/// Called by the client when it is about to rename a file.
@@ -91,59 +119,86 @@ pub(crate) fn will_rename_file(
91
119
Some ( change)
92
120
}
93
121
94
- fn find_definition (
122
+ fn find_definitions (
95
123
sema : & Semantics < RootDatabase > ,
96
124
syntax : & SyntaxNode ,
97
125
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
+ } )
121
169
}
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" ) )
126
184
}
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" ) )
128
195
} else {
129
- None
196
+ // remove duplicates, comparing `Definition`s
197
+ Ok ( v. into_iter ( ) . unique_by ( |t| t. 1 ) )
130
198
}
131
199
}
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) ,
143
201
}
144
- . ok_or_else ( || format_err ! ( "No references found at position" ) ) ?;
145
-
146
- Ok ( ( name_like, def) )
147
202
}
148
203
149
204
fn rename_to_self ( sema : & Semantics < RootDatabase > , local : hir:: Local ) -> RenameResult < SourceChange > {
@@ -515,6 +570,36 @@ fn main() {
515
570
) ;
516
571
}
517
572
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
+
518
603
#[ test]
519
604
fn test_rename_for_macro_args ( ) {
520
605
check (
0 commit comments