1#![allow(dead_code)]
22
23use core::iter::repeat;
24use proc_macro::TokenStream;
25use quote::quote;
26use syn::{
27 braced,
28 parse::{Parse, ParseStream},
29 parse_macro_input,
30 punctuated::Punctuated,
31 spanned::Spanned,
32 token, Attribute, Field, Ident, Lit, LitInt, Meta, NestedMeta, Token, Visibility,
33};
34
35struct Item {
36 attrs: Vec<Attribute>,
37 vis: Visibility,
38 struct_token: Token![struct],
39 ident: Ident,
40 brace_token: token::Brace,
41 fields: Punctuated<Field, Token![,]>,
42}
43
44impl Parse for Item {
45 fn parse(input: ParseStream) -> syn::Result<Self> {
46 let attrs = input.call(Attribute::parse_outer)?;
47 let vis = input.parse()?;
48 let lookahead = input.lookahead1();
49 if lookahead.peek(Token![struct]) {
50 let content;
51 Ok(Item {
52 attrs,
53 vis,
54 struct_token: input.parse()?,
55 ident: input.parse()?,
56 brace_token: braced!(content in input),
57 fields: content.parse_terminated(Field::parse_named)?,
58 })
59 } else {
60 Err(lookahead.error())
61 }
62 }
63}
64
65struct Assertion {
66 start: LitInt,
67 colon: token::Colon,
68 end: LitInt,
69}
70
71impl Parse for Assertion {
72 fn parse(input: ParseStream) -> syn::Result<Self> {
73 Ok(Assertion {
74 start: input.parse()?,
75 colon: input.parse()?,
76 end: input.parse()?,
77 })
78 }
79}
80
81#[proc_macro_attribute]
82pub fn test_structure(attrs: TokenStream, tokens: TokenStream) -> TokenStream {
83 let Item {
84 attrs: struct_attrs,
85 vis,
86 ident,
87 mut fields,
88 ..
89 } = parse_macro_input!(tokens as Item);
90 let meta = parse_macro_input!(attrs as NestedMeta);
91 let size = match meta {
92 NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("size") => match &nv.lit {
93 Lit::Int(tok) => quote! {
94 assert_eq!(#tok, ::core::mem::size_of::<#ident>(), "size of {}", ::core::stringify!(#ident));
95 },
96 tok => syn::Error::new(tok.span(), "Unexpected size type").to_compile_error(),
97 },
98 m => syn::Error::new(m.span(), "Unexpected meta item").to_compile_error(),
99 };
100 let assertions = fields
101 .iter()
102 .flat_map(|field| {
103 field
104 .attrs
105 .iter()
106 .filter(|attr| attr.path.is_ident("loc"))
107 .zip(repeat(field))
108 })
109 .fold(quote! {}, |acc, (attr, field)| {
110 let assertion = if let Some(field_ident) = &field.ident {
111 let typ = &field.ty;
112 match attr.parse_args() {
113 Ok(Assertion { start, end, .. }) => quote! {
114 {
115 let desc = format!(
116 "{}.{} ({})",
117 ::core::stringify!(#ident),
118 ::core::stringify!(#field_ident),
119 ::core::stringify!(#typ),
120 );
121 let offset = ::memoffset::offset_of!(#ident, #field_ident);
122 assert_eq!(#start, offset, "start of {}", desc);
123 assert_eq!(#end, ::core::mem::size_of::<#typ>() + offset - 1, "end of {}", desc);
124 }
125 },
126 Err(e) => e.to_compile_error(),
127 }
128 } else {
129 syn::Error::new(field.span(), "Tuple structs are not supported").to_compile_error()
130 };
131 quote! { #acc #assertion; }
132 });
133 let func_name = Ident::new(format!("structure_{}", ident).as_str(), ident.span());
134 for field in fields.iter_mut() {
135 field.attrs.retain(|attr| !attr.path.is_ident("loc"));
136 }
137 let struct_attrs_stream = struct_attrs
138 .iter()
139 .fold(quote! {}, |acc, attr| quote! { #acc #attr });
140 let output = quote! {
141 #struct_attrs_stream
142 #vis struct #ident {
143 #fields
144 }
145
146 #[cfg(test)]
147 #[allow(non_snake_case)]
148 #[test]
149 fn #func_name() {
150 #assertions
151 #size
152 }
153 };
154 output.into()
155}