Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/rename.rs')
| -rw-r--r-- | crates/ide/src/rename.rs | 238 |
1 files changed, 190 insertions, 48 deletions
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 0423e3da2c..fb84e8e6b4 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -4,11 +4,11 @@ //! tests. This module also implements a couple of magic tricks, like renaming //! `self` and to `self` (to switch between associated function and method). -use hir::{AsAssocItem, InFile, Semantics}; +use hir::{AsAssocItem, InFile, Name, Semantics, sym}; use ide_db::{ FileId, FileRange, RootDatabase, defs::{Definition, NameClass, NameRefClass}, - rename::{IdentifierKind, bail, format_err, source_edit_from_references}, + rename::{IdentifierKind, RenameDefinition, bail, format_err, source_edit_from_references}, source_change::SourceChangeBuilder, }; use itertools::Itertools; @@ -33,8 +33,8 @@ pub(crate) fn prepare_rename( let source_file = sema.parse_guess_edition(position.file_id); let syntax = source_file.syntax(); - let res = find_definitions(&sema, syntax, position)? - .map(|(frange, kind, def)| { + let res = find_definitions(&sema, syntax, position, &Name::new_symbol_root(sym::underscore))? + .map(|(frange, kind, def, _, _)| { // ensure all ranges are valid if def.range_for_rename(&sema).is_none() { @@ -88,22 +88,28 @@ pub(crate) fn rename( let source_file = sema.parse(file_id); let syntax = source_file.syntax(); - let defs = find_definitions(&sema, syntax, position)?; - let alias_fallback = alias_fallback(syntax, position, new_name); + let edition = file_id.edition(db); + let (new_name, kind) = IdentifierKind::classify(edition, new_name)?; + + let defs = find_definitions(&sema, syntax, position, &new_name)?; + let alias_fallback = + alias_fallback(syntax, position, &new_name.display(db, edition).to_string()); let ops: RenameResult<Vec<SourceChange>> = match alias_fallback { Some(_) => defs // FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can // properly find "direct" usages/references. - .map(|(.., def)| { - match IdentifierKind::classify(new_name)? { + .map(|(.., def, new_name, _)| { + match kind { IdentifierKind::Ident => (), IdentifierKind::Lifetime => { bail!("Cannot alias reference to a lifetime identifier") } IdentifierKind::Underscore => bail!("Cannot alias reference to `_`"), + IdentifierKind::LowercaseSelf => { + bail!("Cannot rename alias reference to `self`") + } }; - let mut usages = def.usages(&sema).all(); // FIXME: hack - removes the usage that triggered this rename operation. @@ -120,7 +126,7 @@ pub(crate) fn rename( source_change.extend(usages.references.get_mut(&file_id).iter().map(|refs| { ( position.file_id, - source_edit_from_references(refs, def, new_name, file_id.edition(db)), + source_edit_from_references(db, refs, def, &new_name, edition), ) })); @@ -128,18 +134,18 @@ pub(crate) fn rename( }) .collect(), None => defs - .map(|(.., def)| { + .map(|(.., def, new_name, rename_def)| { if let Definition::Local(local) = def { if let Some(self_param) = local.as_self_param(sema.db) { cov_mark::hit!(rename_self_to_param); - return rename_self_to_param(&sema, local, self_param, new_name); + return rename_self_to_param(&sema, local, self_param, &new_name, kind); } - if new_name == "self" { + if kind == IdentifierKind::LowercaseSelf { cov_mark::hit!(rename_to_self); return rename_to_self(&sema, local); } } - def.rename(&sema, new_name) + def.rename(&sema, new_name.as_str(), rename_def) }) .collect(), }; @@ -159,7 +165,7 @@ pub(crate) fn will_rename_file( let sema = Semantics::new(db); let module = sema.file_to_module_def(file_id)?; let def = Definition::Module(module); - let mut change = def.rename(&sema, new_name_stem).ok()?; + let mut change = def.rename(&sema, new_name_stem, RenameDefinition::Yes).ok()?; change.file_system_edits.clear(); Some(change) } @@ -200,22 +206,40 @@ fn find_definitions( sema: &Semantics<'_, RootDatabase>, syntax: &SyntaxNode, FilePosition { file_id, offset }: FilePosition, -) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition)>> { - let token = syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING)); + new_name: &Name, +) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition, Name, RenameDefinition)>> +{ + let maybe_format_args = + syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING)); if let Some((range, _, _, Some(resolution))) = - token.and_then(|token| sema.check_for_format_args_template(token, offset)) + maybe_format_args.and_then(|token| sema.check_for_format_args_template(token, offset)) { return Ok(vec![( FileRange { file_id, range }, SyntaxKind::STRING, Definition::from(resolution), + new_name.clone(), + RenameDefinition::Yes, )] .into_iter()); } + let original_ident = syntax + .token_at_offset(offset) + .max_by_key(|t| { + t.kind().is_any_identifier() || matches!(t.kind(), SyntaxKind::LIFETIME_IDENT) + }) + .map(|t| { + if t.kind() == SyntaxKind::LIFETIME_IDENT { + Name::new_lifetime(t.text()) + } else { + Name::new_root(t.text()) + } + }) + .ok_or_else(|| format_err!("No references found at position"))?; let symbols = - sema.find_nodes_at_offset_with_descend::<ast::NameLike>(syntax, offset).map(|name_like| { + sema.find_namelike_at_offset_with_descend(syntax, offset).map(|name_like| { let kind = name_like.syntax().kind(); let range = sema .original_range_opt(name_like.syntax()) @@ -284,23 +308,28 @@ fn find_definitions( .ok_or_else(|| format_err!("No references found at position")) } }; - res.map(|def| (range, kind, def)) + res.map(|def| { + let n = def.name(sema.db)?; + if n == original_ident { + Some((range, kind, def, new_name.clone(), RenameDefinition::Yes)) + } else if let Some(suffix) = n.as_str().strip_prefix(original_ident.as_str()) { + Some((range, kind, def, Name::new_root(&format!("{}{suffix}", new_name.as_str())), RenameDefinition::No)) + } else { + n.as_str().strip_suffix(original_ident.as_str().trim_start_matches('\'')) + .map(|prefix| (range, kind, def, Name::new_root(&format!("{prefix}{}", new_name.as_str())), RenameDefinition::No)) + } + }) }); - let res: RenameResult<Vec<_>> = symbols.collect(); + let res: RenameResult<Vec<_>> = symbols.filter_map(Result::transpose).collect(); match res { Ok(v) => { - if v.is_empty() { - // FIXME: some semantic duplication between "empty vec" and "Err()" - Err(format_err!("No references found at position")) - } else { - // remove duplicates, comparing `Definition`s - Ok(v.into_iter() - .unique_by(|&(.., def)| def) - .map(|(a, b, c)| (a.into_file_id(sema.db), b, c)) - .collect::<Vec<_>>() - .into_iter()) - } + // remove duplicates, comparing `Definition`s + Ok(v.into_iter() + .unique_by(|&(.., def, _, _)| def) + .map(|(a, b, c, d, e)| (a.into_file_id(sema.db), b, c, d, e)) + .collect::<Vec<_>>() + .into_iter()) } Err(e) => Err(e), } @@ -370,7 +399,13 @@ fn rename_to_self( source_change.extend(usages.iter().map(|(file_id, references)| { ( file_id.file_id(sema.db), - source_edit_from_references(references, def, "self", file_id.edition(sema.db)), + source_edit_from_references( + sema.db, + references, + def, + &Name::new_symbol_root(sym::self_), + file_id.edition(sema.db), + ), ) })); source_change.insert_source_edit( @@ -384,23 +419,25 @@ fn rename_self_to_param( sema: &Semantics<'_, RootDatabase>, local: hir::Local, self_param: hir::SelfParam, - new_name: &str, + new_name: &Name, + identifier_kind: IdentifierKind, ) -> RenameResult<SourceChange> { - if new_name == "self" { + if identifier_kind == IdentifierKind::LowercaseSelf { // Let's do nothing rather than complain. cov_mark::hit!(rename_self_to_self); return Ok(SourceChange::default()); } - let identifier_kind = IdentifierKind::classify(new_name)?; - let InFile { file_id, value: self_param } = sema.source(self_param).ok_or_else(|| format_err!("cannot find function source"))?; let def = Definition::Local(local); let usages = def.usages(sema).all(); - let edit = text_edit_from_self_param(&self_param, new_name) - .ok_or_else(|| format_err!("No target type found"))?; + let edit = text_edit_from_self_param( + &self_param, + new_name.display(sema.db, file_id.edition(sema.db)).to_string(), + ) + .ok_or_else(|| format_err!("No target type found"))?; if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore { bail!("Cannot rename reference to `_` as it is being referenced multiple times"); } @@ -409,13 +446,19 @@ fn rename_self_to_param( source_change.extend(usages.iter().map(|(file_id, references)| { ( file_id.file_id(sema.db), - source_edit_from_references(references, def, new_name, file_id.edition(sema.db)), + source_edit_from_references( + sema.db, + references, + def, + new_name, + file_id.edition(sema.db), + ), ) })); Ok(source_change) } -fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> { +fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> Option<TextEdit> { fn target_type_name(impl_def: &ast::Impl) -> Option<String> { if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { return Some(p.path()?.segment()?.name_ref()?.text().to_string()); @@ -427,7 +470,7 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Opt Some(impl_def) => { let type_name = target_type_name(&impl_def)?; - let mut replacement_text = String::from(new_name); + let mut replacement_text = new_name; replacement_text.push_str(": "); match (self_param.amp_token(), self_param.mut_token()) { (Some(_), None) => replacement_text.push('&'), @@ -440,7 +483,7 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Opt } None => { cov_mark::hit!(rename_self_outside_of_methods); - let mut replacement_text = String::from(new_name); + let mut replacement_text = new_name; replacement_text.push_str(": _"); Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) } @@ -710,7 +753,7 @@ impl Foo { check( "super", r#"fn main() { let i$0 = 1; }"#, - "error: Invalid name `super`: not an identifier", + "error: Invalid name `super`: cannot rename to a keyword", ); } @@ -759,7 +802,11 @@ impl Foo { #[test] fn test_rename_mod_invalid_raw_ident() { - check("r#self", r#"mod foo$0 {}"#, "error: Invalid name `self`: not an identifier"); + check( + "r#self", + r#"mod foo$0 {}"#, + "error: Invalid name `self`: cannot rename module to self", + ); } #[test] @@ -2359,7 +2406,6 @@ fn foo(foo: Foo) { #[test] fn test_rename_lifetimes() { - cov_mark::check!(rename_lifetime); check( "'yeeee", r#" @@ -2536,7 +2582,7 @@ fn baz() { x.0$0 = 5; } "#, - "error: No identifier available to rename", + "error: No references found at position", ); } @@ -2566,7 +2612,7 @@ impl Foo { } } "#, - "error: Cannot rename `Self`", + "error: No references found at position", ); } @@ -3262,4 +3308,100 @@ trait Trait<U> { "#, ); } + + #[test] + fn rename_macro_generated_type_from_type_with_a_suffix() { + check( + "Bar", + r#" +//- proc_macros: generate_suffixed_type +#[proc_macros::generate_suffixed_type] +struct Foo$0; +fn usage(_: FooSuffix) {} +usage(FooSuffix); +"#, + r#" +#[proc_macros::generate_suffixed_type] +struct Bar; +fn usage(_: BarSuffix) {} +usage(BarSuffix); +"#, + ); + } + + #[test] + // FIXME + #[should_panic] + fn rename_macro_generated_type_from_type_usage_with_a_suffix() { + check( + "Bar", + r#" +//- proc_macros: generate_suffixed_type +#[proc_macros::generate_suffixed_type] +struct Foo; +fn usage(_: FooSuffix) {} +usage(FooSuffix); +fn other_place() { Foo$0; } +"#, + r#" +#[proc_macros::generate_suffixed_type] +struct Bar; +fn usage(_: BarSuffix) {} +usage(BarSuffix); +fn other_place() { Bar; } +"#, + ); + } + + #[test] + fn rename_macro_generated_type_from_variant_with_a_suffix() { + check( + "Bar", + r#" +//- proc_macros: generate_suffixed_type +#[proc_macros::generate_suffixed_type] +enum Quux { + Foo$0, +} +fn usage(_: FooSuffix) {} +usage(FooSuffix); +"#, + r#" +#[proc_macros::generate_suffixed_type] +enum Quux { + Bar, +} +fn usage(_: BarSuffix) {} +usage(BarSuffix); +"#, + ); + } + + #[test] + // FIXME + #[should_panic] + fn rename_macro_generated_type_from_variant_usage_with_a_suffix() { + check( + "Bar", + r#" +//- proc_macros: generate_suffixed_type +#[proc_macros::generate_suffixed_type] +enum Quux { + Foo, +} +fn usage(_: FooSuffix) {} +usage(FooSuffix); +fn other_place() { Quux::Foo$0; } +"#, + r#" +#[proc_macros::generate_suffixed_type] +enum Quux { + Bar, +} +fn usage(_: BarSuffix) {} +usage(BartSuffix); +fn other_place() { Quux::Bar$0; } +"#, + ); + } } |