use super::{marker_traits::*, options::*, repr::*, trait_object::*, vtable::*};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
use std::convert::TryFrom;
use syn::{
parse::Parser,
punctuated::Punctuated,
Abi,
Attribute,
ItemTrait,
Path,
PathArguments,
PathSegment,
TraitBound,
Visibility,
};
pub fn attribute_main(attr: TokenStream, item: TokenStream) -> Result<TokenStream, syn::Error> {
let options = Punctuated::parse_terminated.parse2(attr)?;
let config = Config::from(options);
let trait_def = syn::parse2::<ItemTrait>(item)?;
if !trait_def.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
trait_def.generics.params,
"generic traits are not yet supported by #[thin_trait_object]",
));
}
let vtable_name = config
.vtable_name
.unwrap_or_else(|| format_ident!("{}Vtable", &trait_def.ident));
let trait_object_name = config
.trait_object_name
.unwrap_or_else(|| format_ident!("Boxed{}", &trait_def.ident));
let vtable_items = trait_def
.items
.into_iter()
.map(VtableItem::try_from)
.collect::<Result<Vec<_>, _>>()?;
let (markers, lifetime_bounds) = supertraits_to_markers_and_lifetimes(
trait_def.supertraits,
config.marker_traits.map_or(
Box::new(default_marker_filter) as Box<dyn FnMut(_) -> _>,
|markers| {
Box::new(move |bound: TraitBound| {
for marker in &markers {
if bound.path == marker.path {
return Some((bound, marker.unsafety.is_some()));
}
}
None
})
},
),
);
let has_static_bound = lifetime_bounds
.iter()
.any(|lifetime| lifetime.ident == "static");
let mut stash = StageStash {
trait_name: trait_def.ident.clone(),
vtable_name,
trait_object_name,
repr_name: repr_name_from_trait_name(trait_def.ident.clone()),
vtable_items,
};
let trait_visibility = &trait_def.vis;
let vtable = generate_vtable(
&mut stash,
config
.vtable_visibility
.unwrap_or_else(|| trait_visibility.clone()),
config.vtable_attributes,
config.drop_abi.as_ref(),
config.store_layout,
);
let repr = generate_repr(
&mut stash,
config.inline_vtable,
path_to_box(),
config.drop_abi.as_ref(),
config.store_layout,
);
let trait_object = generate_trait_object(
&mut stash,
config
.trait_object_visibility
.unwrap_or_else(|| trait_visibility.clone()),
config.inline_vtable,
has_static_bound,
&config.trait_object_attributes,
&markers,
)?;
let output = quote! {
#vtable #repr #trait_object
};
Ok(output)
}
struct Config {
vtable_attributes: Vec<Attribute>,
vtable_visibility: Option<Visibility>,
vtable_name: Option<Ident>,
inline_vtable: bool,
trait_object_attributes: Vec<Attribute>,
trait_object_visibility: Option<Visibility>,
trait_object_name: Option<Ident>,
drop_abi: Option<Abi>,
marker_traits: Option<Vec<MarkerTrait>>,
store_layout: bool,
}
impl From<AttrOptions> for Config {
fn from(options: AttrOptions) -> Self {
let mut config = Self::default();
for option in options {
match option {
AttrOption::Vtable { additions, .. } => {
config.vtable_attributes = additions.attributes;
config.vtable_visibility = Some(additions.visibility);
config.vtable_name = Some(additions.name);
}
AttrOption::InlineVtable { val, .. } => {
config.inline_vtable = val.value;
}
AttrOption::TraitObject { additions, .. } => {
config.trait_object_attributes = additions.attributes;
config.trait_object_visibility = Some(additions.visibility);
config.trait_object_name = Some(additions.name);
}
AttrOption::DropAbi { abi, .. } => {
config.drop_abi = Some(Abi {
extern_token: Default::default(),
name: Some(abi),
})
}
AttrOption::MarkerTraits { marker_traits, .. } => {
config.marker_traits = Some(marker_traits.into_iter().collect());
}
AttrOption::StoreLayout { val, .. } => {
config.store_layout = val.value;
}
}
}
config
}
}
impl Default for Config {
fn default() -> Self {
Self {
vtable_attributes: Vec::new(),
vtable_visibility: None,
vtable_name: None,
inline_vtable: false,
trait_object_attributes: Vec::new(),
trait_object_visibility: None,
trait_object_name: None,
drop_abi: None,
marker_traits: None,
store_layout: false,
}
}
}
pub struct StageStash {
pub trait_name: Ident,
pub vtable_name: Ident,
pub repr_name: Ident,
pub trait_object_name: Ident,
pub vtable_items: Vec<VtableItem>,
}
fn path_to_box() -> Path {
let mut segments = Punctuated::new();
let mut push_segment = |name| {
segments.push(PathSegment {
ident: Ident::new(name, Span::call_site()),
arguments: PathArguments::None,
});
};
#[cfg(feature = "std")]
push_segment("std");
#[cfg(not(feature = "std"))]
push_segment("alloc");
push_segment("boxed");
push_segment("Box");
Path {
leading_colon: Some(Default::default()),
segments,
}
}