use alloc::vec::Vec;
use proc_macro2::Ident;
use syn::{
parse_quote,
punctuated::Punctuated,
spanned::Spanned,
visit::{
self,
Visit,
},
Generics,
Result,
Type,
TypePath,
WhereClause,
};
use crate::{
attr::Attributes,
utils,
};
pub fn make_where_clause<'a>(
attrs: &'a Attributes,
input_ident: &'a Ident,
generics: &'a Generics,
data: &'a syn::Data,
scale_info: &syn::Path,
) -> Result<WhereClause> {
let mut where_clause = generics.where_clause.clone().unwrap_or_else(|| {
WhereClause {
where_token: <syn::Token![where]>::default(),
predicates: Punctuated::new(),
}
});
if let Some(custom_bounds) = attrs.bounds() {
custom_bounds.extend_where_clause(&mut where_clause);
for type_param in generics.type_params() {
let ident = &type_param.ident;
where_clause.predicates.push(parse_quote!(#ident: 'static))
}
return Ok(where_clause)
}
for lifetime in generics.lifetimes() {
where_clause
.predicates
.push(parse_quote!(#lifetime: 'static))
}
let ty_params_ids = generics
.type_params()
.map(|type_param| type_param.ident.clone())
.collect::<Vec<Ident>>();
if ty_params_ids.is_empty() {
return Ok(where_clause)
}
let types = collect_types_to_bind(input_ident, data, &ty_params_ids)?;
types.into_iter().for_each(|(ty, is_compact)| {
if is_compact {
where_clause
.predicates
.push(parse_quote!(#ty : #scale_info :: scale::HasCompact));
} else {
where_clause
.predicates
.push(parse_quote!(#ty : #scale_info ::TypeInfo + 'static));
}
});
generics.type_params().for_each(|type_param| {
let ident = type_param.ident.clone();
let mut bounds = type_param.bounds.clone();
if attrs
.skip_type_params()
.map_or(true, |skip| !skip.skip(type_param))
{
bounds.push(parse_quote!(#scale_info ::TypeInfo));
}
bounds.push(parse_quote!('static));
where_clause
.predicates
.push(parse_quote!( #ident : #bounds));
});
Ok(where_clause)
}
fn type_contains_idents(ty: &Type, idents: &[Ident]) -> bool {
struct ContainIdents<'a> {
result: bool,
idents: &'a [Ident],
}
impl<'a, 'ast> Visit<'ast> for ContainIdents<'a> {
fn visit_ident(&mut self, i: &'ast Ident) {
if self.idents.iter().any(|id| id == i) {
self.result = true;
}
}
}
let mut visitor = ContainIdents {
result: false,
idents,
};
visitor.visit_type(ty);
visitor.result
}
fn type_or_sub_type_path_starts_with_ident(ty: &Type, ident: &Ident) -> bool {
struct TypePathStartsWithIdent<'a> {
result: bool,
ident: &'a Ident,
}
impl<'a, 'ast> Visit<'ast> for TypePathStartsWithIdent<'a> {
fn visit_type_path(&mut self, i: &'ast TypePath) {
if i.qself.is_none() {
if let Some(segment) = i.path.segments.first() {
if &segment.ident == self.ident {
self.result = true;
return
}
}
}
visit::visit_type_path(self, i);
}
}
let mut visitor = TypePathStartsWithIdent {
result: false,
ident,
};
visitor.visit_type(ty);
visitor.result
}
fn collect_types_to_bind(
input_ident: &Ident,
data: &syn::Data,
ty_params: &[Ident],
) -> Result<Vec<(Type, bool)>> {
let types_from_fields = |fields: &Punctuated<syn::Field, _>| -> Vec<(Type, bool)> {
fields
.iter()
.filter(|field| {
type_contains_idents(&field.ty, ty_params)
&&
!type_or_sub_type_path_starts_with_ident(&field.ty, input_ident)
})
.map(|f| (f.ty.clone(), utils::is_compact(f)))
.collect()
};
let types = match *data {
syn::Data::Struct(ref data) => {
match &data.fields {
syn::Fields::Named(syn::FieldsNamed { named: fields, .. })
| syn::Fields::Unnamed(syn::FieldsUnnamed {
unnamed: fields, ..
}) => types_from_fields(fields),
syn::Fields::Unit => Vec::new(),
}
}
syn::Data::Enum(ref data) => {
data.variants
.iter()
.flat_map(|variant| {
match &variant.fields {
syn::Fields::Named(syn::FieldsNamed {
named: fields, ..
})
| syn::Fields::Unnamed(syn::FieldsUnnamed {
unnamed: fields,
..
}) => types_from_fields(fields),
syn::Fields::Unit => Vec::new(),
}
})
.collect()
}
syn::Data::Union(ref data) => {
return Err(syn::Error::new(
data.union_token.span(),
"Union types are not supported.",
))
}
};
Ok(types)
}