mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 10:18:11 +08:00
Let record nodes take element-consuming lazy inputs and convert map_points
This commit is contained in:
@@ -42,12 +42,10 @@ pub const ATTR_LETTER_TILT: &str = crate::attribute::LetterTilt::NAME;
|
|||||||
// Implicit attribute defaults
|
// Implicit attribute defaults
|
||||||
// ===========================
|
// ===========================
|
||||||
|
|
||||||
/// Overrides the type's default value for certain attributes.
|
/// The census-declared default for `key`; a mismatched value type degrades
|
||||||
|
/// to the column type's own default in `push_repeated`.
|
||||||
fn implicit_default_value(key: &str) -> Option<Box<dyn AnyAttributeValue>> {
|
fn implicit_default_value(key: &str) -> Option<Box<dyn AnyAttributeValue>> {
|
||||||
match key {
|
crate::attribute::ATTRIBUTE_REGISTRY.lock().unwrap().get(key).map(|info| (info.default)())
|
||||||
ATTR_OPACITY | ATTR_OPACITY_FILL => Some(Box::new(1_f64)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Appends `count` copies of `key`'s implicit default to `attribute` (see [`implicit_default_value`]).
|
/// Appends `count` copies of `key`'s implicit default to `attribute` (see [`implicit_default_value`]).
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::crate_ident::CrateIdent;
|
use crate::crate_ident::CrateIdent;
|
||||||
use crate::parsing::*;
|
use crate::parsing::*;
|
||||||
|
use crate::shader_nodes::{ShaderCodegen, ShaderTokens};
|
||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{ToTokens, format_ident, quote};
|
use quote::{ToTokens, format_ident, quote};
|
||||||
@@ -8,7 +9,6 @@ use syn::punctuated::Punctuated;
|
|||||||
use syn::visit::Visit;
|
use syn::visit::Visit;
|
||||||
use syn::visit_mut::VisitMut;
|
use syn::visit_mut::VisitMut;
|
||||||
use syn::{GenericArgument, GenericParam, Ident, Lifetime, PatIdent, PathArguments, Type, TypeParam, TypeParamBound};
|
use syn::{GenericArgument, GenericParam, Ident, Lifetime, PatIdent, PathArguments, Type, TypeParam, TypeParamBound};
|
||||||
use crate::shader_nodes::{ShaderCodegen, ShaderTokens};
|
|
||||||
|
|
||||||
pub(crate) mod classify;
|
pub(crate) mod classify;
|
||||||
mod entries;
|
mod entries;
|
||||||
@@ -124,9 +124,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let carried_generics: Vec<&syn::GenericParam> = fn_generics
|
let carried_generics: Vec<&syn::GenericParam> = fn_generics
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|param| match param {
|
.filter(|param| match param {
|
||||||
syn::GenericParam::Type(tp) => {
|
syn::GenericParam::Type(tp) => Some(&tp.ident) != ctx_ident_for_flip.as_ref() && !data_field_generic_idents.contains(&tp.ident) && (flip || ranked_carries(&tp.ident)),
|
||||||
Some(&tp.ident) != ctx_ident_for_flip.as_ref() && !data_field_generic_idents.contains(&tp.ident) && (flip || ranked_carries(&tp.ident))
|
|
||||||
}
|
|
||||||
_ => false,
|
_ => false,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -202,6 +200,10 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let slot = format_ident!("__in_{index}");
|
let slot = format_ident!("__in_{index}");
|
||||||
quote!(pub(super) #slot: gcore::record::Layout)
|
quote!(pub(super) #slot: gcore::record::Layout)
|
||||||
}));
|
}));
|
||||||
|
state.extend(crate::codegen::ir::element_lazy_indices(&struct_regular_fields, &node).into_iter().map(|index| {
|
||||||
|
let slot = format_ident!("__in_{index}");
|
||||||
|
quote!(pub(super) #slot: gcore::record::Layout)
|
||||||
|
}));
|
||||||
let total_reads: usize = struct_regular_fields.iter().map(|field| field.attribute_reads.len()).sum();
|
let total_reads: usize = struct_regular_fields.iter().map(|field| field.attribute_reads.len()).sum();
|
||||||
state.extend((0..total_reads).map(|index| {
|
state.extend((0..total_reads).map(|index| {
|
||||||
let slot = format_ident!("__read_{index}");
|
let slot = format_ident!("__read_{index}");
|
||||||
@@ -214,10 +216,14 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
state
|
state
|
||||||
} else if routing_generic.is_some() {
|
} else if routing_generic.is_some() {
|
||||||
let mut state = vec![quote!(pub(super) __layout: gcore::record::Layout)];
|
let mut state = vec![quote!(pub(super) __layout: gcore::record::Layout)];
|
||||||
state.extend(routing_value_indices(&struct_regular_fields, routing_generic.as_ref().expect("guarded by the arm")).into_iter().map(|index| {
|
state.extend(
|
||||||
let slot = format_ident!("__in_{index}");
|
routing_value_indices(&struct_regular_fields, routing_generic.as_ref().expect("guarded by the arm"))
|
||||||
quote!(pub(super) #slot: gcore::record::Layout)
|
.into_iter()
|
||||||
}));
|
.map(|index| {
|
||||||
|
let slot = format_ident!("__in_{index}");
|
||||||
|
quote!(pub(super) #slot: gcore::record::Layout)
|
||||||
|
}),
|
||||||
|
);
|
||||||
state
|
state
|
||||||
} else if opaque {
|
} else if opaque {
|
||||||
vec![quote!(pub(super) __layout: gcore::record::Layout)]
|
vec![quote!(pub(super) __layout: gcore::record::Layout)]
|
||||||
@@ -398,10 +404,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
// offsets from the carrier layout; `new` cannot fill that state.
|
// offsets from the carrier layout; `new` cannot fill that state.
|
||||||
let routing_layout_param = (routing_generic.is_some() || opaque).then(|| quote!(__layout: &gcore::record::Layout,)).into_iter();
|
let routing_layout_param = (routing_generic.is_some() || opaque).then(|| quote!(__layout: &gcore::record::Layout,)).into_iter();
|
||||||
let routing_layout_init = (routing_generic.is_some() || opaque).then(|| quote!(__layout: __layout.clone(),)).into_iter();
|
let routing_layout_init = (routing_generic.is_some() || opaque).then(|| quote!(__layout: __layout.clone(),)).into_iter();
|
||||||
let routing_value_layouts: Vec<usize> = routing_generic
|
let routing_value_layouts: Vec<usize> = routing_generic.as_ref().map(|generic| routing_value_indices(&struct_regular_fields, generic)).unwrap_or_default();
|
||||||
.as_ref()
|
|
||||||
.map(|generic| routing_value_indices(&struct_regular_fields, generic))
|
|
||||||
.unwrap_or_default();
|
|
||||||
let routing_in_params = routing_value_layouts.iter().map(|index| {
|
let routing_in_params = routing_value_layouts.iter().map(|index| {
|
||||||
let slot = format_ident!("__in_{index}");
|
let slot = format_ident!("__in_{index}");
|
||||||
quote!(#slot: &gcore::record::Layout,)
|
quote!(#slot: &gcore::record::Layout,)
|
||||||
@@ -854,9 +857,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
.fn_generics
|
.fn_generics
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|param| match param {
|
.filter(|param| match param {
|
||||||
GenericParam::Type(type_param) => {
|
GenericParam::Type(type_param) => Some(&type_param.ident) != routing_generic.as_ref() && Some(&type_param.ident) != record_token.as_ref(),
|
||||||
Some(&type_param.ident) != routing_generic.as_ref() && Some(&type_param.ident) != record_token.as_ref()
|
|
||||||
}
|
|
||||||
_ => true,
|
_ => true,
|
||||||
})
|
})
|
||||||
.map(&generic_tokens)
|
.map(&generic_tokens)
|
||||||
@@ -932,6 +933,24 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
generics.insert(0, quote!('__record));
|
generics.insert(0, quote!('__record));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if record_io {
|
||||||
|
let mut kernel_lazy = false;
|
||||||
|
for (index, field) in regular_fields.iter().enumerate() {
|
||||||
|
if matches!(&field.ty, ParsedFieldType::Node(_)) && matches!(crate::codegen::ir::lazy_binding(&node, index), LazyBinding::Element) {
|
||||||
|
kernel_lazy = true;
|
||||||
|
let source_generic = format_ident!("__Source{index}");
|
||||||
|
let derived_extra = derives
|
||||||
|
.then(|| quote!(+ for<'__derived> #core_types::record::RecordEdge<'__derived, #core_types::context::Derived<'__derived, #ctx_ident>>))
|
||||||
|
.into_iter();
|
||||||
|
generics.push(quote! {
|
||||||
|
#source_generic: #core_types::node::Node<#ctx_ident, Output = #core_types::record::RecordValue<'__record>> #(#derived_extra)*
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if kernel_lazy && !(derive_routing || (lazy_carrier && derives)) {
|
||||||
|
generics.insert(0, quote!('__record));
|
||||||
|
}
|
||||||
|
}
|
||||||
if opaque {
|
if opaque {
|
||||||
for (index, field) in regular_fields.iter().enumerate() {
|
for (index, field) in regular_fields.iter().enumerate() {
|
||||||
if let ParsedFieldType::Node(NodeParsedField { output_type, .. }) = &field.ty {
|
if let ParsedFieldType::Node(NodeParsedField { output_type, .. }) = &field.ty {
|
||||||
@@ -975,48 +994,44 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
});
|
});
|
||||||
quote!((#value_param #(, #read_pats)*): (#value_ty #(, #read_tys)*))
|
quote!((#value_param #(, #read_pats)*): (#value_ty #(, #read_tys)*))
|
||||||
};
|
};
|
||||||
let kernel_params = regular_fields
|
let kernel_params = regular_fields.iter().enumerate().filter(|(_, field)| !injected_name(&field.pat_ident.ident)).map(|(index, field)| {
|
||||||
.iter()
|
let pat = &field.pat_ident;
|
||||||
.enumerate()
|
match &field.ty {
|
||||||
.filter(|(_, field)| !injected_name(&field.pat_ident.ident))
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) if ir::materialized_levels(&node, index) > 0 => {
|
||||||
.map(|(index, field)| {
|
quote!(#pat: #core_types::node::List<'_, #ty>)
|
||||||
let pat = &field.pat_ident;
|
}
|
||||||
match &field.ty {
|
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(#pat: &#ty),
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) if ir::materialized_levels(&node, index) > 0 => {
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) if !field.attribute_reads.is_empty() => read_tuple_param(field, quote!(#pat), quote!(#ty)),
|
||||||
quote!(#pat: #core_types::node::List<'_, #ty>)
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#pat: #ty),
|
||||||
}
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => {
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(#pat: &#ty),
|
let source_generic = format_ident!("__Source{index}");
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) if !field.attribute_reads.is_empty() => read_tuple_param(field, quote!(#pat), quote!(#ty)),
|
match (ir::lazy_binding(&node, index), raw_lazy) {
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#pat: #ty),
|
(LazyBinding::DeriveRouting, _) => quote!(#pat: #core_types::record::RecordLazyInput<'_, '__record, #source_generic>),
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => {
|
(LazyBinding::DeriveCarrier, _) => {
|
||||||
let source_generic = format_ident!("__Source{index}");
|
let out = lazy_read_out(field, output_type);
|
||||||
match (ir::lazy_binding(&node, index), raw_lazy) {
|
quote!(#pat: #core_types::record::DerivedLazyInput<'_, '__record, #out, #source_generic>)
|
||||||
(LazyBinding::DeriveRouting, _) => quote!(#pat: #core_types::record::RecordLazyInput<'_, '__record, #source_generic>),
|
}
|
||||||
(LazyBinding::DeriveCarrier, _) => {
|
(LazyBinding::OpaqueRecord, _) => quote!(#pat: &#core_types::record::RecordEdgeInput<'_, #source_generic>),
|
||||||
let out = lazy_read_out(field, output_type);
|
(LazyBinding::Element, true) => {
|
||||||
quote!(#pat: #core_types::record::DerivedLazyInput<'_, '__record, #out, #source_generic>)
|
let out = lazy_read_out(field, output_type);
|
||||||
}
|
quote!(#pat: &#core_types::record::ElementEdge<'_, #out, #source_generic>)
|
||||||
(LazyBinding::OpaqueRecord, _) => quote!(#pat: &#core_types::record::RecordEdgeInput<'_, #source_generic>),
|
}
|
||||||
(LazyBinding::Element, true) => {
|
(LazyBinding::Element, false) => {
|
||||||
let out = lazy_read_out(field, output_type);
|
let out = lazy_read_out(field, output_type);
|
||||||
quote!(#pat: &#core_types::record::ElementEdge<'_, #out, #source_generic>)
|
quote!(#pat: #core_types::record::ElementLazyInput<'_, #out, #source_generic>)
|
||||||
}
|
}
|
||||||
(LazyBinding::Element, false) => {
|
(LazyBinding::Plain, true) => {
|
||||||
let out = lazy_read_out(field, output_type);
|
let bound = lazy_bound(output_type);
|
||||||
quote!(#pat: #core_types::record::ElementLazyInput<'_, #out, #source_generic>)
|
quote!(#pat: &impl #bound)
|
||||||
}
|
}
|
||||||
(LazyBinding::Plain, true) => {
|
(LazyBinding::Plain, false) => {
|
||||||
let bound = lazy_bound(output_type);
|
let bound = lazy_bound(output_type);
|
||||||
quote!(#pat: &impl #bound)
|
quote!(#pat: #core_types::node::LazyInput<'_, impl #bound>)
|
||||||
}
|
|
||||||
(LazyBinding::Plain, false) => {
|
|
||||||
let bound = lazy_bound(output_type);
|
|
||||||
quote!(#pat: #core_types::node::LazyInput<'_, impl #bound>)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let record_value_ty: Type = syn::parse_quote!(#core_types::record::RecordValue<'__record>);
|
let record_value_ty: Type = syn::parse_quote!(#core_types::record::RecordValue<'__record>);
|
||||||
let node_bounds = regular_fields.iter().enumerate().zip(&node_generics).map(|((index, field), node_generic)| match &field.ty {
|
let node_bounds = regular_fields.iter().enumerate().zip(&node_generics).map(|((index, field), node_generic)| match &field.ty {
|
||||||
@@ -1041,6 +1056,15 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
ParsedFieldType::Regular(_) if record_io && !field.attribute_reads.is_empty() => {
|
ParsedFieldType::Regular(_) if record_io && !field.attribute_reads.is_empty() => {
|
||||||
quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #record_value_ty>)
|
quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #record_value_ty>)
|
||||||
}
|
}
|
||||||
|
// An element-consuming lazy secondary rides a record edge, derivable
|
||||||
|
// when the kernel evaluates it at derived contexts.
|
||||||
|
ParsedFieldType::Node(_) if record_io && matches!(ir::lazy_binding(&node, index), LazyBinding::Element) => match derives {
|
||||||
|
true => quote! {
|
||||||
|
#node_generic: #core_types::node::Node<#ctx_ident, Output = #record_value_ty>,
|
||||||
|
#node_generic: for<'__derived> #core_types::record::RecordEdge<'__derived, #core_types::context::Derived<'__derived, #ctx_ident>>
|
||||||
|
},
|
||||||
|
false => quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #record_value_ty>),
|
||||||
|
},
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) if routing_source(ty) => {
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) if routing_source(ty) => {
|
||||||
quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #record_value_ty>)
|
quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #record_value_ty>)
|
||||||
}
|
}
|
||||||
@@ -2027,8 +2051,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(index, field)| {
|
.filter(|(index, field)| {
|
||||||
matches!(field.ty, ParsedFieldType::Regular(_))
|
matches!(field.ty, ParsedFieldType::Regular(_))
|
||||||
&& hoists(*index)
|
&& hoists(*index) && matches!(ir::value_binding(&node, *index), ValueBinding::Plain | ValueBinding::ReadingSecondary | ValueBinding::RecordElement)
|
||||||
&& matches!(ir::value_binding(&node, *index), ValueBinding::Plain | ValueBinding::ReadingSecondary | ValueBinding::RecordElement)
|
|
||||||
})
|
})
|
||||||
.map(|(_, field)| {
|
.map(|(_, field)| {
|
||||||
let name = &field.pat_ident.ident;
|
let name = &field.pat_ident.ident;
|
||||||
@@ -2128,10 +2151,14 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
// A reading secondary input's element copies out of its record, as
|
// A reading secondary input's element copies out of its record, as
|
||||||
// does a concrete carrier read.
|
// does a concrete carrier read.
|
||||||
if record_io {
|
if record_io {
|
||||||
bounds.extend(reading_secondary_indices(®ular_fields, skips_carrier).into_iter().filter_map(|index| match ®ular_fields[index].ty {
|
bounds.extend(
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some(quote!(#ty: ::core::clone::Clone)),
|
reading_secondary_indices(®ular_fields, skips_carrier)
|
||||||
_ => None,
|
.into_iter()
|
||||||
}));
|
.filter_map(|index| match ®ular_fields[index].ty {
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some(quote!(#ty: ::core::clone::Clone)),
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
if let Some(ty) = carrier_read_ty {
|
if let Some(ty) = carrier_read_ty {
|
||||||
bounds.push(quote!(#ty: ::core::clone::Clone));
|
bounds.push(quote!(#ty: ::core::clone::Clone));
|
||||||
}
|
}
|
||||||
@@ -2289,12 +2316,20 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let reading_secondaries = reading_secondary_indices(®ular_fields, skips_carrier);
|
let reading_secondaries = reading_secondary_indices(®ular_fields, skips_carrier);
|
||||||
|
// The layout slots the constructor fills: reading secondaries plus the
|
||||||
|
// element-consuming lazy inputs, in field order to match the entries.
|
||||||
|
let layout_slots: Vec<usize> = {
|
||||||
|
let mut slots = reading_secondaries.clone();
|
||||||
|
slots.extend(crate::codegen::ir::element_lazy_indices(®ular_fields, &node));
|
||||||
|
slots.sort_unstable();
|
||||||
|
slots
|
||||||
|
};
|
||||||
let edge_args = regular_fields.iter().zip(&node_generics).map(|(field, generic)| {
|
let edge_args = regular_fields.iter().zip(&node_generics).map(|(field, generic)| {
|
||||||
let name = &field.pat_ident.ident;
|
let name = &field.pat_ident.ident;
|
||||||
quote!(#name: #generic)
|
quote!(#name: #generic)
|
||||||
});
|
});
|
||||||
let carrier_layout_param = (!skips_carrier).then(|| quote!(__carrier_layout: &#core_types::record::Layout,)).into_iter();
|
let carrier_layout_param = (!skips_carrier).then(|| quote!(__carrier_layout: &#core_types::record::Layout,)).into_iter();
|
||||||
let input_layout_params = reading_secondaries.iter().map(|index| {
|
let input_layout_params = layout_slots.iter().map(|index| {
|
||||||
let slot = format_ident!("__in_{index}");
|
let slot = format_ident!("__in_{index}");
|
||||||
quote!(#slot: &#core_types::record::Layout,)
|
quote!(#slot: &#core_types::record::Layout,)
|
||||||
});
|
});
|
||||||
@@ -2313,7 +2348,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
quote!(#name,)
|
quote!(#name,)
|
||||||
});
|
});
|
||||||
let carrier_init = (!skips_carrier).then(|| quote!(__carrier: __carrier_layout.clone(),)).into_iter();
|
let carrier_init = (!skips_carrier).then(|| quote!(__carrier: __carrier_layout.clone(),)).into_iter();
|
||||||
let input_layout_inits = reading_secondaries.iter().map(|index| {
|
let input_layout_inits = layout_slots.iter().map(|index| {
|
||||||
let slot = format_ident!("__in_{index}");
|
let slot = format_ident!("__in_{index}");
|
||||||
quote!(#slot: #slot.clone(),)
|
quote!(#slot: #slot.clone(),)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -96,18 +96,12 @@ pub(crate) enum EvalStep<'a> {
|
|||||||
/// or return-tuple writes. Reads on lazy inputs belong to the record lowering
|
/// or return-tuple writes. Reads on lazy inputs belong to the record lowering
|
||||||
/// of the flip class instead.
|
/// of the flip class instead.
|
||||||
pub(crate) fn has_record_io(parsed: &ParsedNodeFn) -> bool {
|
pub(crate) fn has_record_io(parsed: &ParsedNodeFn) -> bool {
|
||||||
let value_reads = parsed
|
let value_reads = parsed.fields.iter().any(|field| !field.attribute_reads.is_empty() && matches!(field.ty, ParsedFieldType::Regular(_)));
|
||||||
.fields
|
|
||||||
.iter()
|
|
||||||
.any(|field| !field.attribute_reads.is_empty() && matches!(field.ty, ParsedFieldType::Regular(_)));
|
|
||||||
value_reads || record_writes(&slot_value_type(&parsed.output_type)).is_some()
|
value_reads || record_writes(&slot_value_type(&parsed.output_type)).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_lazy_reads(parsed: &ParsedNodeFn) -> bool {
|
pub(crate) fn has_lazy_reads(parsed: &ParsedNodeFn) -> bool {
|
||||||
parsed
|
parsed.fields.iter().any(|field| !field.attribute_reads.is_empty() && matches!(field.ty, ParsedFieldType::Node(_)))
|
||||||
.fields
|
|
||||||
.iter()
|
|
||||||
.any(|field| !field.attribute_reads.is_empty() && matches!(field.ty, ParsedFieldType::Node(_)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The value inputs of a routing node (every regular field that is neither a
|
/// The value inputs of a routing node (every regular field that is neither a
|
||||||
@@ -266,10 +260,7 @@ pub(crate) fn record_shape(parsed: &ParsedNodeFn) -> Option<RecordShape> {
|
|||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
let writes = record_writes(&value);
|
let writes = record_writes(&value);
|
||||||
let has_reads = parsed
|
let has_reads = parsed.fields.iter().any(|field| !field.attribute_reads.is_empty() && matches!(field.ty, ParsedFieldType::Regular(_)));
|
||||||
.fields
|
|
||||||
.iter()
|
|
||||||
.any(|field| !field.attribute_reads.is_empty() && matches!(field.ty, ParsedFieldType::Regular(_)));
|
|
||||||
if !has_reads && writes.is_none() {
|
if !has_reads && writes.is_none() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -283,13 +274,17 @@ pub(crate) fn record_shape(parsed: &ParsedNodeFn) -> Option<RecordShape> {
|
|||||||
// A first-field lazy carrier: the kernel evaluates the derived content
|
// A first-field lazy carrier: the kernel evaluates the derived content
|
||||||
// itself and returns its opaque row token beside the write set.
|
// itself and returns its opaque row token beside the write set.
|
||||||
let lazy_carrier = matches!(&carrier_field.ty, ParsedFieldType::Node(_));
|
let lazy_carrier = matches!(&carrier_field.ty, ParsedFieldType::Node(_));
|
||||||
if parsed.fields.iter().skip(lazy_carrier as usize).any(|field| matches!(field.ty, ParsedFieldType::Node(_))) {
|
// Lazy secondaries are consumed as plain elements; raw record edges and
|
||||||
|
// ranked outputs have no element binding here.
|
||||||
|
let unsupported_lazy_secondary = |field: &ParsedField| match &field.ty {
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => is_record_value(output_type) || crate::codegen::ir::strip_ilist(output_type).1 > 0 || !field.attribute_reads.is_empty(),
|
||||||
|
ParsedFieldType::Regular(_) => false,
|
||||||
|
};
|
||||||
|
if parsed.fields.iter().skip(lazy_carrier as usize).any(|field| unsupported_lazy_secondary(field)) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let reads_well_placed = parsed.fields.iter().enumerate().all(|(index, field)| {
|
let reads_well_placed = parsed.fields.iter().enumerate().all(|(index, field)| {
|
||||||
field.attribute_reads.is_empty()
|
field.attribute_reads.is_empty() || (lazy_carrier && index == 0) || (!field.is_data_field && matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { lend: None, .. })))
|
||||||
|| (lazy_carrier && index == 0)
|
|
||||||
|| (!field.is_data_field && matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { lend: None, .. })))
|
|
||||||
});
|
});
|
||||||
if !reads_well_placed {
|
if !reads_well_placed {
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
@@ -227,6 +227,13 @@ fn single_row_entries(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
match &field.ty {
|
match &field.ty {
|
||||||
|
// An element-consuming lazy secondary of a record node rides a
|
||||||
|
// record edge with a layout slot, like a reading secondary.
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. })
|
||||||
|
if matches!(ir::node_kind(&node), ir::NodeKind::RecordIo) && matches!(ir::lazy_binding(&node, index), ir::LazyBinding::Element) =>
|
||||||
|
{
|
||||||
|
SlotKind::Value(output_type.clone())
|
||||||
|
}
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => SlotKind::Lazy(output_type.clone()),
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => SlotKind::Lazy(output_type.clone()),
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => match ir::value_binding(&node, index) {
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => match ir::value_binding(&node, index) {
|
||||||
ir::ValueBinding::Materialized => SlotKind::Ranked(ty.clone()),
|
ir::ValueBinding::Materialized => SlotKind::Ranked(ty.clone()),
|
||||||
@@ -261,9 +268,7 @@ fn single_row_entries(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields
|
|||||||
.collect();
|
.collect();
|
||||||
let ranked_source = |generic: &Ident| {
|
let ranked_source = |generic: &Ident| {
|
||||||
regular_fields.iter().position(|field| match &field.ty {
|
regular_fields.iter().position(|field| match &field.ty {
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, list_levels, implementations, .. }) => {
|
ParsedFieldType::Regular(RegularParsedField { ty, list_levels, implementations, .. }) => *list_levels > 0 && !implementations.is_empty() && generic_extractable(ty, generic),
|
||||||
*list_levels > 0 && !implementations.is_empty() && generic_extractable(ty, generic)
|
|
||||||
}
|
|
||||||
_ => false,
|
_ => false,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -297,166 +302,171 @@ fn single_row_entries(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields
|
|||||||
let arity = regular_fields.len();
|
let arity = regular_fields.len();
|
||||||
let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect();
|
let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect();
|
||||||
|
|
||||||
let entries: Vec<TokenStream2> = row_assignments.iter().filter_map(|assignments| {
|
let entries: Vec<TokenStream2> = row_assignments
|
||||||
// A row whose assignments did not all solve cannot instantiate the struct.
|
|
||||||
if assignments.len() != carried.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let slots: Vec<SlotKind> = slots
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|slot| match slot {
|
.filter_map(|assignments| {
|
||||||
SlotKind::BaseGeneric(name) => SlotKind::BaseGeneric(name.clone()),
|
// A row whose assignments did not all solve cannot instantiate the struct.
|
||||||
SlotKind::BaseConcrete(ty) => SlotKind::BaseConcrete(substitute_ident_types(ty, assignments)),
|
if assignments.len() != carried.len() {
|
||||||
SlotKind::Value(ty) => SlotKind::Value(substitute_ident_types(ty, assignments)),
|
return None;
|
||||||
SlotKind::Extracted(ty) => SlotKind::Extracted(substitute_ident_types(ty, assignments)),
|
}
|
||||||
SlotKind::Ranked(ty) => SlotKind::Ranked(substitute_ident_types(ty, assignments)),
|
let slots: Vec<SlotKind> = slots
|
||||||
SlotKind::Plain(ty) => SlotKind::Plain(substitute_ident_types(ty, assignments)),
|
.iter()
|
||||||
SlotKind::Lazy(ty) => SlotKind::Lazy(substitute_ident_types(ty, assignments)),
|
.map(|slot| match slot {
|
||||||
})
|
SlotKind::BaseGeneric(name) => SlotKind::BaseGeneric(name.clone()),
|
||||||
.collect();
|
SlotKind::BaseConcrete(ty) => SlotKind::BaseConcrete(substitute_ident_types(ty, assignments)),
|
||||||
|
SlotKind::Value(ty) => SlotKind::Value(substitute_ident_types(ty, assignments)),
|
||||||
|
SlotKind::Extracted(ty) => SlotKind::Extracted(substitute_ident_types(ty, assignments)),
|
||||||
|
SlotKind::Ranked(ty) => SlotKind::Ranked(substitute_ident_types(ty, assignments)),
|
||||||
|
SlotKind::Plain(ty) => SlotKind::Plain(substitute_ident_types(ty, assignments)),
|
||||||
|
SlotKind::Lazy(ty) => SlotKind::Lazy(substitute_ident_types(ty, assignments)),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Every non-base value/plain/lazy input must be concrete.
|
// Every non-base value/plain/lazy input must be concrete.
|
||||||
let values_concrete = regular_fields.iter().zip(&slots).all(|(field, slot)| match slot {
|
let values_concrete = regular_fields.iter().zip(&slots).all(|(field, slot)| match slot {
|
||||||
SlotKind::BaseGeneric(_) | SlotKind::BaseConcrete(_) => true,
|
SlotKind::BaseGeneric(_) | SlotKind::BaseConcrete(_) => true,
|
||||||
SlotKind::Value(ty) | SlotKind::Extracted(ty) | SlotKind::Ranked(ty) | SlotKind::Plain(ty) | SlotKind::Lazy(ty) => {
|
SlotKind::Value(ty) | SlotKind::Extracted(ty) | SlotKind::Ranked(ty) | SlotKind::Plain(ty) | SlotKind::Lazy(ty) => {
|
||||||
!contains_open_generic(parsed, ty) && (lend(field) || !type_disqualifies(ty))
|
!contains_open_generic(parsed, ty) && (lend(field) || !type_disqualifies(ty))
|
||||||
}
|
|
||||||
});
|
|
||||||
if !values_concrete {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let input_types = slots.iter().map(|slot| match slot {
|
|
||||||
SlotKind::BaseGeneric(name) => quote!(gcore::registry::generic_record_edge_type(#name)),
|
|
||||||
SlotKind::BaseConcrete(ty) | SlotKind::Value(ty) | SlotKind::Extracted(ty) | SlotKind::Ranked(ty) => quote!(gcore::registry::record_edge_type::<#ty>()),
|
|
||||||
SlotKind::Plain(ty) | SlotKind::Lazy(ty) => quote!(gcore::registry::edge_type::<#ty>()),
|
|
||||||
});
|
|
||||||
|
|
||||||
let downcasts = names.iter().zip(&slots).enumerate().map(|(index, (name, slot))| {
|
|
||||||
let handle = format_ident!("__handle_{index}");
|
|
||||||
let layout = format_ident!("__layout_{index}");
|
|
||||||
let ty = format_ident!("__ty_{index}");
|
|
||||||
match slot {
|
|
||||||
SlotKind::BaseGeneric(_) | SlotKind::BaseConcrete(_) => quote! {
|
|
||||||
let #handle = inputs.next().unwrap();
|
|
||||||
let #ty = #handle.ty().clone();
|
|
||||||
let #layout = #handle.layout().clone();
|
|
||||||
let #name = #handle.downcast_erased::<gcore::registry::ErasedRecordNode>(#ty.clone())?;
|
|
||||||
},
|
|
||||||
SlotKind::Value(value_ty) => quote! {
|
|
||||||
let #handle = inputs.next().unwrap();
|
|
||||||
let #layout = #handle.layout().clone();
|
|
||||||
let #name = #handle.downcast_record::<#value_ty>()?;
|
|
||||||
},
|
|
||||||
SlotKind::Extracted(value_ty) => quote! {
|
|
||||||
let #handle = inputs.next().unwrap();
|
|
||||||
let #layout = #handle.layout().clone();
|
|
||||||
let #name = gcore::record::RecordExtract::<#value_ty, _>::new(#handle.downcast_record::<#value_ty>()?, &#layout);
|
|
||||||
},
|
|
||||||
SlotKind::Ranked(value_ty) => quote! {
|
|
||||||
let #name = inputs.next().unwrap().downcast_record::<#value_ty>()?;
|
|
||||||
},
|
|
||||||
SlotKind::Plain(value_ty) | SlotKind::Lazy(value_ty) => quote!(let #name = inputs.next().unwrap().downcast::<#value_ty>()?;),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let base_indices: Vec<usize> = slots.iter().enumerate().filter(|(_, slot)| slot.is_base()).map(|(index, _)| index).collect();
|
|
||||||
let value_indices: Vec<usize> = slots.iter().enumerate().filter(|(_, slot)| matches!(slot, SlotKind::Value(_))).map(|(index, _)| index).collect();
|
|
||||||
let value_layout_args: Vec<TokenStream2> = value_indices
|
|
||||||
.iter()
|
|
||||||
.map(|index| {
|
|
||||||
let layout = format_ident!("__layout_{index}");
|
|
||||||
quote!(&#layout,)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let carried_meta = || {
|
|
||||||
let meta = ir::layout_meta_tokens(&node, quote!(gcore::record::ElementSpec::Carried), &core_types);
|
|
||||||
quote!(Some(#meta))
|
|
||||||
};
|
|
||||||
|
|
||||||
// The output wire and node wrap follow the output element: a concrete (or
|
|
||||||
// row-assigned) element is a typed record; a generic or opaque element is
|
|
||||||
// an erased record carrying the first base slot's runtime type.
|
|
||||||
let output_element = match &node.output.shape.element {
|
|
||||||
ir::Element::Concrete(element) => Some(substitute_ident_types(element, assignments)),
|
|
||||||
ir::Element::Generic(ident) => assignments.iter().find(|(generic, _)| generic == ident).map(|(_, ty)| ty.clone()),
|
|
||||||
ir::Element::Opaque => None,
|
|
||||||
};
|
|
||||||
let (io_output, wrap) = match &output_element {
|
|
||||||
Some(element) => (
|
|
||||||
quote!(gcore::registry::record_type::<#element>()),
|
|
||||||
quote!(Ok(gcore::registry::EdgeHandle::new_record::<#element>(::std::sync::Arc::new(__node)))),
|
|
||||||
),
|
|
||||||
None => {
|
|
||||||
let name = match &node.output.shape.element {
|
|
||||||
ir::Element::Generic(ident) => ident.to_string(),
|
|
||||||
_ => "T".to_string(),
|
|
||||||
};
|
|
||||||
let base_ty = format_ident!("__ty_{}", base_indices[0]);
|
|
||||||
(
|
|
||||||
quote!(gcore::Type::Record(Box::new(gcore::Type::Generic(::std::borrow::Cow::Borrowed(#name))))),
|
|
||||||
quote!(Ok(gcore::registry::EdgeHandle::new_erased(::std::sync::Arc::new(__node) as ::std::sync::Arc<gcore::registry::ErasedRecordNode>, #base_ty))),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let (prelude, new_layout_args, layout_meta) = match ir::node_kind(&node) {
|
|
||||||
ir::NodeKind::RecordIo => {
|
|
||||||
let carrier_arg = (node.inputs.first().is_some_and(|input| input.subject) && ir::materialized_levels(&node, 0) == 0).then(|| quote!(&__layout_0,));
|
|
||||||
let layout_meta_fn = format_ident!("{}_layout_meta", fn_name);
|
|
||||||
(quote!(), quote!(#carrier_arg #(#value_layout_args)*), quote!(Some(self::#layout_meta_fn())))
|
|
||||||
}
|
|
||||||
ir::NodeKind::Routing => {
|
|
||||||
let source_layouts = base_indices.iter().map(|index| format_ident!("__layout_{index}"));
|
|
||||||
let source_wraps = base_indices.iter().map(|index| {
|
|
||||||
let name = names[*index];
|
|
||||||
let layout = format_ident!("__layout_{index}");
|
|
||||||
quote!(let #name = gcore::record::RecordSource::new(#name, &#layout, &__union);)
|
|
||||||
});
|
|
||||||
let prelude = quote! {
|
|
||||||
let __union = gcore::record::Layout::union(&[#(&#source_layouts),*]);
|
|
||||||
#(#source_wraps)*
|
|
||||||
};
|
|
||||||
(prelude, quote!(&__union, #(#value_layout_args)*), carried_meta())
|
|
||||||
}
|
|
||||||
ir::NodeKind::Opaque => {
|
|
||||||
let record_layout = format_ident!("__layout_{}", base_indices[0]);
|
|
||||||
(quote!(), quote!(&#record_layout), carried_meta())
|
|
||||||
}
|
|
||||||
ir::NodeKind::Flip => unreachable!("flip has its own multi-row emitter"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// A carried generic instantiates through the struct's trailing phantom
|
|
||||||
// parameters, so the constructor names the row's types after one inferred
|
|
||||||
// slot per input field.
|
|
||||||
let turbofish = (!carried.is_empty()).then(|| {
|
|
||||||
let underscores = (0..arity).map(|_| quote!(_));
|
|
||||||
let carried_types = carried.iter().filter_map(|(generic, _)| assignments.iter().find(|(ident, _)| ident == generic).map(|(_, ty)| quote!(#ty)));
|
|
||||||
quote!(::<#(#underscores,)* #(#carried_types,)*>)
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(quote! {
|
|
||||||
gcore::registry::RegistryEntry {
|
|
||||||
layout_meta: #layout_meta,
|
|
||||||
io: gcore::registry::NodeIOTypes::new(
|
|
||||||
gcore::concrete!(gcore::context::ContextImpl<'static>),
|
|
||||||
#io_output,
|
|
||||||
vec![#(#input_types),*],
|
|
||||||
),
|
|
||||||
constructor: |inputs| {
|
|
||||||
if inputs.len() != #arity {
|
|
||||||
return Err(gcore::registry::ConstructionError::Arity { expected: #arity, got: inputs.len() });
|
|
||||||
}
|
}
|
||||||
let mut inputs = inputs.into_iter();
|
});
|
||||||
#(#downcasts)*
|
if !values_concrete {
|
||||||
#prelude
|
return None;
|
||||||
let __node = #struct_name #turbofish::new(#(#names,)* #new_layout_args);
|
}
|
||||||
#wrap
|
|
||||||
},
|
let input_types = slots.iter().map(|slot| match slot {
|
||||||
}
|
SlotKind::BaseGeneric(name) => quote!(gcore::registry::generic_record_edge_type(#name)),
|
||||||
})
|
SlotKind::BaseConcrete(ty) | SlotKind::Value(ty) | SlotKind::Extracted(ty) | SlotKind::Ranked(ty) => quote!(gcore::registry::record_edge_type::<#ty>()),
|
||||||
}).collect();
|
SlotKind::Plain(ty) | SlotKind::Lazy(ty) => quote!(gcore::registry::edge_type::<#ty>()),
|
||||||
|
});
|
||||||
|
|
||||||
|
let downcasts = names.iter().zip(&slots).enumerate().map(|(index, (name, slot))| {
|
||||||
|
let handle = format_ident!("__handle_{index}");
|
||||||
|
let layout = format_ident!("__layout_{index}");
|
||||||
|
let ty = format_ident!("__ty_{index}");
|
||||||
|
match slot {
|
||||||
|
SlotKind::BaseGeneric(_) | SlotKind::BaseConcrete(_) => quote! {
|
||||||
|
let #handle = inputs.next().unwrap();
|
||||||
|
let #ty = #handle.ty().clone();
|
||||||
|
let #layout = #handle.layout().clone();
|
||||||
|
let #name = #handle.downcast_erased::<gcore::registry::ErasedRecordNode>(#ty.clone())?;
|
||||||
|
},
|
||||||
|
SlotKind::Value(value_ty) => quote! {
|
||||||
|
let #handle = inputs.next().unwrap();
|
||||||
|
let #layout = #handle.layout().clone();
|
||||||
|
let #name = #handle.downcast_record::<#value_ty>()?;
|
||||||
|
},
|
||||||
|
SlotKind::Extracted(value_ty) => quote! {
|
||||||
|
let #handle = inputs.next().unwrap();
|
||||||
|
let #layout = #handle.layout().clone();
|
||||||
|
let #name = gcore::record::RecordExtract::<#value_ty, _>::new(#handle.downcast_record::<#value_ty>()?, &#layout);
|
||||||
|
},
|
||||||
|
SlotKind::Ranked(value_ty) => quote! {
|
||||||
|
let #name = inputs.next().unwrap().downcast_record::<#value_ty>()?;
|
||||||
|
},
|
||||||
|
SlotKind::Plain(value_ty) | SlotKind::Lazy(value_ty) => quote!(let #name = inputs.next().unwrap().downcast::<#value_ty>()?;),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let base_indices: Vec<usize> = slots.iter().enumerate().filter(|(_, slot)| slot.is_base()).map(|(index, _)| index).collect();
|
||||||
|
let value_indices: Vec<usize> = slots.iter().enumerate().filter(|(_, slot)| matches!(slot, SlotKind::Value(_))).map(|(index, _)| index).collect();
|
||||||
|
let value_layout_args: Vec<TokenStream2> = value_indices
|
||||||
|
.iter()
|
||||||
|
.map(|index| {
|
||||||
|
let layout = format_ident!("__layout_{index}");
|
||||||
|
quote!(&#layout,)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let carried_meta = || {
|
||||||
|
let meta = ir::layout_meta_tokens(&node, quote!(gcore::record::ElementSpec::Carried), &core_types);
|
||||||
|
quote!(Some(#meta))
|
||||||
|
};
|
||||||
|
|
||||||
|
// The output wire and node wrap follow the output element: a concrete (or
|
||||||
|
// row-assigned) element is a typed record; a generic or opaque element is
|
||||||
|
// an erased record carrying the first base slot's runtime type.
|
||||||
|
let output_element = match &node.output.shape.element {
|
||||||
|
ir::Element::Concrete(element) => Some(substitute_ident_types(element, assignments)),
|
||||||
|
ir::Element::Generic(ident) => assignments.iter().find(|(generic, _)| generic == ident).map(|(_, ty)| ty.clone()),
|
||||||
|
ir::Element::Opaque => None,
|
||||||
|
};
|
||||||
|
let (io_output, wrap) = match &output_element {
|
||||||
|
Some(element) => (
|
||||||
|
quote!(gcore::registry::record_type::<#element>()),
|
||||||
|
quote!(Ok(gcore::registry::EdgeHandle::new_record::<#element>(::std::sync::Arc::new(__node)))),
|
||||||
|
),
|
||||||
|
None => {
|
||||||
|
let name = match &node.output.shape.element {
|
||||||
|
ir::Element::Generic(ident) => ident.to_string(),
|
||||||
|
_ => "T".to_string(),
|
||||||
|
};
|
||||||
|
let base_ty = format_ident!("__ty_{}", base_indices[0]);
|
||||||
|
(
|
||||||
|
quote!(gcore::Type::Record(Box::new(gcore::Type::Generic(::std::borrow::Cow::Borrowed(#name))))),
|
||||||
|
quote!(Ok(gcore::registry::EdgeHandle::new_erased(::std::sync::Arc::new(__node) as ::std::sync::Arc<gcore::registry::ErasedRecordNode>, #base_ty))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (prelude, new_layout_args, layout_meta) = match ir::node_kind(&node) {
|
||||||
|
ir::NodeKind::RecordIo => {
|
||||||
|
let carrier_arg = (node.inputs.first().is_some_and(|input| input.subject) && ir::materialized_levels(&node, 0) == 0).then(|| quote!(&__layout_0,));
|
||||||
|
let layout_meta_fn = format_ident!("{}_layout_meta", fn_name);
|
||||||
|
(quote!(), quote!(#carrier_arg #(#value_layout_args)*), quote!(Some(self::#layout_meta_fn())))
|
||||||
|
}
|
||||||
|
ir::NodeKind::Routing => {
|
||||||
|
let source_layouts = base_indices.iter().map(|index| format_ident!("__layout_{index}"));
|
||||||
|
let source_wraps = base_indices.iter().map(|index| {
|
||||||
|
let name = names[*index];
|
||||||
|
let layout = format_ident!("__layout_{index}");
|
||||||
|
quote!(let #name = gcore::record::RecordSource::new(#name, &#layout, &__union);)
|
||||||
|
});
|
||||||
|
let prelude = quote! {
|
||||||
|
let __union = gcore::record::Layout::union(&[#(&#source_layouts),*]);
|
||||||
|
#(#source_wraps)*
|
||||||
|
};
|
||||||
|
(prelude, quote!(&__union, #(#value_layout_args)*), carried_meta())
|
||||||
|
}
|
||||||
|
ir::NodeKind::Opaque => {
|
||||||
|
let record_layout = format_ident!("__layout_{}", base_indices[0]);
|
||||||
|
(quote!(), quote!(&#record_layout), carried_meta())
|
||||||
|
}
|
||||||
|
ir::NodeKind::Flip => unreachable!("flip has its own multi-row emitter"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// A carried generic instantiates through the struct's trailing phantom
|
||||||
|
// parameters, so the constructor names the row's types after one inferred
|
||||||
|
// slot per input field.
|
||||||
|
let turbofish = (!carried.is_empty()).then(|| {
|
||||||
|
let underscores = (0..arity).map(|_| quote!(_));
|
||||||
|
let carried_types = carried
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(generic, _)| assignments.iter().find(|(ident, _)| ident == generic).map(|(_, ty)| quote!(#ty)));
|
||||||
|
quote!(::<#(#underscores,)* #(#carried_types,)*>)
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(quote! {
|
||||||
|
gcore::registry::RegistryEntry {
|
||||||
|
layout_meta: #layout_meta,
|
||||||
|
io: gcore::registry::NodeIOTypes::new(
|
||||||
|
gcore::concrete!(gcore::context::ContextImpl<'static>),
|
||||||
|
#io_output,
|
||||||
|
vec![#(#input_types),*],
|
||||||
|
),
|
||||||
|
constructor: |inputs| {
|
||||||
|
if inputs.len() != #arity {
|
||||||
|
return Err(gcore::registry::ConstructionError::Arity { expected: #arity, got: inputs.len() });
|
||||||
|
}
|
||||||
|
let mut inputs = inputs.into_iter();
|
||||||
|
#(#downcasts)*
|
||||||
|
#prelude
|
||||||
|
let __node = #struct_name #turbofish::new(#(#names,)* #new_layout_args);
|
||||||
|
#wrap
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
if entries.is_empty() {
|
if entries.is_empty() {
|
||||||
return quote!();
|
return quote!();
|
||||||
|
|||||||
@@ -109,7 +109,12 @@ fn monomorphizations(parsed: &ParsedNodeFn, fields: &[&ParsedField], generics: &
|
|||||||
};
|
};
|
||||||
let positions: Option<Vec<(Ident, usize)>> = generics
|
let positions: Option<Vec<(Ident, usize)>> = generics
|
||||||
.iter()
|
.iter()
|
||||||
.map(|generic| fields.iter().position(|&field| generic_extractable(field_element_type(field), generic)).map(|index| (generic.clone(), index)))
|
.map(|generic| {
|
||||||
|
fields
|
||||||
|
.iter()
|
||||||
|
.position(|&field| generic_extractable(field_element_type(field), generic))
|
||||||
|
.map(|index| (generic.clone(), index))
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let Some(positions) = positions else {
|
let Some(positions) = positions else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
@@ -145,7 +150,13 @@ fn item_shape(element: &Type, depth: u8, reads: &[AttributeRead], generics: &[Id
|
|||||||
ItemShape {
|
ItemShape {
|
||||||
element: element_of(element, generics),
|
element: element_of(element, generics),
|
||||||
depth,
|
depth,
|
||||||
attrs: reads.iter().map(|read| LevelAttr { marker: read.marker.clone(), level: 0 }).collect(),
|
attrs: reads
|
||||||
|
.iter()
|
||||||
|
.map(|read| LevelAttr {
|
||||||
|
marker: read.marker.clone(),
|
||||||
|
level: 0,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,6 +341,20 @@ impl ValueBinding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A record node's lazy inputs consumed as plain elements: their record edges
|
||||||
|
/// need a layout slot at wiring, like the reading secondaries.
|
||||||
|
pub(crate) fn element_lazy_indices(regular_fields: &[&ParsedField], node: &Node) -> Vec<usize> {
|
||||||
|
if !matches!(node_kind(node), NodeKind::RecordIo) {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
regular_fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(index, field)| matches!(field.ty, ParsedFieldType::Node(_)) && matches!(lazy_binding(node, *index), LazyBinding::Element))
|
||||||
|
.map(|(index, _)| index)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub(crate) enum NodeKind {
|
pub(crate) enum NodeKind {
|
||||||
Flip,
|
Flip,
|
||||||
@@ -356,7 +381,10 @@ fn is_routing(node: &Node) -> bool {
|
|||||||
let Element::Generic(output) = &node.output.shape.element else { return false };
|
let Element::Generic(output) = &node.output.shape.element else { return false };
|
||||||
node.monomorphizations.is_empty()
|
node.monomorphizations.is_empty()
|
||||||
&& node.generics.iter().any(|generic| &generic.ident == output && generic.bounds.is_empty())
|
&& node.generics.iter().any(|generic| &generic.ident == output && generic.bounds.is_empty())
|
||||||
&& node.inputs.iter().any(|input| input.subject && matches!(&input.shape.element, Element::Generic(generic) if generic == output))
|
&& node
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.any(|input| input.subject && matches!(&input.shape.element, Element::Generic(generic) if generic == output))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_attr_io(node: &Node) -> bool {
|
fn has_attr_io(node: &Node) -> bool {
|
||||||
@@ -401,7 +429,7 @@ pub(crate) fn lazy_binding(node: &Node, index: usize) -> LazyBinding {
|
|||||||
LazyBinding::DeriveRouting
|
LazyBinding::DeriveRouting
|
||||||
} else if node.derives && matches!(kind, NodeKind::RecordIo) && input.subject {
|
} else if node.derives && matches!(kind, NodeKind::RecordIo) && input.subject {
|
||||||
LazyBinding::DeriveCarrier
|
LazyBinding::DeriveCarrier
|
||||||
} else if matches!(kind, NodeKind::Flip) {
|
} else if matches!(kind, NodeKind::Flip) || (matches!(kind, NodeKind::RecordIo) && !input.subject) {
|
||||||
LazyBinding::Element
|
LazyBinding::Element
|
||||||
} else if matches!(input.shape.element, Element::Opaque) {
|
} else if matches!(input.shape.element, Element::Opaque) {
|
||||||
LazyBinding::OpaqueRecord
|
LazyBinding::OpaqueRecord
|
||||||
@@ -577,7 +605,9 @@ mod tests {
|
|||||||
delta: 0,
|
delta: 0,
|
||||||
}
|
}
|
||||||
} else if kinds.opaque {
|
} else if kinds.opaque {
|
||||||
let record = fields.iter().position(|field| matches!(&field.ty, ParsedFieldType::Node(NodeParsedField { output_type, .. }) if is_record_value(output_type)));
|
let record = fields
|
||||||
|
.iter()
|
||||||
|
.position(|field| matches!(&field.ty, ParsedFieldType::Node(NodeParsedField { output_type, .. }) if is_record_value(output_type)));
|
||||||
Facts {
|
Facts {
|
||||||
sources: record.into_iter().collect(),
|
sources: record.into_iter().collect(),
|
||||||
carried: true,
|
carried: true,
|
||||||
@@ -588,7 +618,12 @@ mod tests {
|
|||||||
} else if kinds.routing {
|
} else if kinds.routing {
|
||||||
let generic = routing_generic(parsed).expect("routing has a generic");
|
let generic = routing_generic(parsed).expect("routing has a generic");
|
||||||
Facts {
|
Facts {
|
||||||
sources: fields.iter().enumerate().filter(|(_, field)| bare_ident(&source_ty(field)) == Some(&generic)).map(|(index, _)| index).collect(),
|
sources: fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, field)| bare_ident(&source_ty(field)) == Some(&generic))
|
||||||
|
.map(|(index, _)| index)
|
||||||
|
.collect(),
|
||||||
carried: true,
|
carried: true,
|
||||||
writes: vec![],
|
writes: vec![],
|
||||||
removes: vec![],
|
removes: vec![],
|
||||||
@@ -617,7 +652,14 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bridge_flip_concrete() {
|
fn bridge_flip_concrete() {
|
||||||
assert_bridge(quote!(category("")), quote!(fn negate(_: impl Ctx, x: f64) -> f64 { -x }));
|
assert_bridge(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(
|
||||||
|
fn negate(_: impl Ctx, x: f64) -> f64 {
|
||||||
|
-x
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -632,17 +674,38 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bridge_record_write() {
|
fn bridge_record_write() {
|
||||||
assert_bridge(quote!(category("")), quote!(fn set_opacity(_: impl Ctx, val: f64) -> (f64, Attr<Opacity>) { (val, Attr(1.)) }));
|
assert_bridge(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(
|
||||||
|
fn set_opacity(_: impl Ctx, val: f64) -> (f64, Attr<Opacity>) {
|
||||||
|
(val, Attr(1.))
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bridge_record_remove() {
|
fn bridge_record_remove() {
|
||||||
assert_bridge(quote!(category("")), quote!(fn strip(_: impl Ctx, val: f64) -> (f64, RemoveAttr<Opacity>) { (val, RemoveAttr) }));
|
assert_bridge(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(
|
||||||
|
fn strip(_: impl Ctx, val: f64) -> (f64, RemoveAttr<Opacity>) {
|
||||||
|
(val, RemoveAttr)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bridge_record_fresh() {
|
fn bridge_record_fresh() {
|
||||||
assert_bridge(quote!(category("")), quote!(fn make(_: impl Ctx, _: (), fill: f64) -> (f64, Attr<Opacity>) { (fill, Attr(1.)) }));
|
assert_bridge(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(
|
||||||
|
fn make(_: impl Ctx, _: (), fill: f64) -> (f64, Attr<Opacity>) {
|
||||||
|
(fill, Attr(1.))
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -724,7 +787,7 @@ mod tests {
|
|||||||
match &field.ty {
|
match &field.ty {
|
||||||
ParsedFieldType::Regular(_) => match value_binding(node, index) {
|
ParsedFieldType::Regular(_) => match value_binding(node, index) {
|
||||||
ValueBinding::Carrier => "carrier",
|
ValueBinding::Carrier => "carrier",
|
||||||
ValueBinding::Materialized => "materialized",
|
ValueBinding::Materialized => "materialized",
|
||||||
ValueBinding::Lend => "lend",
|
ValueBinding::Lend => "lend",
|
||||||
ValueBinding::ReadingSecondary => "reading",
|
ValueBinding::ReadingSecondary => "reading",
|
||||||
ValueBinding::RecordElement => "record",
|
ValueBinding::RecordElement => "record",
|
||||||
@@ -767,57 +830,109 @@ mod tests {
|
|||||||
assert_eq!(actual_kind, expected_kind, "node_kind of {}", parsed.fn_name);
|
assert_eq!(actual_kind, expected_kind, "node_kind of {}", parsed.fn_name);
|
||||||
let fields: Vec<&ParsedField> = parsed.fields.iter().filter(|field| !field.is_data_field).collect();
|
let fields: Vec<&ParsedField> = parsed.fields.iter().filter(|field| !field.is_data_field).collect();
|
||||||
for (index, field) in fields.iter().enumerate() {
|
for (index, field) in fields.iter().enumerate() {
|
||||||
assert_eq!(
|
assert_eq!(ir_label(&node, index, field, raw), reference_label(&parsed, raw, index, field), "field {index} of {}", parsed.fn_name);
|
||||||
ir_label(&node, index, field, raw),
|
|
||||||
reference_label(&parsed, raw, index, field),
|
|
||||||
"field {index} of {}",
|
|
||||||
parsed.fn_name
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bindings_flip() {
|
fn bindings_flip() {
|
||||||
assert_bindings(quote!(category("")), quote!(fn negate(_: impl Ctx, x: f64) -> f64 { -x }));
|
assert_bindings(
|
||||||
assert_bindings(quote!(category("")), quote!(fn add2(_: impl Ctx, a: f64, b: f64) -> f64 { a + b }));
|
quote!(category("")),
|
||||||
|
quote!(
|
||||||
|
fn negate(_: impl Ctx, x: f64) -> f64 {
|
||||||
|
-x
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
assert_bindings(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(
|
||||||
|
fn add2(_: impl Ctx, a: f64, b: f64) -> f64 {
|
||||||
|
a + b
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bindings_lend() {
|
fn bindings_lend() {
|
||||||
assert_bindings(quote!(category("")), quote!(fn borrow(_: impl Ctx, prim: f64, other: &f64) -> f64 { prim + *other }));
|
assert_bindings(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(
|
||||||
|
fn borrow(_: impl Ctx, prim: f64, other: &f64) -> f64 {
|
||||||
|
prim + *other
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bindings_reading_secondary() {
|
fn bindings_reading_secondary() {
|
||||||
assert_bindings(quote!(category("")), quote!(fn read_op(_: impl Ctx, carrier: f64, (other, op): (f64, Attr<Opacity>)) -> f64 { carrier + other }));
|
assert_bindings(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(
|
||||||
|
fn read_op(_: impl Ctx, carrier: f64, (other, op): (f64, Attr<Opacity>)) -> f64 {
|
||||||
|
carrier + other
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bindings_flip_lazy() {
|
fn bindings_flip_lazy() {
|
||||||
assert_bindings(quote!(category("")), quote!(fn apply(_: impl Ctx, inner: impl Node<(), Output = f64>) -> f64 { inner.eval(()) }));
|
assert_bindings(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(
|
||||||
|
fn apply(_: impl Ctx, inner: impl Node<(), Output = f64>) -> f64 {
|
||||||
|
inner.eval(())
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bindings_flip_lazy_reads() {
|
fn bindings_flip_lazy_reads() {
|
||||||
assert_bindings(
|
assert_bindings(
|
||||||
quote!(category("")),
|
quote!(category("")),
|
||||||
quote!(fn apply_reads(_: impl Ctx, carrier: f64, inner: impl Node<(), Output = (f64, Attr<Opacity>)>) -> f64 { carrier + inner.eval(()).0 }),
|
quote!(
|
||||||
|
fn apply_reads(_: impl Ctx, carrier: f64, inner: impl Node<(), Output = (f64, Attr<Opacity>)>) -> f64 {
|
||||||
|
carrier + inner.eval(()).0
|
||||||
|
}
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bindings_flip_raw() {
|
fn bindings_flip_raw() {
|
||||||
assert_bindings(quote!(category("")), quote!(fn poll_apply(_: impl Ctx, inner: impl Node<(), Output = f64>) -> GPoll<f64> { inner.eval(()) }));
|
assert_bindings(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(
|
||||||
|
fn poll_apply(_: impl Ctx, inner: impl Node<(), Output = f64>) -> GPoll<f64> {
|
||||||
|
inner.eval(())
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bindings_skip_impl_generic() {
|
fn bindings_skip_impl_generic() {
|
||||||
// A bounded generic forwarded whole (passthrough) flips, not routes.
|
// A bounded generic forwarded whole (passthrough) flips, not routes.
|
||||||
assert_bindings(quote!(category(""), skip_impl), quote!(fn passthrough<T: Send>(_: impl Ctx, content: T) -> T { content }));
|
assert_bindings(
|
||||||
|
quote!(category(""), skip_impl),
|
||||||
|
quote!(
|
||||||
|
fn passthrough<T: Send>(_: impl Ctx, content: T) -> T {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
// A generic transformed into a different output type flips.
|
// A generic transformed into a different output type flips.
|
||||||
assert_bindings(
|
assert_bindings(
|
||||||
quote!(category(""), skip_impl),
|
quote!(category(""), skip_impl),
|
||||||
quote!(fn into_ty<T: Send + Into<O>, O: Send>(_: impl Ctx, value: T, #[data] _out: PhantomData<O>) -> O { value.into() }),
|
quote!(
|
||||||
|
fn into_ty<T: Send + Into<O>, O: Send>(_: impl Ctx, value: T, #[data] _out: PhantomData<O>) -> O {
|
||||||
|
value.into()
|
||||||
|
}
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,20 +940,35 @@ mod tests {
|
|||||||
fn bindings_routing() {
|
fn bindings_routing() {
|
||||||
assert_bindings(
|
assert_bindings(
|
||||||
quote!(category("")),
|
quote!(category("")),
|
||||||
quote!(fn switch<T>(_: impl Ctx, condition: bool, off: impl Node<(), Output = T>, on: impl Node<(), Output = T>) -> T { if condition { on.eval(()) } else { off.eval(()) } }),
|
quote!(
|
||||||
|
fn switch<T>(_: impl Ctx, condition: bool, off: impl Node<(), Output = T>, on: impl Node<(), Output = T>) -> T {
|
||||||
|
if condition { on.eval(()) } else { off.eval(()) }
|
||||||
|
}
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bindings_derive_routing() {
|
fn bindings_derive_routing() {
|
||||||
assert_bindings(quote!(category("")), quote!(fn ctx_mod<T>(_: impl Ctx + DeriveCtx, inner: impl Node<(), Output = T>) -> T { inner.eval(()) }));
|
assert_bindings(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(
|
||||||
|
fn ctx_mod<T>(_: impl Ctx + DeriveCtx, inner: impl Node<(), Output = T>) -> T {
|
||||||
|
inner.eval(())
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bindings_opaque() {
|
fn bindings_opaque() {
|
||||||
assert_bindings(
|
assert_bindings(
|
||||||
quote!(category("")),
|
quote!(category("")),
|
||||||
quote!(fn memo<'e>(_: impl Ctx, #[data] cache: Store, content: impl Node<Context<'_>, Output = RecordValue<'e>>) -> GPoll<RecordValue<'e>> { content.eval(()) }),
|
quote!(
|
||||||
|
fn memo<'e>(_: impl Ctx, #[data] cache: Store, content: impl Node<Context<'_>, Output = RecordValue<'e>>) -> GPoll<RecordValue<'e>> {
|
||||||
|
content.eval(())
|
||||||
|
}
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -846,7 +976,11 @@ mod tests {
|
|||||||
fn creator_ilist_return_pushes_a_level() {
|
fn creator_ilist_return_pushes_a_level() {
|
||||||
let mut parsed = parse_node_fn(
|
let mut parsed = parse_node_fn(
|
||||||
quote!(category(""), extent(repeat_extent)),
|
quote!(category(""), extent(repeat_extent)),
|
||||||
quote!(fn repeat<T>(_: impl Ctx, (element, transform): (T, Attr<Transform>), count: u32) -> IList<(T, Attr<Transform>)> { emit(element, Attr(count as f64)) }),
|
quote!(
|
||||||
|
fn repeat<T>(_: impl Ctx, (element, transform): (T, Attr<Transform>), count: u32) -> IList<(T, Attr<Transform>)> {
|
||||||
|
emit(element, Attr(count as f64))
|
||||||
|
}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
parsed.replace_impl_trait_in_input();
|
parsed.replace_impl_trait_in_input();
|
||||||
@@ -862,7 +996,15 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reducer_ilist_input_collapses_a_level() {
|
fn reducer_ilist_input_collapses_a_level() {
|
||||||
let mut parsed = parse_node_fn(quote!(category("")), quote!(fn sum(_: impl Ctx, items: IList<f64>) -> f64 { items.into_iter().sum() })).unwrap();
|
let mut parsed = parse_node_fn(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(
|
||||||
|
fn sum(_: impl Ctx, items: IList<f64>) -> f64 {
|
||||||
|
items.into_iter().sum()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
parsed.replace_impl_trait_in_input();
|
parsed.replace_impl_trait_in_input();
|
||||||
let node = build(&parsed);
|
let node = build(&parsed);
|
||||||
// The `IList` input is a depth-1 subject; the scalar output collapses it.
|
// The `IList` input is a depth-1 subject; the scalar output collapses it.
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ pub(crate) fn generate_phantom_data<'a>(fn_generics: impl Iterator<Item = &'a cr
|
|||||||
(fn_generic_params, phantom_data_declerations)
|
(fn_generic_params, phantom_data_declerations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Get only the necessary generics.
|
/// Get only the necessary generics.
|
||||||
struct FilterUsedGenerics {
|
struct FilterUsedGenerics {
|
||||||
all: Vec<crate::GenericParam>,
|
all: Vec<crate::GenericParam>,
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use syn::punctuated::Punctuated;
|
|||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::token::{Comma, RArrow};
|
use syn::token::{Comma, RArrow};
|
||||||
use syn::{
|
use syn::{
|
||||||
AttrStyle, Attribute, Error, Expr, FnArg, GenericArgument, GenericParam, Ident, ItemFn, Lit, LitFloat, LitInt, LitStr, Meta, Pat, PatIdent, PatType, Path, PathArguments, ReturnType,
|
AttrStyle, Attribute, Error, Expr, FnArg, GenericArgument, GenericParam, Ident, ItemFn, Lit, LitFloat, LitInt, LitStr, Meta, Pat, PatIdent, PatType, Path, PathArguments, ReturnType, TraitBound,
|
||||||
TraitBound, Type, TypeImplTrait, TypeParam, TypeParamBound, Visibility, WhereClause, parse_quote,
|
Type, TypeImplTrait, TypeParam, TypeParamBound, Visibility, WhereClause, parse_quote,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::codegen::generate_node_code;
|
use crate::codegen::generate_node_code;
|
||||||
@@ -585,7 +585,9 @@ impl Parse for NodeFnAttributes {
|
|||||||
if extent_raw.is_some() {
|
if extent_raw.is_some() {
|
||||||
return Err(Error::new_spanned(meta, "Multiple 'extent_raw' attributes are not allowed"));
|
return Err(Error::new_spanned(meta, "Multiple 'extent_raw' attributes are not allowed"));
|
||||||
}
|
}
|
||||||
let parsed_path: Path = meta.parse_args().map_err(|_| Error::new_spanned(meta, "Expected a valid path for 'extent_raw', e.g., extent_raw(my_extent)"))?;
|
let parsed_path: Path = meta
|
||||||
|
.parse_args()
|
||||||
|
.map_err(|_| Error::new_spanned(meta, "Expected a valid path for 'extent_raw', e.g., extent_raw(my_extent)"))?;
|
||||||
extent_raw = Some(parsed_path);
|
extent_raw = Some(parsed_path);
|
||||||
}
|
}
|
||||||
// Function overriding the generated `eval_batch` method, replacing the trait's per-lane spec loop.
|
// Function overriding the generated `eval_batch` method, replacing the trait's per-lane spec loop.
|
||||||
@@ -858,10 +860,7 @@ fn parse_read_tuple(pat_tuple: &syn::PatTuple, ty: &Type, attrs: &[Attribute], i
|
|||||||
let Pat::Ident(pat_ident) = pat else {
|
let Pat::Ident(pat_ident) = pat else {
|
||||||
return Err(Error::new_spanned(pat, "Expected a simple identifier for the attribute read"));
|
return Err(Error::new_spanned(pat, "Expected a simple identifier for the attribute read"));
|
||||||
};
|
};
|
||||||
Ok(AttributeRead {
|
Ok(AttributeRead { pat_ident: pat_ident.clone(), marker })
|
||||||
pat_ident: pat_ident.clone(),
|
|
||||||
marker,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.collect::<syn::Result<_>>()?;
|
.collect::<syn::Result<_>>()?;
|
||||||
let mut field = parse_field(value_ident.clone(), value_ty.clone(), attrs).map_err(|e| Error::new_spanned(&value_ident, format!("Failed to parse argument '{}': {}", value_ident.ident, e)))?;
|
let mut field = parse_field(value_ident.clone(), value_ty.clone(), attrs).map_err(|e| Error::new_spanned(&value_ident, format!("Failed to parse argument '{}': {}", value_ident.ident, e)))?;
|
||||||
@@ -1539,7 +1538,7 @@ mod tests {
|
|||||||
description: String::new(),
|
description: String::new(),
|
||||||
widget_override: ParsedWidgetOverride::None,
|
widget_override: ParsedWidgetOverride::None,
|
||||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||||
lend: None,
|
lend: None,
|
||||||
list_levels: 0,
|
list_levels: 0,
|
||||||
ty: parse_quote!(DVec2),
|
ty: parse_quote!(DVec2),
|
||||||
exposed: false,
|
exposed: false,
|
||||||
|
|||||||
@@ -40,10 +40,7 @@ fn validate_record_io(parsed: &ParsedNodeFn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let writes = record_writes(&value);
|
let writes = record_writes(&value);
|
||||||
let has_reads = parsed
|
let has_reads = parsed.fields.iter().any(|field| !field.attribute_reads.is_empty() && matches!(field.ty, ParsedFieldType::Regular(_)));
|
||||||
.fields
|
|
||||||
.iter()
|
|
||||||
.any(|field| !field.attribute_reads.is_empty() && matches!(field.ty, ParsedFieldType::Regular(_)));
|
|
||||||
if !has_reads && writes.is_none() {
|
if !has_reads && writes.is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -56,8 +53,14 @@ fn validate_record_io(parsed: &ParsedNodeFn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for field in parsed.fields.iter().skip(1) {
|
for field in parsed.fields.iter().skip(1) {
|
||||||
if matches!(field.ty, ParsedFieldType::Node(_)) {
|
if let ParsedFieldType::Node(NodeParsedField { output_type, .. }) = &field.ty {
|
||||||
emit_error!(field.pat_ident.span(), "record nodes take no lazy inputs yet");
|
// Lazy secondaries are consumed as plain elements through the wire.
|
||||||
|
if crate::codegen::classify::is_record_value(output_type) || crate::codegen::ir::strip_ilist(output_type).1 > 0 {
|
||||||
|
emit_error!(field.pat_ident.span(), "a record node's lazy inputs consume plain elements, not record or ranked wires");
|
||||||
|
}
|
||||||
|
if !field.attribute_reads.is_empty() {
|
||||||
|
emit_error!(field.pat_ident.span(), "attribute reads on a record node's lazy inputs are not supported yet");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (index, field) in parsed.fields.iter().enumerate() {
|
for (index, field) in parsed.fields.iter().enumerate() {
|
||||||
@@ -91,10 +94,7 @@ fn validate_record_io(parsed: &ParsedNodeFn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Some(carrier) = parsed.fields.first() else {
|
let Some(carrier) = parsed.fields.first() else {
|
||||||
emit_error!(
|
emit_error!(parsed.fn_name.span(), "attribute io needs a primary input as the first parameter after the context (`_: ()` for none)");
|
||||||
parsed.fn_name.span(),
|
|
||||||
"attribute io needs a primary input as the first parameter after the context (`_: ()` for none)"
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let lazy_carrier = matches!(&crate::codegen::record_shape(parsed), Some(shape) if matches!(shape.carrier, crate::codegen::RecordCarrier::LazyToken));
|
let lazy_carrier = matches!(&crate::codegen::record_shape(parsed), Some(shape) if matches!(shape.carrier, crate::codegen::RecordCarrier::LazyToken));
|
||||||
@@ -111,10 +111,7 @@ fn validate_record_io(parsed: &ParsedNodeFn) {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if lazy_carrier && !crate::codegen::ir::build(parsed).derives {
|
if lazy_carrier && !crate::codegen::ir::build(parsed).derives {
|
||||||
emit_error!(
|
emit_error!(parsed.input.pat_ident.span(), "a lazy record carrier evaluates at derived contexts; spell `impl Ctx + DeriveCtx`");
|
||||||
parsed.input.pat_ident.span(),
|
|
||||||
"a lazy record carrier evaluates at derived contexts; spell `impl Ctx + DeriveCtx`"
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,11 +125,7 @@ fn validate_record_io(parsed: &ParsedNodeFn) {
|
|||||||
match &token {
|
match &token {
|
||||||
Some(token) => {
|
Some(token) => {
|
||||||
if !matches!(crate::codegen::bare_ident(element), Some(ident) if ident == token) {
|
if !matches!(crate::codegen::bare_ident(element), Some(ident) if ident == token) {
|
||||||
emit_error!(
|
emit_error!(parsed.output_type.span(), "a generic element passes through unchanged: return `{}` in the first tuple position", token);
|
||||||
parsed.output_type.span(),
|
|
||||||
"a generic element passes through unchanged: return `{}` in the first tuple position",
|
|
||||||
token
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
@@ -1687,11 +1687,32 @@ fn path_is_closed(
|
|||||||
|
|
||||||
// Converts with the write-attribute family: the record form needs a lazy
|
// Converts with the write-attribute family: the record form needs a lazy
|
||||||
// value input on a record node, which the macro does not accept yet, and it
|
// value input on a record node, which the macro does not accept yet, and it
|
||||||
// shares that family's per-item index convention.
|
/// Sets each anchor point's position to the value the mapped input produces, with the point's
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
/// index and current position provided via context.
|
||||||
fn map_points(ctx: impl Ctx + DeriveCtx, content: List<Vector>, mapped: impl Node<Context<'_>, Output = DVec2>) -> Result<List<Vector>, Interrupt> {
|
#[node_macro::node(category("Vector"), path(graphene_core::vector), extent(map_points_extent))]
|
||||||
|
fn map_points<'e>(
|
||||||
|
ctx: impl Ctx + DeriveCtx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
||||||
|
content: IList<Vector>,
|
||||||
|
mapped: impl Node<Context<'_>, Output = DVec2>,
|
||||||
|
) -> Result<
|
||||||
|
IList<(
|
||||||
|
Vector,
|
||||||
|
Attr<'e, TransformAttr>,
|
||||||
|
Attr<'e, Fill>,
|
||||||
|
Attr<'e, StrokeAttr>,
|
||||||
|
Attr<'e, BlendModeAttr>,
|
||||||
|
Attr<'e, Opacity>,
|
||||||
|
Attr<'e, OpacityFill>,
|
||||||
|
Attr<'e, ClippingMask>,
|
||||||
|
Attr<'e, EditorLayerPath>,
|
||||||
|
Attr<'e, EditorMergedLayers>,
|
||||||
|
)>,
|
||||||
|
Interrupt,
|
||||||
|
> {
|
||||||
|
// The pushed copy keeps the legacy convention: the running point index
|
||||||
|
// across all rows rides as a promotion for the mapped input.
|
||||||
let spilled = ctx.index_head();
|
let spilled = ctx.index_head();
|
||||||
let mut content = content;
|
let mut content = legacy_vector_list_of(content);
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
|
|
||||||
for vector in content.iter_element_values_mut() {
|
for vector in content.iter_element_values_mut() {
|
||||||
@@ -1702,7 +1723,11 @@ fn map_points(ctx: impl Ctx + DeriveCtx, content: List<Vector>, mapped: impl Nod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(content)
|
emit_legacy_lane(ctx.arena(), content, ctx.innermost_index() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_points_extent(content: ListIn<'_, Vector>, _mapped: ExtentIn<'_>, level: LevelIn) -> GPoll<Extent> {
|
||||||
|
subject_counts_extent(content, level)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flatten_path_core<'e>(
|
fn flatten_path_core<'e>(
|
||||||
|
|||||||
Reference in New Issue
Block a user