1#![forbid(unsafe_code, future_incompatible)]
2#![deny(
3 missing_debug_implementations,
4 nonstandard_style,
5 missing_copy_implementations,
6 unused_qualifications,
7 rustdoc::missing_crate_level_docs
8)]
9#![warn(clippy::pedantic)]
10use proc_macro::TokenStream;
16use proc_macro2::TokenStream as TokenStream2;
17use quote::quote;
18use syn::{Attribute, Data, DeriveInput};
19
20mod common_settings;
21mod copy_detection;
22mod deprecation;
23mod deref_handling;
24mod r#enum;
25mod errors;
26mod field;
27mod field_attributes;
28mod field_method_attributes;
29mod item_attributes;
30mod item_method_attributes;
31mod method;
32mod option_handling;
33mod query;
34mod resolved;
35mod r#struct;
36
37#[cfg(test)]
38mod coverage_tests;
39
40pub(crate) use common_settings::{CommonSettings, with_common_settings};
41pub(crate) use deprecation::Deprecation;
42pub(crate) use r#enum::{Enum, arm_pattern};
43pub(crate) use field::Field;
44pub(crate) use field_attributes::FieldAttributes;
45pub(crate) use field_method_attributes::FieldMethodAttributes;
46pub(crate) use item_attributes::ItemAttributes;
47pub(crate) use item_method_attributes::ItemMethodAttributes;
48pub(crate) use method::{Method, MethodSettings, with_methods};
49pub(crate) use query::Query;
50pub(crate) use resolved::Resolved;
51pub(crate) use r#struct::Struct;
52
53#[proc_macro_derive(Fieldwork, attributes(fieldwork, field))]
56pub fn derive_fieldwork(input: TokenStream) -> TokenStream {
57 derive_fieldwork_internal(input.into()).into()
58}
59
60pub(crate) fn derive_fieldwork_internal(input: TokenStream2) -> TokenStream2 {
61 let peek = match syn::parse2::<DeriveInput>(input.clone()) {
62 Ok(ok) => ok,
63 Err(e) => return e.to_compile_error(),
64 };
65
66 match &peek.data {
67 Data::Struct(_) => derive_struct(input),
68 Data::Enum(_) => derive_enum(input),
69 Data::Union(_) => {
70 syn::Error::new_spanned(peek, "fieldwork does not support unions").to_compile_error()
71 }
72 }
73}
74
75fn derive_struct(input: TokenStream2) -> TokenStream2 {
76 let Struct {
77 ident,
78 fields,
79 attributes,
80 generics,
81 } = match syn::parse2(input) {
82 Ok(ok) => ok,
83 Err(e) => return e.to_compile_error(),
84 };
85
86 let impls = fields
87 .iter()
88 .flat_map(|field| {
89 Method::all().iter().flat_map(|method| {
90 let query = Query::new(method, std::slice::from_ref(field), &attributes, 1);
91 let canonical = query.resolve();
92 let alternate = query.as_alternate().and_then(|q| q.resolve());
93 canonical.into_iter().chain(alternate)
94 })
95 })
96 .map(|resolved| resolved.build())
97 .collect::<TokenStream2>();
98
99 let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
100 quote! {
101 impl #impl_generics #ident #type_generics #where_clause {
102 #impls
103 }
104 }
105}
106
107fn derive_enum(input: TokenStream2) -> TokenStream2 {
108 let enum_item: Enum = match syn::parse2(input) {
109 Ok(ok) => ok,
110 Err(e) => return e.to_compile_error(),
111 };
112
113 let methods = match enum_item.generate_methods() {
114 Ok(methods) => methods,
115 Err(e) => return e.to_compile_error(),
116 };
117 let ident = &enum_item.ident;
118 let (impl_generics, type_generics, where_clause) = enum_item.generics.split_for_impl();
119
120 quote! {
121 impl #impl_generics #ident #type_generics #where_clause {
122 #methods
123 }
124 }
125}
126
127pub(crate) fn is_fieldwork_attr(attr: &Attribute) -> bool {
128 let path = attr.path();
129 path.is_ident("fieldwork") || path.is_ident("field")
130}