extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::Parser;
use syn::spanned::Spanned;
use syn::*;
fn match_generic_type(ty: &Type, container: &str, containee: &Ident) -> bool {
if let Type::Path(pat) = ty {
if let Some(seg) = pat.path.segments.last() {
if seg.ident != container {
return false;
}
if let PathArguments::AngleBracketed(args) = &seg.arguments {
if let Some(GenericArgument::Type(Type::Path(arg))) = args.args.last() {
return Some(containee) == arg.path.get_ident();
}
}
}
}
false
}
fn is_pin<'a>(ty: &'a Type) -> Option<&'a Type> {
if let Type::Path(pat) = ty {
if let Some(seg) = pat.path.segments.last() {
if seg.ident != "Pin" {
return None;
}
if let PathArguments::AngleBracketed(args) = &seg.arguments {
if let Some(GenericArgument::Type(t)) = args.args.last() {
return Some(t);
}
}
}
}
None
}
#[proc_macro_attribute]
pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(item as ItemStruct);
let fields = if let Fields::Named(fields) = &mut input.fields {
fields
} else {
return Error::new(
proc_macro2::Span::call_site(),
"Only suported with structure with named fields",
)
.to_compile_error()
.into();
};
let vtable_name = input.ident.to_string();
if !vtable_name.ends_with("VTable") {
return Error::new(input.ident.span(), "The structure does not ends in 'VTable'")
.to_compile_error()
.into();
}
let trait_name = Ident::new(&vtable_name[..vtable_name.len() - 6], input.ident.span());
let to_name = quote::format_ident!("{}TO", trait_name);
let module_name = quote::format_ident!("{}_vtable_mod", trait_name);
let static_vtable_macro_name = quote::format_ident!("{}_static", vtable_name);
let vtable_name = input.ident.clone();
let mut drop_impl = None;
let mut generated_trait = ItemTrait {
attrs: input
.attrs
.iter()
.filter(|a| a.path.get_ident().as_ref().map(|i| *i == "doc").unwrap_or(false))
.cloned()
.collect(),
vis: Visibility::Public(VisPublic { pub_token: Default::default() }),
unsafety: None,
auto_token: None,
trait_token: Default::default(),
ident: trait_name.clone(),
generics: Generics::default(),
colon_token: None,
supertraits: Default::default(),
brace_token: Default::default(),
items: Default::default(),
};
let additional_doc =
format!("\nNote: Was generated from the `#[vtable]` macro on `{}`", vtable_name);
generated_trait
.attrs
.append(&mut Attribute::parse_outer.parse2(quote!(#[doc = #additional_doc])).unwrap());
let mut generated_trait_assoc_const = None;
let mut generated_to_fn_trait = vec![];
let mut generated_type_assoc_fn = vec![];
let mut vtable_ctor = vec![];
for field in &mut fields.named {
field.vis = Visibility::Public(VisPublic { pub_token: Default::default() });
let ident = field.ident.as_ref().unwrap();
let mut some = None;
let func_ty = if let Type::BareFn(f) = &mut field.ty {
Some(f)
} else if let Type::Path(pat) = &mut field.ty {
pat.path.segments.last_mut().and_then(|seg| {
if seg.ident == "Option" {
some = Some(quote!(Some));
if let PathArguments::AngleBracketed(args) = &mut seg.arguments {
if let Some(GenericArgument::Type(Type::BareFn(f))) = args.args.first_mut()
{
Some(f)
} else {
None
}
} else {
None
}
} else {
None
}
})
} else {
None
};
if let Some(f) = func_ty {
let mut sig = Signature {
constness: None,
asyncness: None,
unsafety: f.unsafety,
abi: None,
fn_token: f.fn_token.clone(),
ident: ident.clone(),
generics: Default::default(),
paren_token: f.paren_token.clone(),
inputs: Default::default(),
variadic: None,
output: f.output.clone(),
};
let mut sig_extern = sig.clone();
sig_extern.abi = Some(parse_str("extern \"C\"").unwrap());
sig_extern.generics = parse_str(&format!("<T : {}>", trait_name)).unwrap();
let mut call_code = None;
let mut self_call = None;
let mut forward_code = None;
#[derive(Default)]
struct SelfInfo {}
let mut has_self = false;
for param in &f.inputs {
let arg_name = quote::format_ident!("_{}", sig_extern.inputs.len());
let typed_arg = FnArg::Typed(PatType {
attrs: param.attrs.clone(),
pat: Box::new(Pat::Path(syn::PatPath {
attrs: Default::default(),
qself: None,
path: arg_name.clone().into(),
})),
colon_token: Default::default(),
ty: Box::new(param.ty.clone()),
});
sig_extern.inputs.push(typed_arg.clone());
if let Type::Ptr(TypePtr { mutability, elem, .. })
| Type::Reference(TypeReference { mutability, elem, .. }) = ¶m.ty
{
if let Type::Path(p) = &**elem {
if let Some(pointer_to) = p.path.get_ident() {
if pointer_to == &vtable_name {
if mutability.is_some() {
return Error::new(p.span(), "VTable cannot be mutable")
.to_compile_error()
.into();
}
if call_code.is_some() || sig.inputs.len() > 0 {
return Error::new(
p.span(),
"VTable pointer need to be the first",
)
.to_compile_error()
.into();
}
call_code = Some(quote!(vtable as _,));
continue;
}
}
}
}
let (is_pin, self_ty) = match is_pin(¶m.ty) {
Some(t) => (true, t),
None => (false, ¶m.ty),
};
if let (true, mutability) = if match_generic_type(self_ty, "VRef", &vtable_name) {
(true, None)
} else if match_generic_type(self_ty, "VRefMut", &vtable_name) {
(true, Some(Default::default()))
} else {
(false, None)
} {
if sig.inputs.len() > 0 {
return Error::new(param.span(), "Self pointer need to be the first")
.to_compile_error()
.into();
}
let const_or_mut = mutability.map_or_else(|| quote!(const), |x| quote!(#x));
has_self = true;
if !is_pin {
sig.inputs.push(FnArg::Receiver(Receiver {
attrs: param.attrs.clone(),
reference: Some(Default::default()),
mutability,
self_token: Default::default(),
}));
call_code =
Some(quote!(#call_code <#self_ty>::from_raw(self.vtable, self.ptr),));
self_call =
Some(quote!(&#mutability (*(#arg_name.as_ptr() as *#const_or_mut T)),));
} else {
sig.inputs.push(FnArg::Typed(PatType {
attrs: param.attrs.clone(),
pat: Box::new(parse2(quote!(self)).unwrap()),
colon_token: Default::default(),
ty: parse2(quote!(core::pin::Pin<& #mutability Self>)).unwrap(),
}));
call_code = Some(
quote!(#call_code core::pin::Pin::new_unchecked(<#self_ty>::from_raw(self.vtable, self.ptr)),),
);
self_call = Some(
quote!(core::pin::Pin::new_unchecked(&#mutability (*(#arg_name.as_ptr() as *#const_or_mut T))),),
);
}
continue;
}
sig.inputs.push(typed_arg);
call_code = Some(quote!(#call_code #arg_name,));
forward_code = Some(quote!(#forward_code #arg_name,));
}
f.unsafety = Some(Default::default());
if let Some(a) = &f.abi {
if !a.name.as_ref().map(|s| s.value() == "C").unwrap_or(false) {
return Error::new(a.span(), "invalid ABI").to_compile_error().into();
}
} else {
f.abi = sig_extern.abi.clone();
}
let mut wrap_trait_call = None;
if !has_self {
sig.generics = Generics {
where_clause: Some(parse_str("where Self : Sized").unwrap()),
..Default::default()
};
if let ReturnType::Type(_, ret) = &f.output {
if match_generic_type(&**ret, "VBox", &vtable_name) {
sig.output = parse_str("-> Self").unwrap();
wrap_trait_call = Some(quote! {
let wrap_trait_call = |x| unsafe {
let ptr = core::ptr::NonNull::from(Box::leak(Box::new(x)));
VBox::<#vtable_name>::from_raw(vtable, ptr.cast())
};
wrap_trait_call
});
}
}
}
if ident == "drop" {
vtable_ctor.push(quote!(#ident: {
#sig_extern {
unsafe {
Box::from_raw((#self_call).0 as *mut _);
}
}
#ident::<T>
},));
drop_impl = Some(quote! {
impl VTableMetaDrop for #vtable_name {
unsafe fn drop(ptr: *mut #to_name) {
unsafe {
let ptr = &*ptr;
(ptr.vtable.as_ref().#ident)(VRefMut::from_raw(ptr.vtable, ptr.ptr)) }
}
fn new_box<X: HasStaticVTable<#vtable_name>>(value: X) -> VBox<#vtable_name> {
let ptr = core::ptr::NonNull::from(Box::leak(Box::new(value)));
unsafe { VBox::from_raw(core::ptr::NonNull::from(X::static_vtable()), ptr.cast()) }
}
}
});
continue;
}
generated_trait.items.push(TraitItem::Method(TraitItemMethod {
attrs: field.attrs.clone(),
sig: sig.clone(),
default: None,
semi_token: Some(Default::default()),
}));
generated_to_fn_trait.push(ImplItemMethod {
attrs: field.attrs.clone(),
vis: Visibility::Public(VisPublic { pub_token: Default::default() }),
defaultness: None,
sig: sig.clone(),
block: parse2(if has_self {
quote!({
unsafe {
let vtable = self.vtable.as_ref();
if let #some(func) = vtable.#ident {
func (#call_code)
} else {
panic!("Called a not-implemented method")
}
}
})
} else {
quote!({ panic!("Calling Sized method on a Trait Object") })
})
.unwrap(),
});
if !has_self {
sig.inputs.insert(
0,
FnArg::Receiver(Receiver {
attrs: Default::default(),
reference: Some(Default::default()),
mutability: None,
self_token: Default::default(),
}),
);
sig.output = sig_extern.output.clone();
generated_type_assoc_fn.push(ImplItemMethod {
attrs: field.attrs.clone(),
vis: generated_trait.vis.clone(),
defaultness: None,
sig,
block: parse2(quote!({
let vtable = self;
unsafe { (self.#ident)(#call_code) }
}))
.unwrap(),
});
vtable_ctor.push(quote!(#ident: {
#sig_extern {
#[allow(unused)]
let vtable = unsafe { core::ptr::NonNull::from(&*_0) };
#wrap_trait_call(T::#ident(#self_call #forward_code))
}
#some(#ident::<T>)
},));
} else {
vtable_ctor.push(quote!(#ident: {
#sig_extern {
unsafe { T::#ident(#self_call #forward_code) }
}
#ident::<T>
},));
}
} else {
let generated_trait_assoc_const =
generated_trait_assoc_const.get_or_insert_with(|| ItemTrait {
attrs: Attribute::parse_outer.parse_str(&format!(
"/** Trait containing the associated constant relative to the the trait {}.\n{} */",
trait_name, additional_doc
)).unwrap(),
ident: quote::format_ident!("{}Consts", trait_name),
items: vec![],
..generated_trait.clone()
});
let const_type = if let Some(o) = field
.attrs
.iter()
.position(|a| a.path.get_ident().map(|a| a == "field_offset").unwrap_or(false))
{
let a = field.attrs.remove(o);
let member_type = match parse2::<Type>(a.tokens) {
Err(e) => return e.to_compile_error().into(),
Ok(ty) => ty,
};
match &field.ty {
Type::Path(p) if p.path.get_ident().map(|i| i == "usize").unwrap_or(false) => {}
ty @ _ => {
return Error::new(
ty.span(),
"The type of an #[field_offset] member in the vtable must be 'usize'",
)
.to_compile_error()
.into()
}
}
if generated_trait_assoc_const.supertraits.is_empty() {
generated_trait_assoc_const.colon_token = Some(Default::default());
generated_trait_assoc_const.supertraits.push(parse2(quote!(Sized)).unwrap());
}
let offset_type =
parse2::<Type>(quote!(vtable::FieldOffset<Self, #member_type>)).unwrap();
vtable_ctor.push(quote!(#ident: T::#ident.get_byte_offset(),));
let attrs = &field.attrs;
let vis = &field.vis;
generated_to_fn_trait.push(
parse2(quote! {
#(#attrs)*
#vis fn #ident(&self) -> &#member_type {
unsafe {
&*(self.ptr.as_ptr().add(self.vtable.as_ref().#ident) as *const #member_type)
}
}
})
.unwrap(),
);
let ident_mut = quote::format_ident!("{}_mut", ident);
generated_to_fn_trait.push(
parse2(quote! {
#(#attrs)*
#vis fn #ident_mut(&mut self) -> &mut #member_type {
unsafe {
&mut *(self.ptr.as_ptr().add(self.vtable.as_ref().#ident) as *mut #member_type)
}
}
})
.unwrap(),
);
offset_type
} else {
vtable_ctor.push(quote!(#ident: T::#ident,));
field.ty.clone()
};
generated_trait_assoc_const.items.push(TraitItem::Const(TraitItemConst {
attrs: field.attrs.clone(),
const_token: Default::default(),
ident: ident.clone(),
colon_token: Default::default(),
ty: const_type,
default: None,
semi_token: Default::default(),
}));
};
}
let vis = input.vis;
input.vis = Visibility::Public(VisPublic { pub_token: Default::default() });
let new_trait_extra = generated_trait_assoc_const.as_ref().map(|x| {
let i = &x.ident;
quote!(+ #i)
});
let static_vtable_macro_doc = format!(
r"Instantiate a static {vtable} for a given type and implements `vtable::HasStaticVTable<{vtable}>` for it.
```ignore
// The preview above is misleading because of rust-lang/rust#45939, so it is reproctuced bellow
macro_rules! {macro} {{
($(#[$meta:meta])* $vis:vis static $ident:ident for $ty:ty) => {{ ... }}
}}
```
Given a type `MyType` that implements the trait `{trait} {trait_extra}`,
create a static variable of type {vtable},
and implements HasStaticVTable for it.
```ignore
struct Foo {{ ... }}
impl {trait} for Foo {{ ... }}
{macro}!(static FOO_VTABLE for Foo);
// now VBox::new can be called
let vbox = VBox::new(Foo{{ ... }});
```
{extra}",
vtable = vtable_name,
trait = trait_name,
trait_extra = new_trait_extra.as_ref().map(|x| x.to_string()).unwrap_or_default(),
macro = static_vtable_macro_name,
extra = additional_doc,
);
let result = quote!(
#[allow(non_snake_case)]
#[macro_use]
mod #module_name {
#![allow(unused_parens)]
#[allow(unused)]
use super::*;
use ::vtable::*;
use ::std::boxed::Box;
#input
impl #vtable_name {
pub fn new<T: #trait_name #new_trait_extra>() -> Self {
Self {
#(#vtable_ctor)*
}
}
#(#generated_type_assoc_fn)*
}
#generated_trait
#generated_trait_assoc_const
#[doc(hidden)]
#[repr(C)]
pub struct #to_name {
vtable: core::ptr::NonNull<#vtable_name>,
ptr: core::ptr::NonNull<u8>,
}
impl #to_name {
#(#generated_to_fn_trait)*
pub fn get_vtable(&self) -> &#vtable_name {
unsafe { self.vtable.as_ref() }
}
pub fn as_ptr(&self) -> *const u8 {
self.ptr.as_ptr()
}
}
unsafe impl VTableMeta for #vtable_name {
type VTable = #vtable_name;
type Target = #to_name;
}
#drop_impl
}
#[doc(inline)]
#[macro_use]
#vis use #module_name::*;
#[macro_export]
#[doc = #static_vtable_macro_doc]
macro_rules! #static_vtable_macro_name {
($(#[$meta:meta])* $vis:vis static $ident:ident for $ty:ty) => {
$(#[$meta])* $vis static $ident : #vtable_name = {
use vtable::*;
type T = $ty;
#vtable_name {
#(#vtable_ctor)*
}
};
unsafe impl vtable::HasStaticVTable<#vtable_name> for $ty {
fn static_vtable() -> &'static #vtable_name {
&$ident
}
}
}
}
);
result.into()
}