》 Edit: following from the discussion in this post, I published the portrait framework for writing procedural multi-input derives like trait impls.
Goals
- Provide a way to conveniently encapsulate attribute macro parameters
- Provide a way to derive an attribute macro, i.e. to declare an attribute macro in the output of another procedural macro
Non-goals
- This is not to provide a generic syntax to manipulate the input or output body that the attribute macro is applied on. There are some attempts for this, e.g.
macro-attr
andmacro_rules_attribute
both of which not related to the goals of this RFC.
Motivating examples
Reusing derive lists
It is common to reuse the same set of derive macros in many similar types. For example, if we have a trait bound that requires Debug + Copy + Eq + Ord
, it would be very useful to have a single attribute that expands to derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)
.
In this case, I want to be able to write
pub macro [my_derive] = #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)];
#[my_derive] struct Foo;
#[my_derive] struct Bar;
Macro output
Procedural macros are unable to read the contents of other referenced types. For example, if I write an attribute macro on an impl
block, it is not possible to get information about the trait it implements. An alternative method would be to define the attribute macro from the trait itself, so that impl blocks can use this attribute macro:
#[foo]
pub trait MyTrait { fn x(&self) -> &str; }
// Generates:
pub macro [impl_my_trait] = #[foo_impl({fn x(&self) -> &str;})];
#[impl_my_trait]
impl MyTrait for MyType {}
// Equivalent to
#[foo_impl({fn x(&self) -> &str;})]
impl MyTrait for MyType {}
// now `#[foo_impl]` knows that it has to fill this impl block with fn x
Guide-level explanation
Attribute aliases are similar to type aliases, in the sense they also re-export an existing attribute (existing type) but manipulates the attribute input (type/lifetime/const generic parameters).
Attribute aliases can be defined to receive no parameters:
macro [new_identifier] = #[existing_attribute_macro(parameters)];
The (parameters)
part can be any token stream valid for an attribute macro. For example this can be used with the =
syntax:
macro [simple_doc] = #[doc = "Boilerplate documentation goes here"];
Trivia: this also supports the
///
syntaxmacro [simple_doc] = /// Boilerplate documentation goes here /// Can be multiline, which expands to a consecutive #[] #[] stream ;
although the trailing
;
is a bit ugly
Attribute aliases can also receive declarative parameters, defined using the same syntax as declarative parameters:
/// This macro elides #[derive(Serialize, Deserialize)] if #[serde] is passed anyway
macro [serde($($params:tt)*)] =
#[derive(::serde::Serialize, ::serde::Deserialize)]
#[serde($($params)*)];