diff --git a/node-graph/libraries/core-types/src/record.rs b/node-graph/libraries/core-types/src/record.rs index d5a7744ae5..5c1687380b 100644 --- a/node-graph/libraries/core-types/src/record.rs +++ b/node-graph/libraries/core-types/src/record.rs @@ -318,6 +318,25 @@ impl FieldOffset { } } +/// Reads a declared attribute out of a record at a wiring-resolved offset, +/// falling back to the marker's census default where the layout does not carry +/// the name. +/// +/// # Safety +/// `rec` must be a live record of the layout `offset` was resolved against, +/// and `offset`, where present, must be that layout's offset for `A` at the +/// read's level. The census admits one value type per attribute name and +/// panics on a conflicting declaration, so that field's bytes are a value of +/// `A::Value`, differing from the read type only in `'e`, which must not +/// outlive the evaluation a parked payload is arena-resident for. +pub unsafe fn read_at<'e, A: attribute::Attribute>(rec: Rec<'_>, offset: Option) -> attribute::Attr<'e, A> { + attribute::Attr(match offset { + // SAFETY: the caller's contract. + Some(offset) => unsafe { rec.read::>(offset) }, + None => A::default(), + }) +} + /// The stand-in fed to a kernel's unbounded `element: T` parameter. The type /// system forces the kernel to route it to the element position of its return /// tuple, so the passthrough is explicit in the signature while the lowering diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index ccc7dacb14..ab0e506986 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -1243,10 +1243,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn let marker = &read.marker; let slot = format_ident!("__read_{slot}"); quote! { - let #pat = #core_types::attribute::Attr::<#marker>(match self.#slot { - Some(__offset) => unsafe { #rec.read(__offset) }, - None => <#marker as #core_types::attribute::Attribute>::default(), - }); + let #pat = unsafe { #core_types::record::read_at::<#marker>(#rec, self.#slot) }; } }; let reads_of = |field_index: usize| { @@ -2705,12 +2702,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn .collect(); let attr_slots = field.attribute_reads.iter().enumerate().map(|(slot, read)| { let marker = &read.marker; - quote! { - #core_types::attribute::Attr::<#marker>(match __reads[#slot] { - Some(__offset) => unsafe { __rec.read(__offset) }, - None => <#marker as #core_types::attribute::Attribute>::default(), - }) - } + quote!(unsafe { #core_types::record::read_at::<#marker>(__rec, __reads[#slot]) }) }); let attr_tys = field.attribute_reads.iter().map(|read| { let marker = &read.marker;