unscrupulous_derive/
lib.rs

1// Use `README.md` as documentation home page, to reduce duplication
2#![doc = include_str!("../README.md")]
3
4use quote::__private::TokenStream;
5use quote::quote;
6use syn::{parse_macro_input, Data, DeriveInput, Field, Type};
7
8/// Implement `Unscrupulous`, and ensure all fields implement the trait.
9#[proc_macro_derive(Unscrupulous)]
10pub fn derive_unscrupulous(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
11    // Parse the input tokens into a syntax tree
12    let input = parse_macro_input!(input as DeriveInput);
13
14    // Quasi-quotation macro expects plain identifiers
15    let name = input.ident;
16
17    // Ensure types of nested fields implement `Unscrupulous`
18    let assertions = match input.data {
19        Data::Struct(data) => assert_fields(data.fields.iter()),
20        Data::Enum(data) => assert_fields(data.variants.iter().flat_map(|var| var.fields.iter())),
21        Data::Union(data) => assert_fields(data.fields.named.iter()),
22    };
23
24    // Split generics based on where they are declared
25    let (generics, generics_of_ty, where_clause) = input.generics.split_for_impl();
26
27    // Build the output, then hand the resulting tokens back to the compiler
28    proc_macro::TokenStream::from(quote! {
29        unsafe impl #generics unscrupulous::Unscrupulous for #name #generics_of_ty #where_clause {}
30
31        #(#assertions)*
32    })
33}
34
35/// Ensure types of nested fields implement `Unscrupulous`
36#[allow(single_use_lifetimes)]
37fn assert_fields<'a>(fields: impl Iterator<Item = &'a Field>) -> Vec<TokenStream> {
38    fields.map(|f| ensure_type_is_unscrupulous(&f.ty)).collect()
39}
40
41/// Assert at compile time that the provided type implements `Unscrupulous`
42fn ensure_type_is_unscrupulous(ty: &Type) -> TokenStream {
43    quote! {
44        const _: fn() = || {
45            fn ensure_type_is_unscrupulous<T: unscrupulous::Unscrupulous>() {}
46            ensure_type_is_unscrupulous::<#ty>();
47        };
48    }
49}