fp_macros/lib.rs
1//! Procedural macros for the `fp-library` crate.
2//!
3//! This crate provides macros for generating and working with Higher-Kinded Type (HKT) traits.
4//! It includes:
5//! - `Kind!`: Generates the name of a Kind trait based on its signature.
6//! - `def_kind!`: Defines a new Kind trait.
7
8use apply::{ApplyInput, apply_impl};
9use def_kind::def_kind_impl;
10use generate::generate_name;
11use impl_kind::{ImplKindInput, impl_kind_impl};
12use parse::KindInput;
13use proc_macro::TokenStream;
14use quote::quote;
15use syn::parse_macro_input;
16
17pub(crate) mod apply;
18pub(crate) mod canonicalize;
19pub(crate) mod def_kind;
20pub(crate) mod generate;
21pub(crate) mod impl_kind;
22pub(crate) mod parse;
23
24#[cfg(test)]
25mod property_tests;
26
27/// Generates the name of a Kind trait based on its signature.
28///
29/// This macro takes three parenthesized groups representing the signature:
30/// 1. **Lifetimes**: A comma-separated list of lifetimes (e.g., `('a, 'b)`).
31/// 2. **Types**: A comma-separated list of types with optional bounds (e.g., `(T, U: Display)`).
32/// 3. **Output Bounds**: A `+`-separated list of bounds on the output type (e.g., `(Display + Clone)`).
33///
34/// # Example
35///
36/// ```ignore
37/// // Generates the name for a Kind with:
38/// // - 1 lifetime ('a)
39/// // - 1 type parameter (T) bounded by Display and Clone
40/// // - Output type bounded by Debug
41/// let name = Kind!(('a), (T: Display + Clone), (Debug));
42/// ```
43///
44/// # Limitations
45///
46/// Due to Rust syntax restrictions, this macro cannot be used directly in positions where a
47/// concrete path is expected by the parser, such as:
48/// * Supertrait bounds: `trait MyTrait: Kind!(...) {}` (Invalid)
49/// * Type aliases: `type MyKind = Kind!(...);` (Invalid)
50/// * Trait aliases: `trait MyKind = Kind!(...);` (Invalid)
51///
52/// In these cases, you must use the generated name directly (e.g., `Kind_...`).
53#[proc_macro]
54#[allow(non_snake_case)]
55pub fn Kind(input: TokenStream) -> TokenStream {
56 let input = parse_macro_input!(input as KindInput);
57 let name = generate_name(&input);
58 quote!(#name).into()
59}
60
61/// Defines a new Kind trait.
62///
63/// This macro generates a trait definition for a Higher-Kinded Type signature.
64/// It takes the same three arguments as `Kind!`:
65/// 1. **Lifetimes**
66/// 2. **Types**
67/// 3. **Output Bounds**
68///
69/// The generated trait includes a single associated type `Of`.
70///
71/// # Example
72///
73/// ```ignore
74/// // Defines a Kind trait for a signature with:
75/// // - 1 lifetime ('a)
76/// // - 1 type parameter (T) bounded by Display
77/// // - Output type bounded by Debug
78/// def_kind!(('a), (T: Display), (Debug));
79/// ```
80#[proc_macro]
81pub fn def_kind(input: TokenStream) -> TokenStream {
82 let input = parse_macro_input!(input as KindInput);
83 def_kind_impl(input).into()
84}
85
86/// Implements a Kind trait for a brand.
87///
88/// This macro simplifies the implementation of a generated Kind trait for a specific
89/// brand type. It infers the correct Kind trait to implement based on the signature
90/// of the associated type `Of`.
91///
92/// # Syntax
93///
94/// ```ignore
95/// impl_kind! {
96/// impl<GENERICS> for BrandType {
97/// type Of<PARAMS> = ConcreteType;
98/// }
99/// }
100/// ```
101///
102/// Or with where clause:
103///
104/// ```ignore
105/// impl_kind! {
106/// impl<E> for ResultBrand<E> where E: Debug {
107/// type Of<A> = Result<A, E>;
108/// }
109/// }
110/// ```
111///
112/// # Example
113///
114/// ```ignore
115/// impl_kind! {
116/// for OptionBrand {
117/// type Of<A> = Option<A>;
118/// }
119/// }
120/// ```
121#[proc_macro]
122pub fn impl_kind(input: TokenStream) -> TokenStream {
123 let input = parse_macro_input!(input as ImplKindInput);
124 impl_kind_impl(input).into()
125}
126
127/// Applies a brand to type arguments.
128///
129/// This macro projects a brand type to its concrete type using the appropriate
130/// Kind trait. It uses named parameters syntax.
131///
132/// # Modes
133///
134/// The macro supports two modes of operation:
135///
136/// 1. **Unified Signature Mode** (Recommended): Uses a single `signature` parameter to specify both
137/// the schema (for Kind trait name generation) and the concrete values (for projection).
138/// 2. **Explicit Kind Mode** (Advanced): Uses an explicit `kind` parameter along with separate
139/// `lifetimes` and `types` parameters.
140///
141/// # Parameters
142///
143/// * `brand`: (Required) The brand type (e.g., `OptionBrand`).
144/// * `signature`: (Mode 1) The unified signature containing both schema and values.
145/// * `kind`: (Mode 2) An explicit Kind trait to use.
146/// * `lifetimes`: (Mode 2) Lifetime arguments to apply. Required with `kind`.
147/// * `types`: (Mode 2) Type arguments to apply. Required with `kind`.
148/// * `output`: (Optional) The associated type to project to. Defaults to `Of`.
149///
150/// # Unified Signature Syntax
151///
152/// The `signature` parameter uses a syntax similar to a function signature:
153/// `(param1, param2, ...) -> OutputBounds`
154///
155/// * **Lifetimes**: Specified as `'a`, `'static`, etc.
156/// * **Types**: Specified as `Type` or `Type: Bounds`.
157/// * The `Type` part is used as the concrete value for projection.
158/// * The `Bounds` part (optional) is used for Kind trait name generation.
159///
160/// # Examples
161///
162/// ## Unified Signature Mode (Recommended)
163///
164/// ```ignore
165/// // Applies MyBrand to lifetime 'static and type String.
166/// // The schema is inferred as: 1 lifetime, 1 type parameter (unbounded).
167/// type Concrete = Apply!(
168/// brand: MyBrand,
169/// signature: ('static, String)
170/// );
171///
172/// // Applies MyBrand to a generic type T with bounds.
173/// // The schema is inferred as: 1 type parameter with Clone bound.
174/// type Concrete = Apply!(
175/// brand: MyBrand,
176/// signature: (T: Clone)
177/// );
178///
179/// // Complex example with lifetimes, types, and output bounds.
180/// type Concrete = Apply!(
181/// brand: MyBrand,
182/// signature: ('a, T: Clone + Debug) -> Display
183/// );
184///
185/// // Use a custom associated type for projection.
186/// type Concrete = Apply!(
187/// brand: MyBrand,
188/// signature: (T),
189/// output: SendOf
190/// );
191/// ```
192///
193/// ## Explicit Kind Mode (Advanced)
194///
195/// Use this mode when you need to specify a custom Kind trait directly.
196///
197/// ```ignore
198/// // Applies OptionBrand using an explicit Kind trait.
199/// type Concrete = Apply!(
200/// brand: OptionBrand,
201/// kind: SomeKind,
202/// lifetimes: ('a),
203/// types: (String)
204/// );
205/// ```
206#[proc_macro]
207#[allow(non_snake_case)]
208pub fn Apply(input: TokenStream) -> TokenStream {
209 let input = parse_macro_input!(input as ApplyInput);
210 apply_impl(input).into()
211}