1 // SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT 2 // 3 // Copyright 2026 The Fuchsia Authors 4 // 5 // Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0 6 // <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT 7 // license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option. 8 // This file may not be copied, modified, or distributed except according to 9 // those terms. 10 11 use proc_macro2::{Ident, Span, TokenStream, TokenTree}; 12 use quote::{quote, ToTokens as _}; 13 use syn::{ 14 punctuated::Punctuated, spanned::Spanned as _, Attribute, Data, DeriveInput, Error, Expr, 15 Field, MacroDelimiter, Meta, Token, 16 }; 17 18 use crate::util::path_is_ident; 19 20 /// Parses field invariants in attribute order. 21 pub(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 40 /// Validates invariant syntax and placement, returning the first invariant's 41 /// span so derives which cannot honor invariants can reject them. 42 pub(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 73 /// Visits identifiers, including those inside macro arguments. 74 pub(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 85 /// Binds semantic field names in every syntax context used by the expression. 86 /// Preserve the expression's tokens so its other names retain their hygiene. 87 pub(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 // Also support references produced by a macro invoked by the expression. 98 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 // Require a binding; reject names Rust would resolve as constant 108 // patterns. Item aliases could capture helper calls in macros. 109 #[allow(unused_variables, non_snake_case, clippy::redundant_pattern)] 110 let #alias @ _ = #binding; 111 #body 112 }}; 113 } 114 } 115 body 116 } 117