df-derive-macros 0.3.0

Procedural derive macro implementation for df-derive.
Documentation
use crate::ir::StructIR;
use quote::ToTokens;
use syn::{GenericArgument, GenericParam, Ident, PathArguments, Type};

#[allow(clippy::struct_field_names)]
pub(in crate::codegen) struct GenericContext {
    type_params: Vec<Ident>,
    const_params: Vec<Ident>,
    lifetime_params: Vec<Ident>,
}

impl GenericContext {
    pub(in crate::codegen) fn new(ir: &StructIR) -> Self {
        let mut type_params = Vec::new();
        let mut const_params = Vec::new();
        let mut lifetime_params = Vec::new();

        for param in &ir.generics.params {
            match param {
                GenericParam::Type(type_param) => type_params.push(type_param.ident.clone()),
                GenericParam::Const(const_param) => const_params.push(const_param.ident.clone()),
                GenericParam::Lifetime(lifetime_param) => {
                    lifetime_params.push(lifetime_param.lifetime.ident.clone());
                }
            }
        }

        Self {
            type_params,
            const_params,
            lifetime_params,
        }
    }

    fn has_type_param(&self, ident: &Ident) -> bool {
        self.type_params.iter().any(|param| param == ident)
    }

    fn has_lifetime_param(&self, ident: &Ident) -> bool {
        self.lifetime_params.iter().any(|param| param == ident)
    }

    const fn has_const_params(&self) -> bool {
        !self.const_params.is_empty()
    }
}

#[derive(PartialEq, Eq)]
struct TypeKey(String);

impl TypeKey {
    fn new(ty: &Type) -> Self {
        Self(ty.to_token_stream().to_string())
    }
}

pub(in crate::codegen) fn push_unique_type(out: &mut Vec<Type>, ty: &Type) {
    let key = TypeKey::new(ty);
    if !out.iter().any(|existing| TypeKey::new(existing) == key) {
        out.push(ty.clone());
    }
}

fn path_depends_on_generics(path: &syn::Path, generic_ctx: &GenericContext) -> bool {
    path.segments.iter().any(|segment| {
        if generic_ctx.has_type_param(&segment.ident) {
            return true;
        }

        let PathArguments::AngleBracketed(args) = &segment.arguments else {
            return false;
        };

        args.args
            .iter()
            .any(|arg| generic_argument_depends_on_generics(arg, generic_ctx))
    })
}

fn generic_argument_depends_on_generics(
    arg: &GenericArgument,
    generic_ctx: &GenericContext,
) -> bool {
    match arg {
        GenericArgument::Lifetime(lifetime) => generic_ctx.has_lifetime_param(&lifetime.ident),
        GenericArgument::Type(ty) => type_depends_on_generics(ty, generic_ctx),
        GenericArgument::Const(_) => generic_ctx.has_const_params(),
        GenericArgument::AssocType(assoc) => type_depends_on_generics(&assoc.ty, generic_ctx),
        GenericArgument::Constraint(constraint) => constraint.bounds.iter().any(|bound| {
            if let syn::TypeParamBound::Trait(trait_bound) = bound {
                path_depends_on_generics(&trait_bound.path, generic_ctx)
            } else {
                false
            }
        }),
        _ => false,
    }
}

pub(in crate::codegen) fn type_depends_on_generics(
    ty: &Type,
    generic_ctx: &GenericContext,
) -> bool {
    match ty {
        Type::Array(array) => {
            generic_ctx.has_const_params()
                || type_depends_on_generics(array.elem.as_ref(), generic_ctx)
        }
        Type::Group(group) => type_depends_on_generics(group.elem.as_ref(), generic_ctx),
        Type::Paren(paren) => type_depends_on_generics(paren.elem.as_ref(), generic_ctx),
        Type::Path(type_path) => {
            if let Some(qself) = &type_path.qself
                && type_depends_on_generics(qself.ty.as_ref(), generic_ctx)
            {
                return true;
            }

            if type_path.qself.is_none()
                && type_path.path.segments.len() == 1
                && let Some(segment) = type_path.path.segments.last()
                && matches!(segment.arguments, PathArguments::None)
                && generic_ctx.has_type_param(&segment.ident)
            {
                return true;
            }

            path_depends_on_generics(&type_path.path, generic_ctx)
        }
        Type::Ptr(ptr) => type_depends_on_generics(ptr.elem.as_ref(), generic_ctx),
        Type::Reference(reference) => {
            type_depends_on_generics(reference.elem.as_ref(), generic_ctx)
        }
        Type::Slice(slice) => type_depends_on_generics(slice.elem.as_ref(), generic_ctx),
        Type::Tuple(tuple) => tuple
            .elems
            .iter()
            .any(|elem| type_depends_on_generics(elem, generic_ctx)),
        _ => false,
    }
}