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/// # Generated Code
13///
14/// For a struct `MyStruct`, this macro generates:
15///
16/// - A `my_struct` module containing:
17/// - `Fields` trait - for type-level field requirements
18/// - `FieldSet<...>` - zero-cost marker type tracking field presence
19/// - Type aliases in `with::` module for field presence combinations
20/// - Builder methods: `.field(value)` that infers presence from value type
21/// - Getter methods: `.get_field()`, `.get_field_mut()`
22/// - `.merge()` - combine two partial structs
23/// - `.split()` / `.try_split()` - split into selected fields and remainder
24///
25/// # Field States
26///
27/// - **Present**: Field has a value of type `T`
28/// - **Optional**: Field has type `Option<T>`
29/// - **Absent**: Field is `PhantomData<T>` (zero cost)
30///
31/// # Example
32///
33/// ```ignore
34/// use structural_typing::{structural, presence::Present};
35///
36/// #[structural]
37/// struct User {
38/// name: String,
39/// email: String,
40/// }
41///
42/// // Build incrementally
43/// let user = User::empty().name("Alice".to_owned());
44///
45/// // Methods can require specific fields
46/// impl<F: user::Fields<name = Present>> User<F> {
47/// fn greet(&self) -> String {
48/// format!("Hello, {}!", self.name)
49/// }
50/// }
51///
52/// user.greet(); // ✓ Compiles
53/// ```
54///
55/// # Restrictions
56///
57/// - Only named structs supported (not tuple structs or enums)
58/// - No generic parameters allowed
59/// - Struct must have at least one field
60#[proc_macro_attribute]
61pub fn structural(_attr: TokenStream, item: TokenStream) -> TokenStream {
62 let input = parse_macro_input!(item as DeriveInput);
63
64 match codegen::generate(input) {
65 Ok(tokens) => tokens.into(),
66 Err(err) => err.to_compile_error().into(),
67 }
68}