How to set the const generics in `syn::Generics`

138 Views Asked by At

I'm working on a custom builder pattern. I'm trying to define my own const generics, and I'm trying to combine those with the syn::Generics that I get from parsing the struct. Returning the generic identifiers works, but I'm having trouble when they're constant.

At first I tried combining the generics simply by appending them after each other: quote!(#struct_generics #const_generics); or quote!(<#struct_generics #const_generics>); but that outputs <A> <const M_0: bool, const M_1: bool> or <<A> <const M_0: bool, const M_1: bool>> respectively.

After a while I tried it like the following example:

use syn::parse_quote;
use quote::format_ident;

pub struct FieldInfo {
  index: usize
}

impl FieldInfo {
  pub fn mandatory_index(&self) -> usize { self.index }
}

pub fn get_all_generics(fields: &Vec<FieldInfo>, target_generics: &syn::Generics) -> syn::Generics {
    let mut res = target_generics.clone();
    let syn::Generics {
        ref mut params,
        ..
    } = res;
    fields.iter().for_each(|field| {
        let ident = format_ident!("M_{}",field.mandatory_index());
        params.push(parse_quote!(const #ident: bool));
    });
    let (impl_generics, type_generics, where_clause) = res.split_for_impl();
    dbg!(impl_generics, type_generics, where_clause);
    res
}
fn main() {
    let generics = syn::Generics::default();
    let infos = (0..2).map(|index| FieldInfo { index }).collect::<Vec<_>>();
    
    get_all_generics(&infos, &generics);
}

Which works, impl_generics seems to be able to be used for the struct definition, and I hope the where_clause too. But now I need to set specific constants. How do I set them inside of syn::Generics?

params.push(parse_quote!(false)); doesn't seem to work.

The code below shows that I'm adding some const generics. This is all going well, until I'm trying to derive on a generic struct like pub struct Foo<A>:

#[derive(Debug, Builder)]
pub struct Foo<A> {
  bar: A,
  baz: A,
}

Should expand to:

impl Foo<A> {
    pub fn builder() -> FooBuilder<A, false, false> {
        FooBuilder::new()
    }
}
#[derive(Default, Debug)]
pub struct FooBuilder<A, const M_0: bool, const M_1: bool> {
    data: FooData<A>,
}
impl <A> FooBuilder<A, false, false> {
    pub fn new() -> FooBuilder<A, false, false> {
        Self::default()
    }
}
impl<A, const M_1: bool> FooBuilder<A, false, M_1> {
    pub fn bar(self, bar: String) -> FooBuilder<A, true, M_1> {
        let mut data = self.data;
        data.bar = Some(bar);
        FooBuilder { data }
    }
}
impl<A, const M_0: bool> FooBuilder<A, M_0, false> {
    pub fn baz(self, baz: String) -> FooBuilder<A, M_0, true> {
        let mut data = self.data;
        data.baz = Some(baz);
        FooBuilder { data }
    }
}
impl <A> FooBuilder<A, true, true> {
    pub fn build(self) -> Foo<A> {
        self.data.into()
    }
}
#[derive(Default, Debug)]
pub struct FooData<A> {
    pub bar: Option<A>,
    pub baz: Option<A>,
}
impl <A> From<FooData<A>> for Foo<A> {
    fn from(data: FooData<A>) -> Foo<A> {
        Foo {
            bar: data.bar.unwrap(),
            baz: data.baz.unwrap(),
        }
    }
}
1

There are 1 best solutions below

1
Typhaon On

I figured it out, I was getting confused over the difference between impl generics and type generics. I wrote two functions that can be used for impl #impl_generics MyType #type_generics.

// Just filter out the ones that you already have a value for in the implementation. 
// I hardcoded bool as the type, because that is the only constant type in my project.

fn add_const_generics_for_impl(&self, tokens: &mut dyn Iterator<Item = syn::Ident>) -> syn::Generics {
    let mut res = self.target_generics.clone();

    tokens.for_each(|token| {
        res.params.push(parse_quote!(const #token: bool));
    });
    res
}

// I decided to go with `Either<syn::Ident, syn::LitBool>` because I'm only
// using bool contants in this project, and I like to be as precise as possible 
// with my types.
// But you could simply use `TokenStream` and remove the `constants.map(...)` line

fn add_const_generics_valued_for_type(
    &self,
    constants: &mut dyn Iterator<Item = Either<syn::Ident, syn::LitBool>>,
) -> TokenStream {
    let syn::Generics { params, .. } = self.target_generics;
    let tokens: Vec<TokenStream> = constants
        .map(|constant| {
            constant
                .map_either(|iden| iden.to_token_stream(), |lit| lit.to_token_stream())
                .into_inner()
        })
        .collect();
    if params.is_empty() {
        quote!(<#(#tokens),*>)
    } else {
        let type_generics = params.iter().map(|param| match param {
            syn::GenericParam::Lifetime(lt) => &lt.lifetime.ident,
            syn::GenericParam::Type(ty) => &ty.ident,
            syn::GenericParam::Const(cnst) => &cnst.ident,
        });
        quote!(< #(#type_generics),*, #(#tokens),* >)
    }
}

As you can see, the second function doesn't return syn::Generics. I was trying to find a way to do this, but it seems impossible and this does the trick.