concrete_type/lib.rs
1#![doc(html_root_url = "https://docs.rs/concrete-type")]
2
3extern crate proc_macro;
4
5use convert_case::{Boundary, Case, Casing};
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::{
9 Attribute, DeriveInput, Expr, Fields, Ident, Lit, Meta, Token,
10 parse::{Parse, ParseStream},
11 parse_macro_input,
12};
13
14#[proc_macro_derive(DeExchange)]
15pub fn de_exchange_derive(input: TokenStream) -> TokenStream {
16 // Parse Rust code abstract syntax tree with Syn from TokenStream -> DeriveInput
17 let ast: DeriveInput =
18 syn::parse(input).expect("de_exchange_derive() failed to parse input TokenStream");
19
20 // Determine execution name
21 let exchange = &ast.ident;
22
23 let generated = quote! {
24 impl<'de> serde::Deserialize<'de> for #exchange {
25 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
26 where
27 D: serde::de::Deserializer<'de>
28 {
29 let input = <String as serde::Deserialize>::deserialize(deserializer)?;
30 let exchange = #exchange::ID;
31 let expected = exchange.as_str();
32
33 if input.as_str() == expected {
34 Ok(Self::default())
35 } else {
36 Err(serde::de::Error::invalid_value(
37 serde::de::Unexpected::Str(input.as_str()),
38 &expected
39 ))
40 }
41 }
42 }
43 };
44
45 TokenStream::from(generated)
46}
47
48#[proc_macro_derive(SerExchange)]
49pub fn ser_exchange_derive(input: TokenStream) -> TokenStream {
50 // Parse Rust code abstract syntax tree with Syn from TokenStream -> DeriveInput
51 let ast: DeriveInput =
52 syn::parse(input).expect("ser_exchange_derive() failed to parse input TokenStream");
53
54 // Determine Exchange
55 let exchange = &ast.ident;
56
57 let generated = quote! {
58 impl serde::Serialize for #exchange {
59 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
60 where
61 S: serde::ser::Serializer,
62 {
63 serializer.serialize_str(#exchange::ID.as_str())
64 }
65 }
66 };
67
68 TokenStream::from(generated)
69}
70
71#[proc_macro_derive(DeSubKind)]
72pub fn de_sub_kind_derive(input: TokenStream) -> TokenStream {
73 // Parse Rust code abstract syntax tree with Syn from TokenStream -> DeriveInput
74 let ast: DeriveInput =
75 syn::parse(input).expect("de_sub_kind_derive() failed to parse input TokenStream");
76
77 // Determine SubKind name
78 let sub_kind = &ast.ident;
79
80 let expected_sub_kind = sub_kind
81 .to_string()
82 .from_case(Case::Pascal)
83 .without_boundaries(&Boundary::letter_digit())
84 .to_case(Case::Snake);
85
86 let generated = quote! {
87 impl<'de> serde::Deserialize<'de> for #sub_kind {
88 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
89 where
90 D: serde::de::Deserializer<'de>
91 {
92 let input = <String as serde::Deserialize>::deserialize(deserializer)?;
93
94 if input == #expected_sub_kind {
95 Ok(Self)
96 } else {
97 Err(serde::de::Error::invalid_value(
98 serde::de::Unexpected::Str(input.as_str()),
99 &#expected_sub_kind
100 ))
101 }
102 }
103 }
104 };
105
106 TokenStream::from(generated)
107}
108
109#[proc_macro_derive(SerSubKind)]
110pub fn ser_sub_kind_derive(input: TokenStream) -> TokenStream {
111 // Parse Rust code abstract syntax tree with Syn from TokenStream -> DeriveInput
112 let ast: DeriveInput =
113 syn::parse(input).expect("ser_sub_kind_derive() failed to parse input TokenStream");
114
115 // Determine SubKind name
116 let sub_kind = &ast.ident;
117 let sub_kind_string = sub_kind.to_string().to_case(Case::Snake);
118 let sub_kind_str = sub_kind_string.as_str();
119
120 let generated = quote! {
121 impl serde::Serialize for #sub_kind {
122 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
123 where
124 S: serde::ser::Serializer,
125 {
126 serializer.serialize_str(#sub_kind_str)
127 }
128 }
129 };
130
131 TokenStream::from(generated)
132}
133
134/// Helper function to extract concrete type path from an attribute
135fn extract_concrete_type_path(attrs: &[Attribute]) -> Option<syn::Path> {
136 for attr in attrs {
137 if attr.path().is_ident("concrete") {
138 if let Meta::NameValue(meta) = &attr.meta {
139 if let Expr::Lit(expr_lit) = &meta.value {
140 if let Lit::Str(lit_str) = &expr_lit.lit {
141 return syn::parse_str::<syn::Path>(&lit_str.value()).ok();
142 }
143 }
144 }
145 }
146 }
147 None
148}
149
150/// A derive macro that implements the mapping between enum variants and concrete types.
151///
152/// This derive macro is designed for enums where each variant maps to a specific concrete type.
153/// Each variant must be annotated with the `#[concrete = "path::to::Type"]` attribute that
154/// specifies the concrete type that the variant represents.
155///
156/// The macro generates:
157/// 1. A `concrete_type_id` method that returns the `TypeId` of the concrete type for a variant
158/// 2. A `concrete_type_name` method that returns the name of the concrete type as a string
159/// 3. A `with_concrete_type` method that executes a function with knowledge of the concrete type
160/// 4. A macro with the snake_case name of the enum (e.g., `exchange!` for `Exchange`,
161/// `strategy!` for `Strategy`) that can be used to execute code with the concrete type
162///
163/// This enables type-level programming with enums, where you can define enum variants and
164/// map them to concrete type implementations.
165///
166/// # Example
167/// ```
168/// use concrete_type::Concrete;
169/// use std::marker::PhantomData;
170///
171/// // Define concrete types
172/// mod exchanges {
173/// pub struct Binance;
174/// pub struct Okx;
175///
176/// impl Binance {
177/// pub fn new() -> Self { Self }
178/// pub fn name(&self) -> &'static str { "binance" }
179/// }
180///
181/// impl Okx {
182/// pub fn new() -> Self { Self }
183/// pub fn name(&self) -> &'static str { "okx" }
184/// }
185/// }
186///
187/// // Define the exchange enum with concrete type mappings
188/// #[derive(Concrete, Clone, Copy)]
189/// enum Exchange {
190/// #[concrete = "exchanges::Binance"]
191/// Binance,
192/// #[concrete = "exchanges::Okx"]
193/// Okx,
194/// }
195///
196/// // Using the auto-generated exchange! macro:
197/// let exchange = Exchange::Binance;
198/// let name = exchange!(exchange; ExchangeImpl => {
199/// // Inside this block, ExchangeImpl is aliased to exchanges::Binance
200/// let instance = ExchangeImpl::new();
201/// instance.name()
202/// });
203/// assert_eq!(name, "binance");
204///
205/// // Using the Exchange enum's methods directly
206/// let exchange = Exchange::Okx;
207/// // Get the TypeId of the concrete type
208/// let type_id = exchange.concrete_type_id();
209/// // Get the name of the concrete type (as a string)
210/// let type_name = exchange.concrete_type_name();
211/// assert!(type_name.ends_with("exchanges::Okx"));
212/// ```
213#[proc_macro_derive(Concrete, attributes(concrete))]
214pub fn derive_concrete(input: TokenStream) -> TokenStream {
215 // Parse the input tokens into a syntax tree
216 let input = parse_macro_input!(input as DeriveInput);
217
218 // Extract the name of the type
219 let type_name = &input.ident;
220
221 // Create a snake_case version of the type name for the macro_rules! name
222 let type_name_str = type_name.to_string();
223 let macro_name_str = type_name_str.to_case(Case::Snake);
224 let macro_name = syn::Ident::new(¯o_name_str, type_name.span());
225
226 // Check if we're dealing with a struct that has type parameters
227 let is_struct_with_type_params = match &input.data {
228 syn::Data::Struct(_) => !input.generics.params.is_empty(),
229 _ => false,
230 };
231
232 // Handle differently based on whether we're dealing with an enum or a struct with type parameters
233 if is_struct_with_type_params {
234 // The macro name should be 'trading_system' based on the file content, not derived from the struct name
235 let macro_name = syn::Ident::new("trading_system", type_name.span());
236
237 // Generate a macro that accepts multiple arguments and type parameters
238 // This handles syntax like: trading_system!(exchange, strategy; (Exchange, Strategy) => { ... })
239 let trading_system_macro = quote! {
240 /// Generates a macro for working with a struct that has type parameters.
241 /// This macro composes multiple enum-based macros to provide concrete types for generic parameters.
242 ///
243 /// # Example
244 ///
245 /// ```
246 /// use concrete_type::Concrete;
247 /// use std::marker::PhantomData;
248 ///
249 /// // Define the concrete implementation types
250 /// mod exchanges {
251 /// pub struct Binance;
252 /// pub struct Okx;
253 /// }
254 ///
255 /// mod strategies {
256 /// pub struct StrategyA;
257 /// pub struct StrategyB;
258 ///
259 /// impl StrategyA {
260 /// pub fn name() -> &'static str { "strategy_a" }
261 /// }
262 ///
263 /// impl StrategyB {
264 /// pub fn name() -> &'static str { "strategy_b" }
265 /// }
266 /// }
267 ///
268 /// // Define enums that map to concrete types
269 /// #[derive(Concrete, Clone, Copy)]
270 /// enum Exchange {
271 /// #[concrete = "exchanges::Binance"]
272 /// Binance,
273 /// #[concrete = "exchanges::Okx"]
274 /// Okx,
275 /// }
276 ///
277 /// #[derive(Concrete)]
278 /// enum Strategy {
279 /// #[concrete = "strategies::StrategyA"]
280 /// StrategyA,
281 /// #[concrete = "strategies::StrategyB"]
282 /// StrategyB,
283 /// }
284 ///
285 /// // A struct with type parameters that will be resolved at runtime
286 /// #[derive(Concrete)]
287 /// struct TradingSystem<E, S> {
288 /// phantom: PhantomData<(E, S)>,
289 /// }
290 ///
291 /// // Implement for concrete type combinations
292 /// impl TradingSystem<exchanges::Binance, strategies::StrategyA> {
293 /// pub fn new() -> Self {
294 /// Self { phantom: PhantomData }
295 /// }
296 ///
297 /// pub fn name(&self) -> &'static str { "binance_strategy_a" }
298 /// }
299 ///
300 /// impl TradingSystem<exchanges::Okx, strategies::StrategyB> {
301 /// pub fn new() -> Self {
302 /// Self { phantom: PhantomData }
303 /// }
304 ///
305 /// pub fn name(&self) -> &'static str { "okx_strategy_b" }
306 /// }
307 ///
308 /// // Use the trading_system macro
309 /// let exchange = Exchange::Okx;
310 /// let strategy = Strategy::StrategyB;
311 ///
312 /// let name = trading_system!(exchange, strategy; (E, S) => {
313 /// // Here E is exchanges::Okx and S is strategies::StrategyB
314 /// TradingSystem::<E, S>::new().name()
315 /// });
316 /// assert_eq!(name, "okx_strategy_b");
317 /// ```
318 #[macro_export]
319 macro_rules! #macro_name {
320 // Match the pattern with two enum instances and two type parameters
321 ($exchange_enum:expr, $strategy_enum:expr; ($exchange_type:ident, $strategy_type:ident) => $code_block:block) => {
322 exchange!($exchange_enum; $exchange_type => {
323 strategy!($strategy_enum; $strategy_type => {
324 $code_block
325 })
326 })
327 };
328 }
329 };
330
331 TokenStream::from(trading_system_macro)
332 } else {
333 // Handle enum case (exactly as before)
334 // Ensure we're dealing with an enum
335 let data_enum = match &input.data {
336 syn::Data::Enum(data_enum) => data_enum,
337 _ => {
338 return syn::Error::new_spanned(
339 type_name,
340 "Concrete can only be derived for enums or structs with type parameters",
341 )
342 .to_compile_error()
343 .into();
344 }
345 };
346
347 // Extract variant names and their concrete types
348 let mut variant_mappings = Vec::new();
349
350 for variant in &data_enum.variants {
351 let variant_name = &variant.ident;
352
353 // Extract the concrete type path from the variant's attributes
354 if let Some(concrete_type) = extract_concrete_type_path(&variant.attrs) {
355 variant_mappings.push((variant_name, concrete_type));
356 } else {
357 // Variant is missing the #[concrete = "..."] attribute
358 return syn::Error::new_spanned(
359 variant_name,
360 format!(
361 "Enum variant `{}` is missing the #[concrete = \"...\"] attribute",
362 variant_name
363 ),
364 )
365 .to_compile_error()
366 .into();
367 }
368 }
369
370 // Generate match arms for the concrete type mapping
371 let match_arms = variant_mappings
372 .iter()
373 .map(|(variant_name, concrete_type)| {
374 quote! {
375 #type_name::#variant_name => {
376 type_id::<#concrete_type>()
377 }
378 }
379 });
380
381 // Generate match arms for the concrete type name
382 let type_name_arms = variant_mappings
383 .iter()
384 .map(|(variant_name, concrete_type)| {
385 quote! {
386 #type_name::#variant_name => type_name_of::<#concrete_type>()
387 }
388 });
389
390 // Generate match arms for the concrete type aliases
391 let type_alias_arms = variant_mappings
392 .iter()
393 .map(|(variant_name, concrete_type)| {
394 quote! {
395 #type_name::#variant_name => {
396 type ConcreteType = #concrete_type;
397 f()
398 }
399 }
400 });
401
402 // Generate match arms for the macro_rules! version
403 let macro_match_arms = variant_mappings
404 .iter()
405 .map(|(variant_name, concrete_type)| {
406 quote! {
407 #type_name::#variant_name => {
408 type $type_param = #concrete_type;
409 $code_block
410 }
411 }
412 });
413
414 // Generate a top-level macro with the snake_case name of the enum
415 // This way it will be directly accessible in the crate
416 let macro_def = quote! {
417 #[macro_export]
418 macro_rules! #macro_name {
419 ($enum_instance:expr; $type_param:ident => $code_block:block) => {
420 match $enum_instance {
421 #(#macro_match_arms),*
422 }
423 };
424 }
425 };
426
427 // Generate the methods implementation
428 let methods_impl = quote! {
429 impl #type_name {
430 /// Returns the TypeId of the concrete type associated with this enum variant
431 pub fn concrete_type_id(&self) -> std::any::TypeId {
432 use std::any::TypeId;
433
434 fn type_id<T: 'static>() -> TypeId {
435 TypeId::of::<T>()
436 }
437
438 match self {
439 #(#match_arms),*
440 }
441 }
442
443 /// Returns the name of the concrete type associated with this enum variant
444 pub fn concrete_type_name(&self) -> &'static str {
445 use std::any::type_name;
446
447 fn type_name_of<T: 'static>() -> &'static str {
448 type_name::<T>()
449 }
450
451 match self {
452 #(#type_name_arms),*
453 }
454 }
455
456 /// Executes a function with the concrete type associated with this enum variant
457 pub fn with_concrete_type<F, R>(&self, f: F) -> R
458 where
459 F: for<'a> Fn() -> R,
460 {
461 match self {
462 #(#type_alias_arms),*
463 }
464 }
465 }
466 };
467
468 // Combine the macro definition and methods implementation
469 let expanded = quote! {
470 // Define the macro outside any module to make it directly accessible
471 #macro_def
472
473 // Implement methods on the enum
474 #methods_impl
475 };
476
477 // Return the generated implementation
478 TokenStream::from(expanded)
479 }
480}
481
482/// Parser for the concrete macro
483/// Format: concrete!(enum_instance; TypeParam => { code_block })
484struct ConcreteMacroInput {
485 enum_instance: Expr,
486 type_param: Ident,
487 code_block: syn::Block,
488}
489
490impl Parse for ConcreteMacroInput {
491 fn parse(input: ParseStream) -> syn::Result<Self> {
492 let enum_instance = input.parse()?;
493 input.parse::<Token![;]>()?;
494 let type_param = input.parse()?;
495 input.parse::<Token![=>]>()?;
496 let code_block = input.parse()?;
497
498 Ok(ConcreteMacroInput {
499 enum_instance,
500 type_param,
501 code_block,
502 })
503 }
504}
505
506/// A procedural macro for executing code with knowledge of the concrete type mapped to an enum variant.
507///
508/// This macro takes an enum instance that has been derived with `#[derive(Concrete)]` and uses
509/// the concrete type information to execute a block of code with a type alias to the concrete type.
510/// This enables type-level programming based on runtime enum values.
511///
512/// # Usage
513/// ```ignore
514/// concrete!(enum_instance; TypeParam => { code_block })
515/// ```
516///
517/// # Parameters
518/// - `enum_instance`: An instance of an enum with `#[derive(Concrete)]`
519/// - `TypeParam`: The name you want to use as the type alias within the code block
520/// - `code_block`: The code to execute with knowledge of the concrete type
521///
522/// # Example
523/// ```
524/// use concrete_type::Concrete;
525/// use std::marker::PhantomData;
526///
527/// // Define concrete types and trait
528/// trait ExchangeApi {
529/// fn new() -> Self;
530/// fn name(&self) -> &'static str;
531/// }
532///
533/// mod exchanges {
534/// pub struct Binance;
535/// impl Binance {
536/// pub fn new() -> Self { Self }
537/// pub fn name(&self) -> &'static str { "binance" }
538/// }
539///
540/// pub struct Okx;
541/// impl Okx {
542/// pub fn new() -> Self { Self }
543/// pub fn name(&self) -> &'static str { "okx" }
544/// }
545/// }
546///
547/// // Define the exchange enum with concrete type mappings
548/// #[derive(Concrete, Clone, Copy)]
549/// enum Exchange {
550/// #[concrete = "exchanges::Binance"]
551/// Binance,
552/// #[concrete = "exchanges::Okx"]
553/// Okx,
554/// }
555///
556/// // This example uses the auto-generated `exchange!` macro instead of the `concrete!` macro
557/// let exchange = Exchange::Binance;
558/// let name = exchange!(exchange; ExchangeImpl => {
559/// // Here, ExchangeImpl is aliased to the concrete type (exchanges::Binance)
560/// let instance = ExchangeImpl::new();
561/// instance.name()
562/// });
563/// assert_eq!(name, "binance");
564///
565/// // You can also use with_concrete_type for a similar effect:
566/// let exchange = Exchange::Okx;
567/// let name = exchange.with_concrete_type(|| {
568/// // Inside this block, ConcreteType is aliased to exchanges::Okx
569/// type ConcreteType = exchanges::Okx;
570/// let instance = ConcreteType::new();
571/// instance.name()
572/// });
573/// assert_eq!(name, "okx");
574/// ```
575///
576/// This allows for executing code that requires the concrete type at compile time,
577/// even though the enum variant is only known at runtime.
578#[proc_macro]
579pub fn concrete(input: TokenStream) -> TokenStream {
580 let ConcreteMacroInput {
581 enum_instance: _,
582 type_param: _,
583 code_block: _,
584 } = parse_macro_input!(input as ConcreteMacroInput);
585
586 // This is a placeholder implementation
587 // In the examples we're using local macro_rules! macros instead
588 let expanded = quote! {
589 compile_error!("This concrete macro is used for documentation purposes only. The examples use a local macro_rules! for concrete instead.")
590 };
591
592 TokenStream::from(expanded)
593}
594
595/// A derive macro that implements the mapping between enum variants with associated data and concrete types.
596///
597/// This derive macro is designed for enums where each variant has associated configuration data and maps to a specific concrete type.
598/// Each variant must be annotated with the `#[concrete = "path::to::Type"]` attribute and contain a single tuple field
599/// that holds the configuration data for that concrete type.
600///
601/// The macro generates:
602/// 1. A `concrete_type_id` method that returns the `TypeId` of the concrete type for a variant
603/// 2. A `concrete_type_name` method that returns the name of the concrete type as a string
604/// 3. A `config` method that returns a reference to the configuration data
605/// 4. A macro with the snake_case name of the enum + "_config" (with "Config" suffix removed if present)
606/// that allows access to both the concrete type and configuration data
607///
608/// This enables type-level programming with configuration data, where enum variants map to concrete type
609/// implementations and carry the configuration needed by those types.
610///
611/// # Example
612/// ```
613/// use concrete_type::ConcreteConfig;
614///
615/// // Define concrete types and configuration types
616/// mod exchanges {
617/// pub trait ExchangeApi {
618/// type Config;
619/// fn new(config: Self::Config) -> Self;
620/// fn name(&self) -> &'static str;
621/// }
622///
623/// pub struct Binance;
624/// pub struct BinanceConfig;
625///
626/// impl ExchangeApi for Binance {
627/// type Config = BinanceConfig;
628/// fn new(_: Self::Config) -> Self { Self }
629/// fn name(&self) -> &'static str { "binance" }
630/// }
631///
632/// pub struct Okx;
633/// pub struct OkxConfig;
634///
635/// impl ExchangeApi for Okx {
636/// type Config = OkxConfig;
637/// fn new(_: Self::Config) -> Self { Self }
638/// fn name(&self) -> &'static str { "okx" }
639/// }
640/// }
641///
642/// // Define the exchange config enum with concrete type mappings and config data
643/// #[derive(ConcreteConfig)]
644/// enum ExchangeConfig {
645/// #[concrete = "exchanges::Binance"]
646/// Binance(exchanges::BinanceConfig),
647/// #[concrete = "exchanges::Okx"]
648/// Okx(exchanges::OkxConfig),
649/// }
650///
651/// // Import the trait for access to its methods
652/// use exchanges::ExchangeApi;
653///
654/// // Using the auto-generated exchange_config! macro:
655/// let config = ExchangeConfig::Binance(exchanges::BinanceConfig);
656/// let name = exchange_config!(config; (Exchange, config_param) => {
657/// // Inside this block:
658/// // - Exchange is aliased to exchanges::Binance
659/// // - config_param is the BinanceConfig instance
660/// Exchange::new(config_param).name()
661/// });
662/// assert_eq!(name, "binance");
663///
664/// // Create a different instance for demonstrating methods
665/// let config2 = ExchangeConfig::Binance(exchanges::BinanceConfig);
666///
667/// // Using the ExchangeConfig enum's methods directly
668/// // Get the TypeId of the concrete type
669/// let type_id = config2.concrete_type_id();
670/// // Get the name of the concrete type (as a string)
671/// let type_name = config2.concrete_type_name();
672/// assert!(type_name.ends_with("exchanges::Binance"));
673/// ```
674#[proc_macro_derive(ConcreteConfig, attributes(concrete))]
675pub fn derive_concrete_config(input: TokenStream) -> TokenStream {
676 // Parse the input tokens into a syntax tree
677 let input = parse_macro_input!(input as DeriveInput);
678
679 // Extract the name of the type
680 let type_name = &input.ident;
681
682 // Create a snake_case version of the type name for the macro_rules! name
683 let type_name_str = type_name.to_string();
684 // Strip "Config" suffix if present for cleaner macro names
685 let base_name = if type_name_str.ends_with("Config") {
686 &type_name_str[0..type_name_str.len() - 6]
687 } else {
688 &type_name_str
689 };
690 let macro_name_str = format!("{}_config", base_name.to_case(Case::Snake));
691 let macro_name = syn::Ident::new(¯o_name_str, type_name.span());
692
693 // Ensure we're dealing with an enum
694 let data_enum = match &input.data {
695 syn::Data::Enum(data_enum) => data_enum,
696 _ => {
697 return syn::Error::new_spanned(
698 type_name,
699 "ConcreteConfig can only be derived for enums with data",
700 )
701 .to_compile_error()
702 .into();
703 }
704 };
705
706 // Extract variant names, their concrete types, and field types
707 let mut variant_mappings = Vec::new();
708
709 for variant in &data_enum.variants {
710 let variant_name = &variant.ident;
711
712 // Extract the concrete type path from the variant's attributes
713 if let Some(concrete_type) = extract_concrete_type_path(&variant.attrs) {
714 // Verify the variant has a tuple field
715 match &variant.fields {
716 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
717 variant_mappings.push((variant_name, concrete_type));
718 }
719 _ => {
720 return syn::Error::new_spanned(
721 variant_name,
722 format!(
723 "Enum variant `{}` must have exactly one unnamed field for the config",
724 variant_name
725 ),
726 )
727 .to_compile_error()
728 .into();
729 }
730 }
731 } else {
732 // Variant is missing the #[concrete = "..."] attribute
733 return syn::Error::new_spanned(
734 variant_name,
735 format!(
736 "Enum variant `{}` is missing the #[concrete = \"...\"] attribute",
737 variant_name
738 ),
739 )
740 .to_compile_error()
741 .into();
742 }
743 }
744
745 // Generate match arms for the concrete type ID
746 let match_arms = variant_mappings
747 .iter()
748 .map(|(variant_name, concrete_type)| {
749 quote! {
750 #type_name::#variant_name(_) => {
751 type_id::<#concrete_type>()
752 }
753 }
754 });
755
756 // Generate match arms for the concrete type name
757 let type_name_arms = variant_mappings
758 .iter()
759 .map(|(variant_name, concrete_type)| {
760 quote! {
761 #type_name::#variant_name(_) => type_name_of::<#concrete_type>()
762 }
763 });
764
765 // Generate match arms for the config method
766 let config_arms = variant_mappings
767 .iter()
768 .map(|(variant_name, _concrete_type)| {
769 quote! {
770 #type_name::#variant_name(config) => config
771 }
772 });
773
774 // Generate match arms for the macro_rules! version
775 let macro_match_arms = variant_mappings
776 .iter()
777 .map(|(variant_name, concrete_type)| {
778 quote! {
779 #type_name::#variant_name(config) => {
780 type $type_param = #concrete_type;
781 let $config_param = config;
782 $code_block
783 }
784 }
785 });
786
787 // Create the macro name
788
789 // Generate a top-level macro with the snake_case name of the enum + "_config"
790 let macro_def = quote! {
791 #[macro_export]
792 macro_rules! #macro_name {
793 ($enum_instance:expr; ($type_param:ident, $config_param:ident) => $code_block:block) => {
794 match $enum_instance {
795 #(#macro_match_arms),*
796 }
797 };
798 }
799 };
800
801 // Generate the methods implementation
802 let methods_impl = quote! {
803 impl #type_name {
804 /// Returns the TypeId of the concrete type associated with this enum variant
805 pub fn concrete_type_id(&self) -> std::any::TypeId {
806 use std::any::TypeId;
807
808 fn type_id<T: 'static>() -> TypeId {
809 TypeId::of::<T>()
810 }
811
812 match self {
813 #(#match_arms),*
814 }
815 }
816
817 /// Returns the name of the concrete type associated with this enum variant
818 pub fn concrete_type_name(&self) -> &'static str {
819 use std::any::type_name;
820
821 fn type_name_of<T: 'static>() -> &'static str {
822 type_name::<T>()
823 }
824
825 match self {
826 #(#type_name_arms),*
827 }
828 }
829
830 // Get config data from the enum variant
831 pub fn config(&self) -> &dyn std::any::Any {
832 match self {
833 #(#config_arms),*
834 }
835 }
836 }
837 };
838
839 // Combine the macro definition and methods implementation
840 let expanded = quote! {
841 // Define the macro
842 #macro_def
843
844 // Implement methods on the enum
845 #methods_impl
846 };
847
848 TokenStream::from(expanded)
849}