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