df-derive-macros 0.3.0

Procedural derive macro implementation for df-derive.
Documentation
use crate::type_analysis::{AnalyzedBase, AnalyzedType};
use syn::Ident;

use super::errors;

fn is_direct_self_type(ty: &syn::Type, struct_name: &Ident) -> bool {
    let syn::Type::Path(type_path) = ty else {
        return false;
    };

    if type_path.qself.is_some() {
        return false;
    }

    let segments = &type_path.path.segments;

    match segments.len() {
        1 => {
            let Some(last) = segments.last() else {
                return false;
            };

            last.ident == "Self" || last.ident == *struct_name
        }
        2 => {
            let Some(first) = segments.first() else {
                return false;
            };
            let Some(last) = segments.last() else {
                return false;
            };

            matches!(first.arguments, syn::PathArguments::None)
                && (first.ident == "crate" || first.ident == "self")
                && last.ident == *struct_name
        }
        _ => false,
    }
}

pub fn reject_direct_self_reference(
    analyzed: &AnalyzedType,
    field_display_name: &str,
    struct_name: &Ident,
) -> Result<(), syn::Error> {
    match &analyzed.base {
        AnalyzedBase::Struct(ty) if is_direct_self_type(ty, struct_name) => Err(
            errors::direct_self_reference(ty, field_display_name, struct_name),
        ),
        AnalyzedBase::Tuple(elements) => {
            for element in elements {
                reject_direct_self_reference(element, field_display_name, struct_name)?;
            }
            Ok(())
        }
        _ => Ok(()),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn assert_direct(ty: &syn::Type) {
        assert!(is_direct_self_type(ty, &syn::parse_quote!(Node)));
    }

    fn assert_not_direct(ty: &syn::Type) {
        assert!(!is_direct_self_type(ty, &syn::parse_quote!(Node)));
    }

    #[test]
    fn rejects_direct_self_recursion_shapes() {
        assert_direct(&syn::parse_quote!(Self));
        assert_direct(&syn::parse_quote!(Node<T>));
        assert_direct(&syn::parse_quote!(crate::Node<T>));
        assert_direct(&syn::parse_quote!(self::Node<T>));
        assert_direct(&syn::parse_quote!(crate::Node<'a, T>));
        assert_direct(&syn::parse_quote!(crate::Node<Assoc = X>));
    }

    #[test]
    fn allows_non_direct_self_like_paths() {
        assert_not_direct(&syn::parse_quote!(other::Node<T>));
        assert_not_direct(&syn::parse_quote!(super::Node<T>));
        assert_not_direct(&syn::parse_quote!(crate::module::Node<T>));
    }
}