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(¯o_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(¯o_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}