use std::collections::HashSet;
use syn::{
GenericArgument, GenericParam, Ident, Meta, Path, PathArguments, ReturnType, Token, Type,
TypeParamBound,
parse::{Parse, ParseStream},
punctuated::Punctuated,
token::Comma,
};
pub(crate) struct TypeWithPunctuatedMeta {
pub(crate) ty: Type,
pub(crate) list: Punctuated<Meta, Token![,]>,
}
impl Parse for TypeWithPunctuatedMeta {
#[inline]
fn parse(input: ParseStream) -> syn::Result<Self> {
let ty = input.parse::<Type>()?;
if input.is_empty() {
return Ok(Self {
ty,
list: Punctuated::new(),
});
}
input.parse::<Token![,]>()?;
let list = input.parse_terminated(Meta::parse, Token![,])?;
Ok(Self {
ty,
list,
})
}
}
pub(crate) struct BoundExceptions {
pub(crate) unconditional_types: &'static [&'static str],
pub(crate) forwarding_types: &'static [&'static str],
pub(crate) shared_reference_is_unconditional: bool,
}
impl BoundExceptions {
#[inline]
fn path_is_unconditional(&self, path: &Path) -> bool {
if let Some(segment) = path.segments.last() {
let ident = &segment.ident;
if ident == "PhantomData" {
return true;
}
self.unconditional_types.iter().any(|name| ident == name)
} else {
false
}
}
#[inline]
pub(crate) fn type_is_unconditional(&self, ty: &Type) -> bool {
match ty {
Type::Path(ty) => ty.qself.is_none() && self.path_is_unconditional(&ty.path),
Type::Ptr(_) | Type::BareFn(_) => true,
Type::Reference(ty) => {
ty.mutability.is_none() && self.shared_reference_is_unconditional
},
_ => false,
}
}
pub(crate) fn forwarding_type_arguments<'a>(&self, ty: &'a Type) -> Option<Vec<&'a Type>> {
if let Type::Path(ty) = ty
&& ty.qself.is_none()
&& let Some(segment) = ty.path.segments.last()
&& self.forwarding_types.iter().any(|name| segment.ident == name)
&& let PathArguments::AngleBracketed(args) = &segment.arguments
{
let mut types = Vec::new();
for arg in &args.args {
if let GenericArgument::Type(ty) = arg {
types.push(ty);
}
}
return Some(types);
}
None
}
}
fn walk_type<'a>(set: &mut HashSet<&'a Ident>, ty: &'a Type, exceptions: Option<&BoundExceptions>) {
match ty {
Type::Array(ty) => walk_type(set, ty.elem.as_ref(), exceptions),
Type::Group(ty) => walk_type(set, ty.elem.as_ref(), exceptions),
Type::Paren(ty) => walk_type(set, ty.elem.as_ref(), exceptions),
Type::Slice(ty) => walk_type(set, ty.elem.as_ref(), exceptions),
Type::Tuple(ty) => {
for ty in &ty.elems {
walk_type(set, ty, exceptions);
}
},
Type::Reference(ty) => {
if let Some(exceptions) = exceptions
&& ty.mutability.is_none()
&& exceptions.shared_reference_is_unconditional
{
return;
}
walk_type(set, ty.elem.as_ref(), exceptions);
},
Type::Ptr(ty) => {
if exceptions.is_some() {
return;
}
walk_type(set, ty.elem.as_ref(), exceptions);
},
Type::BareFn(ty) => {
if exceptions.is_some() {
return;
}
for arg in &ty.inputs {
walk_type(set, &arg.ty, exceptions);
}
if let ReturnType::Type(_, ty) = &ty.output {
walk_type(set, ty, exceptions);
}
},
Type::ImplTrait(ty) => {
for b in &ty.bounds {
if let TypeParamBound::Trait(b) = b {
walk_path(set, &b.path, exceptions);
}
}
},
Type::TraitObject(ty) => {
for b in &ty.bounds {
if let TypeParamBound::Trait(b) = b {
walk_path(set, &b.path, exceptions);
}
}
},
Type::Path(ty) => {
if let Some(qself) = &ty.qself {
walk_type(set, qself.ty.as_ref(), exceptions);
}
walk_path(set, &ty.path, exceptions);
},
_ => (),
}
}
fn walk_path<'a>(
set: &mut HashSet<&'a Ident>,
path: &'a Path,
exceptions: Option<&BoundExceptions>,
) {
if let Some(exceptions) = exceptions
&& exceptions.path_is_unconditional(path)
{
return;
}
if path.leading_colon.is_none()
&& let Some(segment) = path.segments.first()
{
set.insert(&segment.ident);
}
for segment in &path.segments {
match &segment.arguments {
PathArguments::AngleBracketed(args) => {
for arg in &args.args {
match arg {
GenericArgument::Type(ty) => walk_type(set, ty, exceptions),
GenericArgument::AssocType(ty) => walk_type(set, &ty.ty, exceptions),
_ => (),
}
}
},
PathArguments::Parenthesized(args) => {
for ty in &args.inputs {
walk_type(set, ty, exceptions);
}
if let ReturnType::Type(_, ty) = &args.output {
walk_type(set, ty, exceptions);
}
},
PathArguments::None => (),
}
}
}
#[inline]
pub(crate) fn find_all_idents_in_type<'a>(set: &mut HashSet<&'a Ident>, ty: &'a Type) {
walk_type(set, ty, None);
}
#[inline]
pub(crate) fn find_idents_in_type<'a>(
set: &mut HashSet<&'a Ident>,
ty: &'a Type,
exceptions: &BoundExceptions,
) {
walk_type(set, ty, Some(exceptions));
}
#[inline]
pub(crate) fn find_bare_ident_in_type<'a>(set: &mut HashSet<&'a Ident>, ty: &'a Type) {
if let Type::Path(ty) = ty
&& ty.qself.is_none()
&& let Some(ident) = ty.path.get_ident()
{
set.insert(ident);
}
}
#[inline]
pub(crate) fn type_uses_type_params(ty: &Type, params: &Punctuated<GenericParam, Comma>) -> bool {
let mut set = HashSet::new();
find_all_idents_in_type(&mut set, ty);
params.iter().any(|param| {
if let GenericParam::Type(param) = param { set.contains(¶m.ident) } else { false }
})
}
pub(crate) fn type_mentions_ident(ty: &Type, ident: &Ident) -> bool {
fn path_mentions_ident(path: &Path, ident: &Ident) -> bool {
for segment in &path.segments {
if segment.ident == *ident {
return true;
}
match &segment.arguments {
PathArguments::AngleBracketed(args) => {
for arg in &args.args {
match arg {
GenericArgument::Type(ty) => {
if type_mentions_ident(ty, ident) {
return true;
}
},
GenericArgument::AssocType(ty)
if type_mentions_ident(&ty.ty, ident) =>
{
return true;
},
_ => (),
}
}
},
PathArguments::Parenthesized(args) => {
for ty in &args.inputs {
if type_mentions_ident(ty, ident) {
return true;
}
}
if let ReturnType::Type(_, ty) = &args.output
&& type_mentions_ident(ty, ident)
{
return true;
}
},
PathArguments::None => (),
}
}
false
}
match ty {
Type::Array(ty) => type_mentions_ident(ty.elem.as_ref(), ident),
Type::Group(ty) => type_mentions_ident(ty.elem.as_ref(), ident),
Type::Paren(ty) => type_mentions_ident(ty.elem.as_ref(), ident),
Type::Slice(ty) => type_mentions_ident(ty.elem.as_ref(), ident),
Type::Ptr(ty) => type_mentions_ident(ty.elem.as_ref(), ident),
Type::Reference(ty) => type_mentions_ident(ty.elem.as_ref(), ident),
Type::Tuple(ty) => ty.elems.iter().any(|ty| type_mentions_ident(ty, ident)),
Type::BareFn(ty) => {
ty.inputs.iter().any(|arg| type_mentions_ident(&arg.ty, ident))
|| matches!(&ty.output, ReturnType::Type(_, ty) if type_mentions_ident(ty, ident))
},
Type::ImplTrait(ty) => ty
.bounds
.iter()
.any(|b| matches!(b, TypeParamBound::Trait(b) if path_mentions_ident(&b.path, ident))),
Type::TraitObject(ty) => ty
.bounds
.iter()
.any(|b| matches!(b, TypeParamBound::Trait(b) if path_mentions_ident(&b.path, ident))),
Type::Path(ty) => {
(match &ty.qself {
Some(qself) => type_mentions_ident(qself.ty.as_ref(), ident),
None => false,
}) || path_mentions_ident(&ty.path, ident)
},
_ => false,
}
}
#[inline]
pub(crate) fn dereference(ty: &Type) -> &Type {
if let Type::Reference(ty) = ty { dereference(ty.elem.as_ref()) } else { ty }
}
#[inline]
pub(crate) fn dereference_changed(ty: &Type) -> (&Type, bool) {
if let Type::Reference(ty) = ty { (dereference(ty.elem.as_ref()), true) } else { (ty, false) }
}