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 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/// Derive field accessor methods for a struct or enum. See
52/// [`fieldwork`](https://docs.rs/fieldwork) for full documentation.
53#[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}