use std::collections::BTreeSet;
use syn::{GenericParam, Generics, Ident, Type};
#[allow(dead_code)]
pub trait IdentExt {}
impl IdentExt for Ident {}
#[allow(dead_code)]
pub trait GenericsExt {
fn collect_generic_names(&self) -> BTreeSet<String>;
fn collect_lifetime_names(&self) -> BTreeSet<String>;
fn is_empty(&self) -> bool;
fn has_type_params(&self) -> bool;
fn has_lifetime_params(&self) -> bool;
fn has_const_params(&self) -> bool;
}
impl GenericsExt for Generics {
fn collect_generic_names(&self) -> BTreeSet<String> {
crate::utils::generics::collect_declared_generic_names(self)
}
fn collect_lifetime_names(&self) -> BTreeSet<String> {
self.params
.iter()
.filter_map(|param| match param {
GenericParam::Lifetime(lifetime_param) => {
Some(lifetime_param.lifetime.ident.to_string())
}
_ => None,
})
.collect()
}
fn is_empty(&self) -> bool {
self.params.is_empty()
}
fn has_type_params(&self) -> bool {
self.params
.iter()
.any(|param| matches!(param, GenericParam::Type(_)))
}
fn has_lifetime_params(&self) -> bool {
self.params
.iter()
.any(|param| matches!(param, GenericParam::Lifetime(_)))
}
fn has_const_params(&self) -> bool {
self.params
.iter()
.any(|param| matches!(param, GenericParam::Const(_)))
}
}
#[allow(dead_code)]
pub trait TypeExt {
fn references_generics(&self, declared_generics: &BTreeSet<String>) -> bool;
fn is_simple_path(&self) -> bool;
}
impl TypeExt for Type {
fn references_generics(&self, declared_generics: &BTreeSet<String>) -> bool {
match self {
Type::Path(type_path) => {
if type_path.path.segments.len() == 1 {
let segment = &type_path.path.segments[0];
if segment.arguments.is_empty() {
let ident_str = segment.ident.to_string();
if declared_generics.contains(&ident_str) {
return true;
}
}
}
for segment in &type_path.path.segments {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
for arg in &args.args {
if let syn::GenericArgument::Type(inner_ty) = arg {
if inner_ty.references_generics(declared_generics) {
return true;
}
}
}
}
}
}
Type::Reference(type_ref) => {
return type_ref.elem.references_generics(declared_generics);
}
Type::Tuple(type_tuple) => {
for elem in &type_tuple.elems {
if elem.references_generics(declared_generics) {
return true;
}
}
}
Type::Array(type_array) => {
return type_array.elem.references_generics(declared_generics);
}
Type::Slice(type_slice) => {
return type_slice.elem.references_generics(declared_generics);
}
Type::Ptr(type_ptr) => {
return type_ptr.elem.references_generics(declared_generics);
}
_ => {
}
}
false
}
fn is_simple_path(&self) -> bool {
match self {
Type::Path(type_path) => {
type_path.path.segments.len() == 1
&& type_path.path.segments[0].arguments.is_empty()
}
_ => false,
}
}
}
trait _UnusedTypeAnalysis {
fn collect_generic_names_recursive(&self, names: &mut BTreeSet<String>);
fn collect_lifetime_names_recursive(&self, names: &mut BTreeSet<String>);
}
impl _UnusedTypeAnalysis for Type {
fn collect_generic_names_recursive(&self, names: &mut BTreeSet<String>) {
match self {
Type::Path(type_path) => {
if type_path.path.segments.len() == 1
&& type_path.path.segments[0].arguments.is_empty()
{
let ident_str = type_path.path.segments[0].ident.to_string();
if ident_str.len() == 1
&& ident_str.chars().next().is_some_and(|c| c.is_uppercase())
{
names.insert(ident_str);
}
}
for segment in &type_path.path.segments {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
for arg in &args.args {
if let syn::GenericArgument::Type(inner_ty) = arg {
inner_ty.collect_generic_names_recursive(names);
}
}
}
}
}
Type::Reference(type_ref) => {
type_ref.elem.collect_generic_names_recursive(names);
}
Type::Tuple(type_tuple) => {
for elem in &type_tuple.elems {
elem.collect_generic_names_recursive(names);
}
}
Type::Array(type_array) => {
type_array.elem.collect_generic_names_recursive(names);
}
Type::Slice(type_slice) => {
type_slice.elem.collect_generic_names_recursive(names);
}
Type::Ptr(type_ptr) => {
type_ptr.elem.collect_generic_names_recursive(names);
}
_ => {}
}
}
fn collect_lifetime_names_recursive(&self, names: &mut BTreeSet<String>) {
match self {
Type::Reference(type_ref) => {
if let Some(lifetime) = &type_ref.lifetime {
names.insert(lifetime.ident.to_string());
}
type_ref.elem.collect_lifetime_names_recursive(names);
}
Type::Path(type_path) => {
for segment in &type_path.path.segments {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
for arg in &args.args {
match arg {
syn::GenericArgument::Type(inner_ty) => {
inner_ty.collect_lifetime_names_recursive(names);
}
syn::GenericArgument::Lifetime(lifetime) => {
names.insert(lifetime.ident.to_string());
}
_ => {}
}
}
}
}
}
Type::Tuple(type_tuple) => {
for elem in &type_tuple.elems {
elem.collect_lifetime_names_recursive(names);
}
}
Type::Array(type_array) => {
type_array.elem.collect_lifetime_names_recursive(names);
}
Type::Slice(type_slice) => {
type_slice.elem.collect_lifetime_names_recursive(names);
}
Type::Ptr(type_ptr) => {
type_ptr.elem.collect_lifetime_names_recursive(names);
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn test_generics_ext_collect_names() {
let generics: Generics = parse_quote!(<'a, T: Clone, U, const N: usize>);
let generic_names = generics.collect_generic_names();
assert!(generic_names.contains("T"));
assert!(generic_names.contains("U"));
assert!(generic_names.contains("N"));
assert_eq!(generic_names.len(), 3);
let lifetime_names = generics.collect_lifetime_names();
assert!(lifetime_names.contains("a"));
assert_eq!(lifetime_names.len(), 1);
}
#[test]
fn test_generics_ext_predicates() {
let generics: Generics = parse_quote!(<'a, T: Clone, const N: usize>);
assert!(!generics.is_empty());
assert!(generics.has_type_params());
assert!(generics.has_lifetime_params());
assert!(generics.has_const_params());
let empty_generics: Generics = parse_quote!();
assert!(empty_generics.is_empty());
assert!(!empty_generics.has_type_params());
}
#[test]
fn test_type_ext_references_generics() {
let mut declared_generics = BTreeSet::new();
declared_generics.insert("T".to_string());
let generic_type: Type = parse_quote!(T);
assert!(generic_type.references_generics(&declared_generics));
let concrete_type: Type = parse_quote!(String);
assert!(!concrete_type.references_generics(&declared_generics));
let complex_type: Type = parse_quote!(Vec<T>);
assert!(complex_type.references_generics(&declared_generics));
}
#[test]
fn test_type_ext_is_simple_path() {
let simple_type: Type = parse_quote!(T);
assert!(simple_type.is_simple_path());
let complex_type: Type = parse_quote!(Vec<T>);
assert!(!complex_type.is_simple_path());
let reference_type: Type = parse_quote!(&T);
assert!(!reference_type.is_simple_path());
}
}