]> git.lizzy.rs Git - rust.git/blob - crates/ide_db/src/rename.rs
Merge #11157
[rust.git] / crates / ide_db / src / rename.rs
1 //! Rename infrastructure for rust-analyzer. It is used primarily for the
2 //! literal "rename" in the ide (look for tests there), but it is also available
3 //! as a general-purpose service. For example, it is used by the fix for the
4 //! "incorrect case" diagnostic.
5 //!
6 //! It leverages the [`crate::search`] functionality to find what needs to be
7 //! renamed. The actual renames are tricky -- field shorthands need special
8 //! attention, and, when renaming modules, you also want to rename files on the
9 //! file system.
10 //!
11 //! Another can of worms are macros:
12 //!
13 //! ```
14 //! macro_rules! m { () => { fn f() {} } }
15 //! m!();
16 //! fn main() {
17 //!     f() // <- rename me
18 //! }
19 //! ```
20 //!
21 //! The correct behavior in such cases is probably to show a dialog to the user.
22 //! Our current behavior is ¯\_(ツ)_/¯.
23 use std::fmt;
24
25 use base_db::{AnchoredPathBuf, FileId, FileRange};
26 use either::Either;
27 use hir::{AsAssocItem, FieldSource, HasSource, InFile, ModuleSource, Semantics};
28 use stdx::never;
29 use syntax::{
30     ast::{self, HasName},
31     AstNode, SyntaxKind, TextRange, T,
32 };
33 use text_edit::{TextEdit, TextEditBuilder};
34
35 use crate::{
36     defs::Definition,
37     helpers::node_ext::expr_as_name_ref,
38     search::FileReference,
39     source_change::{FileSystemEdit, SourceChange},
40     RootDatabase,
41 };
42
43 pub type Result<T, E = RenameError> = std::result::Result<T, E>;
44
45 #[derive(Debug)]
46 pub struct RenameError(pub String);
47
48 impl fmt::Display for RenameError {
49     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50         fmt::Display::fmt(&self.0, f)
51     }
52 }
53
54 #[macro_export]
55 macro_rules! _format_err {
56     ($fmt:expr) => { RenameError(format!($fmt)) };
57     ($fmt:expr, $($arg:tt)+) => { RenameError(format!($fmt, $($arg)+)) }
58 }
59 pub use _format_err as format_err;
60
61 #[macro_export]
62 macro_rules! _bail {
63     ($($tokens:tt)*) => { return Err(format_err!($($tokens)*)) }
64 }
65 pub use _bail as bail;
66
67 impl Definition {
68     pub fn rename(&self, sema: &Semantics<RootDatabase>, new_name: &str) -> Result<SourceChange> {
69         match *self {
70             Definition::Module(module) => rename_mod(sema, module, new_name),
71             Definition::BuiltinType(_) => {
72                 bail!("Cannot rename builtin type")
73             }
74             Definition::SelfType(_) => bail!("Cannot rename `Self`"),
75             def => rename_reference(sema, def, new_name),
76         }
77     }
78
79     /// Textual range of the identifier which will change when renaming this
80     /// `Definition`. Note that some definitions, like buitin types, can't be
81     /// renamed.
82     pub fn range_for_rename(self, sema: &Semantics<RootDatabase>) -> Option<FileRange> {
83         let res = match self {
84             Definition::Macro(mac) => {
85                 let src = mac.source(sema.db)?;
86                 let name = match &src.value {
87                     Either::Left(it) => it.name()?,
88                     Either::Right(it) => it.name()?,
89                 };
90                 src.with_value(name.syntax()).original_file_range_opt(sema.db)
91             }
92             Definition::Field(field) => {
93                 let src = field.source(sema.db)?;
94                 match &src.value {
95                     FieldSource::Named(record_field) => {
96                         let name = record_field.name()?;
97                         src.with_value(name.syntax()).original_file_range_opt(sema.db)
98                     }
99                     FieldSource::Pos(_) => None,
100                 }
101             }
102             Definition::Module(module) => {
103                 let src = module.declaration_source(sema.db)?;
104                 let name = src.value.name()?;
105                 src.with_value(name.syntax()).original_file_range_opt(sema.db)
106             }
107             Definition::Function(it) => name_range(it, sema),
108             Definition::Adt(adt) => match adt {
109                 hir::Adt::Struct(it) => name_range(it, sema),
110                 hir::Adt::Union(it) => name_range(it, sema),
111                 hir::Adt::Enum(it) => name_range(it, sema),
112             },
113             Definition::Variant(it) => name_range(it, sema),
114             Definition::Const(it) => name_range(it, sema),
115             Definition::Static(it) => name_range(it, sema),
116             Definition::Trait(it) => name_range(it, sema),
117             Definition::TypeAlias(it) => name_range(it, sema),
118             Definition::Local(local) => {
119                 let src = local.source(sema.db);
120                 let name = match &src.value {
121                     Either::Left(bind_pat) => bind_pat.name()?,
122                     Either::Right(_) => return None,
123                 };
124                 src.with_value(name.syntax()).original_file_range_opt(sema.db)
125             }
126             Definition::GenericParam(generic_param) => match generic_param {
127                 hir::GenericParam::TypeParam(type_param) => {
128                     let src = type_param.source(sema.db)?;
129                     let name = match &src.value {
130                         Either::Left(type_param) => type_param.name()?,
131                         Either::Right(_trait) => return None,
132                     };
133                     src.with_value(name.syntax()).original_file_range_opt(sema.db)
134                 }
135                 hir::GenericParam::LifetimeParam(lifetime_param) => {
136                     let src = lifetime_param.source(sema.db)?;
137                     let lifetime = src.value.lifetime()?;
138                     src.with_value(lifetime.syntax()).original_file_range_opt(sema.db)
139                 }
140                 hir::GenericParam::ConstParam(it) => name_range(it, sema),
141             },
142             Definition::Label(label) => {
143                 let src = label.source(sema.db);
144                 let lifetime = src.value.lifetime()?;
145                 src.with_value(lifetime.syntax()).original_file_range_opt(sema.db)
146             }
147             Definition::BuiltinType(_) => return None,
148             Definition::SelfType(_) => return None,
149             Definition::BuiltinAttr(_) => return None,
150             Definition::ToolModule(_) => return None,
151         };
152         return res;
153
154         fn name_range<D>(def: D, sema: &Semantics<RootDatabase>) -> Option<FileRange>
155         where
156             D: HasSource,
157             D::Ast: ast::HasName,
158         {
159             let src = def.source(sema.db)?;
160             let name = src.value.name()?;
161             src.with_value(name.syntax()).original_file_range_opt(sema.db)
162         }
163     }
164 }
165
166 fn rename_mod(
167     sema: &Semantics<RootDatabase>,
168     module: hir::Module,
169     new_name: &str,
170 ) -> Result<SourceChange> {
171     if IdentifierKind::classify(new_name)? != IdentifierKind::Ident {
172         bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
173     }
174
175     let mut source_change = SourceChange::default();
176
177     let InFile { file_id, value: def_source } = module.definition_source(sema.db);
178     let file_id = file_id.original_file(sema.db);
179     if let ModuleSource::SourceFile(..) = def_source {
180         // mod is defined in path/to/dir/mod.rs
181         let path = if module.is_mod_rs(sema.db) {
182             format!("../{}/mod.rs", new_name)
183         } else {
184             format!("{}.rs", new_name)
185         };
186         let dst = AnchoredPathBuf { anchor: file_id, path };
187         let move_file = FileSystemEdit::MoveFile { src: file_id, dst };
188         source_change.push_file_system_edit(move_file);
189     }
190
191     if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) {
192         let file_id = file_id.original_file(sema.db);
193         match decl_source.name() {
194             Some(name) => source_change.insert_source_edit(
195                 file_id,
196                 TextEdit::replace(name.syntax().text_range(), new_name.to_string()),
197             ),
198             _ => never!("Module source node is missing a name"),
199         }
200     }
201     let def = Definition::Module(module);
202     let usages = def.usages(sema).all();
203     let ref_edits = usages.iter().map(|(&file_id, references)| {
204         (file_id, source_edit_from_references(references, def, new_name))
205     });
206     source_change.extend(ref_edits);
207
208     Ok(source_change)
209 }
210
211 fn rename_reference(
212     sema: &Semantics<RootDatabase>,
213     mut def: Definition,
214     new_name: &str,
215 ) -> Result<SourceChange> {
216     let ident_kind = IdentifierKind::classify(new_name)?;
217
218     if matches!(
219         def,
220         Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_)
221     ) {
222         match ident_kind {
223             IdentifierKind::Ident | IdentifierKind::Underscore => {
224                 cov_mark::hit!(rename_not_a_lifetime_ident_ref);
225                 bail!("Invalid name `{}`: not a lifetime identifier", new_name);
226             }
227             IdentifierKind::Lifetime => cov_mark::hit!(rename_lifetime),
228         }
229     } else {
230         match ident_kind {
231             IdentifierKind::Lifetime => {
232                 cov_mark::hit!(rename_not_an_ident_ref);
233                 bail!("Invalid name `{}`: not an identifier", new_name);
234             }
235             IdentifierKind::Ident => cov_mark::hit!(rename_non_local),
236             IdentifierKind::Underscore => (),
237         }
238     }
239
240     let assoc_item = match def {
241         // HACK: resolve trait impl items to the item def of the trait definition
242         // so that we properly resolve all trait item references
243         Definition::Function(it) => it.as_assoc_item(sema.db),
244         Definition::TypeAlias(it) => it.as_assoc_item(sema.db),
245         Definition::Const(it) => it.as_assoc_item(sema.db),
246         _ => None,
247     };
248     def = match assoc_item {
249         Some(assoc) => assoc
250             .containing_trait_impl(sema.db)
251             .and_then(|trait_| {
252                 trait_.items(sema.db).into_iter().find_map(|it| match (it, assoc) {
253                     (hir::AssocItem::Function(trait_func), hir::AssocItem::Function(func))
254                         if trait_func.name(sema.db) == func.name(sema.db) =>
255                     {
256                         Some(Definition::Function(trait_func))
257                     }
258                     (hir::AssocItem::Const(trait_konst), hir::AssocItem::Const(konst))
259                         if trait_konst.name(sema.db) == konst.name(sema.db) =>
260                     {
261                         Some(Definition::Const(trait_konst))
262                     }
263                     (
264                         hir::AssocItem::TypeAlias(trait_type_alias),
265                         hir::AssocItem::TypeAlias(type_alias),
266                     ) if trait_type_alias.name(sema.db) == type_alias.name(sema.db) => {
267                         Some(Definition::TypeAlias(trait_type_alias))
268                     }
269                     _ => None,
270                 })
271             })
272             .unwrap_or(def),
273         None => def,
274     };
275     let usages = def.usages(sema).all();
276
277     if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {
278         cov_mark::hit!(rename_underscore_multiple);
279         bail!("Cannot rename reference to `_` as it is being referenced multiple times");
280     }
281     let mut source_change = SourceChange::default();
282     source_change.extend(usages.iter().map(|(&file_id, references)| {
283         (file_id, source_edit_from_references(references, def, new_name))
284     }));
285
286     let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
287     source_change.insert_source_edit(file_id, edit);
288     Ok(source_change)
289 }
290
291 pub fn source_edit_from_references(
292     references: &[FileReference],
293     def: Definition,
294     new_name: &str,
295 ) -> TextEdit {
296     let mut edit = TextEdit::builder();
297     // macros can cause multiple refs to occur for the same text range, so keep track of what we have edited so far
298     let mut edited_ranges = Vec::new();
299     for &FileReference { range, ref name, .. } in references {
300         let has_emitted_edit = match name {
301             // if the ranges differ then the node is inside a macro call, we can't really attempt
302             // to make special rewrites like shorthand syntax and such, so just rename the node in
303             // the macro input
304             ast::NameLike::NameRef(name_ref) if name_ref.syntax().text_range() == range => {
305                 source_edit_from_name_ref(&mut edit, name_ref, new_name, def)
306             }
307             ast::NameLike::Name(name) if name.syntax().text_range() == range => {
308                 source_edit_from_name(&mut edit, name, new_name)
309             }
310             _ => false,
311         };
312         if !has_emitted_edit {
313             if !edited_ranges.contains(&range.start()) {
314                 edit.replace(range, new_name.to_string());
315                 edited_ranges.push(range.start());
316             }
317         }
318     }
319
320     edit.finish()
321 }
322
323 fn source_edit_from_name(edit: &mut TextEditBuilder, name: &ast::Name, new_name: &str) -> bool {
324     if ast::RecordPatField::for_field_name(name).is_some() {
325         if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
326             cov_mark::hit!(rename_record_pat_field_name_split);
327             // Foo { ref mut field } -> Foo { new_name: ref mut field }
328             //      ^ insert `new_name: `
329
330             // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
331             // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
332             edit.insert(ident_pat.syntax().text_range().start(), format!("{}: ", new_name));
333             return true;
334         }
335     }
336
337     false
338 }
339
340 fn source_edit_from_name_ref(
341     edit: &mut TextEditBuilder,
342     name_ref: &ast::NameRef,
343     new_name: &str,
344     def: Definition,
345 ) -> bool {
346     if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
347         let rcf_name_ref = record_field.name_ref();
348         let rcf_expr = record_field.expr();
349         match &(rcf_name_ref, rcf_expr.and_then(|it| expr_as_name_ref(&it))) {
350             // field: init-expr, check if we can use a field init shorthand
351             (Some(field_name), Some(init)) => {
352                 if field_name == name_ref {
353                     if init.text() == new_name {
354                         cov_mark::hit!(test_rename_field_put_init_shorthand);
355                         // Foo { field: local } -> Foo { local }
356                         //       ^^^^^^^ delete this
357
358                         // same names, we can use a shorthand here instead.
359                         // we do not want to erase attributes hence this range start
360                         let s = field_name.syntax().text_range().start();
361                         let e = init.syntax().text_range().start();
362                         edit.delete(TextRange::new(s, e));
363                         return true;
364                     }
365                 } else if init == name_ref {
366                     if field_name.text() == new_name {
367                         cov_mark::hit!(test_rename_local_put_init_shorthand);
368                         // Foo { field: local } -> Foo { field }
369                         //            ^^^^^^^ delete this
370
371                         // same names, we can use a shorthand here instead.
372                         // we do not want to erase attributes hence this range start
373                         let s = field_name.syntax().text_range().end();
374                         let e = init.syntax().text_range().end();
375                         edit.delete(TextRange::new(s, e));
376                         return true;
377                     }
378                 }
379             }
380             // init shorthand
381             (None, Some(_)) if matches!(def, Definition::Field(_)) => {
382                 cov_mark::hit!(test_rename_field_in_field_shorthand);
383                 // Foo { field } -> Foo { new_name: field }
384                 //       ^ insert `new_name: `
385                 let offset = name_ref.syntax().text_range().start();
386                 edit.insert(offset, format!("{}: ", new_name));
387                 return true;
388             }
389             (None, Some(_)) if matches!(def, Definition::Local(_)) => {
390                 cov_mark::hit!(test_rename_local_in_field_shorthand);
391                 // Foo { field } -> Foo { field: new_name }
392                 //            ^ insert `: new_name`
393                 let offset = name_ref.syntax().text_range().end();
394                 edit.insert(offset, format!(": {}", new_name));
395                 return true;
396             }
397             _ => (),
398         }
399     } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
400         let rcf_name_ref = record_field.name_ref();
401         let rcf_pat = record_field.pat();
402         match (rcf_name_ref, rcf_pat) {
403             // field: rename
404             (Some(field_name), Some(ast::Pat::IdentPat(pat)))
405                 if field_name == *name_ref && pat.at_token().is_none() =>
406             {
407                 // field name is being renamed
408                 if let Some(name) = pat.name() {
409                     if name.text() == new_name {
410                         cov_mark::hit!(test_rename_field_put_init_shorthand_pat);
411                         // Foo { field: ref mut local } -> Foo { ref mut field }
412                         //       ^^^^^^^ delete this
413                         //                      ^^^^^ replace this with `field`
414
415                         // same names, we can use a shorthand here instead/
416                         // we do not want to erase attributes hence this range start
417                         let s = field_name.syntax().text_range().start();
418                         let e = pat.syntax().text_range().start();
419                         edit.delete(TextRange::new(s, e));
420                         edit.replace(name.syntax().text_range(), new_name.to_string());
421                         return true;
422                     }
423                 }
424             }
425             _ => (),
426         }
427     }
428     false
429 }
430
431 fn source_edit_from_def(
432     sema: &Semantics<RootDatabase>,
433     def: Definition,
434     new_name: &str,
435 ) -> Result<(FileId, TextEdit)> {
436     let FileRange { file_id, range } = def
437         .range_for_rename(sema)
438         .ok_or_else(|| format_err!("No identifier available to rename"))?;
439
440     let mut edit = TextEdit::builder();
441     if let Definition::Local(local) = def {
442         if let Either::Left(pat) = local.source(sema.db).value {
443             // special cases required for renaming fields/locals in Record patterns
444             if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) {
445                 let name_range = pat.name().unwrap().syntax().text_range();
446                 if let Some(name_ref) = pat_field.name_ref() {
447                     if new_name == name_ref.text() && pat.at_token().is_none() {
448                         // Foo { field: ref mut local } -> Foo { ref mut field }
449                         //       ^^^^^^ delete this
450                         //                      ^^^^^ replace this with `field`
451                         cov_mark::hit!(test_rename_local_put_init_shorthand_pat);
452                         edit.delete(
453                             name_ref
454                                 .syntax()
455                                 .text_range()
456                                 .cover_offset(pat.syntax().text_range().start()),
457                         );
458                         edit.replace(name_range, name_ref.text().to_string());
459                     } else {
460                         // Foo { field: ref mut local @ local 2} -> Foo { field: ref mut new_name @ local2 }
461                         // Foo { field: ref mut local } -> Foo { field: ref mut new_name }
462                         //                      ^^^^^ replace this with `new_name`
463                         edit.replace(name_range, new_name.to_string());
464                     }
465                 } else {
466                     // Foo { ref mut field } -> Foo { field: ref mut new_name }
467                     //      ^ insert `field: `
468                     //               ^^^^^ replace this with `new_name`
469                     edit.insert(
470                         pat.syntax().text_range().start(),
471                         format!("{}: ", pat_field.field_name().unwrap()),
472                     );
473                     edit.replace(name_range, new_name.to_string());
474                 }
475             }
476         }
477     }
478     if edit.is_empty() {
479         edit.replace(range, new_name.to_string());
480     }
481     Ok((file_id, edit.finish()))
482 }
483
484 #[derive(Copy, Clone, Debug, PartialEq)]
485 pub enum IdentifierKind {
486     Ident,
487     Lifetime,
488     Underscore,
489 }
490
491 impl IdentifierKind {
492     pub fn classify(new_name: &str) -> Result<IdentifierKind> {
493         match parser::LexedStr::single_token(new_name) {
494             Some(res) => match res {
495                 (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident),
496                 (T![_], _) => Ok(IdentifierKind::Underscore),
497                 (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => {
498                     Ok(IdentifierKind::Lifetime)
499                 }
500                 (SyntaxKind::LIFETIME_IDENT, _) => {
501                     bail!("Invalid name `{}`: not a lifetime identifier", new_name)
502                 }
503                 (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error),
504                 (_, None) => bail!("Invalid name `{}`: not an identifier", new_name),
505             },
506             None => bail!("Invalid name `{}`: not an identifier", new_name),
507         }
508     }
509 }