#![cfg_attr(docs_rs_workaround, feature(proc_macro))]
#![allow(clippy::needless_doctest_main)]
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::parse::{Nothing, Parse, ParseStream, Result};
use syn::{
braced, parenthesized, parse_macro_input, Attribute, Generics, Ident, Token, Type, Visibility,
};
struct FnArg {
ident: Ident,
ty: Type,
}
impl Parse for FnArg {
fn parse(input: ParseStream) -> Result<Self> {
let ident = input.parse()?;
input.parse::<Token![:]>()?;
let ty = input.parse()?;
Ok(FnArg { ident, ty })
}
}
struct HackFn {
impl_attrs: Vec<Attribute>,
generics: Generics,
self_ty: Type,
fn_attrs: Vec<Attribute>,
vis: Visibility,
method: Ident,
args: Vec<FnArg>,
ret_ty: Option<Type>,
body: TokenStream2,
}
impl Parse for HackFn {
fn parse(input: ParseStream) -> Result<Self> {
let impl_attrs = input.call(Attribute::parse_outer)?;
input.parse::<Token![impl]>()?;
let mut generics: Generics = input.parse()?;
let self_ty: Type = input.parse()?;
generics.where_clause = input.parse()?;
let impl_block;
braced!(impl_block in input);
let fn_attrs = impl_block.call(Attribute::parse_outer)?;
let vis: Visibility = impl_block.parse()?;
impl_block.parse::<Token![fn]>()?;
let method: Ident = impl_block.parse()?;
let argument_list;
parenthesized!(argument_list in impl_block);
argument_list.parse::<Token![&]>()?;
argument_list.parse::<Token![self]>()?;
let mut args = Vec::new();
while !argument_list.is_empty() {
argument_list.parse::<Token![,]>()?;
if argument_list.is_empty() {
break;
}
args.push(argument_list.parse::<FnArg>()?);
}
let ret_ty = if impl_block.parse::<Option<Token![->]>>()?.is_some() {
Some(impl_block.parse::<Type>()?)
} else {
None
};
let body;
braced!(body in impl_block);
let body: TokenStream2 = body.parse()?;
Ok(HackFn {
impl_attrs,
generics,
self_ty,
fn_attrs,
vis,
method,
args,
ret_ty,
body,
})
}
}
#[proc_macro_attribute]
pub fn hackfn(args: TokenStream, input: TokenStream) -> TokenStream {
parse_macro_input!(args as Nothing);
let HackFn {
impl_attrs,
generics,
self_ty,
fn_attrs,
vis,
method,
args,
ret_ty,
body,
} = parse_macro_input!(input as HackFn);
let impl_attrs = &impl_attrs;
let where_clause = &generics.where_clause;
let arg_names = args.iter().map(|fn_arg| &fn_arg.ident).collect::<Vec<_>>();
let arg_types = args.iter().map(|fn_arg| &fn_arg.ty).collect::<Vec<_>>();
let ret_ty = ret_ty.map(|ret| quote!(-> #ret));
let target = quote! {
dyn ::std::ops::Fn(#(#arg_types),*) #ret_ty
};
let expanded = quote! {
#(#impl_attrs)*
impl #generics #self_ty #where_clause {
#(#fn_attrs)*
#vis fn #method(&self #(, #arg_names: #arg_types)*) #ret_ty {
#body
}
}
#(#impl_attrs)*
impl #generics ::std::ops::Deref for #self_ty #where_clause {
type Target = #target;
#[allow(clippy::forget_non_drop, clippy::transmute_ptr_to_ptr)]
fn deref(&self) -> &Self::Target {
let __this = ::std::mem::MaybeUninit::<Self>::uninit();
let __closure = move |#(#arg_names : #arg_types),*| #ret_ty {
Self::#method(
unsafe { &*__this.as_ptr() }
#(, #arg_names)*
)
};
let __layout_of_closure = ::std::alloc::Layout::for_value(&__closure);
fn __second<'__a, __T>(__first: &__T, __second: &'__a __T) -> &'__a __T {
__second
}
let __ret = __second(&__closure, unsafe { &*(self as *const Self as *const _) });
::std::mem::forget(__closure);
assert_eq!(__layout_of_closure, ::std::alloc::Layout::new::<Self>());
unsafe { ::std::mem::transmute(__ret as &#target) }
}
}
};
TokenStream::from(expanded)
}