use std::collections::HashSet;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::visit::Visit;
use syn::{ConstParam, GenericParam, Generics, Lifetime, LifetimeParam, Type, TypeParam};
pub struct SelectorGenerics {
decl_params: Vec<GenericParam>,
use_args: Vec<TokenStream2>,
}
impl SelectorGenerics {
pub fn decl(&self) -> TokenStream2 {
if self.decl_params.is_empty() {
return quote! {};
}
let params = &self.decl_params;
quote! { <#(#params),*> }
}
pub fn use_(&self) -> TokenStream2 {
if self.use_args.is_empty() {
return quote! {};
}
let args = &self.use_args;
quote! { <#(#args),*> }
}
pub fn params(&self) -> &[GenericParam] {
&self.decl_params
}
}
pub fn param_name(param: &GenericParam) -> String {
match param {
GenericParam::Lifetime(lt) => lt.lifetime.ident.to_string(),
GenericParam::Type(tp) => tp.ident.to_string(),
GenericParam::Const(cp) => cp.ident.to_string(),
}
}
pub fn project(generics: &Generics, referenced: &ReferencedParams) -> SelectorGenerics {
let mut decl_params = Vec::new();
let mut use_args = Vec::new();
for param in &generics.params {
match param {
GenericParam::Lifetime(lt) if referenced.lifetimes.contains(<.lifetime) => {
let mut bare = lt.clone();
bare.bounds.clear();
bare.colon_token = None;
decl_params.push(GenericParam::Lifetime(bare));
let lifetime = <.lifetime;
use_args.push(quote! { #lifetime });
}
GenericParam::Type(tp) if referenced.types.contains(&tp.ident.to_string()) => {
let mut bare = tp.clone();
bare.bounds.clear();
bare.colon_token = None;
decl_params.push(GenericParam::Type(bare));
let ident = &tp.ident;
use_args.push(quote! { #ident });
}
GenericParam::Const(cp) if referenced.consts.contains(&cp.ident.to_string()) => {
decl_params.push(param.clone());
let ident = &cp.ident;
use_args.push(quote! { #ident });
}
GenericParam::Lifetime(_) | GenericParam::Type(_) | GenericParam::Const(_) => {}
}
}
SelectorGenerics {
decl_params,
use_args,
}
}
pub fn projected_bound_predicates(
generics: &Generics,
projected: &std::collections::HashSet<String>,
) -> Vec<syn::WherePredicate> {
let mut predicates = Vec::new();
for param in &generics.params {
match param {
GenericParam::Type(tp) if projected.contains(&tp.ident.to_string()) => {
if tp.bounds.is_empty() {
continue;
}
let ident = &tp.ident;
let bounds = &tp.bounds;
predicates.push(syn::parse_quote! { #ident: #bounds });
}
GenericParam::Lifetime(lt) if projected.contains(<.lifetime.ident.to_string()) => {
if lt.bounds.is_empty() {
continue;
}
let lifetime = <.lifetime;
let bounds = <.bounds;
predicates.push(syn::parse_quote! { #lifetime: #bounds });
}
GenericParam::Type(_) | GenericParam::Lifetime(_) | GenericParam::Const(_) => {}
}
}
predicates
}
pub fn predicate_named_params(
pred: &syn::WherePredicate,
declared: &DeclaredParams,
) -> std::collections::HashSet<String> {
let mut found = ReferencedParams::default();
match pred {
syn::WherePredicate::Type(ty_pred) => {
found.add_type(&ty_pred.bounded_ty, declared);
for bound in &ty_pred.bounds {
if let syn::TypeParamBound::Trait(tb) = bound {
let mut visitor = RefVisitor {
declared,
found: &mut found,
};
visitor.visit_path(&tb.path);
}
}
}
syn::WherePredicate::Lifetime(lt_pred) => {
if declared
.lifetimes
.contains(<_pred.lifetime.ident.to_string())
{
found.lifetimes.insert(lt_pred.lifetime.clone());
}
for bound in <_pred.bounds {
if declared.lifetimes.contains(&bound.ident.to_string()) {
found.lifetimes.insert(bound.clone());
}
}
}
_ => {}
}
found.names()
}
pub struct DeclaredParams {
types: HashSet<String>,
consts: HashSet<String>,
lifetimes: HashSet<String>,
}
impl DeclaredParams {
pub fn from_generics(generics: &Generics) -> Self {
let mut types = HashSet::new();
let mut consts = HashSet::new();
let mut lifetimes = HashSet::new();
for param in &generics.params {
match param {
GenericParam::Type(TypeParam { ident, .. }) => {
types.insert(ident.to_string());
}
GenericParam::Const(ConstParam { ident, .. }) => {
consts.insert(ident.to_string());
}
GenericParam::Lifetime(LifetimeParam { lifetime, .. }) => {
lifetimes.insert(lifetime.ident.to_string());
}
}
}
Self {
types,
consts,
lifetimes,
}
}
pub fn type_references_param(&self, ty: &Type) -> bool {
let mut probe = ReferencedParams::default();
probe.add_type(ty, self);
!probe.types.is_empty() || !probe.consts.is_empty() || !probe.lifetimes.is_empty()
}
}
#[derive(Default)]
pub struct ReferencedParams {
types: HashSet<String>,
consts: HashSet<String>,
lifetimes: HashSet<Lifetime>,
}
impl ReferencedParams {
pub fn add_type(&mut self, ty: &Type, declared: &DeclaredParams) {
let mut visitor = RefVisitor {
declared,
found: self,
};
visitor.visit_type(ty);
}
pub fn names(&self) -> HashSet<String> {
self.types
.iter()
.cloned()
.chain(self.consts.iter().cloned())
.chain(self.lifetimes.iter().map(|lt| lt.ident.to_string()))
.collect()
}
}
struct RefVisitor<'a> {
declared: &'a DeclaredParams,
found: &'a mut ReferencedParams,
}
impl<'ast> Visit<'ast> for RefVisitor<'_> {
fn visit_lifetime(&mut self, i: &'ast Lifetime) {
if self.declared.lifetimes.contains(&i.ident.to_string()) {
self.found.lifetimes.insert(i.clone());
}
}
fn visit_path(&mut self, i: &'ast syn::Path) {
if let Some(ident) = i.get_ident() {
let name = ident.to_string();
if self.declared.types.contains(&name) {
self.found.types.insert(name.clone());
}
if self.declared.consts.contains(&name) {
self.found.consts.insert(name);
}
}
syn::visit::visit_path(self, i);
}
}