trait_cast_macros/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//! Proc-macro automating the implementation of `trait_cast::TraitcastableAny`.
//!
//! See `make_trait_castable` for more details.

use proc_macro::TokenStream as TokenStream1;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{
  Error, ItemEnum, ItemStruct, Token, TypePath,
  parse::{self, Parse, ParseStream},
  parse_macro_input,
  punctuated::Punctuated,
};

/// Parses a list of `TypePath`s separated by commas.
struct TraitCastTargets {
  targets: Vec<TypePath>,
}

impl Parse for TraitCastTargets {
  fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
    let targets: Vec<TypePath> = Punctuated::<TypePath, Token![,]>::parse_terminated(input)?
      .into_iter()
      .collect();
    Ok(Self { targets })
  }
}

impl quote::ToTokens for TraitCastTargets {
  fn to_tokens(&self, tokens: &mut TokenStream2) {
    let vars = &self.targets;
    tokens.extend(quote!(#(#vars),*));
  }
}

/// Attribute macro implementing `TraitcastableAny` for a struct, enum or union.
///
/// Use the arguments to specify all possible target Traits for witch trait objects are
/// supposed to be downcastable from a dyn `TraitcastableAny`.
///
/// Example:
/// ```no_build
///   extern crate trait_cast_rs;
///
///   use trait_cast::{make_trait_castable, TraitcastTarget, TraitcastTo, TraitcastableAny};
///
///
///   #[make_trait_castable(Print)]
///   struct Source(i32);
///
///   trait Print {
///     fn print(&self);
///   }
///   impl Print for Source {
///     fn print(&self) {
///       println!("{}", self.0)
///     }
///   }
///
///   fn main() {
///     let source = Box::new(Source(5));
///     let castable: Box<dyn TraitcastableAny> = source;
///     let x: &dyn Print = castable.downcast_ref().unwrap();
///     x.print();
///   }
/// ```
#[proc_macro_attribute]
pub fn make_trait_castable(args: TokenStream1, input: TokenStream1) -> TokenStream1 {
  // Convert the input to a TokenStream2
  let input = TokenStream2::from(input);

  let trait_cast_targets = parse_macro_input!(args as TraitCastTargets);

  // First, try to parse the input as a struct
  let input_struct = syn::parse2::<ItemStruct>(input.clone());
  let mut source_ident = input_struct.map(|item_struct| item_struct.ident);

  // Maybe it's an enum
  if source_ident.is_err() {
    let input_enum = syn::parse2::<ItemEnum>(input.clone());
    source_ident = input_enum.map(|item_enum| item_enum.ident);
  }

  if let Err(err) = source_ident {
    let mut custom_error_message = Error::new(err.span(), "Expected a struct or enum");
    custom_error_message.combine(err);
    return custom_error_message.to_compile_error().into();
  }

  let source_ident = source_ident.unwrap();

  TokenStream1::from(quote!(
    #input
    ::trait_cast::make_trait_castable_decl! {
    #source_ident => (#trait_cast_targets)
  }))
}