use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
parse_quote, ConstParam, Field, GenericParam, Generics, Ident, Lifetime, PredicateType, Type,
TypeParam, WhereClause, WherePredicate,
};
#[derive(Copy, Clone)]
pub(super) enum TargetTrait {
ToBoundedStatic,
IntoBoundedStatic,
}
impl TargetTrait {
pub fn method(self) -> Ident {
match self {
Self::ToBoundedStatic => format_ident!("to_static"),
Self::IntoBoundedStatic => format_ident!("into_static"),
}
}
pub fn bound(self) -> Ident {
match self {
Self::ToBoundedStatic => format_ident!("ToBoundedStatic"),
Self::IntoBoundedStatic => format_ident!("IntoBoundedStatic"),
}
}
}
pub(super) fn check_field(field: &Field) {
if let Type::Reference(ty) = &field.ty {
if let Some(Lifetime { ident, .. }) = &ty.lifetime {
#[allow(clippy::manual_assert)]
if *ident != "static" {
panic!(
"non-static references cannot be made static: {:?}",
quote!(#field).to_string()
)
}
}
};
}
pub(super) fn make_target_generics(generics: &Generics) -> Vec<TokenStream> {
generics
.params
.iter()
.map(|param| match param {
GenericParam::Type(TypeParam { ident, .. }) => quote!(#ident::Static),
GenericParam::Lifetime(_) => quote!('static),
GenericParam::Const(ConstParam { ident, .. }) => quote!(#ident),
})
.collect()
}
pub(super) fn make_bounded_generics(generics: &Generics, target: TargetTrait) -> Generics {
let params = make_bounded_generic_params(generics, target);
let predicates = make_bounded_generic_predicates(generics, target);
let static_predicates = make_static_generic_predicates(generics);
let where_items: Vec<_> = predicates.into_iter().chain(static_predicates).collect();
Generics {
params: parse_quote!(#(#params),*),
where_clause: Some(parse_quote!(where #(#where_items),* )),
..*generics
}
}
fn make_bounded_generic_params(generics: &Generics, target: TargetTrait) -> Vec<GenericParam> {
generics
.params
.iter()
.map(|param| match param {
GenericParam::Type(ty) => GenericParam::Type(ty.clone_with_bound(&target.bound())),
other => other.clone(),
})
.collect()
}
fn make_bounded_generic_predicates(
generics: &Generics,
target: TargetTrait,
) -> Vec<WherePredicate> {
match generics.where_clause.as_ref() {
Some(WhereClause { predicates, .. }) => predicates
.iter()
.map(|predicate| match predicate {
WherePredicate::Type(ty) => {
WherePredicate::Type(ty.clone_with_bound(&target.bound()))
}
other => other.clone(),
})
.collect(),
None => vec![],
}
}
fn make_static_generic_predicates(generics: &Generics) -> Vec<WherePredicate> {
generics
.params
.iter()
.filter_map(|param| match param {
GenericParam::Type(param_ty) => {
let var = ¶m_ty.ident;
let param_ty_bounds = ¶m_ty.bounds;
match find_predicate(generics.where_clause.as_ref(), var) {
None if param_ty_bounds.is_empty() => None,
None => Some(parse_quote!(#var::Static: #param_ty_bounds)),
Some(predicate_ty) => {
let predicate_bounds = &predicate_ty.bounds;
if param_ty_bounds.is_empty() {
Some(parse_quote!(#var::Static: #predicate_bounds))
} else {
Some(parse_quote!(#var::Static: #param_ty_bounds + #predicate_bounds))
}
}
}
}
_ => None,
})
.collect()
}
fn find_predicate<'a>(
where_clause: Option<&'a WhereClause>,
var: &Ident,
) -> Option<&'a PredicateType> {
where_clause
.as_ref()
.map(|WhereClause { predicates, .. }| predicates)
.and_then(|predicate| {
predicate.iter().find_map(|p| match p {
WherePredicate::Type(ty) => match &ty.bounded_ty {
Type::Path(path) => path.path.is_ident(var).then(|| ty),
_ => None,
},
_ => None,
})
})
}
trait CloneWithBound {
fn clone_with_bound(&self, bound: &Ident) -> Self;
}
impl CloneWithBound for PredicateType {
fn clone_with_bound(&self, bound: &Ident) -> Self {
let mut bounded = self.clone();
bounded.bounds.push(parse_quote!(::bounded_static::#bound));
bounded
}
}
impl CloneWithBound for TypeParam {
fn clone_with_bound(&self, bound: &Ident) -> Self {
let mut bounded = self.clone();
bounded.bounds.push(parse_quote!(::bounded_static::#bound));
bounded
}
}