rust-lang / wg-macros

Home of the Rust Macros Working Gruop
Apache License 2.0
1 stars 1 forks source link

macros by example for derive and attribute macros #3

Open programmerjake opened 5 months ago

programmerjake commented 5 months ago

It would be useful to support macro_rules!/macro-style macros for derive and attribute macros. For derive macros, I think we'll need enough functionality to be able to write a Clone-syle macro so I think we'll want:

example Clone-style derive macro:

#[derive]
macro_rules! MyClone {
    (
        $(#[$struct_meta:meta])*
        $struct_vis:vis struct $struct_name:ident$(<$generics:generics>)?
        $(where $where:where)?
        $($struct_delim:paren_or_brace{
             $(
                 $(#[$field_meta:meta])*
                 $field_vis:vis $field:member_colon $field_ty:ty
             ),*
             $(,)?
        })?
        $($(where $where2:where)?;)?
    ) => {
        impl$(<${impl{$generics}}>)? $crate::MyClone for $struct_name $(<${type{$generics}}>)?
        where
            $(${type_params{$generics}}: $crate::MyClone,)*
            $($where)?
            $($($where2)?)?
        {
            fn my_clone(&self) -> Self {
                Self $({
                    $($field: $crate::MyClone::my_clone(&self.$field),)*
                })?
            }
        }
    };
    // TODO: enums
}

alternative struct body matching:

e.g.:

#[derive]
macro_rules! MyClone {
    (
        $(#[$struct_meta:meta])*
        $struct_vis:vis struct $struct_name:ident$(<$generics:generics>)?
        $body:struct_body{ // automagically transforms tuple and unit structs
             $(where $where:where)?
             {
                 $(
                     $(#[$field_meta:meta])*
                     $field_vis:vis $field:member: $field_ty:ty,
                 )*
             }
        }
        $(;)?
    ) => {
        impl$(<${impl{$generics}}>)? $crate::MyClone for $struct_name $(<${type{$generics}}>)?
        where
            $(${type_params{$generics}}: $crate::MyClone,)*
            $($where)?
        {
            fn my_clone(&self) -> Self {
                Self $body{
                    $($field: $crate::MyClone::my_clone(&self.$field),)*
                }
            }
        }
    };
    (
        $(#[$enum_meta:meta])*
        $enum_vis:vis enum $enum_name:ident$(<$generics:generics>)?
        $(where $where:where)?
        {
             $($Variant:ident $body:struct_body{ // no where here
             { // automagically transforms tuple and unit variants
                 $(
                     $(#[$field_meta:meta])*
                     $field_vis:vis $field:member: $field_ty:ty,
                 )*
             }} $(= $discriminant:expr)?),*
             $(,)?
        }
    ) => {
        impl$(<${impl{$generics}}>)? $crate::MyClone for $enum_name $(<${type{$generics}}>)?
        where
            $(${type_params{$generics}}: $crate::MyClone,)*
            $($where)?
        {
            fn my_clone(&self) -> Self {
                match *self {
                    $(Self::$Variant $body{$(ref $field,)*} => Self::$Variant $body{
                        $($field: $crate::MyClone::my_clone($field),)*
                    },)*
                }
            }
        }
    };
}
danielhenrymantilla commented 5 months ago

I like the suggestion; however, I worry that having to parse the whole item "manually" may yield a lot of scope creep and effort. A more future-proof, and tractable pattern, following on your idea of impl{} and type{}, would be to rely on macro_metavar_exprs to, in a way, implement "fields" in captures. Yielding something along the lines of:

macro_rules! Clone {(
    $struct:item_struct
) => (
    impl<${struct.generics}>
        ::core::clone::Clone
    for
        ${struct.name} <${struct.forward_generics}>
    where
        ${struct.where_clauses} // <- these things would expand to smth with trailing `,`
        ${add_clone_bound!( ${struct.forward_generics} )} // <- eager-expansion pattern
    {
        fn clone(&self) -> Self {
            Self {
                ${clone_fields!( self, ${struct.field_names} )}
            }
        }
    }
)}

Which would only require simple extra helpers for these eager expansions:

macro_rules! add_clone_bound {(
    $($lt:lifetime),* $(,)?
    $($T:ident),* $(,)?
    $(const $CONST:ident: $ConstTy:ty),* $(,)?
) => (
    $(
        $T : ::core::clone::Clone,
    )*
)}

macro_rules! clone_fields {(
    $self:tt $(, $field_name:ident)* $(,)?
) => (
    $(
        $field_name: ::core::Clone::clone(&self.$field_name),
    )*
)}

This reduces the needs for features required for this to work down to two:

programmerjake commented 5 months ago

add_clone_bound is incorrect, const generics can be mixed with type generics. this is part of why I have ${type_params(...)}

programmerjake commented 5 months ago

also, handling struct and field attributes is commonly needed, e.g. for #[skip] to skip using some field for derive(Debug)-style things.

danielhenrymantilla commented 5 months ago

add_clone_bound is incorrect, const generics can be mixed with type generics. this is part of why I have ${type_params(...)}

Good point; that's what I get for tring to do things manually 😅. Agree on the need for thus extra getters/operators (I'd still lean towards fields/methods for the sake of namespacing and whatnot).

also, handling struct and field attributes is commonly needed, e.g. for #[skip] to skip using some field for derive(Debug)-style things.

Yeah, I'm not advocating fully against manually doing things, just that it should not be mandatory 🙂

programmerjake commented 5 months ago

also, handling struct and field attributes is commonly needed, e.g. for #[skip] to skip using some field for derive(Debug)-style things.

Yeah, I'm not advocating fully against manually doing things, just that it should not be mandatory 🙂

I like the idea of just having $v:struct_item and then a bunch of ${v.struct_meta} or ${v.field_meta} (which would have to be 2-deep repetition since there's multiple fields and multiple meta per field).

extending that to handle enums would probably need both $v:enum_item and $v:enum_variant since you'd want to pass single variants to an eager macro for some processing.