1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! Custom safe derive for the `View` trait of the `structview` crate.

extern crate proc_macro;

use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{
    parse_macro_input, parse_quote, AttrStyle, Data, DeriveInput, Fields, FieldsNamed,
    FieldsUnnamed, Index,
};

/// Derive `structview::View` on a struct or union.
///
/// To ensure safety, this derive:
///   - ensures the deriving type is a struct or a union
///   - ensures the deriving type is repr(C)
///   - ensures that all fields implement `structview::View`
#[proc_macro_derive(View)]
pub fn derive_view(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let item = parse_macro_input!(item as DeriveInput);

    if let Some(error_msg) = check_item(&item) {
        let error = quote! { compile_error!(#error_msg); };
        return proc_macro::TokenStream::from(error);
    }

    let name = item.ident;

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

    let view_asserts = view_asserts(&item.data);

    let expanded = quote! {
        unsafe impl #impl_generics structview::View for #name #ty_generics #where_clause {}

        impl #impl_generics #name #ty_generics #where_clause {
            fn assert_all_fields_are_view(&self) {
                fn assert_view<T: structview::View>(_: &T) {}
                #view_asserts
            }
        }
    };

    proc_macro::TokenStream::from(expanded)
}

/// Check if `item` can safely derive `structview::View`.
///
///   - Enums cannot derive `structview::View`.
///   - Deriving types must be repr(C).
fn check_item(item: &DeriveInput) -> Option<&'static str> {
    if let Data::Enum(_) = item.data {
        return Some("enums cannot derive `structview::View`");
    }

    if is_repr_c(item) {
        None
    } else {
        Some("types that derive `structview::View` must be repr(C)")
    }
}

/// Check if `item` has a #[repr(C)] attribute.
fn is_repr_c(item: &DeriveInput) -> bool {
    item.attrs
        .iter()
        .filter(|a| a.style == AttrStyle::Outer)
        .any(|a| match a.parse_meta() {
            Ok(meta) => meta == parse_quote!(repr(C)),
            Err(_) => false,
        })
}

/// Generate assert calls to ensure `structview::View` is implemented
/// every field.
fn view_asserts(data: &Data) -> TokenStream {
    match data {
        Data::Struct(data) => match data.fields {
            Fields::Named(ref fields) => named_fields_asserts(fields),
            Fields::Unnamed(ref fields) => unnamed_fields_asserts(fields),
            Fields::Unit => TokenStream::new(),
        },
        Data::Union(data) => {
            let asserts = named_fields_asserts(&data.fields);
            quote! { unsafe { #asserts } }
        }
        Data::Enum(_) => unreachable!(),
    }
}

fn named_fields_asserts(fields: &FieldsNamed) -> TokenStream {
    let asserts = fields.named.iter().map(|f| {
        let name = &f.ident;
        quote_spanned! { f.span() => assert_view(&self.#name); }
    });
    quote! { #(#asserts)* }
}

fn unnamed_fields_asserts(fields: &FieldsUnnamed) -> TokenStream {
    let asserts = fields.unnamed.iter().enumerate().map(|(i, f)| {
        let index = Index::from(i);
        quote_spanned! { f.span() => assert_view(&self.#index); }
    });
    quote! { #(#asserts)* }
}