use proc_macro::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{parse::Parse, parse_macro_input, Ident, ItemTrait, Path, Token, TypeParamBound}; struct Attr { name: Ident, traits: Vec, } impl Parse for Attr { fn parse(input: syn::parse::ParseStream) -> syn::Result { let mut traits = Vec::new(); let name = input.parse::()?; input.parse::()?; loop { let ty = input.parse()?; traits.push(ty); if input.is_empty() { break; } input.parse::()?; } Ok(Attr { name, traits }) } } /// This macro enables optional trait bounds on a trait with an appropriate cast trait to convert /// to the optional traits /// # Example /// /// ``` /// #![feature(specialization)] /// /// // Create some traits /// #[impl_cast::device_trait] /// trait OnOff {} /// #[impl_cast::device_trait] /// trait Brightness {} /// /// // Create the main device trait /// #[impl_cast::device(As: OnOff + Brightness)] /// trait Device {} /// /// // Create an implementation /// struct ExampleDevice {} /// impl Device for ExampleDevice {} /// impl OnOff for ExampleDevice {} /// /// // Creates a boxed instance of the example device /// let example_device: Box = Box::new(ExampleDevice {}); /// /// // Cast to the OnOff trait, which is implemented /// let as_on_off = As::::cast(example_device.as_ref()); /// assert!(as_on_off.is_some()); /// /// // Cast to the Brightness trait, which is not implemented /// let as_on_off = As::::cast(example_device.as_ref()); /// assert!(as_on_off.is_none()); /// /// // Finally we are going to consume the example device into an instance of the OnOff trait /// let consumed = As::::consume(example_device); /// assert!(consumed.is_some()) /// ``` #[proc_macro_attribute] pub fn device(attr: TokenStream, item: TokenStream) -> TokenStream { let Attr { name, traits } = parse_macro_input!(attr); let mut interface: ItemTrait = parse_macro_input!(item); let prefix = quote! { pub trait #name { fn consume(self: Box) -> Option>; fn cast(&self) -> Option<&T>; fn cast_mut(&mut self) -> Option<&mut T>; } }; traits.iter().for_each(|device_trait| { interface.supertraits.push(TypeParamBound::Verbatim(quote! { #name })); }); let interface_ident = format_ident!("{}", interface.ident); let impls = traits .iter() .map(|device_trait| { quote! { // Default impl impl #name for T where T: #interface_ident + 'static, { default fn consume(self: Box) -> Option> { None } default fn cast(&self) -> Option<&(dyn #device_trait + 'static)> { None } default fn cast_mut(&mut self) -> Option<&mut (dyn #device_trait + 'static)> { None } } // Specialization, should not cause any unsoundness as we dispatch based on // #device_trait impl #name for T where T: #interface_ident + #device_trait + 'static, { fn consume(self: Box) -> Option> { Some(self) } fn cast(&self) -> Option<&(dyn #device_trait + 'static)> { Some(self) } fn cast_mut(&mut self) -> Option<&mut (dyn #device_trait + 'static)> { Some(self) } } } }) .fold(quote! {}, |acc, x| { quote! { // Not sure if this is the right way to do this #acc #x } }); let tokens = quote! { #interface #prefix #impls }; tokens.into() } // TODO: Not sure if this makes sense to have? /// This macro ensures that the device traits have the correct trait bounds #[proc_macro_attribute] pub fn device_trait(_attr: TokenStream, item: TokenStream) -> TokenStream { let mut interface: ItemTrait = parse_macro_input!(item); interface.supertraits.push(TypeParamBound::Verbatim(quote! { ::core::marker::Sync + ::core::marker::Send })); #[cfg(feature = "debug")] interface.supertraits.push(TypeParamBound::Verbatim(quote! { ::std::fmt::Debug })); interface.into_token_stream().into() }