structural_typing_macros/
lib.rs

1use proc_macro::TokenStream;
2use syn::{DeriveInput, parse_macro_input};
3
4mod codegen;
5mod parsing;
6
7/// Generates type-safe structural typing support for a struct.
8///
9/// This macro transforms a struct to track which fields are present/absent at the type level,
10/// enabling compile-time enforcement of field requirements.
11///
12/// **Note**: If you use `#[derive(...)]` on your struct, you must add `derive-where` to your dependencies:
13/// ```bash
14/// cargo add derive-where
15/// ```
16///
17/// # Generated Code
18///
19/// For a struct `MyStruct`, this macro generates:
20///
21/// - A `my_struct` module containing:
22///   - `Fields` trait - for type-level field requirements
23///   - `FieldSet<...>` - zero-cost marker type tracking field presence
24///   - Type aliases in `with::` module for field presence combinations
25/// - Builder methods: `.field(value)` that infers presence from value type
26/// - Getter methods: `.get_field()`, `.get_field_mut()`
27/// - `.merge()` - combine two partial structs
28/// - `.split()` / `.try_split()` - split into selected fields and remainder
29///
30/// # Field States
31///
32/// - **Present**: Field has a value of type `T`
33/// - **Optional**: Field has type `Option<T>`
34/// - **Absent**: Field is `PhantomData<T>` (zero cost)
35///
36/// # Example
37///
38/// ```ignore
39/// use structural_typing::{structural, presence::Present};
40///
41/// #[structural]
42/// struct User {
43///     name: String,
44///     email: String,
45/// }
46///
47/// // Build incrementally
48/// let user = User::empty().name("Alice".to_owned());
49///
50/// // Methods can require specific fields
51/// impl<F: user::Fields<name = Present>> User<F> {
52///     fn greet(&self) -> String {
53///         format!("Hello, {}!", self.name)
54///     }
55/// }
56///
57/// user.greet(); // ✓ Compiles
58/// ```
59///
60/// # Restrictions
61///
62/// - Only named structs supported (not tuple structs or enums)
63/// - No generic parameters allowed
64/// - Struct must have at least one field
65#[proc_macro_attribute]
66pub fn structural(_attr: TokenStream, item: TokenStream) -> TokenStream {
67    let input = parse_macro_input!(item as DeriveInput);
68
69    match codegen::generate(input) {
70        Ok(tokens) => tokens.into(),
71        Err(err) => err.to_compile_error().into(),
72    }
73}