I've been trying to write a macro_rule to remove an attribute, and blatantly failed.
Say, you have code like:
mymacro! {
#[doc = "example"]
#[derive(Debug)]
pub struct Example {
field: u32,
}
}
with an arbitrary number of attributes, in any order, and you want it to remove the #[derive], like:
#[doc = "example"]
pub struct Example {
field: u32,
}
One of the simplest things I tried, ignoring any form of genericity, was:
macro_rules! foo {
(
#[derive(Debug)]
$vis:vis struct $I:ident { $($t:tt)* }
) => {
$vis struct $I { $($t)* }
};
(
#[$outer:meta]
$(#[$other_outer:meta])*
$vis:vis struct $I:ident { $($t:tt)* }
) => {
#[$outer]
foo! {
$(#[$other_outer])*
$vis struct $I { $($t)* }
}
}
}
And that doesn't even respect its own rules:
= note: expanding `foo! { #[doc = "example"] #[derive(Debug)] pub struct Example { field : u32, } }`
= note: to `#[doc = "example"] foo!
{ #[derive(Debug)] pub struct Example { field : u32, } }`
= note: expanding `foo! { #[derive(Debug)] pub struct Example { field : u32, } }`
= note: to `#[derive(Debug)] foo! { pub struct Example { field : u32, } }`
= note: expanding `foo! { pub struct Example { field : u32, } }`
(note how the second expansion doesn't use the first match)
How are you getting that output ? Trying it on the playground (rust 1.68.2), I get
Summary
error[E0774]: `derive` may only be applied to `struct`s, `enum`s and `union`s
--> src/lib.rs:13:9
|
13 | #[$outer]
| ^^^^^^^^^ not applicable here
14 | / foo! {
15 | | $(#[$other_outer])*
16 | | $vis struct $I { $($t)* }
17 | | }
| |_________- not a `struct`, `enum` or `union`
...
21 | / foo! {
22 | | #[doc = "example"]
23 | | #[derive(Debug)]
24 | | pub struct Example {
25 | | field: u32,
26 | | }
27 | | }
| |_- in this macro invocation
|
= note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)
That being said, I think there is something going on with attributes, as shown by the following:
macro_rules! find_foo {
(#[$m:meta]) => {
find_foo!(@after #[$m])
};
(@after #[foo]) => {
true
};
(@after #[$other:meta]) => {
false
};
}
#[test]
fn testing() {
assert!(find_foo!(@after #[foo]));
assert!(! find_foo!(#[foo]));
}
So, maybe it cannot recognize what is inside a $m:meta
fragment after it has been matched more than once ?
H2CO3
3
I tried to pull off an incremental muncher here, but apparently the derive(Debug)
fragment doesn't match itself literally.
1 Like
Yeah alright, this is touched on in the little book of rust macros, at the end of this page.
So basically, use #[$($m:tt)*]
instead of #[$m:meta]
, and this should hopefully work.
H2CO3
5
@arnaudgolfouse You are right, it works with $($_:tt)*
:
macro_rules! foo {
(
@$(#[$($want:tt)*])*;
#[derive(Debug)]
$(#[$($rest:tt)*])*
$vis:vis struct $I:ident { $($t:tt)* }
) => {
foo! {
@$(#[$($want)*])*;
$(#[$($rest)*])*
$vis struct $I { $($t)* }
}
};
(
@$(#[$($want:tt)*])*;
#[$head:meta]
$(#[$($rest:tt)*])*
$vis:vis struct $I:ident { $($t:tt)* }
) => {
foo! {
@$(#[$($want)*])* #[$head];
$(#[$($rest)*])*
$vis struct $I { $($t)* }
}
};
(
@$(#[$($want:tt)*])*;
$vis:vis struct $I:ident { $($t:tt)* }
) => {
$(#[$($want)*])*
$vis struct $I { $($t)* }
};
(
$(#[$($attr:tt)*])*
$vis:vis struct $I:ident { $($t:tt)* }
) => {
foo!{
@;
$(#[$($attr)*])*
$vis struct $I { $($t)* }
}
};
}
foo! {
#[doc = "example"]
#[derive(Debug)]
pub struct Example {
field: u32,
}
}
foo! {
#[repr(C)]
#[derive(Debug)]
#[derive(Clone)]
struct Other {
x: String,
y: f64
}
}
2 Likes
#![feature(trace_macros)]
trace_macros!(true);
Oh, I should have thought about this. Thank you all.
1 Like
system
Closed
8
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.