Support author-named serving lifetimes in the node macro

This commit is contained in:
Dennis Kobert
2026-08-28 13:30:06 +00:00
parent f34ec257e7
commit 1ef7c9c2c2
4 changed files with 190 additions and 30 deletions
+77 -17
View File
@@ -275,7 +275,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
})); }));
let async_source = parsed.injects_async_source_fields(); let async_source = parsed.injects_async_source_fields();
let slot_value_type = slot_value_type(output_type); let slot_value_type = crate::codegen::classify::substitute_lifetimes(&slot_value_type(output_type), "'static");
let slot_field = async_source let slot_field = async_source
.then(|| quote! { pub(super) slot: std::sync::Arc<std::sync::Mutex<std::collections::HashMap<u64, Option<gcore::gpoll::GPoll<#slot_value_type>>>>> }) .then(|| quote! { pub(super) slot: std::sync::Arc<std::sync::Mutex<std::collections::HashMap<u64, Option<gcore::gpoll::GPoll<#slot_value_type>>>>> })
.into_iter(); .into_iter();
@@ -886,6 +886,12 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
.iter() .iter()
.filter(|param| match param { .filter(|param| match param {
GenericParam::Type(type_param) => Some(&type_param.ident) != routing_generic.as_ref() && Some(&type_param.ident) != record_token.as_ref(), GenericParam::Type(type_param) => Some(&type_param.ident) != routing_generic.as_ref() && Some(&type_param.ident) != record_token.as_ref(),
// A serving lifetime stays only while the ctx bound constrains it
// (`ExtractArena<'e>`); wire types substitute its erased
// projection, which would leave it unconstrained. A flipped
// kernel's serving lifetime rebinds to the record lifetime, so
// the impl drops it entirely.
GenericParam::Lifetime(lifetime_param) => !flip && ctx_param.is_some_and(|ctx| quote!(#ctx).to_string().contains(&lifetime_param.lifetime.to_string())),
_ => true, _ => true,
}) })
.map(&generic_tokens) .map(&generic_tokens)
@@ -897,6 +903,21 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
if routing_generic.is_some() || record_io || flip { if routing_generic.is_some() || record_io || flip {
impl_generics.insert(0, quote!('__record)); impl_generics.insert(0, quote!('__record));
} }
// A flipped kernel's serving lifetime is the record lifetime at the impl:
// the ctx bound rebinds under the impl's own name.
if flip {
let serving_names: Vec<String> = parsed
.fn_generics
.iter()
.filter_map(|param| match param {
GenericParam::Lifetime(lifetime_param) => Some(lifetime_param.lifetime.ident.to_string()),
_ => None,
})
.collect();
if !serving_names.is_empty() {
impl_generics = impl_generics.into_iter().map(|tokens| crate::codegen::classify::rename_lifetimes_to_record(tokens, &serving_names)).collect();
}
}
let lazy_carrier = record_io && carrier_present && matches!(parsed.fields.iter().find(|field| !field.is_data_field).map(|field| &field.ty), Some(ParsedFieldType::Node(_))); let lazy_carrier = record_io && carrier_present && matches!(parsed.fields.iter().find(|field| !field.is_data_field).map(|field| &field.ty), Some(ParsedFieldType::Node(_)));
if derive_routing || (lazy_carrier && derives) { if derive_routing || (lazy_carrier && derives) {
generics.insert(0, quote!('__record)); generics.insert(0, quote!('__record));
@@ -1027,10 +1048,30 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
match &field.ty { match &field.ty {
ParsedFieldType::Regular(RegularParsedField { ty, .. }) if ir::materialized_levels(&node, index) > 0 => { ParsedFieldType::Regular(RegularParsedField { ty, .. }) if ir::materialized_levels(&node, index) > 0 => {
// The gathered lane borrows this list, so both take the kernel's // The gathered lane borrows this list, so both take the kernel's
// own subject lifetime. // own subject lifetime. An element type naming a serving
match ir::gathered_subject(&node) == Some(index) { // lifetime ties the view to the same region; a fn-declared
true => quote!(#pat: #core_types::node::List<'__lane, #ty>), // serving lifetime binds a generic subject's view, so the
false => quote!(#pat: #core_types::node::List<'_, #ty>), // output can borrow the materialized level.
let declared = || {
let mut lifetimes = parsed.fn_generics.iter().filter_map(|param| match param {
GenericParam::Lifetime(lifetime_param) => Some(lifetime_param.lifetime.clone()),
_ => None,
});
lifetimes
.next()
.filter(|_| lifetimes.next().is_none())
.filter(|lifetime| match &node.output.shape.element {
ir::Element::Concrete(element) => crate::codegen::classify::named_serving_lifetime(element).as_ref() == Some(lifetime),
_ => false,
})
// An arena-bound lifetime serves the output from the
// arena, not from the subject's batch view.
.filter(|lifetime| !ctx_param.is_some_and(|ctx| quote!(#ctx).to_string().contains(&lifetime.to_string())))
};
match (crate::codegen::classify::named_serving_lifetime(ty).or_else(declared), ir::gathered_subject(&node) == Some(index)) {
(Some(lifetime), _) => quote!(#pat: #core_types::node::List<#lifetime, #ty>),
(None, true) => quote!(#pat: #core_types::node::List<'__lane, #ty>),
(None, false) => quote!(#pat: #core_types::node::List<'_, #ty>),
} }
} }
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(#pat: &#ty), ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(#pat: &#ty),
@@ -1214,6 +1255,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
ValueBinding::Carrier => quote!(), ValueBinding::Carrier => quote!(),
ValueBinding::Materialized => { ValueBinding::Materialized => {
let fn_name = &parsed.fn_name; let fn_name = &parsed.fn_name;
let ty = &crate::codegen::classify::substitute_lifetimes(ty, "'_");
let cache_slot = format_ident!("__mat_cache_{index}"); let cache_slot = format_ident!("__mat_cache_{index}");
let non_exact = fail(quote!(#core_types::gpoll::GraphError::new(::std::concat!("reduce over a non-exact extent in ", ::std::stringify!(#fn_name))))); let non_exact = fail(quote!(#core_types::gpoll::GraphError::new(::std::concat!("reduce over a non-exact extent in ", ::std::stringify!(#fn_name)))));
let batch_error = fail(quote!(__error)); let batch_error = fail(quote!(__error));
@@ -1479,6 +1521,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
// span, as in eval), so a data-dependent extent can walk // span, as in eval), so a data-dependent extent can walk
// its lanes. // its lanes.
ValueBinding::Materialized => { ValueBinding::Materialized => {
let ty = &crate::codegen::classify::substitute_lifetimes(ty, "'_");
quote! { quote! {
let #query = || { let #query = || {
let __arena = #core_types::context::ExtractArena::arena(__input); let __arena = #core_types::context::ExtractArena::arena(__input);
@@ -1875,6 +1918,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
}; };
// A droppable element parks in the arena and rides as a reference. // A droppable element parks in the arena and rides as a reference.
let element_store = element_write.map(|ty| { let element_store = element_write.map(|ty| {
let ty = &crate::codegen::classify::substitute_lifetimes(ty, "'_");
quote! { quote! {
if unsafe { #core_types::record::write_element::<#ty>(__dst, __element, #core_types::context::ExtractArena::arena(__input)) }.is_none() { if unsafe { #core_types::record::write_element::<#ty>(__dst, __element, #core_types::context::ExtractArena::arena(__input)) }.is_none() {
return #core_types::gpoll::Interrupt::from(#core_types::gpoll::GraphError { return #core_types::gpoll::Interrupt::from(#core_types::gpoll::GraphError {
@@ -2044,6 +2088,13 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
Tail::Forward if routing_generic.is_some() => Some(quote!(let __poll = #lift;)), Tail::Forward if routing_generic.is_some() => Some(quote!(let __poll = #lift;)),
_ => None, _ => None,
}; };
// A serving-lifetime element rides the per-lane fill loop: the hoisted
// batch fill cannot yet carry an arena-lifetimed element through the
// caller's scratch.
let hoisted_lane_poll = match &node.output.shape.element {
ir::Element::Concrete(element) if crate::codegen::classify::named_serving_lifetime(element).is_some() => None,
_ => hoisted_lane_poll,
};
let hoisted_batch = parsed.attributes.batch.is_none() && produces_records && hoisted_lane_poll.is_some(); let hoisted_batch = parsed.attributes.batch.is_none() && produces_records && hoisted_lane_poll.is_some();
let batch_impl = match (&parsed.attributes.batch, produces_records, hoisted_lane_poll) { let batch_impl = match (&parsed.attributes.batch, produces_records, hoisted_lane_poll) {
(Some(path), ..) => quote! { (Some(path), ..) => quote! {
@@ -2256,22 +2307,22 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
reading_secondary_indices(&regular_fields, skips_carrier) reading_secondary_indices(&regular_fields, skips_carrier)
.into_iter() .into_iter()
.filter_map(|index| match &regular_fields[index].ty { .filter_map(|index| match &regular_fields[index].ty {
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some(quote!(#ty: ::core::clone::Clone)), ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some({ let ty = &crate::codegen::classify::substitute_lifetimes(ty, "'static"); quote!(#ty: ::core::clone::Clone) }),
_ => None, _ => None,
}), }),
); );
if let Some(ty) = carrier_read_ty { if let Some(ty) = carrier_read_ty {
bounds.push(quote!(#ty: ::core::clone::Clone)); bounds.push({ let ty = &crate::codegen::classify::substitute_lifetimes(ty, "'static"); quote!(#ty: ::core::clone::Clone) });
} }
// The element store parks droppable elements in the arena. // The element store parks droppable elements in the arena.
if let Some(ty) = element_write { if let Some(ty) = element_write {
bounds.push(quote!(#ty: ::core::marker::Send + ::core::marker::Sync + 'static)); bounds.push({ let ty = &crate::codegen::classify::substitute_lifetimes(ty, "'static"); quote!(#ty: ::core::marker::Send + ::core::marker::Sync + 'static) });
} }
} }
// A routing node's value elements copy out of their records. // A routing node's value elements copy out of their records.
if let Some(generic) = &routing_generic { if let Some(generic) = &routing_generic {
bounds.extend(routing_value_indices(&regular_fields, generic).into_iter().filter_map(|index| match &regular_fields[index].ty { bounds.extend(routing_value_indices(&regular_fields, generic).into_iter().filter_map(|index| match &regular_fields[index].ty {
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some(quote!(#ty: ::core::clone::Clone)), ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some({ let ty = &crate::codegen::classify::substitute_lifetimes(ty, "'static"); quote!(#ty: ::core::clone::Clone) }),
_ => None, _ => None,
})); }));
} }
@@ -2281,7 +2332,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
ParsedFieldType::Regular(RegularParsedField { ty, .. }) ParsedFieldType::Regular(RegularParsedField { ty, .. })
if !node.inputs[index].subject && matches!(ir::value_binding(&node, index), ValueBinding::Plain | ValueBinding::ReadingSecondary | ValueBinding::RecordElement) => if !node.inputs[index].subject && matches!(ir::value_binding(&node, index), ValueBinding::Plain | ValueBinding::ReadingSecondary | ValueBinding::RecordElement) =>
{ {
Some(quote!(#ty: ::core::clone::Clone)) Some({ let ty = &crate::codegen::classify::substitute_lifetimes(ty, "'static"); quote!(#ty: ::core::clone::Clone) })
} }
_ => None, _ => None,
})); }));
@@ -2301,12 +2352,12 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
.filter(|(index, _)| ir::materialized_levels(&node, *index) == 0) .filter(|(index, _)| ir::materialized_levels(&node, *index) == 0)
.filter_map(|(_, field)| match &field.ty { .filter_map(|(_, field)| match &field.ty {
// The conditional arena-park moves a lend element once. // The conditional arena-park moves a lend element once.
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => Some(quote!(#ty: ::core::marker::Send + ::core::marker::Sync + 'static)), ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => Some({ let ty = &crate::codegen::classify::substitute_lifetimes(ty, "'static"); quote!(#ty: ::core::marker::Send + ::core::marker::Sync + 'static) }),
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some(quote!(#ty: ::core::clone::Clone)), ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some({ let ty = &crate::codegen::classify::substitute_lifetimes(ty, "'static"); quote!(#ty: ::core::clone::Clone) }),
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => Some(quote!(#output_type: ::core::clone::Clone)), ParsedFieldType::Node(NodeParsedField { output_type, .. }) => Some({ let output_type = &crate::codegen::classify::substitute_lifetimes(output_type, "'static"); quote!(#output_type: ::core::clone::Clone) }),
}) })
.collect(); .collect();
let out = slot_value_type(&parsed.output_type); let out = crate::codegen::classify::substitute_lifetimes(&slot_value_type(&parsed.output_type), "'static");
bounds.push(quote!(#out: ::core::marker::Send + ::core::marker::Sync + 'static)); bounds.push(quote!(#out: ::core::marker::Send + ::core::marker::Sync + 'static));
bounds bounds
} }
@@ -2363,7 +2414,10 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
let flip_layout_meta_fn = flip_meta_concrete.then(|| { let flip_layout_meta_fn = flip_meta_concrete.then(|| {
let layout_meta_fn = format_ident!("{}_layout_meta", fn_name); let layout_meta_fn = format_ident!("{}_layout_meta", fn_name);
let element_spec = match element_write { let element_spec = match element_write {
Some(ty) => quote!(#core_types::record::ElementSpec::Concrete({ use #core_types::record::{ElementWritePickHashed as _, ElementWritePickPlain as _}; (&#core_types::record::ElementWritePick::<#ty>(::core::marker::PhantomData)).element_write() })), Some(ty) => {
let ty = &crate::codegen::classify::substitute_lifetimes(ty, "'static");
quote!(#core_types::record::ElementSpec::Concrete({ use #core_types::record::{ElementWritePickHashed as _, ElementWritePickPlain as _}; (&#core_types::record::ElementWritePick::<#ty>(::core::marker::PhantomData)).element_write() }))
}
None => quote!(#core_types::record::ElementSpec::Carried), None => quote!(#core_types::record::ElementSpec::Carried),
}; };
let layout_meta = crate::codegen::ir::layout_meta_tokens(&node, element_spec, core_types); let layout_meta = crate::codegen::ir::layout_meta_tokens(&node, element_spec, core_types);
@@ -2392,7 +2446,10 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
.collect(); .collect();
let subtraction = (!remove_pairs.is_empty()).then(|| quote!(.without(&[#(#remove_pairs),*]))); let subtraction = (!remove_pairs.is_empty()).then(|| quote!(.without(&[#(#remove_pairs),*])));
let element = match element_write { let element = match element_write {
Some(ty) => quote!({ use #core_types::record::{ElementWritePickHashed as _, ElementWritePickPlain as _}; (&#core_types::record::ElementWritePick::<#ty>(::core::marker::PhantomData)).element_write() }), Some(ty) => {
let ty = &crate::codegen::classify::substitute_lifetimes(ty, "'static");
quote!({ use #core_types::record::{ElementWritePickHashed as _, ElementWritePickPlain as _}; (&#core_types::record::ElementWritePick::<#ty>(::core::marker::PhantomData)).element_write() })
}
None => quote!(__carrier.element), None => quote!(__carrier.element),
}; };
// A gather carrier's base is the gathered subject's layout, so its free // A gather carrier's base is the gathered subject's layout, so its free
@@ -2411,7 +2468,10 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
}; };
let layout_meta_fn = format_ident!("{}_layout_meta", fn_name); let layout_meta_fn = format_ident!("{}_layout_meta", fn_name);
let element_spec = match element_write { let element_spec = match element_write {
Some(ty) => quote!(#core_types::record::ElementSpec::Concrete({ use #core_types::record::{ElementWritePickHashed as _, ElementWritePickPlain as _}; (&#core_types::record::ElementWritePick::<#ty>(::core::marker::PhantomData)).element_write() })), Some(ty) => {
let ty = &crate::codegen::classify::substitute_lifetimes(ty, "'static");
quote!(#core_types::record::ElementSpec::Concrete({ use #core_types::record::{ElementWritePickHashed as _, ElementWritePickPlain as _}; (&#core_types::record::ElementWritePick::<#ty>(::core::marker::PhantomData)).element_write() }))
}
None => quote!(#core_types::record::ElementSpec::Carried), None => quote!(#core_types::record::ElementSpec::Carried),
}; };
let layout_meta = crate::codegen::ir::layout_meta_tokens(&node, element_spec, core_types); let layout_meta = crate::codegen::ir::layout_meta_tokens(&node, element_spec, core_types);
+102 -6
View File
@@ -425,7 +425,7 @@ pub(crate) fn has_materialized_input(parsed: &ParsedNodeFn) -> bool {
} }
pub(crate) fn record_flip(parsed: &ParsedNodeFn) -> bool { pub(crate) fn record_flip(parsed: &ParsedNodeFn) -> bool {
if record_shape(parsed).is_some() || has_record_io(parsed) || routing_io(parsed).is_some() { if record_shape(parsed).is_some() || has_record_io(parsed) || routing_io(parsed).is_some() || record_opaque(parsed) {
return false; return false;
} }
// Shader nodes flip like any value node: the kernel doubles as the // Shader nodes flip like any value node: the kernel doubles as the
@@ -457,7 +457,10 @@ pub(crate) fn record_flip(parsed: &ParsedNodeFn) -> bool {
return false; return false;
} }
} }
GenericParam::Lifetime(_) | GenericParam::Const(_) => return false, // A named lifetime is the serving lifetime: wire types substitute
// its erased projection in registry and layout contexts.
GenericParam::Lifetime(_) => {}
GenericParam::Const(_) => return false,
} }
} }
true true
@@ -683,10 +686,6 @@ pub(crate) fn type_disqualifies(ty: &Type) -> bool {
fn visit_type_impl_trait(&mut self, _: &'ast syn::TypeImplTrait) { fn visit_type_impl_trait(&mut self, _: &'ast syn::TypeImplTrait) {
self.found = true; self.found = true;
} }
fn visit_lifetime(&mut self, _: &'ast Lifetime) {
self.found = true;
}
} }
let mut visitor = Disqualifier { found: false }; let mut visitor = Disqualifier { found: false };
@@ -694,7 +693,91 @@ pub(crate) fn type_disqualifies(ty: &Type) -> bool {
visitor.found visitor.found
} }
/// The wire type with every named serving lifetime replaced: `'static` for
/// registry, layout, and declaration contexts (the erased projection shares
/// its type id and layout), `'_` for eval bindings, where inference recovers
/// the serving lifetime.
pub(crate) fn substitute_lifetimes(ty: &Type, replacement: &str) -> Type {
struct Subst {
replacement: &'static str,
}
impl VisitMut for Subst {
fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
if lifetime.ident != "static" {
*lifetime = Lifetime::new(self.replacement, lifetime.span());
}
}
}
let replacement = match replacement {
"'static" => "'static",
_ => "'_",
};
let mut ty = ty.clone();
Subst { replacement }.visit_type_mut(&mut ty);
ty
}
/// The serving lifetime a wire type names, so a materialized binding can tie
/// the list view to the element's own region.
pub(crate) fn named_serving_lifetime(ty: &Type) -> Option<Lifetime> {
struct Find {
found: Option<Lifetime>,
}
impl<'ast> Visit<'ast> for Find {
fn visit_lifetime(&mut self, lifetime: &'ast Lifetime) {
if self.found.is_none() && lifetime.ident != "static" {
self.found = Some(lifetime.clone());
}
}
}
let mut visitor = Find { found: None };
visitor.visit_type(ty);
visitor.found
}
pub(crate) fn desugar_extract_lifetime(bound: &TypeParamBound, core_types: &TokenStream2) -> TokenStream2 { pub(crate) fn desugar_extract_lifetime(bound: &TypeParamBound, core_types: &TokenStream2) -> TokenStream2 {
desugar_extract_lifetime_at(bound, core_types, None)
}
/// Renames every occurrence of the named lifetimes to `'__record` in a token
/// stream: a flipped kernel's serving lifetime is the record lifetime at the
/// impl, under whichever name the author picked.
pub(crate) fn rename_lifetimes_to_record(stream: TokenStream2, names: &[String]) -> TokenStream2 {
use proc_macro2::{Group, TokenTree};
let mut out = Vec::new();
let mut tokens = stream.into_iter().peekable();
while let Some(token) = tokens.next() {
match token {
TokenTree::Group(group) => {
let renamed = rename_lifetimes_to_record(group.stream(), names);
let mut fresh = Group::new(group.delimiter(), renamed);
fresh.set_span(group.span());
out.push(TokenTree::Group(fresh));
}
TokenTree::Punct(punct) if punct.as_char() == '\'' => {
match tokens.peek() {
Some(TokenTree::Ident(ident)) if names.iter().any(|name| ident == name) => {
let span = ident.span();
tokens.next();
out.push(TokenTree::Punct(punct));
out.push(TokenTree::Ident(proc_macro2::Ident::new("__record", span)));
}
_ => out.push(TokenTree::Punct(punct)),
}
}
token => out.push(token),
}
}
out.into_iter().collect()
}
/// As [`desugar_extract_lifetime`], with the arena lifetime overridden: a
/// flipped kernel's serving lifetime is the record lifetime at the impl.
pub(crate) fn desugar_extract_lifetime_at(bound: &TypeParamBound, core_types: &TokenStream2, at: Option<TokenStream2>) -> TokenStream2 {
let TypeParamBound::Trait(trait_bound) = bound else { let TypeParamBound::Trait(trait_bound) = bound else {
return quote!(#bound); return quote!(#bound);
}; };
@@ -713,5 +796,18 @@ pub(crate) fn desugar_extract_lifetime(bound: &TypeParamBound, core_types: &Toke
let Some(GenericArgument::Lifetime(lifetime)) = args.args.first() else { let Some(GenericArgument::Lifetime(lifetime)) = args.args.first() else {
return quote!(#bound); return quote!(#bound);
}; };
let lifetime = at.unwrap_or_else(|| quote!(#lifetime));
quote!(#core_types::context::ExtractArena<ArenaRef = &#lifetime #core_types::arena::Arena>) quote!(#core_types::context::ExtractArena<ArenaRef = &#lifetime #core_types::arena::Arena>)
} }
#[cfg(test)]
mod lifetime_subst_tests {
use super::*;
#[test]
fn named_lifetimes_erase_to_static() {
let ty: Type = syn::parse_quote!(Graphic<'e>);
let erased = substitute_lifetimes(&ty, "'static");
assert_eq!(quote::quote!(#erased).to_string(), "Graphic < 'static >");
}
}
+10 -7
View File
@@ -24,7 +24,7 @@ fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_field
if rows.is_empty() { if rows.is_empty() {
return quote!(); return quote!();
} }
let output = slot_value_type(&parsed.output_type); let output = substitute_lifetimes(&slot_value_type(&parsed.output_type), "'static");
let field_type = |field: &ParsedField| match &field.ty { let field_type = |field: &ParsedField| match &field.ty {
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty.clone(), ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty.clone(),
@@ -111,6 +111,8 @@ fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_field
.iter() .iter()
.map(|(generic, index)| generic_assignment(&field_type(regular_fields[*index]), &row[*index], generic).map(|assigned| (generic.clone(), assigned))) .map(|(generic, index)| generic_assignment(&field_type(regular_fields[*index]), &row[*index], generic).map(|assigned| (generic.clone(), assigned)))
.collect::<Option<_>>()?; .collect::<Option<_>>()?;
let row: Vec<Type> = row.iter().map(|ty| substitute_lifetimes(ty, "'static")).collect();
let assignments: Vec<(Ident, Type)> = assignments.into_iter().map(|(generic, ty)| (generic, substitute_lifetimes(&ty, "'static"))).collect();
if type_disqualifies(&substitute_ident_types(&output, &assignments)) { if type_disqualifies(&substitute_ident_types(&output, &assignments)) {
return None; return None;
} }
@@ -313,12 +315,12 @@ fn single_row_entries(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields
.iter() .iter()
.map(|slot| match slot { .map(|slot| match slot {
SlotKind::BaseGeneric(name) => SlotKind::BaseGeneric(name.clone()), SlotKind::BaseGeneric(name) => SlotKind::BaseGeneric(name.clone()),
SlotKind::BaseConcrete(ty) => SlotKind::BaseConcrete(substitute_ident_types(ty, assignments)), SlotKind::BaseConcrete(ty) => SlotKind::BaseConcrete(substitute_lifetimes(&substitute_ident_types(ty, assignments), "'static")),
SlotKind::Value(ty) => SlotKind::Value(substitute_ident_types(ty, assignments)), SlotKind::Value(ty) => SlotKind::Value(substitute_lifetimes(&substitute_ident_types(ty, assignments), "'static")),
SlotKind::Extracted(ty) => SlotKind::Extracted(substitute_ident_types(ty, assignments)), SlotKind::Extracted(ty) => SlotKind::Extracted(substitute_lifetimes(&substitute_ident_types(ty, assignments), "'static")),
SlotKind::Ranked(ty) => SlotKind::Ranked(substitute_ident_types(ty, assignments)), SlotKind::Ranked(ty) => SlotKind::Ranked(substitute_lifetimes(&substitute_ident_types(ty, assignments), "'static")),
SlotKind::Plain(ty) => SlotKind::Plain(substitute_ident_types(ty, assignments)), SlotKind::Plain(ty) => SlotKind::Plain(substitute_lifetimes(&substitute_ident_types(ty, assignments), "'static")),
SlotKind::Lazy(ty) => SlotKind::Lazy(substitute_ident_types(ty, assignments)), SlotKind::Lazy(ty) => SlotKind::Lazy(substitute_lifetimes(&substitute_ident_types(ty, assignments), "'static")),
}) })
.collect(); .collect();
@@ -390,6 +392,7 @@ fn single_row_entries(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields
ir::Element::Generic(ident) => assignments.iter().find(|(generic, _)| generic == ident).map(|(_, ty)| ty.clone()), ir::Element::Generic(ident) => assignments.iter().find(|(generic, _)| generic == ident).map(|(_, ty)| ty.clone()),
ir::Element::Opaque => None, ir::Element::Opaque => None,
}; };
let output_element = output_element.map(|element| substitute_lifetimes(&element, "'static"));
let (io_output, wrap) = match &output_element { let (io_output, wrap) = match &output_element {
Some(element) => ( Some(element) => (
quote!(gcore::registry::record_type::<#element>()), quote!(gcore::registry::record_type::<#element>()),
@@ -21,6 +21,7 @@ pub(crate) fn generate_node_input_references(
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty.clone(), ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty.clone(),
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => crate::codegen::ir::strip_ilist(output_type).0, ParsedFieldType::Node(NodeParsedField { output_type, .. }) => crate::codegen::ir::strip_ilist(output_type).0,
}; };
let mut ty = crate::codegen::classify::substitute_lifetimes(&ty, "'static");
// We only want the necessary generics. // We only want the necessary generics.
let used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty); let used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty);