Name the shapes the record lowering refuses

This commit is contained in:
Dennis Kobert
2026-09-14 13:28:49 +02:00
parent 652c1c0e1b
commit 7fb55cf59f
2 changed files with 61 additions and 18 deletions

View File

@@ -15,6 +15,7 @@ pub fn validate_node_fn(parsed: &ParsedNodeFn) -> syn::Result<()> {
validate_lend_fields,
validate_record_io,
validate_lazy_reads,
validate_lowering_supported,
];
for validator in validators {
@@ -24,6 +25,28 @@ pub fn validate_node_fn(parsed: &ParsedNodeFn) -> syn::Result<()> {
Ok(())
}
/// A signature no lowering claims generates no `Node` impl at all, so the node
/// compiles and is simply missing at runtime. Naming the gap is the only thing
/// that ends it, since nothing downstream can tell "declined" from "absent".
fn validate_lowering_supported(parsed: &ParsedNodeFn) {
// A record-io refusal already reported its own reason.
if crate::codegen::classify::record_shape_checked(parsed).is_err() || crate::codegen::classify::analyze(parsed).is_some() {
return;
}
// An async kernel with lazy inputs is refused by `validate_async_source`, which
// names the spawn boundary rather than the missing lowering.
if parsed.is_async && parsed.fields.iter().any(|field| matches!(field.ty, ParsedFieldType::Node(_))) {
return;
}
emit_error!(
parsed.fn_name.span(),
"no lowering supports this signature, so the node would generate no `Node` impl";
help = "a node is one of: attribute io (`Attr<..>` reads or writes), routing (an unbounded generic forwarded whole), a flipped kernel over an owned primary, or one with a ranked `IList` input"
);
}
fn validate_record_io(parsed: &ParsedNodeFn) {
let value = crate::codegen::slot_value_type(&parsed.output_type);
if let Type::Tuple(tuple) = &value {
@@ -44,6 +67,15 @@ fn validate_record_io(parsed: &ParsedNodeFn) {
return;
}
// A shape the record lowering refuses generates no node impl at all, so without a
// diagnostic the node compiles and is simply absent from the registry. The lowering
// is the authority on what it can serve, so its refusal is the one error reported:
// the checks below describe a shape it already accepted.
if let Err(reason) = crate::codegen::classify::record_shape_checked(parsed) {
emit_error!(parsed.fn_name.span(), "this node declares attribute io the record lowering cannot serve: {}", reason);
return;
}
let async_source = crate::codegen::classify::is_async_source(parsed);
if async_source && has_reads {
emit_error!(parsed.fn_name.span(), "attribute reads are not supported on async source kernels, only writes");