use crate::parsing::{Implementation, ParsedField, ParsedNodeFn}; use proc_macro_error2::emit_error; use quote::quote; use syn::{spanned::Spanned, GenericParam, Type}; pub fn validate_node_fn(parsed: &ParsedNodeFn) -> syn::Result<()> { let validators: &[fn(&ParsedNodeFn)] = &[ // Add more validators here as needed validate_implementations_for_generics, validate_primary_input_expose, ]; for validator in validators { validator(parsed); } Ok(()) } fn validate_primary_input_expose(parsed: &ParsedNodeFn) { if let Some(ParsedField::Regular { exposed: true, pat_ident, .. }) = parsed.fields.first() { emit_error!( pat_ident.span(), "Unnecessary #[expose] attribute on primary input `{}`. Primary inputs are always exposed.", pat_ident.ident; help = "You can safely remove the #[expose] attribute from this field."; note = "The function's second argument, `{}`, is the node's primary input and it's always exposed by default", pat_ident.ident ); } } fn validate_implementations_for_generics(parsed: &ParsedNodeFn) { let has_skip_impl = parsed.attributes.skip_impl; if !has_skip_impl && !parsed.fn_generics.is_empty() { for field in &parsed.fields { match field { ParsedField::Regular { ty, implementations, pat_ident, .. } => { if contains_generic_param(ty, &parsed.fn_generics) && implementations.is_empty() { emit_error!( ty.span(), "Generic type `{}` in field `{}` requires an #[implementations(...)] attribute", quote!(#ty), pat_ident.ident; help = "Add #[implementations(ConcreteType1, ConcreteType2)] to field '{}'", pat_ident.ident; help = "Or use #[skip_impl] if you want to manually implement the node" ); } } ParsedField::Node { input_type, output_type, implementations, pat_ident, .. } => { if (contains_generic_param(input_type, &parsed.fn_generics) || contains_generic_param(output_type, &parsed.fn_generics)) && implementations.is_empty() { emit_error!( pat_ident.span(), "Generic types in Node field `{}` require an #[implementations(...)] attribute", pat_ident.ident; help = "Add #[implementations(InputType1 -> OutputType1, InputType2 -> OutputType2)] to field '{}'", pat_ident.ident; help = "Or use #[skip_impl] if you want to manually implement the node" ); } // Additional check for Node implementations for impl_ in implementations { validate_node_implementation(impl_, input_type, output_type, &parsed.fn_generics); } } } } } } fn validate_node_implementation(impl_: &Implementation, input_type: &Type, output_type: &Type, fn_generics: &[GenericParam]) { if contains_generic_param(&impl_.input, fn_generics) || contains_generic_param(&impl_.output, fn_generics) { emit_error!( impl_.input.span(), "Implementation types `{}` and `{}` must be concrete, not generic", quote!(#input_type), quote!(#output_type); help = "Replace generic types with concrete types in the implementation" ); } } fn contains_generic_param(ty: &Type, fn_generics: &[GenericParam]) -> bool { struct GenericParamChecker<'a> { fn_generics: &'a [GenericParam], found: bool, } impl<'a> syn::visit::Visit<'a> for GenericParamChecker<'a> { fn visit_ident(&mut self, ident: &'a syn::Ident) { if self .fn_generics .iter() .any(|param| if let GenericParam::Type(type_param) = param { type_param.ident == *ident } else { false }) { self.found = true; } } } let mut checker = GenericParamChecker { fn_generics, found: false }; syn::visit::visit_type(&mut checker, ty); checker.found }