use std::collections::HashSet;
use quote::{ToTokens, quote};
use syn::{
Expr, GenericParam, Ident, Lit, Meta, MetaNameValue, Path, Token, Type, WherePredicate,
parse::{Parse, ParseStream},
punctuated::Punctuated,
token::Comma,
};
use super::{
path::path_to_string,
r#type::{
BoundExceptions, find_bare_ident_in_type, find_idents_in_type, type_mentions_ident,
type_uses_type_params,
},
};
pub(crate) type WherePredicates = Punctuated<WherePredicate, Token![,]>;
pub(crate) enum WherePredicatesOrBool {
WherePredicates(WherePredicates),
Bool(bool),
All,
}
impl WherePredicatesOrBool {
fn from_lit(lit: &Lit) -> syn::Result<Self> {
match lit {
Lit::Bool(lit) => Ok(Self::Bool(lit.value)),
Lit::Str(lit) => match lit.parse_with(WherePredicates::parse_terminated) {
Ok(where_predicates) => Ok(Self::WherePredicates(where_predicates)),
Err(_) if lit.value().is_empty() => Ok(Self::Bool(false)),
Err(error) => Err(error),
},
_ => Err(syn::Error::new_spanned(
lit,
"expected a boolean literal or a string of where predicates",
)),
}
}
}
impl Parse for WherePredicatesOrBool {
#[inline]
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.parse::<Token![*]>().is_ok() {
return Ok(Self::All);
}
if let Ok(lit) = input.parse::<Lit>() {
return Self::from_lit(&lit);
}
Ok(Self::WherePredicates(input.parse_terminated(WherePredicate::parse, Token![,])?))
}
}
#[inline]
pub(crate) fn meta_name_value_2_where_predicates_bool(
name_value: &MetaNameValue,
) -> syn::Result<WherePredicatesOrBool> {
if let Expr::Lit(lit) = &name_value.value
&& matches!(&lit.lit, Lit::Bool(_) | Lit::Str(_))
{
return WherePredicatesOrBool::from_lit(&lit.lit);
}
Err(syn::Error::new_spanned(
&name_value.value,
format!(
"expected `{path} = \"where_predicates\"` or `{path} = false`",
path = path_to_string(&name_value.path)
),
))
}
#[inline]
pub(crate) fn meta_2_where_predicates(meta: &Meta) -> syn::Result<WherePredicatesOrBool> {
match &meta {
Meta::NameValue(name_value) => meta_name_value_2_where_predicates_bool(name_value),
Meta::List(list) => list.parse_args::<WherePredicatesOrBool>(),
Meta::Path(path) => Err(syn::Error::new_spanned(
path,
format!(
"expected `{path} = \"where_predicates\"`, `{path}(where_predicates)`, \
`{path}(*)`, `{path} = false`, or `{path}(false)`",
path = path.clone().into_token_stream()
),
)),
}
}
#[inline]
pub(crate) fn create_where_predicates_from_all_generic_parameters(
params: &Punctuated<GenericParam, Comma>,
bound_trait: &Path,
) -> WherePredicates {
let mut where_predicates = Punctuated::new();
for param in params {
if let GenericParam::Type(ty) = param {
let ident = &ty.ident;
where_predicates.push(syn::parse2(quote! { #ident: #bound_trait }).unwrap());
}
}
where_predicates
}
pub(crate) fn create_where_predicates_from_field_types(
params: &Punctuated<GenericParam, Comma>,
bound_trait: &Path,
types: &[&Type],
self_ident: &Ident,
exceptions: &BoundExceptions,
) -> WherePredicates {
fn process_type(
ty: &Type,
params: &Punctuated<GenericParam, Comma>,
bound_trait: &Path,
self_ident: &Ident,
exceptions: &BoundExceptions,
where_predicates: &mut WherePredicates,
seen: &mut HashSet<String>,
) {
let mut push = |where_predicates: &mut WherePredicates, predicate: WherePredicate| {
if seen.insert(predicate.to_token_stream().to_string()) {
where_predicates.push(predicate);
}
};
if exceptions.type_is_unconditional(ty) {
return;
}
if !type_uses_type_params(ty, params) {
return;
}
if let Some(argument_types) = exceptions.forwarding_type_arguments(ty) {
for ty in argument_types {
process_type(
ty,
params,
bound_trait,
self_ident,
exceptions,
where_predicates,
seen,
);
}
return;
}
if type_mentions_ident(ty, self_ident) {
let mut used = HashSet::new();
find_idents_in_type(&mut used, ty, exceptions);
for param in params {
if let GenericParam::Type(param) = param
&& used.contains(¶m.ident)
{
let ident = ¶m.ident;
push(where_predicates, syn::parse2(quote! { #ident: #bound_trait }).unwrap());
}
}
} else {
push(where_predicates, syn::parse2(quote! { #ty: #bound_trait }).unwrap());
}
}
let mut where_predicates = Punctuated::new();
let mut seen: HashSet<String> = HashSet::new();
for ty in types {
process_type(
ty,
params,
bound_trait,
self_ident,
exceptions,
&mut where_predicates,
&mut seen,
);
}
where_predicates
}
pub(crate) fn create_where_predicates_from_bare_field_types(
params: &Punctuated<GenericParam, Comma>,
bound_trait: &Path,
types: &[&Type],
) -> WherePredicates {
let mut where_predicates = Punctuated::new();
let mut set = HashSet::new();
for t in types {
find_bare_ident_in_type(&mut set, t);
}
for param in params {
if let GenericParam::Type(ty) = param {
let ident = &ty.ident;
if set.contains(ident) {
where_predicates.push(syn::parse2(quote! { #ident: #bound_trait }).unwrap());
}
}
}
where_predicates
}