use std::collections::HashSet;
use syn::ext::IdentExt as _;
use syn::visit::{
visit_bound_lifetimes, visit_generic_param, visit_lifetime, visit_type, visit_type_path,
visit_where_clause, Visit,
};
use syn::{GenericParam, Ident, Lifetime, Type, TypePath, WhereClause};
use super::lifetimes::ignored_lifetime_ident;
struct Visitor<'a> {
lt_param: &'a Ident,
found_lt_param_usage: bool,
typarams: &'a HashSet<Ident>,
found_typaram_usage: bool,
min_underscores_for_yoke_lt: usize,
}
impl<'ast> Visit<'ast> for Visitor<'_> {
fn visit_lifetime(&mut self, lt: &'ast Lifetime) {
if lt.ident.unraw() == *self.lt_param {
self.found_lt_param_usage = true;
}
visit_lifetime(self, lt)
}
fn visit_type_path(&mut self, ty: &'ast TypePath) {
if let Some(ident) = ty.path.get_ident() {
if self.typarams.contains(&ident.unraw()) {
self.found_typaram_usage = true;
}
}
visit_type_path(self, ty)
}
fn visit_bound_lifetimes(&mut self, lts: &'ast syn::BoundLifetimes) {
for lt in <s.lifetimes {
if let GenericParam::Lifetime(lt) = lt {
let maybe_underscores_yoke = lt.lifetime.ident.unraw().to_string();
if let Some(underscores) = maybe_underscores_yoke.strip_suffix("yoke") {
if underscores.as_bytes().iter().all(|byte| *byte == b'_') {
self.min_underscores_for_yoke_lt = self.min_underscores_for_yoke_lt.max(
underscores.len() + 1,
);
}
}
}
}
visit_bound_lifetimes(self, lts);
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct CheckResult {
pub uses_lifetime_param: bool,
pub uses_type_params: bool,
pub min_underscores_for_yoke_lt: usize,
}
pub fn check_type_for_parameters(
lt_param: &Ident,
typarams: &HashSet<Ident>,
ty: &Type,
) -> CheckResult {
let mut visit = Visitor {
lt_param,
found_lt_param_usage: false,
typarams,
found_typaram_usage: false,
min_underscores_for_yoke_lt: 0,
};
visit_type(&mut visit, ty);
CheckResult {
uses_lifetime_param: visit.found_lt_param_usage,
uses_type_params: visit.found_typaram_usage,
min_underscores_for_yoke_lt: visit.min_underscores_for_yoke_lt,
}
}
pub fn check_parameter_for_bound_lts(param: &GenericParam) -> usize {
let mut visit = Visitor {
lt_param: &ignored_lifetime_ident(),
found_lt_param_usage: false,
typarams: &HashSet::new(),
found_typaram_usage: false,
min_underscores_for_yoke_lt: 0,
};
visit_generic_param(&mut visit, param);
visit.min_underscores_for_yoke_lt
}
pub fn check_where_clause_for_bound_lts(where_clause: &WhereClause) -> usize {
let mut visit = Visitor {
lt_param: &ignored_lifetime_ident(),
found_lt_param_usage: false,
typarams: &HashSet::new(),
found_typaram_usage: false,
min_underscores_for_yoke_lt: 0,
};
visit_where_clause(&mut visit, where_clause);
visit.min_underscores_for_yoke_lt
}
#[cfg(test)]
mod tests {
use proc_macro2::Span;
use std::collections::HashSet;
use syn::{parse_quote, Ident};
use super::{check_type_for_parameters, CheckResult};
fn a_ident() -> Ident {
Ident::new("a", Span::call_site())
}
fn yoke_ident() -> Ident {
Ident::new("yoke", Span::call_site())
}
fn make_typarams(params: &[&str]) -> HashSet<Ident> {
params
.iter()
.map(|x| Ident::new(x, Span::call_site()))
.collect()
}
fn uses(lifetime: bool, typarams: bool) -> CheckResult {
CheckResult {
uses_lifetime_param: lifetime,
uses_type_params: typarams,
min_underscores_for_yoke_lt: 0,
}
}
#[test]
fn test_simple_type() {
let environment = make_typarams(&["T", "U", "V"]);
let ty = parse_quote!(Foo<'a, T>);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(true, true));
let ty = parse_quote!(Foo<T>);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(false, true));
let ty = parse_quote!(Foo<'static, T>);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(false, true));
let ty = parse_quote!(Foo<'a>);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(true, false));
let ty = parse_quote!(Foo<'a, Bar<U>, Baz<(V, u8)>>);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(true, true));
let ty = parse_quote!(Foo<'a, W>);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(true, false));
}
#[test]
fn test_assoc_types() {
let environment = make_typarams(&["T"]);
let ty = parse_quote!(<Foo as SomeTrait<'a, T>>::Output);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(true, true));
let ty = parse_quote!(<Foo as SomeTrait<'static, T>>::Output);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(false, true));
let ty = parse_quote!(<T as SomeTrait<'static, Foo>>::Output);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(false, true));
}
#[test]
fn test_macro_types() {
let environment = make_typarams(&["T"]);
let ty = parse_quote!(foo!(Foo<'a, T>));
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(false, false));
let ty = parse_quote!(foo!(Foo<T>));
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(false, false));
let ty = parse_quote!(foo!(Foo<'static, T>));
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(false, false));
let ty = parse_quote!(foo!(Foo<'a>));
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(false, false));
let ty = parse_quote!(foo!(Foo<'a, Bar<U>, Baz<(V, u8)>>));
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(false, false));
let ty = parse_quote!(foo!(Foo<'a, W>));
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(false, false));
}
#[test]
fn test_raw_types() {
let environment = make_typarams(&["T", "U", "V"]);
let ty = parse_quote!(Foo<'a, r#T>);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(true, true));
let ty = parse_quote!(Foo<r#T>);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(false, true));
let ty = parse_quote!(Foo<'static, r#T>);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(false, true));
let ty = parse_quote!(Foo<'a>);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(true, false));
let ty = parse_quote!(Foo<'a, Bar<r#U>, Baz<(r#V, u8)>>);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(true, true));
let ty = parse_quote!(Foo<'a, r#W>);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check, uses(true, false));
}
#[test]
fn test_yoke_lifetime() {
let environment = make_typarams(&["T", "U", "V"]);
let ty = parse_quote!(Foo<'yoke, r#T>);
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check.min_underscores_for_yoke_lt, 0);
let ty = parse_quote!(for<'yoke> fn(&'yoke ()));
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check.min_underscores_for_yoke_lt, 1);
let ty = parse_quote!(for<'_yoke> fn(&'_yoke ()));
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check.min_underscores_for_yoke_lt, 2);
let ty = parse_quote!(for<'_yoke_> fn(&'_yoke_ ()));
let check = check_type_for_parameters(&yoke_ident(), &environment, &ty);
assert_eq!(check.min_underscores_for_yoke_lt, 0);
let ty = parse_quote!(for<'_yoke, '___yoke> fn(&'_yoke (), &'___yoke ()));
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check.min_underscores_for_yoke_lt, 4);
let ty = parse_quote!(for<'___yoke> fn(for<'_yoke> fn(&'_yoke (), &'___yoke ())));
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check.min_underscores_for_yoke_lt, 4);
let ty = parse_quote! {
for<'yoke> fn(for<'_yoke> fn(for<'b> fn(&'b (), &'_yoke (), &'yoke ())))
};
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check.min_underscores_for_yoke_lt, 2);
let ty = parse_quote! {
for<'yoke> fn(for<'r#_yoke> fn(for<'b> fn(&'b (), &'_yoke (), &'yoke ())))
};
let check = check_type_for_parameters(&a_ident(), &environment, &ty);
assert_eq!(check.min_underscores_for_yoke_lt, 2);
}
}