Skip to main content

structural_typing_macros/
lib.rs

1use proc_macro::TokenStream;
2use syn::{DeriveInput, parse_macro_input};
3
4mod analysis;
5mod codegen;
6mod parsing;
7
8/// Generates type-safe structural typing support for a struct.
9///
10/// This macro transforms a struct to track which fields are present/absent at the type level,
11/// enabling compile-time enforcement of field requirements.
12///
13/// **Note**: If you use `#[derive(...)]` on your struct, you must add `derive-where` to your dependencies:
14/// ```bash
15/// cargo add derive-where
16/// ```
17///
18/// # Generated Code
19///
20/// For a struct `MyStruct`, this macro generates:
21///
22/// - A `my_struct` module with `Fields` trait, `FieldSet<...>` type, and `with::` aliases
23/// - Builder methods: `.field(value)`
24/// - `.merge()`, `.extract()`, `.try_extract()` operations
25///
26/// Supports user-defined generic type parameters and nested structural types (see `examples/nested.rs`).
27///
28/// # Field States
29///
30/// - **Present**: Value of type `T`
31/// - **Optional**: `Option<T>`
32/// - **Absent**: `PhantomData<T>`
33///
34/// # Example
35///
36/// ```ignore
37/// use structural_typing::{structural, presence::Present};
38///
39/// #[structural]
40/// struct User {
41///     name: String,
42///     email: String,
43/// }
44///
45/// // Build incrementally
46/// let user = User::empty().name("Alice".to_owned());
47///
48/// // Methods can require specific fields
49/// impl<F: user::Fields<name = Present>> User<F> {
50///     fn greet(&self) -> String {
51///         format!("Hello, {}!", self.name)
52///     }
53/// }
54///
55/// user.greet(); // ✓ Compiles
56/// ```
57///
58/// # Serde Support
59///
60/// Deserialization uses a helper struct where all fields are `Option<T>` with `#[serde(default)]`,
61/// then converts via `TryFrom`. Present fields error if missing, Optional fields become `Some`/`None`,
62/// Absent fields are ignored.
63///
64/// **Limitations:**
65/// - Present fields cannot use the field type's `Default` impl during deserialization
66/// - Optional fields cannot distinguish between missing and explicit `null` in JSON
67/// - Absent fields silently ignore data if present in input
68/// - All field types must implement `Deserialize` even if marked Absent
69///
70/// **Custom deserializers:** If using `#[serde(deserialize_with)]`, the function must produce `Option<T>`.
71///
72/// **Incompatible:** `#[serde(default)]`, `#[serde(skip)]`, `#[serde(skip_deserializing)]`, `#[serde(flatten)]`.
73/// **Compatible:** `rename`, `alias`, `rename_all`, `deserialize_with` (with `Option<T>` output).
74///
75/// # Restrictions
76///
77/// - Named structs only
78/// - At least one field
79#[proc_macro_attribute]
80pub fn structural(_attr: TokenStream, item: TokenStream) -> TokenStream {
81    let input = parse_macro_input!(item as DeriveInput);
82
83    match codegen::generate(input) {
84        Ok(tokens) => tokens.into(),
85        Err(err) => err.to_compile_error().into(),
86    }
87}