Macro_rule to remove an attribute

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 ?

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.

@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

Oh nice, thanks !

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.