use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
ConstParam, Field, GenericParam, Generics, Ident, Lifetime, PredicateType, Type, TypeParam,
WhereClause, WherePredicate, parse_quote,
};
pub(super) fn has_non_static_lifetime(ty: &Type, generic_idents: &[&Ident]) -> bool {
match ty {
Type::Array(array) => has_non_static_lifetime(&array.elem, generic_idents),
Type::Paren(paren) => has_non_static_lifetime(&paren.elem, generic_idents),
Type::Path(syn::TypePath { qself: None, path }) => {
if path.segments.len() == 1
&& path
.segments
.first()
.is_some_and(|segment| segment.arguments.is_empty())
{
path.segments
.first()
.is_none_or(|segment| generic_idents.contains(&&segment.ident))
} else {
path.segments
.iter()
.any(|segment| match &segment.arguments {
syn::PathArguments::None => false,
syn::PathArguments::AngleBracketed(arguments) => {
arguments.args.iter().any(|argument| match argument {
syn::GenericArgument::Type(ty) => {
has_non_static_lifetime(ty, generic_idents)
}
syn::GenericArgument::AssocType(assoc_type) => {
has_non_static_lifetime(&assoc_type.ty, generic_idents)
}
syn::GenericArgument::Lifetime(syn::Lifetime { ident, .. }) => {
*ident != "static"
}
_ => true,
})
}
syn::PathArguments::Parenthesized(_) => true,
})
}
}
Type::Tuple(tuple) => tuple
.elems
.iter()
.any(|elem| has_non_static_lifetime(elem, generic_idents)),
_ => true,
}
}
pub(super) fn generic_idents(generics: &Generics) -> Vec<&Ident> {
generics
.params
.iter()
.filter_map(|param| match param {
GenericParam::Type(type_param) => Some(&type_param.ident),
_ => None,
})
.collect()
}
#[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 const fn needs_clone(self) -> bool {
match self {
Self::ToBoundedStatic => true,
Self::IntoBoundedStatic => false,
}
}
}
pub(super) fn check_field(field: &Field) {
if let Type::Reference(ty) = &field.ty
&& 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, target: TargetTrait) -> Vec<TokenStream> {
generics
.params
.iter()
.map(|param| match param {
GenericParam::Type(TypeParam { ident, .. }) => {
let target_bound = target.bound();
quote!(<#ident as ::bounded_static::#target_bound>::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, target);
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, target: TargetTrait) -> 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;
let target_bound = target.bound();
match find_predicate(generics.where_clause.as_ref(), var) {
None if param_ty_bounds.is_empty() => None,
None => Some(parse_quote!(<#var as #target_bound>::Static: #param_ty_bounds)),
Some(predicate_ty) => {
let predicate_bounds = &predicate_ty.bounds;
if param_ty_bounds.is_empty() {
Some(parse_quote!(<#var as #target_bound>::Static: #predicate_bounds))
} else {
Some(parse_quote!(<#var as #target_bound>::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_some(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
}
}