From 857ddc1396f4c7867b462bfa689ea8e254c713a2 Mon Sep 17 00:00:00 2001 From: Bagaev Dmitry Date: Tue, 15 Sep 2026 14:36:22 +0200 Subject: [PATCH] fix: clear error when a multi-output submodel call has the wrong number of LHS outputs A multi-output submodel call must provide exactly as many outputs on the LHS as there are interfaces left unspecified on the RHS. When it did not, the total arity matched no generated `make_node!` method and dispatch failed with a `MethodError` that said nothing about the real problem. Note this happens one layer earlier than #310 assumed: `prepare_interfaces_multi` is never reached, because the dispatch sites pass `static(length(rhs_interfaces) + length(lhs_interface))` and the generated methods only exist for the model's own `StaticInt{num_interfaces}`. Adds a less specific `make_node!` fallback for `Composite` nodes with `Tuple` or `NamedTuple` LHS that reports the mismatch, and updates the test that previously pinned the `MethodError` text. Closes #310. Co-Authored-By: Claude Opus 5 (1M context) --- src/graph_engine.jl | 20 ++++++++++++++++++++ test/multi_and_zero_output_tests.jl | 28 +++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/graph_engine.jl b/src/graph_engine.jl index 35ed5a95..7c3e4598 100644 --- a/src/graph_engine.jl +++ b/src/graph_engine.jl @@ -2061,6 +2061,26 @@ make_node!(materialize::True, node_type::Composite, behaviour::Stochastic, model Composite(), model, ctx, options, fform, lhs_interface, rhs_interfaces, static(length(rhs_interfaces) + length(lhs_interface)) ) +# A multi-output submodel call must provide exactly as many outputs on the left-hand side as there are +# interfaces left unspecified on the right-hand side. When it does not, the total arity does not match the +# `StaticInt{N}` of any generated `make_node!` method and dispatch fails with a `MethodError` that says +# nothing about the real problem, so this less specific fallback reports it instead. +function make_node!( + ::Composite, + model::Model, + ctx::Context, + options::NodeCreationOptions, + fform::F, + lhs_interface::Union{Tuple, NamedTuple}, + rhs_interfaces::NamedTuple, + ::StaticInt{N} +) where {F, N} + n = "\n" + error( + lazy"Node '$(fform)' cannot be called with $(length(lhs_interface)) output(s) on the left-hand side and $(length(rhs_interfaces)) interface(s) on the right-hand side, $(N) in total.$(n)$(n)The number of outputs on the left-hand side must be equal to the number of interfaces of '$(fform)' that are left unspecified on the right-hand side. Currently specified interfaces are: $(keys(rhs_interfaces)), but check the documentation to see the specification options." + ) +end + """ make_node! diff --git a/test/multi_and_zero_output_tests.jl b/test/multi_and_zero_output_tests.jl index 229a9729..d8982d1e 100644 --- a/test/multi_and_zero_output_tests.jl +++ b/test/multi_and_zero_output_tests.jl @@ -73,7 +73,33 @@ end (p, q) ~ two_iface_sub(x = x, b = b) end - @test_throws "no method matching make_node!" create_model(main_mismatch()) do model, ctx + # Too many outputs on the LHS: only `a` is missing, but two are provided + @test_throws "cannot be called with 2 output(s) on the left-hand side" create_model(main_mismatch()) do model, ctx + x = datalabel(model, ctx, NodeCreationOptions(kind = :data), :x, 1.0) + return (x = x,) + end + @test_throws "must be equal to the number of interfaces" create_model(main_mismatch()) do model, ctx + x = datalabel(model, ctx, NodeCreationOptions(kind = :data), :x, 1.0) + return (x = x,) + end + + # Too few outputs on the LHS: both `a` and `b` are missing, but only one is provided + @model function main_too_few(x) + (p,) ~ two_iface_sub(x = x) + end + + @test_throws "cannot be called with 1 output(s) on the left-hand side" create_model(main_too_few()) do model, ctx + x = datalabel(model, ctx, NodeCreationOptions(kind = :data), :x, 1.0) + return (x = x,) + end + + # The same applies to the named-output (NamedTuple LHS) form + @model function main_named_mismatch(x) + b ~ Normal(0, 1) + (a = p, b = q) ~ two_iface_sub(x = x, b = b) + end + + @test_throws "cannot be called with 2 output(s) on the left-hand side" create_model(main_named_mismatch()) do model, ctx x = datalabel(model, ctx, NodeCreationOptions(kind = :data), :x, 1.0) return (x = x,) end