@@ -9,6 +9,7 @@ use ide_db::{
99 base_db:: { FileId , FileRange } ,
1010 defs:: { Definition , NameClass , NameRefClass } ,
1111 rename:: { bail, format_err, source_edit_from_references, IdentifierKind } ,
12+ source_change:: SourceChangeBuilder ,
1213 RootDatabase ,
1314} ;
1415use itertools:: Itertools ;
@@ -90,24 +91,60 @@ pub(crate) fn rename(
9091 let syntax = source_file. syntax ( ) ;
9192
9293 let defs = find_definitions ( & sema, syntax, position) ?;
94+ let alias_fallback = alias_fallback ( syntax, position, new_name) ;
95+
96+ let ops: RenameResult < Vec < SourceChange > > = match alias_fallback {
97+ Some ( _) => defs
98+ // FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can
99+ // properly find "direct" usages/references.
100+ . map ( |( .., def) | {
101+ match IdentifierKind :: classify ( new_name) ? {
102+ IdentifierKind :: Ident => ( ) ,
103+ IdentifierKind :: Lifetime => {
104+ bail ! ( "Cannot alias reference to a lifetime identifier" )
105+ }
106+ IdentifierKind :: Underscore => bail ! ( "Cannot alias reference to `_`" ) ,
107+ } ;
93108
94- let ops: RenameResult < Vec < SourceChange > > = defs
95- . map ( |( .., def) | {
96- if let Definition :: Local ( local) = def {
97- if let Some ( self_param) = local. as_self_param ( sema. db ) {
98- cov_mark:: hit!( rename_self_to_param) ;
99- return rename_self_to_param ( & sema, local, self_param, new_name) ;
100- }
101- if new_name == "self" {
102- cov_mark:: hit!( rename_to_self) ;
103- return rename_to_self ( & sema, local) ;
109+ let mut usages = def. usages ( & sema) . all ( ) ;
110+
111+ // FIXME: hack - removes the usage that triggered this rename operation.
112+ match usages. references . get_mut ( & position. file_id ) . and_then ( |refs| {
113+ refs. iter ( )
114+ . position ( |ref_| ref_. range . contains_inclusive ( position. offset ) )
115+ . map ( |idx| refs. remove ( idx) )
116+ } ) {
117+ Some ( _) => ( ) ,
118+ None => never ! ( ) ,
119+ } ;
120+
121+ let mut source_change = SourceChange :: default ( ) ;
122+ source_change. extend ( usages. iter ( ) . map ( |( & file_id, refs) | {
123+ ( file_id, source_edit_from_references ( refs, def, new_name) )
124+ } ) ) ;
125+
126+ Ok ( source_change)
127+ } )
128+ . collect ( ) ,
129+ None => defs
130+ . map ( |( .., def) | {
131+ if let Definition :: Local ( local) = def {
132+ if let Some ( self_param) = local. as_self_param ( sema. db ) {
133+ cov_mark:: hit!( rename_self_to_param) ;
134+ return rename_self_to_param ( & sema, local, self_param, new_name) ;
135+ }
136+ if new_name == "self" {
137+ cov_mark:: hit!( rename_to_self) ;
138+ return rename_to_self ( & sema, local) ;
139+ }
104140 }
105- }
106- def . rename ( & sema , new_name )
107- } )
108- . collect ( ) ;
141+ def . rename ( & sema , new_name )
142+ } )
143+ . collect ( ) ,
144+ } ;
109145
110146 ops?. into_iter ( )
147+ . chain ( alias_fallback)
111148 . reduce ( |acc, elem| acc. merge ( elem) )
112149 . ok_or_else ( || format_err ! ( "No references found at position" ) )
113150}
@@ -130,6 +167,38 @@ pub(crate) fn will_rename_file(
130167 Some ( change)
131168}
132169
170+ // FIXME: Should support `extern crate`.
171+ fn alias_fallback (
172+ syntax : & SyntaxNode ,
173+ FilePosition { file_id, offset } : FilePosition ,
174+ new_name : & str ,
175+ ) -> Option < SourceChange > {
176+ let use_tree = syntax
177+ . token_at_offset ( offset)
178+ . flat_map ( |syntax| syntax. parent_ancestors ( ) )
179+ . find_map ( ast:: UseTree :: cast) ?;
180+
181+ let last_path_segment = use_tree. path ( ) ?. segments ( ) . last ( ) ?. name_ref ( ) ?;
182+ if !last_path_segment. syntax ( ) . text_range ( ) . contains_inclusive ( offset) {
183+ return None ;
184+ } ;
185+
186+ let mut builder = SourceChangeBuilder :: new ( file_id) ;
187+
188+ match use_tree. rename ( ) {
189+ Some ( rename) => {
190+ let offset = rename. syntax ( ) . text_range ( ) ;
191+ builder. replace ( offset, format ! ( "as {new_name}" ) ) ;
192+ }
193+ None => {
194+ let offset = use_tree. syntax ( ) . text_range ( ) . end ( ) ;
195+ builder. insert ( offset, format ! ( " as {new_name}" ) ) ;
196+ }
197+ }
198+
199+ Some ( builder. finish ( ) )
200+ }
201+
133202fn find_definitions (
134203 sema : & Semantics < ' _ , RootDatabase > ,
135204 syntax : & SyntaxNode ,
@@ -2626,7 +2695,8 @@ use qux as frob;
26262695//- /lib.rs crate:lib new_source_root:library
26272696pub struct S;
26282697//- /main.rs crate:main deps:lib new_source_root:local
2629- use lib::S$0;
2698+ use lib::S;
2699+ fn main() { let _: S$0; }
26302700"# ,
26312701 "error: Cannot rename a non-local definition" ,
26322702 ) ;
@@ -2686,4 +2756,27 @@ fn test() {
26862756"# ,
26872757 ) ;
26882758 }
2759+
2760+ #[ test]
2761+ fn rename_path_inside_use_tree ( ) {
2762+ check (
2763+ "Baz" ,
2764+ r#"
2765+ mod foo { pub struct Foo; }
2766+ mod bar { use super::Foo; }
2767+
2768+ use foo::Foo$0;
2769+
2770+ fn main() { let _: Foo; }
2771+ "# ,
2772+ r#"
2773+ mod foo { pub struct Foo; }
2774+ mod bar { use super::Baz; }
2775+
2776+ use foo::Foo as Baz;
2777+
2778+ fn main() { let _: Baz; }
2779+ "# ,
2780+ )
2781+ }
26892782}
0 commit comments