Rewrote impl_cast as a proc_macro to make it easier to work with
This commit is contained in:
@@ -3,7 +3,13 @@ name = "impl_cast"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
paste = "1.0.10"
|
||||
syn = { version = "2.0", features = ["extra-traits", "full"] }
|
||||
quote = "1.0"
|
||||
|
||||
[features]
|
||||
debug = [
|
||||
] # If enabled it will add std::fmt::Debug as a trait bound to device_traits
|
||||
|
||||
@@ -1,47 +1,161 @@
|
||||
pub extern crate paste;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::{parse::Parse, parse_macro_input, Ident, ItemTrait, Path, Token, TypeParamBound};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_setup {
|
||||
() => {
|
||||
pub trait As<T: ?Sized> {
|
||||
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>;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_cast {
|
||||
($base:ident, $trait:ident) => {
|
||||
$crate::paste::paste! {
|
||||
impl<T: $base + $trait> As<dyn $trait> for T {
|
||||
fn consume(self: Box<Self>) -> Option<Box<dyn $trait>> {
|
||||
Some(self)
|
||||
}
|
||||
traits.iter().for_each(|device_trait| {
|
||||
interface.supertraits.push(TypeParamBound::Verbatim(quote! {
|
||||
#name<dyn #device_trait>
|
||||
}));
|
||||
});
|
||||
|
||||
fn cast(&self) -> Option<&dyn $trait> {
|
||||
Some(self)
|
||||
}
|
||||
let interface_ident = format_ident!("{}", interface.ident);
|
||||
let impls = traits
|
||||
.iter()
|
||||
.map(|device_trait| {
|
||||
quote! {
|
||||
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_mut(&mut self) -> Option<&mut dyn $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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> #name<dyn #device_trait> for T
|
||||
where
|
||||
T: #interface_ident + ?Sized,
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: $base> As<dyn $trait> for T {
|
||||
default fn consume(self: Box<Self>) -> Option<Box<dyn $trait>> {
|
||||
None
|
||||
}
|
||||
|
||||
default fn cast(&self) -> Option<&dyn $trait> {
|
||||
None
|
||||
}
|
||||
|
||||
default fn cast_mut(&mut self) -> Option<&mut dyn $trait> {
|
||||
None
|
||||
}
|
||||
})
|
||||
.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 + 'static
|
||||
}));
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
interface.supertraits.push(TypeParamBound::Verbatim(quote! {
|
||||
::std::fmt::Debug
|
||||
}));
|
||||
|
||||
interface.into_token_stream().into()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user