Proc_macro/syn: write `#field` or `Some(#field)` conditionally

Hi,

inside a proc_macro, I have a fields_nullable: Vec<bool> and fields_to_push: Vec<&Ident>, of the same length.

How can I write something like

#(self.#fields_names.push(#fields_to_push);)*

but where #fields_to_push will be either Some(#fields_to_push) or #fields_to_push depending on whether the Vec<bool> is true or false?

I am trying to write something like

let fields_to_push = fields_to_push
        .iter()
        .zip(fields_nullable.iter())
        .map(|(field, is_nullable)| {
            if *is_nullable {
                let a = syn::parse(quote! {Some(#field)}.into()).unwrap();
                a
            } else {
                (*field).clone()
            }
        })
        .collect::<Vec<_>>();

but the parsing to an identifier fails, since Some(#field) is not an identifier? I may also been abusing the syn::parse. Any suggestions on how to make this a bit cleaner (I may be losing the hygiene information with the above) would be much appreciated also!

I'm not sure this entirely solves your problem, but if you have a value and you're trying to put it into a context that may be a bare T or might be an Option<T> you can do that without any conditional at all - write value.into(). This is because you can into any type into itself, but you can also into a T into a Some(T) and the compiler will infer which one you mean based on the surrounding type constraints

3 Likes

That is neat; learnt something new! Unfortunately in this case the push is itself a generic with T: AsRef<str>, so the into is not enough.


My current solution is to split the fields in two and it in two separate loops:

#(self.#required_fields.push(Some(#required_fields));)*;
#(self.#nullable_fields.push(#nullable_fields);)*

That will certainly work and is maybe the more readable option, but I think your original logic probably also works if instead of handling Idents, you use Exprs. (Although not in a position to test this ATM)

let fields_to_push = fields_to_push
        .iter()
        .zip(fields_nullable.iter())
        .map(|(field, is_nullable)| {
            if *is_nullable {
-               let a = syn::parse(quote! { Some(#field) }.into()).unwrap();
+               let a = quote!( Some(#field) );
                a
            } else {
-               (*field).clone()
+               quote!( #field )
            }
        })
        .collect::<Vec<_>>();

This will yield a Vec<TokenStream2>, which will be just as usable as you originally wanted.

If you really want to keep the "type safety" of syn data types, then you can use Expressions instead. Also note that syn already features a handy parse_quote! macro for that:

.map(|(field, &is_nullable)| -> Expr {
    if is_nullable {
        parse_quote!( Some(#field) )
    } else {
        parse_quote!( #field )
    }
})
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.