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)
}))
}