automation_rs/impl_cast/src/lib.rs
Dreaded_X 33fcb95dfa
All checks were successful
continuous-integration/drone/push Build is passing
Improved impl_cast
2023-04-12 05:38:15 +02:00

165 lines
4.8 KiB
Rust

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<Path>,
}
impl Parse for Attr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut traits = Vec::new();
let name = input.parse::<Ident>()?;
input.parse::<Token![:]>()?;
loop {
let ty = input.parse()?;
traits.push(ty);
if input.is_empty() {
break;
}
input.parse::<Token![+]>()?;
}
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<dyn Device> = Box::new(ExampleDevice {});
///
/// // Cast to the OnOff trait, which is implemented
/// let as_on_off = As::<dyn OnOff>::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::<dyn Brightness>::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::<dyn OnOff>::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<T: ?Sized + 'static> {
fn consume(self: Box<Self>) -> Option<Box<T>>;
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<dyn #device_trait>
}));
});
let interface_ident = format_ident!("{}", interface.ident);
let impls = traits
.iter()
.map(|device_trait| {
quote! {
// Default impl
impl<T> #name<dyn #device_trait> for T
where
T: #interface_ident + 'static,
{
default fn consume(self: Box<Self>) -> Option<Box<dyn #device_trait>> {
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<T> #name<dyn #device_trait> for T
where
T: #interface_ident + #device_trait + 'static,
{
fn consume(self: Box<Self>) -> Option<Box<dyn #device_trait>> {
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()
}