concrete_type/
lib.rs

1#![doc(html_root_url = "https://docs.rs/concrete-type")]
2#![warn(missing_docs)]
3
4//! # Concrete Type
5//!
6//! A procedural macro library for mapping enum variants to concrete types.
7//!
8//! This crate provides two main derive macros:
9//!
10//! - [`Concrete`] - For enums where each variant maps to a specific concrete type
11//! - [`ConcreteConfig`] - For enums where each variant has associated configuration data
12//!   and maps to a specific concrete type
13//!
14//! These macros enable type-level programming based on runtime enum values by generating
15//! helper methods and macros that provide access to the concrete types associated with
16//! enum variants.
17//!
18//! ## Examples
19//!
20//! ### Basic Usage with `Concrete`
21//!
22//! ```rust,ignore
23//! use concrete_type::Concrete;
24//!
25//! #[derive(Concrete, Clone, Copy)]
26//! enum Exchange {
27//!     #[concrete = "exchanges::Binance"]
28//!     Binance,
29//!     #[concrete = "exchanges::Coinbase"]
30//!     Coinbase,
31//! }
32//!
33//! mod exchanges {
34//!     pub struct Binance;
35//!     pub struct Coinbase;
36//!     
37//!     impl Binance {
38//!         pub fn new() -> Self { Binance }
39//!         pub fn name(&self) -> &'static str { "binance" }
40//!     }
41//!     
42//!     impl Coinbase {
43//!         pub fn new() -> Self { Coinbase }
44//!         pub fn name(&self) -> &'static str { "coinbase" }
45//!     }
46//! }
47//!
48//! // Use the auto-generated exchange! macro for type-level dispatch
49//! let exchange = Exchange::Binance;
50//! let name = exchange!(exchange; ExchangeImpl => {
51//!     // ExchangeImpl is aliased to the concrete type (exchanges::Binance)
52//!     let instance = ExchangeImpl::new();
53//!     instance.name()
54//! });
55//! assert_eq!(name, "binance");
56//! ```
57//!
58//! ### Using `ConcreteConfig` with Configuration Data
59//!
60//! ```rust,ignore
61//! use concrete_type::ConcreteConfig;
62//!
63//! // Define concrete types and configuration types
64//! mod exchanges {
65//!     pub trait ExchangeApi {
66//!         type Config;
67//!         fn new(config: Self::Config) -> Self;
68//!         fn name(&self) -> &'static str;
69//!     }
70//!
71//!     pub struct Binance;
72//!     pub struct BinanceConfig {
73//!         pub api_key: String,
74//!     }
75//!
76//!     impl ExchangeApi for Binance {
77//!         type Config = BinanceConfig;
78//!         fn new(_: Self::Config) -> Self { Self }
79//!         fn name(&self) -> &'static str { "binance" }
80//!     }
81//! }
82//!
83//! // Define the enum with concrete type mappings and config data
84//! #[derive(ConcreteConfig)]
85//! enum ExchangeConfig {
86//!     #[concrete = "exchanges::Binance"]
87//!     Binance(exchanges::BinanceConfig),
88//! }
89//!
90//! // Using the auto-generated macro with access to both type and config
91//! let config = ExchangeConfig::Binance(
92//!     exchanges::BinanceConfig { api_key: "secret".to_string() }
93//! );
94//!
95//! let name = exchange_config!(config; (Exchange, cfg) => {
96//!     // Inside this block:
97//!     // - Exchange is the concrete type (exchanges::Binance)
98//!     // - cfg is the configuration instance (BinanceConfig)
99//!     use exchanges::ExchangeApi;
100//!     Exchange::new(cfg).name()
101//! });
102//! ```
103//!
104//! See the crate documentation and examples for more details.
105
106extern crate proc_macro;
107
108use convert_case::{Case, Casing};
109use proc_macro::TokenStream;
110use quote::quote;
111use syn::{Attribute, DeriveInput, Expr, Fields, Lit, Meta, parse_macro_input};
112
113/// Helper function to extract concrete type path from an attribute
114fn extract_concrete_type_path(attrs: &[Attribute]) -> Option<syn::Path> {
115    for attr in attrs {
116        if attr.path().is_ident("concrete") {
117            if let Meta::NameValue(meta) = &attr.meta {
118                if let Expr::Lit(expr_lit) = &meta.value {
119                    if let Lit::Str(lit_str) = &expr_lit.lit {
120                        return syn::parse_str::<syn::Path>(&lit_str.value()).ok();
121                    }
122                }
123            }
124        }
125    }
126    None
127}
128
129/// A derive macro that implements the mapping between enum variants and concrete types.
130///
131/// This macro is designed for enums where each variant maps to a specific concrete type.
132/// Each variant must be annotated with the `#[concrete = "path::to::Type"]` attribute that
133/// specifies the concrete type that the variant represents.
134///
135/// # Generated Code
136///
137/// The macro generates a macro with the snake_case name of the enum
138/// (e.g., `exchange!` for `Exchange`, `strategy_kind!` for `StrategyKind`) that can be used
139/// to execute code with the concrete type.
140///
141/// # Example
142///
143/// ```rust,ignore
144/// use concrete_type::Concrete;
145///
146/// #[derive(Concrete)]
147/// enum StrategyKind {
148///     #[concrete = "strategies::StrategyA"]
149///     StrategyA,
150///     #[concrete = "strategies::StrategyB"]
151///     StrategyB,
152/// }
153///
154/// // The generated macro is named after the enum in snake_case
155/// let strategy = StrategyKind::StrategyA;
156/// let result = strategy_kind!(strategy; T => {
157///     // T is aliased to strategies::StrategyA here
158///     std::any::type_name::<T>()
159/// });
160/// ```
161///
162/// This enables type-level programming with enums, where you can define enum variants and
163/// map them to concrete type implementations.
164#[proc_macro_derive(Concrete, attributes(concrete))]
165pub fn derive_concrete(input: TokenStream) -> TokenStream {
166    // Parse the input tokens into a syntax tree
167    let input = parse_macro_input!(input as DeriveInput);
168
169    // Extract the name of the type
170    let type_name = &input.ident;
171
172    // Create a snake_case version of the type name for the macro_rules! name
173    let type_name_str = type_name.to_string();
174    let macro_name_str = type_name_str.to_case(Case::Snake);
175    let macro_name = syn::Ident::new(&macro_name_str, type_name.span());
176
177    // Handle enum case
178    let data_enum = match &input.data {
179        syn::Data::Enum(data_enum) => data_enum,
180        _ => {
181            return syn::Error::new_spanned(
182                type_name,
183                "Concrete can only be derived for enums or structs with type parameters",
184            )
185            .to_compile_error()
186            .into();
187        }
188    };
189
190    // Extract variant names and their concrete types
191    let mut variant_mappings = Vec::new();
192
193    for variant in &data_enum.variants {
194        let variant_name = &variant.ident;
195
196        // Extract the concrete type path from the variant's attributes
197        if let Some(concrete_type) = extract_concrete_type_path(&variant.attrs) {
198            variant_mappings.push((variant_name, concrete_type));
199        } else {
200            // Variant is missing the #[concrete = "..."] attribute
201            return syn::Error::new_spanned(
202                variant_name,
203                format!(
204                    "Enum variant `{}` is missing the #[concrete = \"...\"] attribute",
205                    variant_name
206                ),
207            )
208            .to_compile_error()
209            .into();
210        }
211    }
212
213    // Generate match arms for the macro_rules! version
214    let macro_match_arms = variant_mappings
215        .iter()
216        .map(|(variant_name, concrete_type)| {
217            quote! {
218                #type_name::#variant_name => {
219                    type $type_param = #concrete_type;
220                    $code_block
221                }
222            }
223        });
224
225    // Generate a top-level macro with the snake_case name of the enum
226    let macro_def = quote! {
227        #[macro_export]
228        macro_rules! #macro_name {
229            ($enum_instance:expr; $type_param:ident => $code_block:block) => {
230                match $enum_instance {
231                    #(#macro_match_arms),*
232                }
233            };
234        }
235    };
236
237    // Combine the macro definition and methods implementation
238    let expanded = quote! {
239        // Define the macro outside any module to make it directly accessible
240        #macro_def
241    };
242
243    // Return the generated implementation
244    TokenStream::from(expanded)
245}
246
247/// A derive macro that implements the mapping between enum variants with associated data and
248/// concrete types.
249///
250/// This macro is designed for enums where each variant has associated configuration data and maps
251/// to a specific concrete type. Each variant must be annotated with the
252/// `#[concrete = "path::to::Type"]` attribute and contain a single tuple field
253/// that holds the configuration data for that concrete type.
254///
255/// # Generated Code
256///
257/// The macro generates:
258/// 1. A `config` method that returns a reference to the configuration data
259/// 2. A macro with the snake_case name of the enum + "_config" (with "Config" suffix removed if present)
260///    that allows access to both the concrete type and configuration data
261///
262/// # Example
263///
264/// ```rust,ignore
265/// use concrete_type::ConcreteConfig;
266///
267/// // Define concrete types and configuration types
268/// struct BinanceConfig {
269///     api_key: String,
270/// }
271///
272/// struct Binance;
273///
274/// #[derive(ConcreteConfig)]
275/// enum ExchangeConfig {
276///     #[concrete = "Binance"]
277///     Binance(BinanceConfig),
278/// }
279///
280/// // Using the generated macro
281/// let config = ExchangeConfig::Binance(BinanceConfig { api_key: "key".to_string() });
282/// let result = exchange_config!(config; (Exchange, cfg) => {
283///     // Exchange is aliased to Binance
284///     // cfg is a reference to BinanceConfig
285///     format!("{} with key: {}", std::any::type_name::<Exchange>(), cfg.api_key)
286/// });
287/// ```
288#[proc_macro_derive(ConcreteConfig, attributes(concrete))]
289pub fn derive_concrete_config(input: TokenStream) -> TokenStream {
290    // Parse the input tokens into a syntax tree
291    let input = parse_macro_input!(input as DeriveInput);
292
293    // Extract the name of the type
294    let type_name = &input.ident;
295
296    // Create a snake_case version of the type name for the macro_rules! name
297    let type_name_str = type_name.to_string();
298    // Strip "Config" suffix if present for cleaner macro names
299    let base_name = if type_name_str.ends_with("Config") {
300        &type_name_str[0..type_name_str.len() - 6]
301    } else {
302        &type_name_str
303    };
304    let macro_name_str = format!("{}_config", base_name.to_case(Case::Snake));
305    let macro_name = syn::Ident::new(&macro_name_str, type_name.span());
306
307    // Ensure we're dealing with an enum
308    let data_enum = match &input.data {
309        syn::Data::Enum(data_enum) => data_enum,
310        _ => {
311            return syn::Error::new_spanned(
312                type_name,
313                "ConcreteConfig can only be derived for enums with data",
314            )
315            .to_compile_error()
316            .into();
317        }
318    };
319
320    // Extract variant names, their concrete types, and field types
321    let mut variant_mappings = Vec::new();
322
323    for variant in &data_enum.variants {
324        let variant_name = &variant.ident;
325
326        // Extract the concrete type path from the variant's attributes
327        if let Some(concrete_type) = extract_concrete_type_path(&variant.attrs) {
328            // Verify the variant has a tuple field
329            match &variant.fields {
330                Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
331                    variant_mappings.push((variant_name, concrete_type));
332                }
333                _ => {
334                    return syn::Error::new_spanned(
335                        variant_name,
336                        format!(
337                            "Enum variant `{}` must have exactly one unnamed field for the config",
338                            variant_name
339                        ),
340                    )
341                    .to_compile_error()
342                    .into();
343                }
344            }
345        } else {
346            // Variant is missing the #[concrete = "..."] attribute
347            return syn::Error::new_spanned(
348                variant_name,
349                format!(
350                    "Enum variant `{}` is missing the #[concrete = \"...\"] attribute",
351                    variant_name
352                ),
353            )
354            .to_compile_error()
355            .into();
356        }
357    }
358
359    // Generate match arms for the config method
360    let config_arms = variant_mappings
361        .iter()
362        .map(|(variant_name, _concrete_type)| {
363            quote! {
364                #type_name::#variant_name(config) => config
365            }
366        });
367
368    // Generate match arms for the macro_rules! version
369    let macro_match_arms = variant_mappings
370        .iter()
371        .map(|(variant_name, concrete_type)| {
372            quote! {
373                #type_name::#variant_name(config) => {
374                    type $type_param = #concrete_type;
375                    let $config_param = config;
376                    $code_block
377                }
378            }
379        });
380
381    // Generate a top-level macro with the snake_case name of the enum + "_config"
382    let macro_def = quote! {
383        #[macro_export]
384        macro_rules! #macro_name {
385            ($enum_instance:expr; ($type_param:ident, $config_param:ident) => $code_block:block) => {
386                match $enum_instance {
387                    #(#macro_match_arms),*
388                }
389            };
390        }
391    };
392
393    // Generate the methods implementation
394    let methods_impl = quote! {
395        impl #type_name {
396            /// Returns a reference to the configuration data associated with this enum variant
397            pub fn config(&self) -> &dyn std::any::Any {
398                match self {
399                    #(#config_arms),*
400                }
401            }
402        }
403    };
404
405    // Combine the macro definition and methods implementation
406    let expanded = quote! {
407        // Define the macro
408        #macro_def
409
410        // Implement methods on the enum
411        #methods_impl
412    };
413
414    TokenStream::from(expanded)
415}