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
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,
};
#[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)
}
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)")
}
}
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,
})
}
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)* }
}