Unnamed repository; edit this file 'description' to name the repository.
Enhance renaming to include identifiers that are generated from the original symbol
Co-authored-by: Jake Goulding <[email protected]>
Lukas Wirth 11 months ago
parent 04439c8 · commit 42e8e4a
-rw-r--r--crates/hir/src/semantics.rs15
-rw-r--r--crates/ide-assists/src/handlers/remove_underscore.rs3
-rw-r--r--crates/ide-db/src/rename.rs28
-rw-r--r--crates/ide-diagnostics/src/handlers/incorrect_case.rs4
-rw-r--r--crates/ide/src/rename.rs176
-rw-r--r--crates/test-fixture/src/lib.rs69
6 files changed, 251 insertions, 44 deletions
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index e01774650b..09377df615 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -222,6 +222,21 @@ impl<DB: HirDatabase> Semantics<'_, DB> {
self.imp.descend_node_at_offset(node, offset).filter_map(|mut it| it.find_map(N::cast))
}
+ // FIXME: Rethink this API
+ pub fn find_namelike_at_offset_with_descend<'slf>(
+ &'slf self,
+ node: &SyntaxNode,
+ offset: TextSize,
+ ) -> impl Iterator<Item = ast::NameLike> + 'slf {
+ node.token_at_offset(offset)
+ .map(move |token| self.descend_into_macros_no_opaque(token))
+ .map(|descendants| descendants.into_iter().filter_map(move |it| it.value.parent()))
+ // re-order the tokens from token_at_offset by returning the ancestors with the smaller first nodes first
+ // See algo::ancestors_at_offset, which uses the same approach
+ .kmerge_by(|left, right| left.text_range().len().lt(&right.text_range().len()))
+ .filter_map(ast::NameLike::cast)
+ }
+
pub fn resolve_range_pat(&self, range_pat: &ast::RangePat) -> Option<Struct> {
self.imp.resolve_range_pat(range_pat).map(Struct::from)
}
diff --git a/crates/ide-assists/src/handlers/remove_underscore.rs b/crates/ide-assists/src/handlers/remove_underscore.rs
index 912e1936b5..a8e27416d5 100644
--- a/crates/ide-assists/src/handlers/remove_underscore.rs
+++ b/crates/ide-assists/src/handlers/remove_underscore.rs
@@ -1,6 +1,7 @@
use ide_db::{
assists::AssistId,
defs::{Definition, NameClass, NameRefClass},
+ rename::RenameDefinition,
};
use syntax::{AstNode, ast};
@@ -61,7 +62,7 @@ pub(crate) fn remove_underscore(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
"Remove underscore from a used variable",
text_range,
|builder| {
- let changes = def.rename(&ctx.sema, new_name).unwrap();
+ let changes = def.rename(&ctx.sema, new_name, RenameDefinition::Yes).unwrap();
builder.source_change = changes;
},
)
diff --git a/crates/ide-db/src/rename.rs b/crates/ide-db/src/rename.rs
index fa2a46a0f7..e4d42146ca 100644
--- a/crates/ide-db/src/rename.rs
+++ b/crates/ide-db/src/rename.rs
@@ -70,15 +70,19 @@ macro_rules! _bail {
}
pub use _bail as bail;
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum RenameDefinition {
+ Yes,
+ No,
+}
+
impl Definition {
pub fn rename(
&self,
sema: &Semantics<'_, RootDatabase>,
new_name: &str,
+ rename_definition: RenameDefinition,
) -> Result<SourceChange> {
- // We append `r#` if needed.
- let new_name = new_name.trim_start_matches("r#");
-
// self.krate() returns None if
// self is a built-in attr, built-in type or tool module.
// it is not allowed for these defs to be renamed.
@@ -103,8 +107,10 @@ impl Definition {
bail!("Cannot rename a builtin attr.")
}
Definition::SelfType(_) => bail!("Cannot rename `Self`"),
- Definition::Macro(mac) => rename_reference(sema, Definition::Macro(mac), new_name),
- def => rename_reference(sema, def, new_name),
+ Definition::Macro(mac) => {
+ rename_reference(sema, Definition::Macro(mac), new_name, rename_definition)
+ }
+ def => rename_reference(sema, def, new_name, rename_definition),
}
}
@@ -328,6 +334,7 @@ fn rename_reference(
sema: &Semantics<'_, RootDatabase>,
def: Definition,
new_name: &str,
+ rename_definition: RenameDefinition,
) -> Result<SourceChange> {
let ident_kind = IdentifierKind::classify(new_name)?;
@@ -366,11 +373,12 @@ fn rename_reference(
source_edit_from_references(references, def, new_name, file_id.edition(sema.db)),
)
}));
-
- // This needs to come after the references edits, because we change the annotation of existing edits
- // if a conflict is detected.
- let (file_id, edit) = source_edit_from_def(sema, def, new_name, &mut source_change)?;
- source_change.insert_source_edit(file_id, edit);
+ if rename_definition == RenameDefinition::Yes {
+ // This needs to come after the references edits, because we change the annotation of existing edits
+ // if a conflict is detected.
+ let (file_id, edit) = source_edit_from_def(sema, def, new_name, &mut source_change)?;
+ source_change.insert_source_edit(file_id, edit);
+ }
Ok(source_change)
}
diff --git a/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/crates/ide-diagnostics/src/handlers/incorrect_case.rs
index 38f10c778d..519ff19279 100644
--- a/crates/ide-diagnostics/src/handlers/incorrect_case.rs
+++ b/crates/ide-diagnostics/src/handlers/incorrect_case.rs
@@ -1,5 +1,5 @@
use hir::{CaseType, InFile, db::ExpandDatabase};
-use ide_db::{assists::Assist, defs::NameClass};
+use ide_db::{assists::Assist, defs::NameClass, rename::RenameDefinition};
use syntax::AstNode;
use crate::{
@@ -44,7 +44,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option<Vec<Ass
let label = format!("Rename to {}", d.suggested_text);
let mut res = unresolved_fix("change_case", &label, frange.range);
if ctx.resolve.should_resolve(&res.id) {
- let source_change = def.rename(&ctx.sema, &d.suggested_text);
+ let source_change = def.rename(&ctx.sema, &d.suggested_text, RenameDefinition::Yes);
res.source_change = Some(source_change.ok().unwrap_or_default());
}
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index 0423e3da2c..e959a9bd76 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};
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, "?")?
+ .map(|(frange, kind, def, _, _)| {
// ensure all ranges are valid
if def.range_for_rename(&sema).is_none() {
@@ -88,15 +88,15 @@ pub(crate) fn rename(
let source_file = sema.parse(file_id);
let syntax = source_file.syntax();
- let defs = find_definitions(&sema, syntax, position)?;
+ let defs = find_definitions(&sema, syntax, position, new_name)?;
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)? {
+ .map(|(.., def, new_name, _)| {
+ match IdentifierKind::classify(&new_name)? {
IdentifierKind::Ident => (),
IdentifierKind::Lifetime => {
bail!("Cannot alias reference to a lifetime identifier")
@@ -120,7 +120,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(refs, def, &new_name, file_id.edition(db)),
)
}));
@@ -128,18 +128,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);
}
if new_name == "self" {
cov_mark::hit!(rename_to_self);
return rename_to_self(&sema, local);
}
}
- def.rename(&sema, new_name)
+ def.rename(&sema, &new_name, rename_def)
})
.collect(),
};
@@ -159,7 +159,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 +200,34 @@ 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: &str,
+) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition, String, 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.to_owned(),
+ 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| 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 +296,29 @@ 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.to_owned(), RenameDefinition::Yes))
+ } else if let Some(suffix) = n.as_str().strip_prefix(original_ident.as_str()) {
+ Some((range, kind, def, format!("{new_name}{suffix}"), RenameDefinition::No))
+ } else if let Some(prefix) = n.as_str().strip_suffix(original_ident.as_str()) {
+ Some((range, kind, def, format!("{prefix}{new_name}"), RenameDefinition::No))
+ } else {
+ None
+ }
+ })
});
- 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),
}
@@ -2536,7 +2554,7 @@ fn baz() {
x.0$0 = 5;
}
"#,
- "error: No identifier available to rename",
+ "error: No references found at position",
);
}
@@ -2566,7 +2584,7 @@ impl Foo {
}
}
"#,
- "error: Cannot rename `Self`",
+ "error: No references found at position",
);
}
@@ -3262,4 +3280,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; }
+"#,
+ );
+ }
}
diff --git a/crates/test-fixture/src/lib.rs b/crates/test-fixture/src/lib.rs
index 96e1301f22..8eb48f8d93 100644
--- a/crates/test-fixture/src/lib.rs
+++ b/crates/test-fixture/src/lib.rs
@@ -538,6 +538,21 @@ pub fn disallow_cfg(_attr: TokenStream, input: TokenStream) -> TokenStream {
disabled: false,
},
),
+ (
+ r#"
+#[proc_macro_attribute]
+pub fn generate_suffixed_type(_attr: TokenStream, input: TokenStream) -> TokenStream {
+ input
+}
+"#
+ .into(),
+ ProcMacro {
+ name: Symbol::intern("generate_suffixed_type"),
+ kind: ProcMacroKind::Attr,
+ expander: sync::Arc::new(GenerateSuffixedTypeProcMacroExpander),
+ disabled: false,
+ },
+ ),
])
}
@@ -919,3 +934,57 @@ impl ProcMacroExpander for DisallowCfgProcMacroExpander {
Ok(subtree.clone())
}
}
+
+// Generates a new type by adding a suffix to the original name
+#[derive(Debug)]
+struct GenerateSuffixedTypeProcMacroExpander;
+impl ProcMacroExpander for GenerateSuffixedTypeProcMacroExpander {
+ fn expand(
+ &self,
+ subtree: &TopSubtree,
+ _attrs: Option<&TopSubtree>,
+ _env: &Env,
+ _def_site: Span,
+ call_site: Span,
+ _mixed_site: Span,
+ _current_dir: String,
+ ) -> Result<TopSubtree, ProcMacroExpansionError> {
+ let TokenTree::Leaf(Leaf::Ident(ident)) = &subtree.0[1] else {
+ return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
+ };
+
+ let ident = match ident.sym.as_str() {
+ "struct" => {
+ let TokenTree::Leaf(Leaf::Ident(ident)) = &subtree.0[2] else {
+ return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
+ };
+ ident
+ }
+
+ "enum" => {
+ let TokenTree::Leaf(Leaf::Ident(ident)) = &subtree.0[4] else {
+ return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
+ };
+ ident
+ }
+
+ _ => {
+ return Err(ProcMacroExpansionError::Panic("incorrect Input".into()));
+ }
+ };
+
+ let generated_ident = tt::Ident {
+ sym: Symbol::intern(&format!("{}Suffix", ident.sym)),
+ span: ident.span,
+ is_raw: tt::IdentIsRaw::No,
+ };
+
+ let ret = quote! { call_site =>
+ #subtree
+
+ struct #generated_ident;
+ };
+
+ Ok(ret)
+ }
+}