diff options
| author | bors <bors@rust-lang.org> | 2024-02-19 12:03:31 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2024-02-19 12:03:31 +0000 |
| commit | ac1029fac3fe7d2c76cf2df71e0614fdb9683d51 (patch) | |
| tree | d9b94f9b119986d72a94c80dfe7a7ce3fb83b7c1 | |
| parent | ff8fe522e899b00f363adb46bc81c9ef75af9b7e (diff) | |
| parent | 6dd5dc10ef2a7e305045657a9b71662a53884f9f (diff) | |
| download | rust-ac1029fac3fe7d2c76cf2df71e0614fdb9683d51.tar.gz rust-ac1029fac3fe7d2c76cf2df71e0614fdb9683d51.zip | |
Auto merge of #16489 - UserIsntAvailable:feat/use-import-alias, r=Veykril
feat!: create alias when renaming an import.  Closes #15858 Implemented: - [x] - Prevent using `reserved` keywords (e.g self) and `_`. - [x] - Rename other modules that might be referencing the import. - [x] - Fix "broken" tests. - [ ] - Rename **only** "direct" references. - [ ] - Test more cases. Future possibilities: 1. Also support `extern crate <name>` syntax. 2. Allow aliasing `self` when it is inside an `UseTreeList`. ~3. If import path already has an alias, "rename" the alias.~ ~[4. Create alias even if path is not the last path segment.](https://github.com/rust-lang/rust-analyzer/pull/16489#issuecomment-1930541697)~
| -rw-r--r-- | crates/ide/src/rename.rs | 123 |
1 files changed, 108 insertions, 15 deletions
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index f2eedfa4316..f78153df38b 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -9,6 +9,7 @@ use ide_db::{ base_db::{FileId, FileRange}, defs::{Definition, NameClass, NameRefClass}, rename::{bail, format_err, source_edit_from_references, IdentifierKind}, + source_change::SourceChangeBuilder, RootDatabase, }; use itertools::Itertools; @@ -90,24 +91,60 @@ pub(crate) fn rename( let syntax = source_file.syntax(); let defs = find_definitions(&sema, syntax, position)?; + let alias_fallback = alias_fallback(syntax, position, new_name); + + 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)? { + IdentifierKind::Ident => (), + IdentifierKind::Lifetime => { + bail!("Cannot alias reference to a lifetime identifier") + } + IdentifierKind::Underscore => bail!("Cannot alias reference to `_`"), + }; - let ops: RenameResult<Vec<SourceChange>> = defs - .map(|(.., 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); - } - if new_name == "self" { - cov_mark::hit!(rename_to_self); - return rename_to_self(&sema, local); + let mut usages = def.usages(&sema).all(); + + // FIXME: hack - removes the usage that triggered this rename operation. + match usages.references.get_mut(&position.file_id).and_then(|refs| { + refs.iter() + .position(|ref_| ref_.range.contains_inclusive(position.offset)) + .map(|idx| refs.remove(idx)) + }) { + Some(_) => (), + None => never!(), + }; + + let mut source_change = SourceChange::default(); + source_change.extend(usages.iter().map(|(&file_id, refs)| { + (file_id, source_edit_from_references(refs, def, new_name)) + })); + + Ok(source_change) + }) + .collect(), + None => defs + .map(|(.., 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); + } + if new_name == "self" { + cov_mark::hit!(rename_to_self); + return rename_to_self(&sema, local); + } } - } - def.rename(&sema, new_name) - }) - .collect(); + def.rename(&sema, new_name) + }) + .collect(), + }; ops?.into_iter() + .chain(alias_fallback) .reduce(|acc, elem| acc.merge(elem)) .ok_or_else(|| format_err!("No references found at position")) } @@ -130,6 +167,38 @@ pub(crate) fn will_rename_file( Some(change) } +// FIXME: Should support `extern crate`. +fn alias_fallback( + syntax: &SyntaxNode, + FilePosition { file_id, offset }: FilePosition, + new_name: &str, +) -> Option<SourceChange> { + let use_tree = syntax + .token_at_offset(offset) + .flat_map(|syntax| syntax.parent_ancestors()) + .find_map(ast::UseTree::cast)?; + + let last_path_segment = use_tree.path()?.segments().last()?.name_ref()?; + if !last_path_segment.syntax().text_range().contains_inclusive(offset) { + return None; + }; + + let mut builder = SourceChangeBuilder::new(file_id); + + match use_tree.rename() { + Some(rename) => { + let offset = rename.syntax().text_range(); + builder.replace(offset, format!("as {new_name}")); + } + None => { + let offset = use_tree.syntax().text_range().end(); + builder.insert(offset, format!(" as {new_name}")); + } + } + + Some(builder.finish()) +} + fn find_definitions( sema: &Semantics<'_, RootDatabase>, syntax: &SyntaxNode, @@ -2626,7 +2695,8 @@ use qux as frob; //- /lib.rs crate:lib new_source_root:library pub struct S; //- /main.rs crate:main deps:lib new_source_root:local -use lib::S$0; +use lib::S; +fn main() { let _: S$0; } "#, "error: Cannot rename a non-local definition", ); @@ -2686,4 +2756,27 @@ fn test() { "#, ); } + + #[test] + fn rename_path_inside_use_tree() { + check( + "Baz", + r#" +mod foo { pub struct Foo; } +mod bar { use super::Foo; } + +use foo::Foo$0; + +fn main() { let _: Foo; } +"#, + r#" +mod foo { pub struct Foo; } +mod bar { use super::Baz; } + +use foo::Foo as Baz; + +fn main() { let _: Baz; } +"#, + ) + } } |
