Sweep the data-flow wire and edge wording to input and source

This commit is contained in:
Dennis Kobert
2026-09-06 15:14:55 +00:00
parent 96521333d6
commit 9d3abe385f
34 changed files with 156 additions and 156 deletions

View File

@@ -431,8 +431,8 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
register_metadata();
}
};
// Record nodes construct through the generated `wire` fn, which resolves
// offsets from the carrier layout; `new` cannot fill that state.
// Record nodes construct through `new` with the carrier layout, which
// resolves the offsets their reads and writes address.
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();
// The lane-invariance mask arrives with the resolved layout, so `new` starts
@@ -878,7 +878,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
})
});
let derive_routing = derives && routing_generic.is_some();
// A kernel that holds records (a forwarded routing wire, or a lazy edge it
// A kernel that holds records (a forwarded routing source, or a lazy input it
// serves itself) names the record lifetime; unless it declared a serving
// lifetime of its own, the context binds the arena at that lifetime.
let kernel_lazy = parsed.fields.iter().any(|field| !field.is_data_field && matches!(field.ty, ParsedFieldType::Node(_)));
@@ -935,7 +935,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
})
.map(|param| match param {
// Flipped kernels clone bare-typed elements out of their records,
// so those generics carry the bound wire values satisfy; a
// so those generics carry the bound input values satisfy; a
// generic only nested in a field's type stays as declared.
GenericParam::Type(type_param)
if flip
@@ -1044,7 +1044,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
}
}
}
// The record lifetime the kernel's wire types and arena bound name; the
// The record lifetime the kernel's input types and arena bound name; the
// impl infers it from the serving lifetime at every call.
if wants_record_lifetime {
generics.insert(0, quote!('__record));
@@ -1156,7 +1156,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
let node_bounds = regular_fields.iter().enumerate().zip(&node_generics).map(|((index, field), node_generic)| {
let plain = quote!(#node_generic: #core_types::node::Node<#ctx_ident>);
// A lazy edge the kernel evaluates at derived contexts needs the
// A lazy input the kernel evaluates at derived contexts needs the
// derived form: the derived context's arena binding is unnameable
// under a higher rank.
let derived = quote!(#node_generic: #derived_edge);
@@ -1173,7 +1173,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
true => derived,
false => plain,
},
// An element-consuming lazy secondary rides a record edge, derivable
// An element-consuming lazy secondary rides a record input, 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 => derived_plus,
@@ -1188,8 +1188,8 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
let bound = lazy_bound();
quote!(#node_generic: #bound)
}
// Every wire is a record edge; a value input's element copies out of
// the record its edge serves.
// Every input is a record input; a value input's element copies out of
// the record its source serves.
ParsedFieldType::Regular(_) => plain,
}
});
@@ -1202,7 +1202,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
lend_outlives.push(quote!(#inner: #lifetime));
}
// The slot persists the plain value even on record wires, so the Clone
// The slot persists the plain value even on record inputs, so the Clone
// bound targets the slot type, not the (possibly lifted) trait output.
let slot_ty = crate::codegen::classify::slot_static_type(&parsed.output_type);
let mut async_bounds = match (async_fn, future_kernel) {
@@ -1263,8 +1263,8 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
#[allow(unused_mut, unused_variables)]
let mut __frame = __run.slot(__lane, &__lane_frames);
};
// A lazy edge claims beyond every input frame this node holds, and its
// cursor is shared, so the edges a kernel drives claim past each other.
// A lazy input claims beyond every input frame this node holds, and its
// cursor is shared, so the inputs a kernel drives claim past each other.
let lazy_frames_entry = quote! {
let __lazy_frames = __frame.frames().reborrow();
};
@@ -1296,7 +1296,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
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_failed = fail(quote!(#core_types::gpoll::GraphError::new("reduce batch failed")));
// A fold consumes the whole subject wire: a deeper wire's
// A fold consumes the whole subject input: a deeper input's
// total flat span, sized under the evaluation context, so a
// fold inside a pushed level covers that copy's span. The
// span caches per (lane-normalized context, generation):
@@ -1369,7 +1369,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
let #name = unsafe { #core_types::node::List::<#ty>::new(__batch) };
}
}
// A reading secondary input claims a record edge: the element and
// A reading secondary input claims a record input: the element and
// the declared reads copy out right after its eval.
ValueBinding::ReadingSecondary => {
let slot = format_ident!("__in_{index}");
@@ -1398,7 +1398,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
let #name = unsafe { #core_types::record::borrow_element::<#ty>(self.#slot.rec(&#record_local)) };
}
}
// A flip value or a routing non-source value rides a record edge; the
// A flip value or a routing non-source value rides a record input; the
// element copies out into `name`. The mark/rewind that reclaims the
// record's frame is applied by the step lowering (see `reads_out`).
ValueBinding::RecordElement => {
@@ -1411,8 +1411,8 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
let #name: #ty = unsafe { #core_types::record::read_element(self.#slot.rec(&#name)) };
}
}
// A plain value rides a record edge like every other input; the
// element copies out against the edge's own layout, except for a
// A plain value rides a record input like every other input; the
// element copies out against the input's own layout, except for a
// routing source, whose record is what the kernel forwards.
ValueBinding::Plain => {
let read = (!routing_source(ty)).then(|| {
@@ -1432,7 +1432,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
}
},
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => match (ir::lazy_binding(&node, index), raw_lazy) {
// A raw poll edge is threaded straight through, so it does not bind here.
// A raw poll input is threaded straight through, so it does not bind here.
(LazyBinding::Generic, true) => quote!(),
(LazyBinding::DeriveRouting, _) => quote! {
let #name = #core_types::record::RecordLazyInput::new(&self.#name, &__cell, #index, self.__layout.depth.saturating_sub(#pushed_levels), &__lazy_frames);
@@ -1493,7 +1493,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
}
};
// A bind whose element copies out reclaims the edge's frame; a forwarded
// A bind whose element copies out reclaims the input's frame; a forwarded
// record must outlive the bind, so its frame stays.
let _reads_out_at = |index: usize| match &regular_fields[index].ty {
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => !routing_source(ty) && ir::value_binding(&node, index).reads_out(),
@@ -1518,7 +1518,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
let call_args = regular_fields.iter().enumerate().filter(|(_, field)| !injected_name(&field.pat_ident.ident)).map(|(index, field)| {
let name = &field.pat_ident.ident;
match &field.ty {
// A lend param binds an owned edge; the kernel borrows the
// A lend param binds an owned input; the kernel borrows the
// evaluated value.
ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. }) if !flip => quote!(&#name),
ParsedFieldType::Regular(_) => quote!(#name),
@@ -1534,7 +1534,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
// composite `extent(ctx, Level)`, which the trait derives from it. A node
// without `extent = fn` keeps the scalar default (one item at every level).
// The typed extent surface: the node's inputs in declaration order (values
// readable without unsafe, edges as per-level extent queries, derived
// readable without unsafe, inputs as per-level extent queries, derived
// content promoted per copy), then the level paired with the node's depth.
let extent_impl = if let Some(path) = &parsed.attributes.extent {
let mut arg_decls: Vec<TokenStream2> = Vec::new();
@@ -1565,7 +1565,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
_ => extent_edge(&query, &arg),
},
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => match ir::value_binding(&node, index) {
// A ranked input materializes whole (the wire's total flat
// A ranked input materializes whole (the input's total flat
// span, as in eval), so a data-dependent extent can walk
// its lanes.
ValueBinding::Materialized => {
@@ -1603,7 +1603,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
};
quote! {
let #query = || {
// The element copies out by value, so the edge's
// The element copies out by value, so the input's
// claim dies with the query.
let __scope = __frames.scope();
#core_types::record::serve_input(&self.#name, __input, &__scope)
@@ -1613,7 +1613,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
}
}
// A carrier, lent, or materialized ranked input is a record
// edge; its extents are the queryable quantity.
// input; its extents are the queryable quantity.
_ => extent_edge(&query, &arg),
},
};
@@ -1853,7 +1853,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
})
});
// Async slots persist plain values across evaluations; the source lifts
// the slot value onto its record wire at every merge point, into the
// the slot value onto its record input at every merge point, into the
// carried frame when the node has a carrier.
// A writing source stores the kernel's whole tuple as that plain value:
// the lift writes the attributes through the claim, then lifts the
@@ -1961,7 +1961,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
let value_args = regular_fields.iter().skip(if skips_carrier { 0 } else { 1 }).map(|field| {
let name = &field.pat_ident.ident;
match &field.ty {
// A lend param binds an owned edge; the kernel borrows the
// A lend param binds an owned input; the kernel borrows the
// evaluated value.
ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. }) => quote!(&#name),
_ => tuple_arg(field, quote!(#name)),
@@ -2230,7 +2230,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
&lazy_frames_entry,
);
// The rebind path with nothing hoisted: every non-carrier input binds
// fresh per lane, so an index-dependent edge reaches its own lane.
// fresh per lane, so an index-dependent input reaches its own lane.
let rebound_lane_binds: Vec<TokenStream2> = lazy_last(
regular_fields
.iter()

View File

@@ -131,7 +131,7 @@ pub(crate) fn lazy_read_fields<'a>(regular_fields: &[&'a ParsedField]) -> Vec<(u
}
/// The indices (into the unit-skipped regular fields) of value inputs whose
/// reads resolve against their own wire rather than the carrier's.
/// reads resolve against their own input rather than the carrier's.
pub(crate) fn reading_secondary_indices(regular_fields: &[&ParsedField], skips_carrier: bool) -> Vec<usize> {
regular_fields
.iter()
@@ -177,7 +177,7 @@ pub(crate) fn substitute_ident_types(ty: &Type, assignments: &[(Ident, Type)]) -
}
/// Replaces the routing generic in a derive-routing kernel's return type with
/// the routing record value, since the kernel's edges rebind to '__record.
/// the routing record value, since the kernel's inputs rebind to '__record.
pub(crate) fn substitute_routing_record(output: &Type, generic: &Ident, core_types: &TokenStream2) -> Type {
struct Subst<'a> {
generic: &'a Ident,
@@ -295,7 +295,7 @@ pub(crate) fn record_shape(parsed: &ParsedNodeFn) -> Option<RecordShape> {
return None;
}
// An async source's slot stores the kernel's plain tuple; the per-eval lift
// writes it through the claim, and the reads have no wire to bind against.
// writes it through the claim, and the reads have no input to bind against.
if source && (has_reads || writes.is_none()) {
return None;
}
@@ -306,7 +306,7 @@ pub(crate) fn record_shape(parsed: &ParsedNodeFn) -> Option<RecordShape> {
// A first-field lazy carrier: the kernel evaluates the derived content
// itself and returns its opaque row token beside the write set.
let lazy_carrier = matches!(&carrier_field.ty, ParsedFieldType::Node(_));
// Lazy secondaries are consumed as plain elements; raw record edges and
// Lazy secondaries are consumed as plain elements; raw record inputs and
// ranked outputs have no element binding here.
let unsupported_lazy_secondary = |field: &ParsedField| match &field.ty {
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => is_served(output_type) || crate::codegen::ir::strip_ilist(output_type).1 > 0 || !field.attribute_reads.is_empty(),
@@ -423,7 +423,7 @@ pub(crate) fn flip_carrier(parsed: &ParsedNodeFn) -> bool {
!(async_kernel && lend.is_some())
}
/// Whether a plain node's lowering flips onto record wires: sync,
/// Whether a plain node's lowering flips onto record inputs: sync,
/// fully-concrete value-input nodes in this cut; batch, shader, async, lend,
/// lazy, and generic nodes keep the plain lowering until their record forms
/// land.
@@ -467,7 +467,7 @@ pub(crate) fn record_flip(parsed: &ParsedNodeFn) -> bool {
return false;
}
}
// A named lifetime is the serving lifetime: wire types substitute
// A named lifetime is the serving lifetime: input types substitute
// its erased projection in registry and layout contexts.
GenericParam::Lifetime(_) => {}
GenericParam::Const(_) => return false,
@@ -520,7 +520,7 @@ pub(crate) fn is_served(ty: &Type) -> bool {
}
/// Whether a kernel operates on whole records: it serves through the claim it
/// was handed, receives raw record edges paired with the node's layout, and
/// was handed, receives raw record inputs paired with the node's layout, and
/// takes on the record APIs' unsafe contracts itself.
pub(crate) fn record_opaque(parsed: &ParsedNodeFn) -> bool {
is_served(&slot_value_type(&parsed.output_type))
@@ -723,7 +723,7 @@ pub(crate) fn type_disqualifies(ty: &Type) -> bool {
visitor.found
}
/// The wire type with every named serving lifetime replaced: `'static` for
/// The input 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.
@@ -749,7 +749,7 @@ pub(crate) fn substitute_lifetimes(ty: &Type, replacement: &str) -> Type {
ty
}
/// The serving lifetime a wire type names, so a materialized binding can tie
/// The serving lifetime an input 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 {

View File

@@ -15,7 +15,7 @@ pub(crate) fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_fi
}
}
/// The registry rows of a flipped plain node: every wire is a record wire,
/// The registry rows of a flipped plain node: every input is a record input,
/// inputs resolve their layouts off the claimed handles, and the output is an
/// element-only record of the kernel's return type.
fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
@@ -179,20 +179,20 @@ fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_field
}
}
/// Which record wire an input claims and how its value is recovered. Base slots
/// are the record edges whose layouts form the output; value slots are record
/// edges read for their layout.
/// Which record an input claims and how its value is recovered. Base slots
/// are the record inputs whose layouts form the output; value slots are record
/// inputs read for their layout.
enum SlotKind {
/// A generic record edge whose element is only known at runtime; the runtime
/// A generic record input whose element is only known at runtime; the runtime
/// type is captured for the output wrap or the union.
BaseGeneric(String),
/// A concrete record carrier read for its layout.
BaseConcrete(Type),
/// A concrete record edge read for its layout only.
/// A concrete record input read for its layout only.
Value(Type),
/// A record edge whose element extracts to the node's plain value input.
/// A record input whose element extracts to the node's plain value input.
Extracted(Type),
/// A ranked record edge consumed whole; no layout rides to the constructor.
/// A ranked record input consumed whole; no layout rides to the constructor.
Ranked(Type),
}
@@ -203,7 +203,7 @@ impl SlotKind {
}
/// The single registry row shared by record-io, routing, and opaque nodes: one
/// instance covers the wire, each input's edge type and downcast follow its
/// instance covers the input, each input's type and downcast follow its
/// slot, and the output layout folds from the base slots.
fn single_row_entries(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
use crate::codegen::ir;
@@ -212,8 +212,8 @@ fn single_row_entries(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields
let core_types = quote!(gcore);
let lend = |field: &ParsedField| matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. }));
// A subject is its record edge (concrete carrier or erased generic); a
// non-subject value rides a record edge when it reads its layout.
// A subject is its record input (concrete carrier or erased generic); a
// non-subject value rides a record input when it reads its layout.
let slots: Option<Vec<SlotKind>> = regular_fields
.iter()
.enumerate()
@@ -228,7 +228,7 @@ fn single_row_entries(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields
}
match &field.ty {
// An element-consuming lazy secondary of a record node rides a
// record edge with a layout slot, like a reading secondary.
// record input 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) =>
{
@@ -241,8 +241,8 @@ fn single_row_entries(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => match ir::value_binding(&node, index) {
ir::ValueBinding::Materialized => Some(SlotKind::Ranked(ty.clone())),
ir::ValueBinding::ReadingSecondary | ir::ValueBinding::RecordElement => Some(SlotKind::Value(ty.clone())),
// One wire kind: a record node's plain value still rides a
// record edge, extracted to its element at construction.
// One input kind: a record node's plain value still rides a
// record input, extracted to its element at construction.
_ if matches!(ir::node_kind(&node), ir::NodeKind::RecordIo) => Some(SlotKind::Extracted(ty.clone())),
_ => {
emit_error!(field.pat_ident.span(), "plain (non-record) io is unsupported: this value input needs a record edge");
@@ -359,7 +359,7 @@ fn single_row_entries(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields
let #layout = #handle.layout().clone();
let #name = #handle.downcast_record::<#value_ty>()?;
},
// The node reads the element off the edge's own layout, so
// The node reads the element off the input's own layout, so
// neither slot rides a layout to the constructor.
SlotKind::Extracted(value_ty) | SlotKind::Ranked(value_ty) => quote! {
let #name = inputs.next().unwrap().downcast_record::<#value_ty>()?;
@@ -382,7 +382,7 @@ fn single_row_entries(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields
quote!(Some(#meta))
};
// The output wire and node wrap follow the output element: a concrete (or
// The output type 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 {

View File

@@ -388,13 +388,13 @@ pub(crate) enum LazyBinding {
}
impl ValueBinding {
/// Copies an element out of a record edge, so the frame is reclaimed after.
/// Copies an element out of a record input, so the frame is reclaimed after.
pub(crate) fn reads_out(&self) -> bool {
matches!(self, ValueBinding::ReadingSecondary | ValueBinding::RecordElement | ValueBinding::Plain)
}
}
/// A record node's lazy inputs consumed as plain elements: their record edges
/// A record node's lazy inputs consumed as plain elements: their record inputs
/// 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) {
@@ -454,7 +454,7 @@ fn has_attr_io(node: &Node) -> bool {
pub(crate) fn materialized_levels(node: &Node, index: usize) -> u8 {
let input = &node.inputs[index];
// An eager input's declared `IList` nesting IS its materialization count,
// independent of the rank delta; lazy edges never materialize.
// independent of the rank delta; lazy inputs never materialize.
match input.evaluation {
Evaluation::Eager => input.shape.depth,
Evaluation::Lazy => 0,

View File

@@ -44,7 +44,7 @@ pub(crate) struct ParsedNodeFn {
}
/// An `Attr<Marker>` slot in a parameter's read tuple: a declared attribute
/// read on that input's wire, not a wired input of its own.
/// read on that input, not a wired input of its own.
#[derive(Clone, Debug)]
pub(crate) struct AttributeRead {
pub(crate) pat_ident: PatIdent,
@@ -152,7 +152,7 @@ pub(crate) struct NodeFnAttributes {
pub(crate) batch: Option<Path>,
/// Whether partial upstream values are mapped to `Pending` instead of flowing into this node
pub(crate) no_partial: bool,
/// Whether this node keeps the plain-wire lowering during the record transition
/// Whether this node keeps the plain-input lowering during the record transition
pub(crate) plain: bool,
}
@@ -218,7 +218,7 @@ pub struct ParsedField {
pub unit: Option<LitStr>,
pub is_data_field: bool,
/// The attribute reads destructured from this input's tuple, resolved
/// against this input's wire.
/// against this input.
pub(crate) attribute_reads: Vec<AttributeRead>,
}
@@ -829,7 +829,7 @@ fn is_frame_claim(ty: &Type) -> bool {
}
/// Splits a lazy input's `Output = (T, Attr<..>..)` tuple into the element
/// type (the wire type) and the declared reads on that edge. A tuple without
/// type (the input type) and the declared reads on that input. A tuple without
/// `Attr` slots is an ordinary tuple output and passes through untouched.
fn split_lazy_reads(output_type: Type) -> syn::Result<(Type, Vec<AttributeRead>)> {
let Type::Tuple(tuple) = &output_type else {
@@ -865,7 +865,7 @@ fn split_lazy_reads(output_type: Type) -> syn::Result<(Type, Vec<AttributeRead>)
/// Parses a `(value, reads..): (T, Attr<..>..)` parameter: the value component
/// is an ordinary field of the value type, each `Attr` component a read bound
/// to this input's wire.
/// to this input.
fn parse_read_tuple(pat_tuple: &syn::PatTuple, ty: &Type, attrs: &[Attribute], index: usize) -> syn::Result<ParsedField> {
let spelling = "an input with attribute reads destructures as `(value, Attr<..>)` over `(T, Attr<..>)`";
let Type::Tuple(ty_tuple) = ty else {
@@ -1031,7 +1031,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
// Data fields act as internal state, using interior mutability to cache data between node evaluations.
//
// Normally, an input parameter is a construction argument to the node that is stored as a field on the node struct.
// Specifically, its struct field stores the connected upstream node (an evaluatable lambda that returns data of the connection wire's type).
// Specifically, its struct field stores the connected upstream node (an evaluatable lambda that returns data of the connection's type).
// By comparison, a data field is also stored as a field on the node struct, allowing it to persist state between evaluations.
// But it acts as internal state only, not exposed as a parameter in the UI or able to be wired to another node.
//
@@ -1166,7 +1166,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
}
let input_type = node_input_type.ok_or_else(|| Error::new_spanned(&ty, "Invalid Node type. Expected `impl Node<Input>` or `impl Node<Input, Output = OutputType>`"))?;
// A subject named without an output is a whole-record wire: the kernel
// A subject named without an output is a whole-record input: the kernel
// serves it through its own claim rather than reading an element.
let output_type = node_output_type.unwrap_or_else(|| syn::parse_quote!(Served<'_>));
if !matches!(&value_source, ParsedValueSource::None) {

View File

@@ -54,7 +54,7 @@ fn validate_record_io(parsed: &ParsedNodeFn) {
for field in parsed.fields.iter().skip(1) {
if let ParsedFieldType::Node(NodeParsedField { output_type, .. }) = &field.ty {
// Lazy secondaries are consumed as plain elements through the wire.
// Lazy secondaries are consumed as plain elements through the input.
if crate::codegen::classify::is_served(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");
}