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