extern crate alloc;
extern crate proc_macro;
mod attr;
mod trait_bounds;
mod utils;
use self::attr::{
Attributes,
CaptureDocsAttr,
CratePathAttr,
};
use proc_macro::TokenStream;
use proc_macro2::{
Span,
TokenStream as TokenStream2,
};
use quote::quote;
use syn::{
parse::{
Error,
Result,
},
parse_quote,
punctuated::Punctuated,
token::Comma,
visit_mut::VisitMut,
Data,
DataEnum,
DataStruct,
DeriveInput,
Field,
Fields,
Ident,
Lifetime,
};
#[proc_macro_derive(TypeInfo, attributes(scale_info, codec))]
pub fn type_info(input: TokenStream) -> TokenStream {
match generate(input.into()) {
Ok(output) => output.into(),
Err(err) => err.to_compile_error().into(),
}
}
fn generate(input: TokenStream2) -> Result<TokenStream2> {
let type_info_impl = TypeInfoImpl::parse(input)?;
let type_info_impl_toks = type_info_impl.expand()?;
Ok(quote! {
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
const _: () = {
#type_info_impl_toks;
};
})
}
struct TypeInfoImpl {
ast: DeriveInput,
attrs: Attributes,
}
impl TypeInfoImpl {
fn parse(input: TokenStream2) -> Result<Self> {
let ast: DeriveInput = syn::parse2(input)?;
let attrs = attr::Attributes::from_ast(&ast)?;
Ok(Self { ast, attrs })
}
fn expand(&self) -> Result<TokenStream2> {
let ident = &self.ast.ident;
let scale_info = crate_path(self.attrs.crate_path())?;
let where_clause = trait_bounds::make_where_clause(
&self.attrs,
ident,
&self.ast.generics,
&self.ast.data,
&scale_info,
)?;
let (impl_generics, ty_generics, _) = self.ast.generics.split_for_impl();
let type_params = self.ast.generics.type_params().map(|tp| {
let ty_ident = &tp.ident;
let ty = if self.attrs.skip_type_params().map_or(true, |skip| !skip.skip(tp)) {
quote! { ::core::option::Option::Some(#scale_info::meta_type::<#ty_ident>()) }
} else {
quote! { ::core::option::Option::None }
};
quote! {
#scale_info::TypeParameter::new(::core::stringify!(#ty_ident), #ty)
}
});
let build_type = match &self.ast.data {
Data::Struct(ref s) => self.generate_composite_type(s, &scale_info),
Data::Enum(ref e) => self.generate_variant_type(e, &scale_info),
Data::Union(_) => {
return Err(Error::new_spanned(&self.ast, "Unions not supported"))
}
};
let docs = self.generate_docs(&self.ast.attrs);
Ok(quote! {
impl #impl_generics #scale_info::TypeInfo for #ident #ty_generics #where_clause {
type Identity = Self;
fn type_info() -> #scale_info::Type {
#scale_info::Type::builder()
.path(#scale_info::Path::new(::core::stringify!(#ident), ::core::module_path!()))
.type_params(#scale_info::prelude::vec![ #( #type_params ),* ])
#docs
.#build_type
}
}
})
}
fn generate_composite_type(
&self,
data_struct: &DataStruct,
scale_info: &syn::Path,
) -> TokenStream2 {
let fields = match data_struct.fields {
Fields::Named(ref fs) => {
let fields = self.generate_fields(&fs.named);
quote! { named()#( #fields )* }
}
Fields::Unnamed(ref fs) => {
let fields = self.generate_fields(&fs.unnamed);
quote! { unnamed()#( #fields )* }
}
Fields::Unit => {
quote! {
unit()
}
}
};
quote! {
composite(#scale_info::build::Fields::#fields)
}
}
fn generate_fields(&self, fields: &Punctuated<Field, Comma>) -> Vec<TokenStream2> {
fields
.iter()
.filter(|f| !utils::should_skip(&f.attrs))
.map(|f| {
let (ty, ident) = (&f.ty, &f.ident);
struct StaticLifetimesReplace;
impl VisitMut for StaticLifetimesReplace {
fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
*lifetime = parse_quote!('static)
}
}
let mut ty = match ty {
syn::Type::Group(group) => (*group.elem).clone(),
_ => ty.clone(),
};
StaticLifetimesReplace.visit_type_mut(&mut ty);
let type_name = clean_type_string("e!(#ty).to_string());
let docs = self.generate_docs(&f.attrs);
let type_of_method = if utils::is_compact(f) {
quote!(compact)
} else {
quote!(ty)
};
let name = if let Some(ident) = ident {
quote!(.name(::core::stringify!(#ident)))
} else {
quote!()
};
quote!(
.field(|f| f
.#type_of_method::<#ty>()
#name
.type_name(#type_name)
#docs
)
)
})
.collect()
}
fn generate_variant_type(
&self,
data_enum: &DataEnum,
scale_info: &syn::Path,
) -> TokenStream2 {
let variants = &data_enum.variants;
let variants = variants
.into_iter()
.filter(|v| !utils::should_skip(&v.attrs))
.enumerate()
.map(|(i, v)| {
let ident = &v.ident;
let v_name = quote! {::core::stringify!(#ident) };
let docs = self.generate_docs(&v.attrs);
let index = utils::variant_index(v, i);
let fields = match v.fields {
Fields::Named(ref fs) => {
let fields = self.generate_fields(&fs.named);
Some(quote! {
.fields(#scale_info::build::Fields::named()
#( #fields )*
)
})
}
Fields::Unnamed(ref fs) => {
let fields = self.generate_fields(&fs.unnamed);
Some(quote! {
.fields(#scale_info::build::Fields::unnamed()
#( #fields )*
)
})
}
Fields::Unit => None,
};
quote! {
.variant(#v_name, |v|
v
.index(#index as ::core::primitive::u8)
#fields
#docs
)
}
});
quote! {
variant(
#scale_info::build::Variants::new()
#( #variants )*
)
}
}
fn generate_docs(&self, attrs: &[syn::Attribute]) -> Option<TokenStream2> {
let docs_builder_fn = match self.attrs.capture_docs() {
CaptureDocsAttr::Never => None, CaptureDocsAttr::Default => Some(quote!(docs)),
CaptureDocsAttr::Always => Some(quote!(docs_always)),
}?;
let docs = attrs
.iter()
.filter_map(|attr| {
if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() {
if meta.path.get_ident().map_or(false, |ident| ident == "doc") {
if let syn::Lit::Str(lit) = &meta.lit {
let lit_value = lit.value();
let stripped =
lit_value.strip_prefix(' ').unwrap_or(&lit_value);
let lit: syn::Lit = parse_quote!(#stripped);
Some(lit)
} else {
None
}
} else {
None
}
} else {
None
}
})
.collect::<Vec<_>>();
if !docs.is_empty() {
Some(quote! {
.#docs_builder_fn(&[ #( #docs ),* ])
})
} else {
None
}
}
}
fn crate_name_path(name: &str) -> Result<syn::Path> {
proc_macro_crate::crate_name(name)
.map(|crate_name| {
use proc_macro_crate::FoundCrate::*;
match crate_name {
Itself => Ident::new("self", Span::call_site()).into(),
Name(name) => {
let crate_ident = Ident::new(&name, Span::call_site());
parse_quote!( ::#crate_ident )
}
}
})
.map_err(|e| syn::Error::new(Span::call_site(), &e))
}
fn crate_path(crate_path_attr: Option<&CratePathAttr>) -> Result<syn::Path> {
crate_path_attr
.map(|path_attr| Ok(path_attr.path().clone()))
.unwrap_or_else(|| crate_name_path("scale-info"))
}
fn clean_type_string(input: &str) -> String {
input
.replace(" ::", "::")
.replace(":: ", "::")
.replace(" ,", ",")
.replace(" ;", ";")
.replace(" [", "[")
.replace("[ ", "[")
.replace(" ]", "]")
.replace(" (", "(")
.replace(",(", ", (")
.replace("( ", "(")
.replace(" )", ")")
.replace(" <", "<")
.replace("< ", "<")
.replace(" >", ">")
.replace("& \'", "&'")
}