Handling lifetime in procedural macro

I have the following procedural macro:

use proc_macro::{self, TokenStream as ts1};
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Generics};
use proc_macro2::TokenStream as ts2;

#[proc_macro_derive(AsRef)]
pub fn derive(input: ts1) -> ts1 {
    let k: DeriveInput = parse_macro_input!(input);
    let output= match k {
        DeriveInput { ident, generics: Generics { lt_token: None, .. }, .. } => {
            quote! {
                impl AsRef<#ident> for #ident {
                    fn as_ref(&self) -> &#ident {
                        self
                    }
                }  
            }
        }
        DeriveInput { ident, generics, .. } => {
            let type_params1 = generics.type_params();
            let type_params2 = generics.type_params();
            let type_params3 = generics.type_params();
            let type_params4 = generics.type_params();
            quote! {
                impl<#(#type_params1),*> AsRef<#ident<#(#type_params2),*>> for #ident<#(#type_params3),*> {
                    fn as_ref(&self) -> &#ident<#(#type_params4),*> {
                        self
                    } 
                }
            }
        }
    };
    output.into()
}

With AsRef macro, I can implement trait AsRef autommatially like this:

#[derive(AsRef)]
struct MyStruct;

let my_struct = MyStruct;
my_struct.as_ref()
// &my_struct

But now I have a type with a generic liftetime params:

struct LifteTimeStruct<'a>(&'a i32);

I can't figure out how to write down the macro AsRef so I can do this:

#[derive(AsRef)]
struct LifteTimeStruct<'a>(&'a i32);

syn::Generics::split_for_impl()

2 Likes

Without writing the logic for handling the generics myself, here’s what I could come up with

use proc_macro2::TokenStream;
use quote::quote;
use synstructure::{AddBounds, Structure};

fn derive_as_ref_self(mut structure: Structure<'_>) -> TokenStream {
    structure
        .underscore_const(true)
        .add_bounds(AddBounds::None)
        .gen_impl(quote! {
            gen impl ::core::convert::AsRef<Self> for @Self {
                fn as_ref(&self) -> &Self {
                    &self
                }
            }
        })
}

synstructure::decl_derive!([AsRefSelf] => derive_as_ref_self);
// test case
#[derive(AsRefSelf)]
struct Foo<'a, const N: usize, T: std::fmt::Debug>(&'a T)
where
    T: 'a;
// expanded
struct Foo<'a, const N: usize, T: std::fmt::Debug>(
    &'a T,
)
where
    T: 'a;
const _: () = {
    impl<'a, const N: usize, T: std::fmt::Debug> ::core::convert::AsRef<Self>
    for Foo<'a, N, T>
    where
        T: 'a,
    {
        fn as_ref(&self) -> &Self {
            &self
        }
    }
};

This will correctly handle types and lifetimes and consts and where clauses. Also, I’ve used the Self type to avoid the need to fully write out the actual type more than once; this idea would transfer to any alternative approach, too, to make it slightly easier.


Edit: While researching available helper crates for writing derive macros, I also came across this crate. I didn’t see a need for it in this case, but it seems to be a useful potentially useful tool, especially for restricting/validating the shape of macro input, or allowing custom #[foo(key=value)] extra attributes and the like.

2 Likes

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.