Strip IList nesting from kernel returns at parse time

This commit is contained in:
Dennis Kobert
2026-08-16 11:27:13 +00:00
parent 99ce2ea565
commit 15f4aa8f98
4 changed files with 45 additions and 5 deletions

View File

@@ -734,7 +734,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
let subject_depth = node.inputs.iter().find(|input| input.subject).map_or(0, |input| input.shape.depth);
let level_delta = node.output.shape.depth as i8 - subject_depth as i8;
let pushed_levels = level_delta.max(0) as u8;
let output_row = crate::codegen::ir::strip_ilist(&slot_value_type(&parsed.output_type)).0;
let output_row = slot_value_type(&parsed.output_type);
let snapshot_ctx = async_fn && matches!(&parsed.input.ty, Type::Path(path) if path.path.segments.last().is_some_and(|segment| segment.ident == "CtxSnapshot"));
let mut ctx_bounds: Vec<TokenStream2> = match ctx_param {
@@ -1270,7 +1270,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
// A bare `Attr<M>` in the return type cannot elide its lifetime, so the
// kernel gets a fresh one; reference-valued writes name their real
// lifetime explicitly and pass through untouched.
let kernel_output = record_io.then(|| inject_attr_lifetimes(&crate::codegen::ir::strip_ilist(&parsed.output_type).0)).flatten();
let kernel_output = record_io.then(|| inject_attr_lifetimes(&parsed.output_type)).flatten();
let attr_lifetime = kernel_output.is_some().then(|| quote!('__attr,));
let kernel_output = match derive_routing {
true => {

View File

@@ -82,8 +82,8 @@ fn subject(index: usize, field: &ParsedField, carrier_subject: bool, routing: Op
}
fn output(parsed: &ParsedNodeFn, generics: &[Ident]) -> Output {
// Strip the `IList` rank markers first so the write set is read from the row.
let (row, depth) = strip_ilist(&slot_value_type(&parsed.output_type));
let row = slot_value_type(&parsed.output_type);
let depth = parsed.output_depth;
let (element, writes, removes) = match record_writes(&row) {
Some(RecordWrites { element, markers, removes }) => (element, markers, removes),
None => (row, Vec::new(), Vec::new()),
@@ -167,6 +167,36 @@ pub(crate) fn strip_ilist(ty: &Type) -> (Type, u8) {
(element, depth)
}
/// Strips `IList` rank nesting from the output's value position, preserving the
/// dialect wrapper (`Result`/`GPoll`), and returns the removed depth.
pub(crate) fn strip_output_rank(output: &Type) -> (Type, u8) {
use crate::codegen::classify::{KernelKind, kernel_kind};
match kernel_kind(output) {
KernelKind::Plain => strip_ilist(output),
KernelKind::Interrupt(inner) | KernelKind::Poll(inner) => {
let (row, depth) = strip_ilist(&inner);
(replace_first_type_arg(output, row), depth)
}
KernelKind::Future(_) | KernelKind::FutureInterrupt(_) => (output.clone(), 0),
}
}
fn replace_first_type_arg(ty: &Type, replacement: Type) -> Type {
let mut ty = ty.clone();
if let Type::Path(path) = &mut ty
&& let Some(segment) = path.path.segments.last_mut()
&& let PathArguments::AngleBracketed(args) = &mut segment.arguments
{
for arg in args.args.iter_mut() {
if let GenericArgument::Type(inner) = arg {
*inner = replacement;
break;
}
}
}
ty
}
fn ilist_inner(ty: &Type) -> Option<Type> {
let Type::Path(path) = ty else { return None };
let segment = path.path.segments.last()?;

View File

@@ -33,6 +33,7 @@ pub(crate) struct ParsedNodeFn {
pub(crate) where_clause: Option<WhereClause>,
pub(crate) input: Input,
pub(crate) output_type: Type,
pub(crate) output_depth: u8,
pub(crate) is_async: bool,
pub(crate) fields: Vec<ParsedField>,
pub(crate) body: TokenStream2,
@@ -662,7 +663,7 @@ pub(crate) fn parse_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Resu
let is_async = input_fn.sig.asyncness.is_some();
let (input, fields) = parse_inputs(&input_fn.sig.inputs)?;
let output_type = parse_output(&input_fn.sig.output)?;
let (output_type, output_depth) = crate::codegen::ir::strip_output_rank(&parse_output(&input_fn.sig.output)?);
let where_clause = input_fn.sig.generics.where_clause;
let body = input_fn.block.to_token_stream();
let description = input_fn
@@ -691,6 +692,7 @@ pub(crate) fn parse_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Resu
fn_generics,
input,
output_type,
output_depth,
is_async,
fields,
where_clause,
@@ -1407,6 +1409,7 @@ mod tests {
context_features: vec![],
},
output_type: parse_quote!(f64),
output_depth: 0,
is_async: false,
fields: vec![ParsedField {
pat_ident: pat_ident("b"),
@@ -1485,6 +1488,7 @@ mod tests {
context_features: vec![],
},
output_type: parse_quote!(T),
output_depth: 0,
is_async: false,
fields: vec![
ParsedField {
@@ -1578,6 +1582,7 @@ mod tests {
context_features: vec![format_ident!("ExtractFootprint")],
},
output_type: parse_quote!(Vector),
output_depth: 0,
is_async: false,
fields: vec![ParsedField {
pat_ident: pat_ident("radius"),
@@ -1652,6 +1657,7 @@ mod tests {
context_features: vec![],
},
output_type: parse_quote!(List<Raster<P>>),
output_depth: 0,
is_async: false,
fields: vec![ParsedField {
pat_ident: pat_ident("shadows"),
@@ -1738,6 +1744,7 @@ mod tests {
context_features: vec![],
},
output_type: parse_quote!(f64),
output_depth: 0,
is_async: false,
fields: vec![ParsedField {
pat_ident: pat_ident("b"),
@@ -1827,6 +1834,7 @@ mod tests {
context_features: vec![],
},
output_type: parse_quote!(List<Raster<CPU>>),
output_depth: 0,
is_async: true,
fields: vec![ParsedField {
pat_ident: pat_ident("path"),
@@ -1901,6 +1909,7 @@ mod tests {
context_features: vec![],
},
output_type: parse_quote!(i32),
output_depth: 0,
is_async: false,
fields: vec![],
body: TokenStream2::new(),

View File

@@ -317,6 +317,7 @@ impl PerPixelAdjustCodegen<'_> {
context_features: self.parsed.input.context_features.clone(),
},
output_type: raster_gpu,
output_depth: 0,
is_async: false,
fields,
body,