use crate::crate_ident::CrateIdent; use crate::parsing::*; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; use syn::visit::Visit; use syn::{GenericArgument, GenericParam, Ident, Lifetime, PathArguments, Type, TypeParam, TypeParamBound}; pub(crate) struct GNodeTokens { pub(crate) in_mod: TokenStream2, pub(crate) top_level: TokenStream2, } pub(crate) fn generate_gnode_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn) -> syn::Result { let core_types = crate_ident.gcore()?; let ctx_param = context_param(parsed); let ctx_ident = match ctx_param { Some(ctx_param) => ctx_param.ident.clone(), None => format_ident!("__Ctx"), }; let ctx_bounds: Vec = match ctx_param { Some(ctx_param) => ctx_param .bounds .iter() .filter_map(|bound| match bound { TypeParamBound::Lifetime(_) => None, bound => Some(desugar_extract_lifetime(bound, core_types)), }) .collect(), None => vec![quote!(#core_types::Ctx)], }; let derives = ctx_param.is_some_and(|ctx_param| { ctx_param.bounds.iter().any(|bound| match bound { TypeParamBound::Trait(trait_bound) => trait_bound.path.segments.last().is_some_and(|segment| segment.ident == "DeriveCtx"), _ => false, }) }); let ctx_generic = match ctx_bounds.is_empty() { true => quote!(#ctx_ident), false => quote!(#ctx_ident: #(#ctx_bounds)+*), }; let mut generics: Vec = parsed .fn_generics .iter() .map(|param| match param { GenericParam::Type(type_param) if Some(&type_param.ident) == ctx_param.map(|ctx_param| &ctx_param.ident) => ctx_generic.clone(), param => quote!(#param), }) .collect(); if ctx_param.is_none() { generics.push(ctx_generic); } let fn_name = &parsed.fn_name; let mod_name = format_ident!("_{}_mod", parsed.mod_name); let struct_name = format_ident!("{}Node", parsed.struct_name); let output_type = &parsed.output_type; let trait_output = match kernel_kind(&parsed.output_type) { KernelKind::Interrupt(inner) | KernelKind::Poll(inner) => inner, KernelKind::Plain => parsed.output_type.clone(), }; let raw_lazy = matches!(kernel_kind(&parsed.output_type), KernelKind::Poll(_)); let where_predicates: Vec = parsed.where_clause.iter().flat_map(|clause| clause.predicates.iter()).map(|predicate| quote!(#predicate)).collect(); let (data_fields, regular_fields): (Vec<_>, Vec<_>) = parsed.fields.iter().partition(|field| field.is_data_field); let data_field_generic_idents: Vec = parsed .fn_generics .iter() .filter_map(|generic| match generic { GenericParam::Type(type_param) => Some(type_param.ident.clone()), _ => None, }) .filter(|ident| { data_fields.iter().any(|field| match &field.ty { ParsedFieldType::Regular(RegularParsedField { ty, .. }) => crate::codegen::type_contains_ident(ty, ident), _ => false, }) }) .collect(); let node_generics: Vec = regular_fields.iter().enumerate().map(|(index, _)| format_ident!("Node{}", index)).collect(); let struct_type_params: Vec = data_field_generic_idents.iter().cloned().chain(node_generics.iter().cloned()).collect(); let data_names: Vec<&Ident> = data_fields.iter().map(|field| &field.pat_ident.ident).collect(); let data_params = data_fields.iter().map(|field| { let pat = &field.pat_ident; let ParsedFieldType::Regular(RegularParsedField { ty, .. }) = &field.ty else { unreachable!("data fields are regular types"); }; quote!(#pat: &#ty) }); let lazy_bound = |output_type: &Type| match derives { true => quote!(for<'__derived> #core_types::gnode::GNode<#core_types::context::Derived<'__derived, #ctx_ident>, Output = #output_type>), false => quote!(#core_types::gnode::GNode<#ctx_ident, Output = #output_type>), }; let kernel_params = regular_fields.iter().map(|field| { let pat = &field.pat_ident; match &field.ty { ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#pat: #ty), ParsedFieldType::Node(NodeParsedField { output_type, .. }) if raw_lazy => { let bound = lazy_bound(output_type); quote!(#pat: &impl #bound) } ParsedFieldType::Node(NodeParsedField { output_type, .. }) => { let bound = lazy_bound(output_type); quote!(#pat: #core_types::gnode::LazyInput<'_, impl #bound>) } } }); let node_bounds = regular_fields.iter().zip(&node_generics).map(|(field, node_generic)| match &field.ty { ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#node_generic: #core_types::gnode::GNode<#ctx_ident, Output = #ty>), ParsedFieldType::Node(NodeParsedField { output_type, .. }) => { let bound = lazy_bound(output_type); quote!(#node_generic: #bound) } }); let clampable_bounds = regular_fields.iter().filter_map(|field| { let ParsedFieldType::Regular(RegularParsedField { ty, number_hard_min, number_hard_max, .. }) = &field.ty else { return None; }; (number_hard_min.is_some() || number_hard_max.is_some()).then(|| quote!(#ty: #core_types::misc::Clampable)) }); let eval_values = regular_fields.iter().enumerate().map(|(index, field)| { let name = &field.pat_ident.ident; match &field.ty { ParsedFieldType::Regular(_) => quote! { let #name = match __cell.eval_input(#index, &self.#name, __input) { Ok(value) => value, Err(interrupt) => return interrupt.into(), }; }, ParsedFieldType::Node(_) if raw_lazy => quote!(), ParsedFieldType::Node(_) => quote! { let #name = #core_types::gnode::LazyInput::new(&self.#name, &__cell, #index); }, } }); let clamps = regular_fields.iter().filter_map(|field| { let ParsedFieldType::Regular(RegularParsedField { number_hard_min, number_hard_max, .. }) = &field.ty else { return None; }; let name = &field.pat_ident.ident; let mut tokens = quote!(); if let Some(min) = number_hard_min { tokens.extend(quote!(let #name = #core_types::misc::Clampable::clamp_hard_min(#name, #min);)); } if let Some(max) = number_hard_max { tokens.extend(quote!(let #name = #core_types::misc::Clampable::clamp_hard_max(#name, #max);)); } (!tokens.is_empty()).then_some(tokens) }); let call_args = regular_fields.iter().map(|field| { let name = &field.pat_ident.ident; match &field.ty { ParsedFieldType::Node(_) if raw_lazy => quote!(&self.#name), _ => quote!(#name), } }); let value_field_names: Vec<&Ident> = regular_fields .iter() .filter(|field| matches!(field.ty, ParsedFieldType::Regular(_))) .map(|field| &field.pat_ident.ident) .collect(); let extent_impl = match &parsed.attributes.extent { Some(path) => quote! { fn extent(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent> { #path(self, __input) } }, None if value_field_names.is_empty() => quote!(), None => { let first = value_field_names[0]; let mut meet = quote!(self.#first.extent(__input)); for name in &value_field_names[1..] { meet = quote!(#core_types::gpoll::Extent::meet(#meet, self.#name.extent(__input))); } quote! { fn extent(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent> { #meet } } } }; let batch_impl = match &parsed.attributes.batch { Some(path) => quote! { fn eval_batch<'__batch>( &self, __input: &'__batch #ctx_ident, __range: ::std::ops::Range, __scratch: Option<&'__batch mut [::std::mem::MaybeUninit]>, ) -> #core_types::gnode::BatchStatus<'__batch, Self::Output> where #ctx_ident: #core_types::context::InjectIndex + Copy, { #path(self, __input, __range, __scratch) } }, None => quote!(), }; let ctx_pat = &parsed.input.pat_ident; let fn_where = &parsed.where_clause; let body = &parsed.body; let vis = &parsed.vis; let kernel = quote! { #[allow(clippy::too_many_arguments)] #vis fn #fn_name<#(#generics,)*>(#ctx_pat: &#ctx_ident #(, #data_params)* #(, #kernel_params)*) -> #output_type #fn_where #body }; let cell_constructor = match parsed.attributes.no_partial { true => quote!(#core_types::gnode::StatusCell::no_partial()), false => quote!(#core_types::gnode::StatusCell::new()), }; let kernel_call = quote!(self::#fn_name(__input #(, &self.#data_names)* #(, #call_args)*)); let lift = match kernel_kind(&parsed.output_type) { KernelKind::Interrupt(_) => quote! { match #kernel_call { Ok(value) => __cell.finish(value), Err(interrupt) => interrupt.into(), } }, KernelKind::Poll(_) => quote!(__cell.merge(#kernel_call)), KernelKind::Plain => quote!(__cell.finish(#kernel_call)), }; let wire = entries_tokens(parsed, &struct_name, &data_field_generic_idents, ®ular_fields); let cfg = crate::shader_nodes::modify_cfg(&parsed.attributes); let wire_reexport = match wire.is_empty() { true => quote!(), false => { let entries_name = format_ident!("{}_entries", fn_name); quote! { #cfg #[doc(hidden)] pub use #mod_name::#entries_name; } } }; let top_level = quote! { #wire_reexport #cfg #[automatically_derived] impl<#(#generics,)* #(#node_generics,)*> #core_types::gnode::GNode<#ctx_ident> for #mod_name::#struct_name<#(#struct_type_params,)*> where #(#node_bounds,)* #(#clampable_bounds,)* #(#where_predicates,)* { type Output = #trait_output; fn eval(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll { let __cell = #cell_constructor; #(#eval_values)* #(#clamps)* #lift } #extent_impl #batch_impl } }; Ok(GNodeTokens { in_mod: wire, top_level: quote! { #kernel #top_level }, }) } enum KernelKind { Plain, Interrupt(Type), Poll(Type), } fn kernel_kind(output: &Type) -> KernelKind { let plain = || KernelKind::Plain; let Type::Path(path) = output else { return plain() }; let Some(segment) = path.path.segments.last() else { return plain() }; match segment.ident.to_string().as_str() { "GPoll" => { let PathArguments::AngleBracketed(args) = &segment.arguments else { return plain() }; let inner = args.args.iter().find_map(|argument| match argument { GenericArgument::Type(ty) => Some(ty.clone()), _ => None, }); inner.map(KernelKind::Poll).unwrap_or_else(plain) } "Result" => { let PathArguments::AngleBracketed(args) = &segment.arguments else { return plain() }; let mut types = args.args.iter().filter_map(|argument| match argument { GenericArgument::Type(ty) => Some(ty), _ => None, }); let (Some(inner), Some(Type::Path(error_path))) = (types.next(), types.next()) else { return plain(); }; match error_path.path.segments.last().is_some_and(|segment| segment.ident == "Interrupt") { true => KernelKind::Interrupt(inner.clone()), false => plain(), } } _ => plain(), } } fn context_param<'a>(parsed: &'a ParsedNodeFn) -> Option<&'a TypeParam> { let Type::Path(path) = &parsed.input.ty else { return None; }; let ident = path.path.get_ident()?; parsed.fn_generics.iter().find_map(|param| match param { GenericParam::Type(type_param) if &type_param.ident == ident => Some(type_param), _ => None, }) } fn type_disqualifies(ty: &Type) -> bool { struct Disqualifier { found: bool, } impl<'ast> Visit<'ast> for Disqualifier { fn visit_type_reference(&mut self, _: &'ast syn::TypeReference) { self.found = true; } fn visit_type_impl_trait(&mut self, _: &'ast syn::TypeImplTrait) { self.found = true; } fn visit_lifetime(&mut self, _: &'ast Lifetime) { self.found = true; } } let mut visitor = Disqualifier { found: false }; visitor.visit_type(ty); visitor.found } fn desugar_extract_lifetime(bound: &TypeParamBound, core_types: &TokenStream2) -> TokenStream2 { let TypeParamBound::Trait(trait_bound) = bound else { return quote!(#bound); }; let Some(segment) = trait_bound.path.segments.last() else { return quote!(#bound); }; if segment.ident != "ExtractArena" { return quote!(#bound); } let PathArguments::AngleBracketed(args) = &segment.arguments else { return quote!(#bound); }; if args.args.len() != 1 { return quote!(#bound); } let Some(GenericArgument::Lifetime(lifetime)) = args.args.first() else { return quote!(#bound); }; quote!(#core_types::context::ExtractArena) } fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic_idents: &[Ident], regular_fields: &[&ParsedField]) -> TokenStream2 { if !data_field_generic_idents.is_empty() { return quote!(); } let Some(rows) = implementation_rows(parsed, regular_fields) else { return quote!(); }; let rows: Vec<&Vec> = rows.iter().filter(|row| row.iter().all(|ty| !type_disqualifies(ty))).collect(); if rows.is_empty() { return quote!(); } let fn_name = &parsed.fn_name; let entries_name = format_ident!("{}_entries", fn_name); let arity = regular_fields.len(); let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect(); let entries = rows.iter().map(|row| { let types = row.iter(); let boxed_types = row.iter().map(|ty| quote!(::std::boxed::Box>)); let output = quote!(<#struct_name<#(#boxed_types),*> as gcore::gnode::GNode>>::Output); let downcasts = names.iter().zip(row.iter()).map(|(name, ty)| { quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;) }); quote! { gcore::wire::RegistryEntry { io: gcore::wire::NodeIoRecord { inputs: vec![#(gcore::concrete!(#types)),*], output: gcore::concrete!(#output), }, wire: |inputs| { if inputs.len() != #arity { return Err(gcore::wire::WireError::Arity { expected: #arity, got: inputs.len() }); } let mut inputs = inputs.into_iter(); #(#downcasts)* Ok(gcore::wire::EdgeHandle::new(::std::boxed::Box::new(#struct_name::new(#(#names),*)) as ::std::boxed::Box>)) }, } } }); quote! { pub fn #entries_name() -> ::std::vec::Vec { vec![#(#entries),*] } } } fn implementation_rows(parsed: &ParsedNodeFn, regular_fields: &[&ParsedField]) -> Option>> { let ctx_ident = context_param(parsed).map(|ctx| ctx.ident.clone()); let open_generics: Vec<&Ident> = parsed .fn_generics .iter() .filter_map(|param| match param { GenericParam::Type(type_param) if Some(&type_param.ident) != ctx_ident.as_ref() => Some(&type_param.ident), _ => None, }) .collect(); let candidates: Vec> = regular_fields .iter() .map(|field| match &field.ty { ParsedFieldType::Regular(RegularParsedField { ty, implementations, .. }) => match implementations.is_empty() { false => Some(implementations.iter().cloned().collect()), true => open_generics.iter().all(|generic| !crate::codegen::type_contains_ident(ty, generic)).then(|| vec![ty.clone()]), }, ParsedFieldType::Node(NodeParsedField { output_type, implementations, .. }) => match implementations.is_empty() { false => Some(implementations.iter().map(|implementation| implementation.output.clone()).collect()), true => open_generics.iter().all(|generic| !crate::codegen::type_contains_ident(output_type, generic)).then(|| vec![output_type.clone()]), }, }) .collect::>()?; let row_count = candidates.iter().map(|types| types.len()).max().unwrap_or(1).max(1); Some( (0..row_count) .map(|row| candidates.iter().map(|types| types[row.min(types.len() - 1)].clone()).collect()) .collect(), ) }