diff options
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/src/references/rename.rs | 160 |
1 files changed, 119 insertions, 41 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 50cc1f963..e1ed6de35 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -5,9 +5,9 @@ | |||
5 | use std::fmt::{self, Display}; | 5 | use std::fmt::{self, Display}; |
6 | 6 | ||
7 | use either::Either; | 7 | use either::Either; |
8 | use hir::{AsAssocItem, InFile, Module, ModuleDef, ModuleSource, Semantics}; | 8 | use hir::{AsAssocItem, FieldSource, HasSource, InFile, ModuleSource, Semantics}; |
9 | use ide_db::{ | 9 | use ide_db::{ |
10 | base_db::{AnchoredPathBuf, FileId}, | 10 | base_db::{AnchoredPathBuf, FileId, FileRange}, |
11 | defs::{Definition, NameClass, NameRefClass}, | 11 | defs::{Definition, NameClass, NameRefClass}, |
12 | search::FileReference, | 12 | search::FileReference, |
13 | RootDatabase, | 13 | RootDatabase, |
@@ -20,7 +20,7 @@ use syntax::{ | |||
20 | 20 | ||
21 | use text_edit::TextEdit; | 21 | use text_edit::TextEdit; |
22 | 22 | ||
23 | use crate::{display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange}; | 23 | use crate::{FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange}; |
24 | 24 | ||
25 | type RenameResult<T> = Result<T, RenameError>; | 25 | type RenameResult<T> = Result<T, RenameError>; |
26 | #[derive(Debug)] | 26 | #[derive(Debug)] |
@@ -52,26 +52,9 @@ pub(crate) fn prepare_rename( | |||
52 | let syntax = source_file.syntax(); | 52 | let syntax = source_file.syntax(); |
53 | 53 | ||
54 | let def = find_definition(&sema, syntax, position)?; | 54 | let def = find_definition(&sema, syntax, position)?; |
55 | match def { | 55 | let frange = def_name_range(&&sema, def) |
56 | Definition::SelfType(_) => bail!("Cannot rename `Self`"), | ||
57 | Definition::ModuleDef(ModuleDef::BuiltinType(_)) => bail!("Cannot rename builtin type"), | ||
58 | Definition::ModuleDef(ModuleDef::Module(_)) => (), | ||
59 | _ => { | ||
60 | let nav = def | ||
61 | .try_to_nav(sema.db) | ||
62 | .ok_or_else(|| format_err!("No references found at position"))?; | ||
63 | nav.focus_range.ok_or_else(|| format_err!("No identifier available to rename"))?; | ||
64 | } | ||
65 | }; | ||
66 | let name_like = sema | ||
67 | .find_node_at_offset_with_descend(syntax, position.offset) | ||
68 | .ok_or_else(|| format_err!("No references found at position"))?; | 56 | .ok_or_else(|| format_err!("No references found at position"))?; |
69 | let node = match &name_like { | 57 | Ok(RangeInfo::new(frange.range, ())) |
70 | ast::NameLike::Name(it) => it.syntax(), | ||
71 | ast::NameLike::NameRef(it) => it.syntax(), | ||
72 | ast::NameLike::Lifetime(it) => it.syntax(), | ||
73 | }; | ||
74 | Ok(RangeInfo::new(sema.original_range(node).range, ())) | ||
75 | } | 58 | } |
76 | 59 | ||
77 | // Feature: Rename | 60 | // Feature: Rename |
@@ -104,9 +87,11 @@ pub(crate) fn rename_with_semantics( | |||
104 | 87 | ||
105 | let def = find_definition(sema, syntax, position)?; | 88 | let def = find_definition(sema, syntax, position)?; |
106 | match def { | 89 | match def { |
107 | Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(sema, module, new_name), | 90 | Definition::ModuleDef(hir::ModuleDef::Module(module)) => rename_mod(sema, module, new_name), |
108 | Definition::SelfType(_) => bail!("Cannot rename `Self`"), | 91 | Definition::SelfType(_) => bail!("Cannot rename `Self`"), |
109 | Definition::ModuleDef(ModuleDef::BuiltinType(_)) => bail!("Cannot rename builtin type"), | 92 | Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => { |
93 | bail!("Cannot rename builtin type") | ||
94 | } | ||
110 | def => rename_reference(sema, def, new_name), | 95 | def => rename_reference(sema, def, new_name), |
111 | } | 96 | } |
112 | } | 97 | } |
@@ -194,7 +179,7 @@ fn find_definition( | |||
194 | 179 | ||
195 | fn rename_mod( | 180 | fn rename_mod( |
196 | sema: &Semantics<RootDatabase>, | 181 | sema: &Semantics<RootDatabase>, |
197 | module: Module, | 182 | module: hir::Module, |
198 | new_name: &str, | 183 | new_name: &str, |
199 | ) -> RenameResult<SourceChange> { | 184 | ) -> RenameResult<SourceChange> { |
200 | if IdentifierKind::Ident != check_identifier(new_name)? { | 185 | if IdentifierKind::Ident != check_identifier(new_name)? { |
@@ -227,7 +212,7 @@ fn rename_mod( | |||
227 | _ => never!("Module source node is missing a name"), | 212 | _ => never!("Module source node is missing a name"), |
228 | } | 213 | } |
229 | } | 214 | } |
230 | let def = Definition::ModuleDef(ModuleDef::Module(module)); | 215 | let def = Definition::ModuleDef(hir::ModuleDef::Module(module)); |
231 | let usages = def.usages(sema).all(); | 216 | let usages = def.usages(sema).all(); |
232 | let ref_edits = usages.iter().map(|(&file_id, references)| { | 217 | let ref_edits = usages.iter().map(|(&file_id, references)| { |
233 | (file_id, source_edit_from_references(references, def, new_name)) | 218 | (file_id, source_edit_from_references(references, def, new_name)) |
@@ -293,21 +278,21 @@ fn rename_reference( | |||
293 | .and_then(|it| it.containing_trait_impl(sema.db)) | 278 | .and_then(|it| it.containing_trait_impl(sema.db)) |
294 | .and_then(|it| { | 279 | .and_then(|it| { |
295 | it.items(sema.db).into_iter().find_map(|it| match (it, mod_def) { | 280 | it.items(sema.db).into_iter().find_map(|it| match (it, mod_def) { |
296 | (hir::AssocItem::Function(trait_func), ModuleDef::Function(func)) | 281 | (hir::AssocItem::Function(trait_func), hir::ModuleDef::Function(func)) |
297 | if trait_func.name(sema.db) == func.name(sema.db) => | 282 | if trait_func.name(sema.db) == func.name(sema.db) => |
298 | { | 283 | { |
299 | Some(Definition::ModuleDef(ModuleDef::Function(trait_func))) | 284 | Some(Definition::ModuleDef(hir::ModuleDef::Function(trait_func))) |
300 | } | 285 | } |
301 | (hir::AssocItem::Const(trait_konst), ModuleDef::Const(konst)) | 286 | (hir::AssocItem::Const(trait_konst), hir::ModuleDef::Const(konst)) |
302 | if trait_konst.name(sema.db) == konst.name(sema.db) => | 287 | if trait_konst.name(sema.db) == konst.name(sema.db) => |
303 | { | 288 | { |
304 | Some(Definition::ModuleDef(ModuleDef::Const(trait_konst))) | 289 | Some(Definition::ModuleDef(hir::ModuleDef::Const(trait_konst))) |
305 | } | 290 | } |
306 | ( | 291 | ( |
307 | hir::AssocItem::TypeAlias(trait_type_alias), | 292 | hir::AssocItem::TypeAlias(trait_type_alias), |
308 | ModuleDef::TypeAlias(type_alias), | 293 | hir::ModuleDef::TypeAlias(type_alias), |
309 | ) if trait_type_alias.name(sema.db) == type_alias.name(sema.db) => { | 294 | ) if trait_type_alias.name(sema.db) == type_alias.name(sema.db) => { |
310 | Some(Definition::ModuleDef(ModuleDef::TypeAlias(trait_type_alias))) | 295 | Some(Definition::ModuleDef(hir::ModuleDef::TypeAlias(trait_type_alias))) |
311 | } | 296 | } |
312 | _ => None, | 297 | _ => None, |
313 | }) | 298 | }) |
@@ -557,12 +542,11 @@ fn source_edit_from_def( | |||
557 | def: Definition, | 542 | def: Definition, |
558 | new_name: &str, | 543 | new_name: &str, |
559 | ) -> RenameResult<(FileId, TextEdit)> { | 544 | ) -> RenameResult<(FileId, TextEdit)> { |
560 | let nav = | 545 | let frange: FileRange = def_name_range(sema, def) |
561 | def.try_to_nav(sema.db).ok_or_else(|| format_err!("No references found at position"))?; | 546 | .ok_or_else(|| format_err!("No identifier available to rename"))?; |
562 | 547 | ||
563 | let mut replacement_text = String::new(); | 548 | let mut replacement_text = String::new(); |
564 | let mut repl_range = | 549 | let mut repl_range = frange.range; |
565 | nav.focus_range.ok_or_else(|| format_err!("No identifier available to rename"))?; | ||
566 | if let Definition::Local(local) = def { | 550 | if let Definition::Local(local) = def { |
567 | if let Either::Left(pat) = local.source(sema.db).value { | 551 | if let Either::Left(pat) = local.source(sema.db).value { |
568 | if matches!( | 552 | if matches!( |
@@ -582,7 +566,101 @@ fn source_edit_from_def( | |||
582 | replacement_text.push_str(new_name); | 566 | replacement_text.push_str(new_name); |
583 | } | 567 | } |
584 | let edit = TextEdit::replace(repl_range, replacement_text); | 568 | let edit = TextEdit::replace(repl_range, replacement_text); |
585 | Ok((nav.file_id, edit)) | 569 | Ok((frange.file_id, edit)) |
570 | } | ||
571 | |||
572 | fn def_name_range(sema: &Semantics<RootDatabase>, def: Definition) -> Option<FileRange> { | ||
573 | // FIXME: the `original_file_range` calls here are wrong -- they never fail, | ||
574 | // and _fall back_ to the entirety of the macro call. Such fall back is | ||
575 | // incorrect for renames. The safe behavior would be to return an error for | ||
576 | // such cases. The correct behavior would be to return an auxiliary list of | ||
577 | // "can't rename these occurrences in macros" items, and then show some kind | ||
578 | // of a dialog to the user. | ||
579 | |||
580 | let res = match def { | ||
581 | Definition::Macro(mac) => { | ||
582 | let src = mac.source(sema.db)?; | ||
583 | let name = match &src.value { | ||
584 | Either::Left(it) => it.name()?, | ||
585 | Either::Right(it) => it.name()?, | ||
586 | }; | ||
587 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
588 | } | ||
589 | Definition::Field(field) => { | ||
590 | let src = field.source(sema.db)?; | ||
591 | |||
592 | match &src.value { | ||
593 | FieldSource::Named(record_field) => { | ||
594 | let name = record_field.name()?; | ||
595 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
596 | } | ||
597 | FieldSource::Pos(_) => { | ||
598 | return None; | ||
599 | } | ||
600 | } | ||
601 | } | ||
602 | Definition::ModuleDef(module_def) => match module_def { | ||
603 | hir::ModuleDef::Module(module) => { | ||
604 | let src = module.declaration_source(sema.db)?; | ||
605 | let name = src.value.name()?; | ||
606 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
607 | } | ||
608 | hir::ModuleDef::Function(it) => name_range(it, sema)?, | ||
609 | hir::ModuleDef::Adt(adt) => match adt { | ||
610 | hir::Adt::Struct(it) => name_range(it, sema)?, | ||
611 | hir::Adt::Union(it) => name_range(it, sema)?, | ||
612 | hir::Adt::Enum(it) => name_range(it, sema)?, | ||
613 | }, | ||
614 | hir::ModuleDef::Variant(it) => name_range(it, sema)?, | ||
615 | hir::ModuleDef::Const(it) => name_range(it, sema)?, | ||
616 | hir::ModuleDef::Static(it) => name_range(it, sema)?, | ||
617 | hir::ModuleDef::Trait(it) => name_range(it, sema)?, | ||
618 | hir::ModuleDef::TypeAlias(it) => name_range(it, sema)?, | ||
619 | hir::ModuleDef::BuiltinType(_) => return None, | ||
620 | }, | ||
621 | Definition::SelfType(_) => return None, | ||
622 | Definition::Local(local) => { | ||
623 | let src = local.source(sema.db); | ||
624 | let name = match &src.value { | ||
625 | Either::Left(bind_pat) => bind_pat.name()?, | ||
626 | Either::Right(_) => return None, | ||
627 | }; | ||
628 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
629 | } | ||
630 | Definition::GenericParam(generic_param) => match generic_param { | ||
631 | hir::GenericParam::TypeParam(type_param) => { | ||
632 | let src = type_param.source(sema.db)?; | ||
633 | let name = match &src.value { | ||
634 | Either::Left(_) => return None, | ||
635 | Either::Right(type_param) => type_param.name()?, | ||
636 | }; | ||
637 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
638 | } | ||
639 | hir::GenericParam::LifetimeParam(lifetime_param) => { | ||
640 | let src = lifetime_param.source(sema.db)?; | ||
641 | let lifetime = src.value.lifetime()?; | ||
642 | src.with_value(lifetime.syntax()).original_file_range(sema.db) | ||
643 | } | ||
644 | hir::GenericParam::ConstParam(it) => name_range(it, sema)?, | ||
645 | }, | ||
646 | Definition::Label(label) => { | ||
647 | let src = label.source(sema.db); | ||
648 | let lifetime = src.value.lifetime()?; | ||
649 | src.with_value(lifetime.syntax()).original_file_range(sema.db) | ||
650 | } | ||
651 | }; | ||
652 | return Some(res); | ||
653 | |||
654 | fn name_range<D>(def: D, sema: &Semantics<RootDatabase>) -> Option<FileRange> | ||
655 | where | ||
656 | D: HasSource, | ||
657 | D::Ast: ast::NameOwner, | ||
658 | { | ||
659 | let src = def.source(sema.db)?; | ||
660 | let name = src.value.name()?; | ||
661 | let res = src.with_value(name.syntax()).original_file_range(sema.db); | ||
662 | Some(res) | ||
663 | } | ||
586 | } | 664 | } |
587 | 665 | ||
588 | #[cfg(test)] | 666 | #[cfg(test)] |
@@ -659,7 +737,7 @@ mod tests { | |||
659 | fn test_prepare_rename_namelikes() { | 737 | fn test_prepare_rename_namelikes() { |
660 | check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]); | 738 | check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]); |
661 | check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]); | 739 | check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]); |
662 | check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]); | 740 | check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"3..7: name"#]]); |
663 | } | 741 | } |
664 | 742 | ||
665 | #[test] | 743 | #[test] |
@@ -691,7 +769,7 @@ fn baz() { | |||
691 | x.0$0 = 5; | 769 | x.0$0 = 5; |
692 | } | 770 | } |
693 | "#, | 771 | "#, |
694 | expect![[r#"No identifier available to rename"#]], | 772 | expect![[r#"No references found at position"#]], |
695 | ); | 773 | ); |
696 | } | 774 | } |
697 | 775 | ||
@@ -703,7 +781,7 @@ fn foo() { | |||
703 | let x: i32$0 = 0; | 781 | let x: i32$0 = 0; |
704 | } | 782 | } |
705 | "#, | 783 | "#, |
706 | expect![[r#"Cannot rename builtin type"#]], | 784 | expect![[r#"No references found at position"#]], |
707 | ); | 785 | ); |
708 | } | 786 | } |
709 | 787 | ||
@@ -719,7 +797,7 @@ impl Foo { | |||
719 | } | 797 | } |
720 | } | 798 | } |
721 | "#, | 799 | "#, |
722 | expect![[r#"Cannot rename `Self`"#]], | 800 | expect![[r#"No references found at position"#]], |
723 | ); | 801 | ); |
724 | } | 802 | } |
725 | 803 | ||