aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2021-06-14 13:43:59 +0100
committerAleksey Kladov <[email protected]>2021-06-14 13:43:59 +0100
commite696188672be804889f012e19b1c799cc59adee2 (patch)
treec7eb9b10b0122c5a39a66b2c044743d8d067218c
parenta274ae384e38be5ad1b23cd2b7f2120e5a284209 (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.rs160
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 @@
5use std::fmt::{self, Display}; 5use std::fmt::{self, Display};
6 6
7use either::Either; 7use either::Either;
8use hir::{AsAssocItem, InFile, Module, ModuleDef, ModuleSource, Semantics}; 8use hir::{AsAssocItem, FieldSource, HasSource, InFile, ModuleSource, Semantics};
9use ide_db::{ 9use 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
21use text_edit::TextEdit; 21use text_edit::TextEdit;
22 22
23use crate::{display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange}; 23use crate::{FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange};
24 24
25type RenameResult<T> = Result<T, RenameError>; 25type 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
195fn rename_mod( 180fn 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
572fn 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