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,
}
fn path_starts_with_type_param(path: &Path, params: &Punctuated<GenericParam, Comma>) -> bool {
path.leading_colon.is_none()
&& path.segments.first().is_some_and(|segment| {
params.iter().any(
|param| matches!(param, GenericParam::Type(param) if param.ident == segment.ident),
)
})
}
impl BoundExceptions {
#[inline]
fn path_is_unconditional(&self, path: &Path, params: &Punctuated<GenericParam, Comma>) -> bool {
if path_starts_with_type_param(path, params) {
return false;
}
if let Some(segment) = path.segments.last() {
let ident = &segment.ident;
if ident == "PhantomData" {
return true;
}
if let PathArguments::AngleBracketed(args) = &segment.arguments {
let count =
args.args.iter().filter(|arg| matches!(arg, GenericArgument::Type(_))).count();
if (ident == "HashMap" && count > 2) || (ident == "HashSet" && count > 1) {
return false;
}
}
self.unconditional_types.iter().any(|name| ident == name)
} else {
false
}
}
#[inline]
pub(crate) fn type_is_unconditional(
&self,
ty: &Type,
params: &Punctuated<GenericParam, Comma>,
) -> bool {
match ty {
Type::Path(ty) => ty.qself.is_none() && self.path_is_unconditional(&ty.path, params),
Type::Ptr(_) | Type::FnPtr(_) => 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,
params: &Punctuated<GenericParam, Comma>,
) -> Option<Vec<&'a Type>> {
if let Type::Path(ty) = ty
&& ty.qself.is_none()
&& let Some(segment) = ty.path.segments.last()
&& !path_starts_with_type_param(&ty.path, params)
&& 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);
}
}
if (segment.ident == "HashMap" && types.len() > 2)
|| (segment.ident == "HashSet" && types.len() > 1)
{
return None;
}
return Some(types);
}
None
}
}
fn walk_type<'a>(
set: &mut HashSet<&'a Ident>,
ty: &'a Type,
exceptions: Option<(&BoundExceptions, &Punctuated<GenericParam, Comma>)>,
) {
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::FnPtr(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, &Punctuated<GenericParam, Comma>)>,
) {
if let Some((exceptions, params)) = exceptions
&& exceptions.path_is_unconditional(path, params)
{
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 arg in &args.inputs {
walk_type(set, &arg.ty, exceptions);
}
if let ReturnType::Type(_, ty) = &args.output {
walk_type(set, ty, exceptions);
}
},
PathArguments::None => (),
}
}
}
#[inline]
pub(crate) fn find_idents_in_type<'a>(
set: &mut HashSet<&'a Ident>,
ty: &'a Type,
exceptions: &BoundExceptions,
params: &Punctuated<GenericParam, Comma>,
) {
walk_type(set, ty, Some((exceptions, params)));
}
#[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);
}
}
pub(crate) fn type_uses_generic_params(
ty: &Type,
params: &Punctuated<GenericParam, Comma>,
) -> bool {
use syn::visit::Visit;
if !params.iter().any(|param| matches!(param, GenericParam::Type(_) | GenericParam::Const(_))) {
return false;
}
struct FindParam<'a> {
params: &'a Punctuated<GenericParam, Comma>,
found: bool,
}
impl<'ast> Visit<'ast> for FindParam<'_> {
fn visit_type(&mut self, ty: &'ast Type) {
if !self.found {
syn::visit::visit_type(self, ty);
}
}
fn visit_expr(&mut self, expr: &'ast syn::Expr) {
if !self.found {
syn::visit::visit_expr(self, expr);
}
}
fn visit_path(&mut self, path: &'ast Path) {
if self.found {
return;
}
if path.leading_colon.is_none()
&& let Some(segment) = path.segments.first()
{
self.found = self.params.iter().any(|param| match param {
GenericParam::Type(param) => param.ident == segment.ident,
GenericParam::Const(param) => param.ident == segment.ident,
GenericParam::Lifetime(_) => false,
});
}
if !self.found {
syn::visit::visit_path(self, path);
}
}
}
let mut visitor = FindParam {
params,
found: false,
};
visitor.visit_type(ty);
visitor.found
}
pub(crate) fn type_mentions_ident(
ty: &Type,
ident: &Ident,
params: &Punctuated<GenericParam, Comma>,
) -> bool {
fn path_mentions_ident(
path: &Path,
ident: &Ident,
params: &Punctuated<GenericParam, Comma>,
qualified: bool,
) -> bool {
let check_names = !qualified && !path_starts_with_type_param(path, params);
if check_names
&& path.leading_colon.is_none()
&& path.segments.first().is_some_and(|segment| segment.ident == "Self")
{
return true;
}
for segment in &path.segments {
if check_names && 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, params) {
return true;
}
},
GenericArgument::AssocType(ty)
if type_mentions_ident(&ty.ty, ident, params) =>
{
return true;
},
_ => (),
}
}
},
PathArguments::Parenthesized(args) => {
for arg in &args.inputs {
if type_mentions_ident(&arg.ty, ident, params) {
return true;
}
}
if let ReturnType::Type(_, ty) = &args.output
&& type_mentions_ident(ty, ident, params)
{
return true;
}
},
PathArguments::None => (),
}
}
false
}
match ty {
Type::Array(ty) => type_mentions_ident(ty.elem.as_ref(), ident, params),
Type::Group(ty) => type_mentions_ident(ty.elem.as_ref(), ident, params),
Type::Paren(ty) => type_mentions_ident(ty.elem.as_ref(), ident, params),
Type::Slice(ty) => type_mentions_ident(ty.elem.as_ref(), ident, params),
Type::Ptr(ty) => type_mentions_ident(ty.elem.as_ref(), ident, params),
Type::Reference(ty) => type_mentions_ident(ty.elem.as_ref(), ident, params),
Type::Tuple(ty) => ty.elems.iter().any(|ty| type_mentions_ident(ty, ident, params)),
Type::FnPtr(ty) => {
ty.inputs.iter().any(|arg| type_mentions_ident(&arg.ty, ident, params))
|| matches!(&ty.output, ReturnType::Type(_, ty) if type_mentions_ident(ty, ident, params))
},
Type::ImplTrait(ty) => ty
.bounds
.iter()
.any(|b| matches!(b, TypeParamBound::Trait(b) if path_mentions_ident(&b.path, ident, params, false))),
Type::TraitObject(ty) => ty
.bounds
.iter()
.any(|b| matches!(b, TypeParamBound::Trait(b) if path_mentions_ident(&b.path, ident, params, false))),
Type::Path(ty) => {
(match &ty.qself {
Some(qself) => type_mentions_ident(qself.ty.as_ref(), ident, params),
None => false,
}) || path_mentions_ident(&ty.path, ident, params, ty.qself.is_some())
},
_ => 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) }
}