Skip to main content

fieldwork_derive/
lib.rs

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)]
10//! Procedural macro implementation for [fieldwork](https://docs.rs/fieldwork).
11//!
12//! This crate is the proc-macro backend. For documentation, configuration options,
13//! and examples, see the [`fieldwork`](https://docs.rs/fieldwork) crate.
14
15use 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/// Derive field accessor methods for a struct or enum. See
54/// [`fieldwork`](https://docs.rs/fieldwork) for full documentation.
55#[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}