Skip to main content

structible_macros/
lib.rs

1//! Procedural macro implementation for `structible`.
2//!
3//! This crate provides the `#[structible]` attribute macro, which transforms
4//! structs into map-backed types with generated accessors. Users should depend
5//! on the main `structible` crate, which re-exports this macro.
6//!
7//! # Design
8//!
9//! The macro generates several items from a single struct definition:
10//!
11//! - A **field enum** used as map keys (one variant per field)
12//! - A **value enum** used as map values (wrapping each field's type)
13//! - A **fields struct** for ownership extraction via `into_fields()`
14//! - The **main struct** backed by the chosen map type
15//! - An **impl block** with accessors for all fields
16//!
17//! # Invariants
18//!
19//! Required fields (non-`Option`) are guaranteed to be present after
20//! construction. The generated constructor enforces this by requiring values
21//! for all required fields. Getters for required fields return references
22//! directly, not `Option`, because the field is always present.
23//!
24//! # Optional Field Storage
25//!
26//! Fields typed as `Option<T>` are stored as `T` in the backing map, not as
27//! `Option<T>`. Presence or absence in the map represents `Some` or `None`.
28//! This means:
29//!
30//! - Getters return `Option<&T>` (present = `Some`, absent = `None`)
31//! - Setters accept `T` (the inner type) and insert the value
32//! - Removers extract the value if present; use `remove_*` to clear a field
33
34extern crate proc_macro;
35
36mod codegen;
37mod parse;
38mod util;
39
40use proc_macro::TokenStream;
41use quote::quote;
42use syn::{ItemStruct, parse_macro_input};
43
44use crate::codegen::{
45    generate_debug_impl, generate_default_impl, generate_field_enum, generate_fields_debug_impl,
46    generate_fields_impl, generate_fields_struct, generate_impl, generate_struct,
47    generate_value_enum,
48};
49use crate::parse::{StructibleConfig, parse_struct_fields};
50
51/// Transforms a struct into a map-backed type with generated accessors.
52///
53/// # Example
54///
55/// ```ignore
56/// use structible::structible;
57///
58/// #[structible(HashMap)]
59/// pub struct Person {
60///     pub name: String,
61///     pub age: u32,
62///     pub email: Option<String>,
63/// }
64/// ```
65///
66/// This generates:
67/// - A field enum for map keys (one variant per field)
68/// - A value enum for map values (wrapping each field type)
69/// - A `PersonFields` struct for ownership extraction
70/// - The `Person` struct backed by `HashMap`
71/// - Getters, setters, and removers for each field
72///
73/// # Optional Fields
74///
75/// Fields typed as `Option<T>` are stored without the `Option` wrapper.
76/// Presence in the map represents `Some`, absence represents `None`:
77///
78/// - `email()` returns `Option<&String>`
79/// - `set_email(v)` inserts the value
80/// - `remove_email()` removes and returns the value if present
81///
82/// # Required Fields
83///
84/// Non-optional fields are guaranteed present after construction:
85///
86/// - `name()` returns `&String` (not `Option`)
87/// - `set_name(v)` replaces the value
88/// - Use `into_fields()` then `take_name()` to extract owned value
89#[proc_macro_attribute]
90pub fn structible(attr: TokenStream, item: TokenStream) -> TokenStream {
91    let config = match syn::parse::<StructibleConfig>(attr) {
92        Ok(c) => c,
93        Err(e) => return e.to_compile_error().into(),
94    };
95
96    let input = parse_macro_input!(item as ItemStruct);
97
98    let fields = match parse_struct_fields(&input) {
99        Ok(f) => f,
100        Err(e) => return e.to_compile_error().into(),
101    };
102
103    let name = &input.ident;
104    let vis = &input.vis;
105    let attrs = &input.attrs;
106    let generics = &input.generics;
107
108    let field_enum = generate_field_enum(name, &fields);
109    let value_enum = generate_value_enum(name, &fields, &config, generics);
110    let fields_struct = generate_fields_struct(name, vis, &fields, &config, generics);
111    let fields_impl = generate_fields_impl(name, &fields, &config, generics);
112    let fields_debug_impl = generate_fields_debug_impl(name, &fields, generics);
113    let struct_def = generate_struct(name, vis, &config, attrs, generics);
114    let debug_impl = generate_debug_impl(name, &fields, generics);
115    let impl_block = generate_impl(name, &fields, &config, generics);
116    let default_impl = generate_default_impl(name, &fields, &config, generics);
117
118    let expanded = quote! {
119        #field_enum
120        #value_enum
121        #fields_struct
122        #fields_impl
123        #fields_debug_impl
124        #struct_def
125        #debug_impl
126        #impl_block
127        #default_impl
128    };
129
130    expanded.into()
131}