trait_cast_macros/
lib.rs

1//! Proc-macro automating the implementation of `trait_cast::TraitcastableAny`.
2//!
3//! See `make_trait_castable` for more details.
4#![feature(map_try_insert)]
5
6use cargo_manifest_proc_macros::CargoManifest;
7use proc_macro::TokenStream as TokenStream1;
8use proc_macro2::TokenStream as TokenStream2;
9use quote::quote;
10use syn::{
11  Error, ItemEnum, ItemStruct, Token, TypePath,
12  parse::{self, Parse, ParseStream},
13  parse_macro_input,
14  punctuated::Punctuated,
15};
16use tracing_proc_macros_ink::proc_macro_logger_default_setup;
17
18/// Parses a list of `TypePath`s separated by commas.
19struct TraitCastTargets {
20  targets: Vec<TypePath>,
21}
22
23impl Parse for TraitCastTargets {
24  fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
25    let targets: Vec<TypePath> = Punctuated::<TypePath, Token![,]>::parse_terminated(input)?
26      .into_iter()
27      .collect();
28    Ok(Self { targets })
29  }
30}
31
32impl quote::ToTokens for TraitCastTargets {
33  fn to_tokens(&self, tokens: &mut TokenStream2) {
34    let vars = &self.targets;
35    tokens.extend(quote!(#(#vars),*));
36  }
37}
38
39/// Attribute macro implementing `TraitcastableAny` for a struct, enum or union.
40///
41/// Use the arguments to specify all possible target Traits for witch trait objects are
42/// supposed to be downcastable from a dyn `TraitcastableAny`.
43///
44/// Example:
45/// ```ignore
46/// extern crate trait_cast;
47///
48/// use trait_cast::{make_trait_castable, TraitcastTarget, TraitcastTo, TraitcastableAny};
49///
50///
51/// #[make_trait_castable(Print)]
52/// struct Source(i32);
53///
54/// trait Print {
55///   fn print(&self);
56/// }
57/// impl Print for Source {
58///   fn print(&self) {
59///     println!("{}", self.0)
60///   }
61/// }
62///
63/// fn main() {
64///   let source = Box::new(Source(5));
65///   let castable: Box<dyn TraitcastableAny> = source;
66///   let x: &dyn Print = castable.downcast_ref().unwrap();
67///   x.print();
68/// }
69/// ```
70#[proc_macro_attribute]
71pub fn make_trait_castable(args: TokenStream1, input: TokenStream1) -> TokenStream1 {
72  proc_macro_logger_default_setup();
73
74  let crate_path = CargoManifest::shared().resolve_crate_path("trait-cast", &[]);
75
76  // Convert the input to a TokenStream2
77  let input = TokenStream2::from(input);
78
79  let trait_cast_targets = parse_macro_input!(args as TraitCastTargets);
80
81  // First, try to parse the input as a struct
82  let input_struct = syn::parse2::<ItemStruct>(input.clone());
83  let mut source_ident = input_struct.map(|item_struct| item_struct.ident);
84
85  // Maybe it's an enum
86  if source_ident.is_err() {
87    let input_enum = syn::parse2::<ItemEnum>(input.clone());
88    source_ident = input_enum.map(|item_enum| item_enum.ident);
89  }
90
91  if let Err(err) = source_ident {
92    let mut custom_error_message = Error::new(err.span(), "Expected a struct or enum");
93    custom_error_message.combine(err);
94    return custom_error_message.to_compile_error().into();
95  }
96
97  let source_ident = source_ident.unwrap();
98
99  TokenStream1::from(quote!(
100    #input
101    #crate_path::make_trait_castable_decl! {
102    #source_ident => (#trait_cast_targets)
103  }))
104}