@@ -9,6 +9,7 @@ use ide_db::{
9
9
base_db:: { FileId , FileRange } ,
10
10
defs:: { Definition , NameClass , NameRefClass } ,
11
11
rename:: { bail, format_err, source_edit_from_references, IdentifierKind } ,
12
+ source_change:: SourceChangeBuilder ,
12
13
RootDatabase ,
13
14
} ;
14
15
use itertools:: Itertools ;
@@ -91,24 +92,52 @@ pub(crate) fn rename(
91
92
let syntax = source_file. syntax ( ) ;
92
93
93
94
let defs = find_definitions ( & sema, syntax, position) ?;
95
+ let alias_fallback = alias_fallback ( & sema, syntax, position, new_name) ?;
96
+
97
+ let ops: RenameResult < Vec < SourceChange > > = match alias_fallback {
98
+ Some ( _) => defs
99
+ . map ( |( .., def) | {
100
+ let mut usages = def. usages ( & sema) . all ( ) ;
101
+
102
+ // Remove the usage that triggered this rename operation.
103
+ match usages. references . get_mut ( & position. file_id ) {
104
+ Some ( refs) => {
105
+ let position = refs
106
+ . iter ( )
107
+ . position ( |ref_| ref_. range . contains_inclusive ( position. offset ) ) ;
108
+ never ! ( position. is_none( ) ) ;
109
+ refs. remove ( position. unwrap ( ) ) ;
110
+ }
111
+ None => never ! ( ) ,
112
+ } ;
94
113
95
- let ops: RenameResult < Vec < SourceChange > > = defs
96
- . map ( |( .., def) | {
97
- if let Definition :: Local ( local) = def {
98
- if let Some ( self_param) = local. as_self_param ( sema. db ) {
99
- cov_mark:: hit!( rename_self_to_param) ;
100
- return rename_self_to_param ( & sema, local, self_param, new_name) ;
101
- }
102
- if new_name == "self" {
103
- cov_mark:: hit!( rename_to_self) ;
104
- return rename_to_self ( & sema, local) ;
114
+ let mut source_change = SourceChange :: default ( ) ;
115
+ source_change. extend ( usages. iter ( ) . map ( |( & file_id, refs) | {
116
+ ( file_id, source_edit_from_references ( refs, def, new_name) )
117
+ } ) ) ;
118
+
119
+ Ok ( source_change)
120
+ } )
121
+ . collect ( ) ,
122
+ None => defs
123
+ . map ( |( .., def) | {
124
+ if let Definition :: Local ( local) = def {
125
+ if let Some ( self_param) = local. as_self_param ( sema. db ) {
126
+ cov_mark:: hit!( rename_self_to_param) ;
127
+ return rename_self_to_param ( & sema, local, self_param, new_name) ;
128
+ }
129
+ if new_name == "self" {
130
+ cov_mark:: hit!( rename_to_self) ;
131
+ return rename_to_self ( & sema, local) ;
132
+ }
105
133
}
106
- }
107
- def . rename ( & sema , new_name , rename_external )
108
- } )
109
- . collect ( ) ;
134
+ def . rename ( & sema , new_name , rename_external )
135
+ } )
136
+ . collect ( ) ,
137
+ } ;
110
138
111
139
ops?. into_iter ( )
140
+ . chain ( alias_fallback)
112
141
. reduce ( |acc, elem| acc. merge ( elem) )
113
142
. ok_or_else ( || format_err ! ( "No references found at position" ) )
114
143
}
@@ -131,6 +160,46 @@ pub(crate) fn will_rename_file(
131
160
Some ( change)
132
161
}
133
162
163
+ // FIXME: Allow usage with `extern crate`?
164
+ fn alias_fallback (
165
+ sema : & Semantics < ' _ , RootDatabase > ,
166
+ syntax : & SyntaxNode ,
167
+ FilePosition { file_id, offset } : FilePosition ,
168
+ new_name : & str ,
169
+ ) -> RenameResult < Option < SourceChange > > {
170
+ match sema. find_node_at_offset_with_descend :: < ast:: UseTree > ( syntax, offset) {
171
+ Some ( use_tree) => {
172
+ if let Some ( _rename) = use_tree. rename ( ) {
173
+ return Ok ( None ) ; // FIXME: path already has an alias
174
+ } ;
175
+
176
+ if let Some ( last_segment) = use_tree. path ( ) . and_then ( |path| path. segments ( ) . last ( ) ) {
177
+ if !last_segment
178
+ . name_ref ( )
179
+ . is_some_and ( |name| name. syntax ( ) . text_range ( ) . contains_inclusive ( offset) )
180
+ {
181
+ return Ok ( None ) ; // FIXME: path isn't last path segment
182
+ }
183
+ } ;
184
+
185
+ match IdentifierKind :: classify ( new_name) ? {
186
+ IdentifierKind :: Ident => ( ) ,
187
+ IdentifierKind :: Lifetime => {
188
+ bail ! ( "Cannot alias reference to a lifetime identifier" )
189
+ }
190
+ IdentifierKind :: Underscore => bail ! ( "Cannot alias reference to `_`" ) ,
191
+ } ;
192
+
193
+ let mut builder = SourceChangeBuilder :: new ( file_id) ;
194
+ let offset = use_tree. syntax ( ) . text_range ( ) . end ( ) ;
195
+ builder. insert ( offset, format ! ( " as {new_name}" ) ) ;
196
+
197
+ Ok ( Some ( builder. finish ( ) ) )
198
+ }
199
+ None => Ok ( None ) ,
200
+ }
201
+ }
202
+
134
203
fn find_definitions (
135
204
sema : & Semantics < ' _ , RootDatabase > ,
136
205
syntax : & SyntaxNode ,
@@ -2707,4 +2776,27 @@ fn test() {
2707
2776
"# ,
2708
2777
) ;
2709
2778
}
2779
+
2780
+ #[ test]
2781
+ fn rename_path_inside_use_tree ( ) {
2782
+ check (
2783
+ "Baz" ,
2784
+ r#"
2785
+ mod foo { pub struct Foo; }
2786
+ mod bar { use super::Foo; }
2787
+
2788
+ use foo::Foo$0;
2789
+
2790
+ fn main() { let _: Foo; }
2791
+ "# ,
2792
+ r#"
2793
+ mod foo { pub struct Foo; }
2794
+ mod bar { use super::Baz; }
2795
+
2796
+ use foo::Foo as Baz;
2797
+
2798
+ fn main() { let _: Baz; }
2799
+ "# ,
2800
+ )
2801
+ }
2710
2802
}
0 commit comments