diff options
author | Aleksey Kladov <[email protected]> | 2021-06-14 13:43:59 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2021-06-14 13:43:59 +0100 |
commit | e696188672be804889f012e19b1c799cc59adee2 (patch) | |
tree | c7eb9b10b0122c5a39a66b2c044743d8d067218c | |
parent | a274ae384e38be5ad1b23cd2b7f2120e5a284209 (diff) |
fix: don't use display-related functionality where semantics matters
NavigationTarget is strictly a UI-level thing -- it describes where the
cursor should be placed when the user presses goto definition. It
doesn't make any semantic guaratees about rage and focus range and, as
such, is not suitable for driving renames.
-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 | ||