mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-11-02 21:04:18 +00:00
Enhance renaming to include identifiers that are generated from the original symbol
Co-authored-by: Jake Goulding <jake.goulding@integer32.com>
This commit is contained in:
parent
04439c8cce
commit
42e8e4ac4e
6 changed files with 251 additions and 44 deletions
|
|
@ -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))
|
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> {
|
pub fn resolve_range_pat(&self, range_pat: &ast::RangePat) -> Option<Struct> {
|
||||||
self.imp.resolve_range_pat(range_pat).map(Struct::from)
|
self.imp.resolve_range_pat(range_pat).map(Struct::from)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
assists::AssistId,
|
assists::AssistId,
|
||||||
defs::{Definition, NameClass, NameRefClass},
|
defs::{Definition, NameClass, NameRefClass},
|
||||||
|
rename::RenameDefinition,
|
||||||
};
|
};
|
||||||
use syntax::{AstNode, ast};
|
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",
|
"Remove underscore from a used variable",
|
||||||
text_range,
|
text_range,
|
||||||
|builder| {
|
|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;
|
builder.source_change = changes;
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -70,15 +70,19 @@ macro_rules! _bail {
|
||||||
}
|
}
|
||||||
pub use _bail as bail;
|
pub use _bail as bail;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum RenameDefinition {
|
||||||
|
Yes,
|
||||||
|
No,
|
||||||
|
}
|
||||||
|
|
||||||
impl Definition {
|
impl Definition {
|
||||||
pub fn rename(
|
pub fn rename(
|
||||||
&self,
|
&self,
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
new_name: &str,
|
new_name: &str,
|
||||||
|
rename_definition: RenameDefinition,
|
||||||
) -> Result<SourceChange> {
|
) -> Result<SourceChange> {
|
||||||
// We append `r#` if needed.
|
|
||||||
let new_name = new_name.trim_start_matches("r#");
|
|
||||||
|
|
||||||
// self.krate() returns None if
|
// self.krate() returns None if
|
||||||
// self is a built-in attr, built-in type or tool module.
|
// self is a built-in attr, built-in type or tool module.
|
||||||
// it is not allowed for these defs to be renamed.
|
// it is not allowed for these defs to be renamed.
|
||||||
|
|
@ -103,8 +107,10 @@ impl Definition {
|
||||||
bail!("Cannot rename a builtin attr.")
|
bail!("Cannot rename a builtin attr.")
|
||||||
}
|
}
|
||||||
Definition::SelfType(_) => bail!("Cannot rename `Self`"),
|
Definition::SelfType(_) => bail!("Cannot rename `Self`"),
|
||||||
Definition::Macro(mac) => rename_reference(sema, Definition::Macro(mac), new_name),
|
Definition::Macro(mac) => {
|
||||||
def => rename_reference(sema, def, new_name),
|
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>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
def: Definition,
|
def: Definition,
|
||||||
new_name: &str,
|
new_name: &str,
|
||||||
|
rename_definition: RenameDefinition,
|
||||||
) -> Result<SourceChange> {
|
) -> Result<SourceChange> {
|
||||||
let ident_kind = IdentifierKind::classify(new_name)?;
|
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)),
|
source_edit_from_references(references, def, new_name, file_id.edition(sema.db)),
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
|
if rename_definition == RenameDefinition::Yes {
|
||||||
// This needs to come after the references edits, because we change the annotation of existing edits
|
// This needs to come after the references edits, because we change the annotation of existing edits
|
||||||
// if a conflict is detected.
|
// if a conflict is detected.
|
||||||
let (file_id, edit) = source_edit_from_def(sema, def, new_name, &mut source_change)?;
|
let (file_id, edit) = source_edit_from_def(sema, def, new_name, &mut source_change)?;
|
||||||
source_change.insert_source_edit(file_id, edit);
|
source_change.insert_source_edit(file_id, edit);
|
||||||
|
}
|
||||||
Ok(source_change)
|
Ok(source_change)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use hir::{CaseType, InFile, db::ExpandDatabase};
|
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 syntax::AstNode;
|
||||||
|
|
||||||
use crate::{
|
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 label = format!("Rename to {}", d.suggested_text);
|
||||||
let mut res = unresolved_fix("change_case", &label, frange.range);
|
let mut res = unresolved_fix("change_case", &label, frange.range);
|
||||||
if ctx.resolve.should_resolve(&res.id) {
|
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());
|
res.source_change = Some(source_change.ok().unwrap_or_default());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@
|
||||||
//! tests. This module also implements a couple of magic tricks, like renaming
|
//! tests. This module also implements a couple of magic tricks, like renaming
|
||||||
//! `self` and to `self` (to switch between associated function and method).
|
//! `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::{
|
use ide_db::{
|
||||||
FileId, FileRange, RootDatabase,
|
FileId, FileRange, RootDatabase,
|
||||||
defs::{Definition, NameClass, NameRefClass},
|
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,
|
source_change::SourceChangeBuilder,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
@ -33,8 +33,8 @@ pub(crate) fn prepare_rename(
|
||||||
let source_file = sema.parse_guess_edition(position.file_id);
|
let source_file = sema.parse_guess_edition(position.file_id);
|
||||||
let syntax = source_file.syntax();
|
let syntax = source_file.syntax();
|
||||||
|
|
||||||
let res = find_definitions(&sema, syntax, position)?
|
let res = find_definitions(&sema, syntax, position, "?")?
|
||||||
.map(|(frange, kind, def)| {
|
.map(|(frange, kind, def, _, _)| {
|
||||||
// ensure all ranges are valid
|
// ensure all ranges are valid
|
||||||
|
|
||||||
if def.range_for_rename(&sema).is_none() {
|
if def.range_for_rename(&sema).is_none() {
|
||||||
|
|
@ -88,15 +88,15 @@ pub(crate) fn rename(
|
||||||
let source_file = sema.parse(file_id);
|
let source_file = sema.parse(file_id);
|
||||||
let syntax = source_file.syntax();
|
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 alias_fallback = alias_fallback(syntax, position, new_name);
|
||||||
|
|
||||||
let ops: RenameResult<Vec<SourceChange>> = match alias_fallback {
|
let ops: RenameResult<Vec<SourceChange>> = match alias_fallback {
|
||||||
Some(_) => defs
|
Some(_) => defs
|
||||||
// FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can
|
// FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can
|
||||||
// properly find "direct" usages/references.
|
// properly find "direct" usages/references.
|
||||||
.map(|(.., def)| {
|
.map(|(.., def, new_name, _)| {
|
||||||
match IdentifierKind::classify(new_name)? {
|
match IdentifierKind::classify(&new_name)? {
|
||||||
IdentifierKind::Ident => (),
|
IdentifierKind::Ident => (),
|
||||||
IdentifierKind::Lifetime => {
|
IdentifierKind::Lifetime => {
|
||||||
bail!("Cannot alias reference to a lifetime identifier")
|
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| {
|
source_change.extend(usages.references.get_mut(&file_id).iter().map(|refs| {
|
||||||
(
|
(
|
||||||
position.file_id,
|
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(),
|
.collect(),
|
||||||
None => defs
|
None => defs
|
||||||
.map(|(.., def)| {
|
.map(|(.., def, new_name, rename_def)| {
|
||||||
if let Definition::Local(local) = def {
|
if let Definition::Local(local) = def {
|
||||||
if let Some(self_param) = local.as_self_param(sema.db) {
|
if let Some(self_param) = local.as_self_param(sema.db) {
|
||||||
cov_mark::hit!(rename_self_to_param);
|
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" {
|
if new_name == "self" {
|
||||||
cov_mark::hit!(rename_to_self);
|
cov_mark::hit!(rename_to_self);
|
||||||
return rename_to_self(&sema, local);
|
return rename_to_self(&sema, local);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def.rename(&sema, new_name)
|
def.rename(&sema, &new_name, rename_def)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
|
|
@ -159,7 +159,7 @@ pub(crate) fn will_rename_file(
|
||||||
let sema = Semantics::new(db);
|
let sema = Semantics::new(db);
|
||||||
let module = sema.file_to_module_def(file_id)?;
|
let module = sema.file_to_module_def(file_id)?;
|
||||||
let def = Definition::Module(module);
|
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();
|
change.file_system_edits.clear();
|
||||||
Some(change)
|
Some(change)
|
||||||
}
|
}
|
||||||
|
|
@ -200,22 +200,34 @@ fn find_definitions(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
syntax: &SyntaxNode,
|
syntax: &SyntaxNode,
|
||||||
FilePosition { file_id, offset }: FilePosition,
|
FilePosition { file_id, offset }: FilePosition,
|
||||||
) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition)>> {
|
new_name: &str,
|
||||||
let token = syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING));
|
) -> 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))) =
|
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![(
|
return Ok(vec![(
|
||||||
FileRange { file_id, range },
|
FileRange { file_id, range },
|
||||||
SyntaxKind::STRING,
|
SyntaxKind::STRING,
|
||||||
Definition::from(resolution),
|
Definition::from(resolution),
|
||||||
|
new_name.to_owned(),
|
||||||
|
RenameDefinition::Yes,
|
||||||
)]
|
)]
|
||||||
.into_iter());
|
.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 =
|
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 kind = name_like.syntax().kind();
|
||||||
let range = sema
|
let range = sema
|
||||||
.original_range_opt(name_like.syntax())
|
.original_range_opt(name_like.syntax())
|
||||||
|
|
@ -284,24 +296,30 @@ fn find_definitions(
|
||||||
.ok_or_else(|| format_err!("No references found at position"))
|
.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 {
|
match res {
|
||||||
Ok(v) => {
|
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
|
// remove duplicates, comparing `Definition`s
|
||||||
Ok(v.into_iter()
|
Ok(v.into_iter()
|
||||||
.unique_by(|&(.., def)| def)
|
.unique_by(|&(.., def, _, _)| def)
|
||||||
.map(|(a, b, c)| (a.into_file_id(sema.db), b, c))
|
.map(|(a, b, c, d, e)| (a.into_file_id(sema.db), b, c, d, e))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_iter())
|
.into_iter())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2536,7 +2554,7 @@ fn baz() {
|
||||||
x.0$0 = 5;
|
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",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3259,6 +3277,102 @@ trait Trait<T> {
|
||||||
trait Trait<U> {
|
trait Trait<U> {
|
||||||
fn foo() -> impl use<U> Trait {}
|
fn foo() -> impl use<U> Trait {}
|
||||||
}
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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; }
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -538,6 +538,21 @@ pub fn disallow_cfg(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
disabled: false,
|
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())
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue