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
- use syntax:: { ast, AstNode , SyntaxNode } ;
16
+ use syntax:: {
17
+ ast:: { self } ,
18
+ AstNode , SyntaxNode ,
19
+ } ;
15
20
16
21
use text_edit:: TextEdit ;
17
22
@@ -31,11 +36,12 @@ pub(crate) fn prepare_rename(
31
36
let source_file = sema. parse ( position. file_id ) ;
32
37
let syntax = source_file. syntax ( ) ;
33
38
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
- }
39
+ let mut defs = find_definitions ( & sema, syntax, position) ?;
38
40
41
+ // TODO:
42
+ // - empty case possible or already caught by `find_definitions`?
43
+ // - is "just take the first" correct? If not, what do?
44
+ let ( name_like, _def) = defs. next ( ) . unwrap ( ) ;
39
45
let frange = sema. original_range ( name_like. syntax ( ) ) ;
40
46
always ! ( frange. range. contains_inclusive( position. offset) && frange. file_id == position. file_id) ;
41
47
Ok ( RangeInfo :: new ( frange. range , ( ) ) )
@@ -61,20 +67,26 @@ pub(crate) fn rename(
61
67
let source_file = sema. parse ( position. file_id ) ;
62
68
let syntax = source_file. syntax ( ) ;
63
69
64
- let ( _name_like, def) = find_definition ( & sema, syntax, position) ?;
65
-
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
- }
70
+ let defs = find_definitions ( & sema, syntax, position) ?;
76
71
77
- def. rename ( & sema, new_name)
72
+ let ops: RenameResult < Vec < SourceChange > > = defs
73
+ . map ( |( _namelike, def) | {
74
+ if let Definition :: Local ( local) = def {
75
+ if let Some ( self_param) = local. as_self_param ( sema. db ) {
76
+ cov_mark:: hit!( rename_self_to_param) ;
77
+ return rename_self_to_param ( & sema, local, self_param, new_name) ;
78
+ }
79
+ if new_name == "self" {
80
+ cov_mark:: hit!( rename_to_self) ;
81
+ return rename_to_self ( & sema, local) ;
82
+ }
83
+ }
84
+ def. rename ( & sema, new_name)
85
+ } )
86
+ . collect ( ) ;
87
+ ops?. into_iter ( )
88
+ . reduce ( |acc, elem| acc. merge ( elem) )
89
+ . ok_or_else ( || format_err ! ( "No references found at position" ) )
78
90
}
79
91
80
92
/// Called by the client when it is about to rename a file.
@@ -91,59 +103,76 @@ pub(crate) fn will_rename_file(
91
103
Some ( change)
92
104
}
93
105
94
- fn find_definition (
106
+ fn find_definitions (
95
107
sema : & Semantics < RootDatabase > ,
96
108
syntax : & SyntaxNode ,
97
109
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)
110
+ ) -> RenameResult < impl Iterator < Item = ( ast:: NameLike , Definition ) > > {
111
+ let symbols = sema
112
+ . find_nodes_at_offset_with_descend :: < ast:: NameLike > ( syntax, position. offset )
113
+ . map ( |name_like| {
114
+ let res = match & name_like {
115
+ // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
116
+ ast:: NameLike :: Name ( name)
117
+ if name
118
+ . syntax ( )
119
+ . parent ( )
120
+ . map_or ( false , |it| ast:: Rename :: can_cast ( it. kind ( ) ) ) =>
121
+ {
122
+ bail ! ( "Renaming aliases is currently unsupported" )
121
123
}
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" ) ;
124
+ ast:: NameLike :: Name ( name) => NameClass :: classify ( sema, name)
125
+ . map ( |class| match class {
126
+ NameClass :: Definition ( it) | NameClass :: ConstReference ( it) => it,
127
+ NameClass :: PatFieldShorthand { local_def, field_ref : _ } => {
128
+ Definition :: Local ( local_def)
129
+ }
130
+ } )
131
+ . map ( |def| ( name_like. clone ( ) , def) )
132
+ . ok_or_else ( || format_err ! ( "No references found at position" ) ) ,
133
+ ast:: NameLike :: NameRef ( name_ref) => NameRefClass :: classify ( sema, name_ref)
134
+ . map ( |class| match class {
135
+ NameRefClass :: Definition ( def) => def,
136
+ NameRefClass :: FieldShorthand { local_ref, field_ref : _ } => {
137
+ Definition :: Local ( local_ref)
138
+ }
139
+ } )
140
+ . and_then ( |def| {
141
+ // if the name differs from the definitions name it has to be an alias
142
+ if def. name ( sema. db ) . map_or ( false , |it| it. to_string ( ) != name_ref. text ( ) ) {
143
+ None
144
+ } else {
145
+ Some ( ( name_like. clone ( ) , def) )
146
+ }
147
+ } )
148
+ . ok_or_else ( || format_err ! ( "Renaming aliases is currently unsupported" ) ) ,
149
+ ast:: NameLike :: Lifetime ( lifetime) => {
150
+ NameRefClass :: classify_lifetime ( sema, lifetime)
151
+ . and_then ( |class| match class {
152
+ NameRefClass :: Definition ( def) => Some ( def) ,
153
+ _ => None ,
154
+ } )
155
+ . or_else ( || {
156
+ NameClass :: classify_lifetime ( sema, lifetime) . and_then ( |it| match it {
157
+ NameClass :: Definition ( it) => Some ( it) ,
158
+ _ => None ,
159
+ } )
160
+ } )
161
+ . map ( |def| ( name_like, def) )
162
+ . ok_or_else ( || format_err ! ( "No references found at position" ) )
126
163
}
127
- Some ( def)
128
- } else {
129
- None
130
- }
131
- }
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
- } ) ,
143
- }
144
- . ok_or_else ( || format_err ! ( "No references found at position" ) ) ?;
145
-
146
- Ok ( ( name_like, def) )
164
+ } ;
165
+ res
166
+ } ) ;
167
+
168
+ // TODO avoid collect() somehow?
169
+ let v: RenameResult < Vec < ( ast:: NameLike , Definition ) > > = symbols. collect ( ) ;
170
+ match v {
171
+ // remove duplicates
172
+ // TODO is "unique by `Definition`" correct?
173
+ Ok ( v) => Ok ( v. into_iter ( ) . unique_by ( |t| t. 1 ) ) ,
174
+ Err ( e) => Err ( e) ,
175
+ }
147
176
}
148
177
149
178
fn rename_to_self ( sema : & Semantics < RootDatabase > , local : hir:: Local ) -> RenameResult < SourceChange > {
0 commit comments