preprocess_macro/
lib.rs

1//! This crate provides a procedural macro to preprocess structs and enums.
2
3use proc_macro::TokenStream;
4use quote::ToTokens;
5use syn::{Attribute, ItemEnum, ItemStruct, Token, parse::Parse};
6
7mod ext_traits;
8mod preprocessor;
9mod process_enum;
10mod process_struct;
11mod processed_fields;
12
13/// The `Item` enum represents either a struct or an enum, along with its
14/// attributes and visibility. It is used to parse the input of the procedural
15/// macro and to process the item accordingly.
16enum Item {
17	/// Represents a struct item.
18	Struct(ItemStruct),
19	/// Represents an enum item.
20	Enum(ItemEnum),
21}
22
23impl Parse for Item {
24	fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
25		let attrs = input.call(Attribute::parse_outer)?;
26		let vis = input.parse()?;
27
28		if input.peek(Token![struct]) {
29			input
30				.parse()
31				.map(|item: ItemStruct| ItemStruct { vis, attrs, ..item })
32				.map(Item::Struct)
33		} else {
34			input
35				.parse()
36				.map(|item: ItemEnum| ItemEnum { attrs, vis, ..item })
37				.map(Item::Enum)
38		}
39	}
40}
41
42impl From<Item> for TokenStream {
43	fn from(val: Item) -> Self {
44		match val {
45			Item::Struct(item) => item.into_token_stream().into(),
46			Item::Enum(item) => item.into_token_stream().into(),
47		}
48	}
49}
50
51impl Item {
52	/// Processes the item and returns a `TokenStream` with the processed
53	/// version of the item.
54	fn into_processed(self, strict_mode: bool) -> TokenStream {
55		let result = match self {
56			Item::Struct(item) => {
57				process_struct::into_processed(item, strict_mode)
58			}
59			Item::Enum(item) => process_enum::into_processed(item, strict_mode),
60		};
61
62		match result {
63			Ok(token_stream) => token_stream,
64			Err(error) => error.to_compile_error().into(),
65		}
66	}
67}
68
69/// A procedural macro that preprocesses structs and enums in a synchronous
70/// context.
71#[proc_macro_attribute]
72pub fn sync(args: TokenStream, input: TokenStream) -> TokenStream {
73	let input = syn::parse_macro_input!(input as Item);
74
75	let strict_mode = if !args.is_empty() {
76		let meta = syn::parse_macro_input!(args as syn::Meta);
77		let name_value = match meta.require_name_value() {
78			Ok(name_value) => name_value,
79			Err(err) => {
80				return err.to_compile_error().into();
81			}
82		};
83		if !name_value.path.is_ident("strict_mode") {
84			return syn::Error::new_spanned(
85				name_value.path.clone(),
86				"expected `strict_mode` as the attribute argument",
87			)
88			.to_compile_error()
89			.into();
90		}
91
92		match &name_value.value {
93			syn::Expr::Lit(syn::ExprLit {
94				attrs: _,
95				lit: syn::Lit::Bool(lit),
96			}) => lit.value,
97			_ => {
98				return syn::Error::new_spanned(
99					name_value.value.clone(),
100					"expected a boolean literal as the attribute argument",
101				)
102				.to_compile_error()
103				.into();
104			}
105		}
106	} else {
107		false
108	};
109
110	input.into_processed(strict_mode)
111}