extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Expr, Fields, Lit, Type};
#[proc_macro_derive(Interface, attributes(polybox))]
pub fn derive_interface(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let enum_name = &input.ident;
let mut base_path: syn::Path = syn::parse_str("::polybox").unwrap();
for attr in &input.attrs {
if attr.path().is_ident("polybox") {
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("crate") {
let value = meta.value()?;
let expr: Expr = value.parse()?;
if let Expr::Lit(syn::ExprLit {
lit: Lit::Str(lit_str),
..
}) = expr
{
if let Ok(parsed_path) = syn::parse_str::<syn::Path>(&lit_str.value()) {
base_path = parsed_path;
}
}
}
Ok(())
});
}
}
let variants = match &input.data {
Data::Enum(data_enum) => &data_enum.variants,
_ => panic!("Interface derive can only be used on enums"),
};
let mut inner_types = Vec::new();
let mut try_from_matches = Vec::new();
let mut try_into_matches = Vec::new();
let mut into_matches = Vec::new();
let mut from_impls = Vec::new();
for variant in variants {
let variant_name = &variant.ident;
match &variant.fields {
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
let field_type = &fields.unnamed[0].ty;
let inner_type = extract_inner_type(field_type)
.expect("Interface variants must be of type Payload<T>");
inner_types.push(inner_type);
try_from_matches.push(quote! {
let payload = match payload.downcast::<#inner_type>() {
Ok(payload) => return Ok(Self::#variant_name(payload)),
Err(payload) => payload,
};
});
try_into_matches.push(quote! {
if id == std::any::TypeId::of::<#inner_type>() {
if let Self::#variant_name(payload) = self {
let converted = unsafe {
std::mem::transmute_copy::<#base_path::Payload<#inner_type>, #base_path::Payload<I>>(&payload)
};
std::mem::forget(payload);
return Ok(converted);
}
}
});
into_matches.push(quote! {
Self::#variant_name(payload) => #base_path::BoxedPayload::new::<#inner_type>(payload),
});
from_impls.push(quote! {
impl #base_path::FromPayload<#inner_type> for #enum_name {
fn from_payload(payload: #base_path::Payload<#inner_type>) -> Self {
Self::#variant_name(payload)
}
}
impl #base_path::TryIntoPayload<#inner_type> for #enum_name {
fn try_into_payload(self) -> Result<#base_path::Payload<#inner_type>, Self> {
if let #enum_name::#variant_name(payload) = self {
Ok(payload)
} else {
Err(self)
}
}
}
});
}
_ => panic!("Interface derive only supports variants with a single unnamed field, e.g., A(Payload<T>)"),
}
}
let expanded = quote! {
impl #base_path::Interface for #enum_name {
fn try_from_boxed_payload(payload: #base_path::BoxedPayload) -> Result<Self, #base_path::BoxedPayload> {
#(#try_from_matches)*
Err(payload)
}
fn into_boxed_payload(self) -> #base_path::BoxedPayload {
match self {
#(#into_matches)*
}
}
}
impl #base_path::Message for #enum_name {
type Kind = #base_path::FireAndForget;
}
impl #base_path::AsSet for #enum_name {
type Set = #base_path::Set![#(#inner_types),*];
}
impl #base_path::TryIntoPayload<#enum_name> for #enum_name {
fn try_into_payload(self) -> Result<#base_path::Payload<#enum_name>, Self> {
Ok(self)
}
}
impl #base_path::FromPayload<#enum_name> for #enum_name {
fn from_payload(payload: #base_path::Payload<#enum_name>) -> Self {
payload
}
}
#(#from_impls)*
};
TokenStream::from(expanded)
}
fn extract_inner_type(ty: &Type) -> Option<&Type> {
if let Type::Path(type_path) = ty {
let segment = type_path.path.segments.last()?;
if segment.ident == "Payload" {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
return Some(inner_ty);
}
}
}
}
None
}
#[proc_macro_derive(Message, attributes(polybox, msg))]
pub fn derive_invocation(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let polybox_attr = &input
.attrs
.iter()
.find(|attr| attr.path().is_ident("polybox"));
let base_path = if let Some(attr) = polybox_attr {
let mut base_path: syn::Path = syn::parse_str("::polybox").unwrap();
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("crate") {
let value = meta.value()?;
let expr: Expr = value.parse()?;
if let Expr::Lit(syn::ExprLit {
lit: Lit::Str(lit_str),
..
}) = expr
{
if let Ok(parsed_path) = syn::parse_str::<syn::Path>(&lit_str.value()) {
base_path = parsed_path;
}
}
}
Ok(())
});
base_path
} else {
syn::parse_str("::polybox").unwrap()
};
let invoke_attr = &input.attrs.iter().find(|attr| attr.path().is_ident("msg"));
let kind_type = if let Some(attr) = invoke_attr {
let mut kind_type = quote!(#base_path::FireAndForget);
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("reply") {
let value = meta.value()?;
if let Ok(parsed_type) = value.parse::<Type>() {
kind_type = quote! {
#base_path::Request<#parsed_type>
}
}
} else {
panic!("Only `reply` is expected")
}
Ok(())
});
kind_type
} else {
quote!(#base_path::FireAndForget)
};
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let expanded = quote! {
impl #impl_generics #base_path::Message for #name #ty_generics #where_clause {
type Kind = #kind_type;
}
};
TokenStream::from(expanded)
}