concrete_type/
lib.rs

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