zerocopy_derive/
invariant.rs1use proc_macro2::{Ident, Span, TokenStream, TokenTree};
12use quote::{quote, ToTokens as _};
13use syn::{
14 punctuated::Punctuated, spanned::Spanned as _, Attribute, Data, DeriveInput, Error, Expr,
15 Field, MacroDelimiter, Meta, Token,
16};
17
18use crate::util::path_is_ident;
19
20pub(crate) fn parse(attrs: &[Attribute]) -> Result<Vec<Expr>, Error> {
22 let mut invariants = Vec::new();
23 for attr in attrs.iter().filter(|attr| path_is_ident(attr.path(), "zerocopy")) {
24 let options = attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
25 for option in options {
26 if !path_is_ident(option.path(), "invariant") {
27 return Err(Error::new_spanned(option, "expected `invariant(...)`"));
28 }
29 match option {
30 Meta::List(list) if matches!(list.delimiter, MacroDelimiter::Paren(_)) => {
31 invariants.push(list.parse_args::<Expr>()?)
32 }
33 other => return Err(Error::new_spanned(other, "expected `invariant(...)`")),
34 }
35 }
36 }
37 Ok(invariants)
38}
39
40pub(crate) fn validate(ast: &DeriveInput) -> Result<Option<Span>, Error> {
43 let fields: Vec<&Field> = match &ast.data {
44 Data::Struct(data) => data.fields.iter().collect(),
45 Data::Enum(data) => {
46 for variant in &data.variants {
47 if let Some(invariant) = parse(&variant.attrs)?.first() {
48 return Err(Error::new(
49 invariant.span(),
50 "invariants are only supported on named fields",
51 ));
52 }
53 }
54 data.variants.iter().flat_map(|variant| &variant.fields).collect()
55 }
56 Data::Union(data) => data.fields.named.iter().collect(),
57 };
58 let mut first = None;
59 for field in fields {
60 if let Some(invariant) = parse(&field.attrs)?.first() {
61 if field.ident.is_none() {
62 return Err(Error::new(
63 invariant.span(),
64 "invariants are only supported on named fields",
65 ));
66 }
67 first.get_or_insert(invariant.span());
68 }
69 }
70 Ok(first)
71}
72
73pub(crate) fn idents(tokens: TokenStream) -> Vec<Ident> {
75 tokens
76 .into_iter()
77 .flat_map(|token| match token {
78 TokenTree::Ident(ident) => vec![ident],
79 TokenTree::Group(group) => idents(group.stream()),
80 _ => Vec::new(),
81 })
82 .collect()
83}
84
85pub(crate) fn bind_fields(fields: &[(&Field, Ident)], expression: &Expr) -> TokenStream {
88 let mut body = expression.to_token_stream();
89 let references = idents(body.clone());
90 for (field, binding) in fields {
91 let name = match &field.ident {
92 Some(name) => name,
93 None => continue,
94 };
95 let spelling = name.to_string();
96 let spelling = spelling.trim_start_matches("r#");
97 let mut call_site = name.clone();
99 call_site.set_span(Span::call_site());
100 for alias in [name.clone(), call_site].into_iter().chain(
101 references
102 .iter()
103 .filter(|ident| ident.to_string().trim_start_matches("r#") == spelling)
104 .cloned(),
105 ) {
106 body = quote! {{
107 #[allow(unused_variables, non_snake_case, clippy::redundant_pattern)]
110 let #alias @ _ = #binding;
111 #body
112 }};
113 }
114 }
115 body
116}