4

I'm trying to filter out all the fields of a struct that are of type bool. But the syn::Type enum doesn't seem to have a case for it, or I'm reading the definitions incorrectly:

pub enum Type {
    Array(TypeArray),
    BareFn(TypeBareFn),
    Group(TypeGroup),
    ImplTrait(TypeImplTrait),
    Infer(TypeInfer),
    Macro(TypeMacro),
    Never(TypeNever),
    Paren(TypeParen),
    Path(TypePath),
    Ptr(TypePtr),
    Reference(TypeReference),
    Slice(TypeSlice),
    TraitObject(TypeTraitObject),
    Tuple(TypeTuple),
    Verbatim(TokenStream),
    // some variants omitted
}

I looked trough syn::Types source, to check which variants where ommited, but that didn't bring me any further. Here's what I have until now:

#[proc_macro_derive(Creator)]
pub fn derive_creator(_item: TokenStream) -> TokenStream {
    let item = parse_macro_input!(_item as syn::DeriveInput);
    let item_ident = item.ident;

    let fields = if let syn::Data::Struct(syn::DataStruct {
        fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
        ..
    }) = item.data
    {
        named
    } else {
        panic!("You can derive Creator only on a struct!")
    };

    let bool_fields = fields.iter().filter(|field| 
        match field.ty {
            // case when field type is bool => true
            _ => false
        }
    );
    unimplemented!()
}

Am I going down the wrong path? Or is this simply not possible? Or am I missing something?

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Typhaon
  • 828
  • 8
  • 27
  • 1
    The proc-macro can't actually know what a `bool` is. The `Type` refers to syntax rules (e.g. "this is a tuple, because it looks like a tuple"), but the proc-macro can't actually do type-resolution. So by definition, if I do `type Foo = bool;` or `struct Foo(bool)`, the proc-macro will only ever see a type named `Foo`. You could special-case for a type named `bool` and hope that this will resolve to `core::primitive::bool`. – user2722968 Apr 01 '21 at 14:34

2 Answers2

6

I feel like there might be a cleaner way (without having to clone and allocate a string), but in the past I've done something like:

match field.ty {
    Type::Path(type_path) if type_path.clone().into_token_stream().to_string() == "bool" => {
        true
    }
    _ => false
}

You might be able to define the bool type once and then compare it for equality:

let bool_ty = Type::Path(TypePath {
    qself: None,
    path: Path::from(Ident::new("bool", Span::call_site())),
});

if field.ty == bool_ty {
    // it's a bool
}

But I'm not sure if a difference in the span would affect equality. Span appears to not implement PartialEq, so my guess is that this is ok.*


*Edits to clarify this are welcome.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • I like your first solution. I'm going to see if this works though: ```rust let bool_fields = fields.iter().filter(|field| match &field.ty { syn::Type::Path(type_path) if type_path.path.is_ident("bool") => true, _ => false, } ); ``` – Typhaon Apr 02 '21 at 19:10
  • Works like a charm! Thanks for pointing me in the right direction. You could use `is_ident` to get around cloning thought. – Typhaon Apr 03 '21 at 11:12
1

Another way:

    let bool_fields = fields.iter().filter(|field| {
        if let Type::Path(tp) = &field.ty {
            let segments = &tp.path.segments;
            // Check len to avoid "bool::SomeOtherType".
            if segments.len() == 1 {
                return segments[0].ident == "bool";
            }
        }
        false
    });

Bear in mind:

Worth pointing out that this checks for equality by name. A type alias, a single-field tuple or core::primitive::bool will not match, although they are all bool after type resolution. – user2722968

Ben
  • 560
  • 3
  • 9