Add lifetime to procedural macro

Hi all,

In procedural macro, I can't find how to add an additional lifetime to the set of lifetimes returned by syn::Generics::split_for_impl()

For example, if have this:

let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

I don't know how to add another lifetime to impl_generics variable:

  impl 'a #impl_generics MyTrait for #struct_name #ty_generics #where_clause {
      fn my_fn<R: std::io::Read>(&mut self, buffer: &mut R) -> Result<(), Error> {
          #( #method_calls)*
          Ok(())
      }
  }

doesn't work.

Any hint ?

Thanks a lot.

What I usually do is to create a second instance of Generics where I add the new lifetime/type generics and bounds, then I split both and use the ty_generics from the original one and the impl_generics and where_clause from the new one. It would be nice if there was a way that doesn't involve cloning though.

1 Like

Ok thanks, but how do you add another lifetime to the second impl_generics ?

Looks to me, peeking at the source code of ToTokens for ImplGenerics<'_> that out-of-order lifetimes (e.g. lifetimes appearing later than consts/types) and missing < > tokens are properly supported and fixed up while printing. So it might simply work to (clone the Generics, if you still need the unmodified original one, and then) use .push on the params field. Or maybe use .insert at index 0 if you really want that new lifetime first.

If you don’t want to construct the datatype of the generic yourself, you could try generics.param.push(parse_quote!('a)). I’m not a hygiene expert but one should probably also consider parse_quote_spanned!(Span::mixed_site() => 'a) as an option… feel free to test which works better, especially if the struct already comes with another lifetime names 'a.

Yes, works like that:

let mut gen_clone = ast.generics.clone();
let lt = Lifetime::new("'a", Span::call_site());
let ltp = LifetimeParam::new(lt);
gen_clone.params.push(GenericParam::from(ltp));
let (impl_generics2, _, _) = gen_clone.split_for_impl();

Thanks @steffahn & @SkiFire13 for your help !

Ah you already answered… I’ve included an edit to also consider Span::mixed_site() for the new lifetime; which – as far as I understand from the docs – may help avoid any naming conflict with other pre-existing lifetimes also named 'a that come from the user of your procedural macro. Feel free to test if it actually works like that :wink: – also, even with hygiene, I’m not sure if it won’t generate confusing docs with the same lifetime re-appearing (feel free to answer that in case you test it), in which case a less common name than 'a might be nicer

1 Like