Problem
With variable-length tuple parameters (#2833), the empty tuple is a valid concretization of tuple[T, ...] (zero elements trivially satisfy any element type, see type_info.is_concretizable). Embedded execution handles such a call fine — there is simply nothing to compute or write. Every compiled backend crashes with an internal error instead:
@gtx.field_operator
def scale(tracers: tuple[IField, ...], factor: float64) -> tuple[IField, ...]:
return tuple(t * factor for t in tracers)
scale((), 2.0, out=(), offset_provider={}) # embedded: OK; any compiled backend: crash
The root cause is that a SetAt for an empty out tuple is unconstructible: both its target and its domain are derived from the out fields, and with zero fields there is neither. The failure therefore surfaces as a cascade of internal errors, one per toolchain layer (each verified on sf_n_tracer_support):
past_to_itir._visit_stencil_call_out_arg — ValueError: not enough values to unpack (expected 2, got 0) while deriving target/domain from the empty out tuple.
- If (1) is bypassed by skipping the statement,
ir_utils.misc.grid_type_from_program fails on the domain-less program with the (misleading for this case) ValueError: Found 'set_at' with more than one 'GridType': 'set()'.
- The roundtrip executor then generates a Python function with an empty body —
IndentationError at module load.
- The DaCe bindings refuse zero-length tuple unpacking (
bindings._unpack_args: "Cannot unpack argument with length zero"), and the DaCe decoration's first-call detection is exception-driven (decoration.py: a TypeError from iterating the None argument vector) — with zero arguments the loop never touches it, so construction is silently skipped and fast_call hits AssertionError: Argument vector was not set properly.
Possible directions
Option A — skip emission: generate no SetAt for an empty out in past_to_itir and make the later stages tolerate a statement-less program (default grid type when no domains exist, non-empty roundtrip function body, zero-length unpack in the DaCe bindings, explicit first-call detection in the DaCe decoration). A working implementation of exactly this exists and passed the full test_tuples.py backend matrix; it was deliberately kept out of #2833 as an unrelated toolchain concern.
Option B — reject with a diagnostic: refuse an empty out at call time with a proper located error instead of supporting the no-op program.
Independent of the choice, the DaCe first-call detection issue in (4) is arguably worth fixing on its own — the exception-driven control flow is fragile by design and the code's own TODO asks for a refactor.
Current state
test_var_len_tuple_comprehension_empty in test_tuples.py (on the #2833 branch) pins the working embedded behavior and xfails on all compiled backends, pointing here. The type-level acceptance is pinned by test_is_concretizable_vararg_to_tuple in test_type_info.py.
Disclaimer: This issue was written largely with the help of AI.
Problem
With variable-length tuple parameters (#2833), the empty tuple is a valid concretization of
tuple[T, ...](zero elements trivially satisfy any element type, seetype_info.is_concretizable). Embedded execution handles such a call fine — there is simply nothing to compute or write. Every compiled backend crashes with an internal error instead:The root cause is that a
SetAtfor an emptyouttuple is unconstructible: both its target and its domain are derived from the out fields, and with zero fields there is neither. The failure therefore surfaces as a cascade of internal errors, one per toolchain layer (each verified onsf_n_tracer_support):past_to_itir._visit_stencil_call_out_arg—ValueError: not enough values to unpack (expected 2, got 0)while deriving target/domain from the empty out tuple.ir_utils.misc.grid_type_from_programfails on the domain-less program with the (misleading for this case)ValueError: Found 'set_at' with more than one 'GridType': 'set()'.IndentationErrorat module load.bindings._unpack_args: "Cannot unpack argument with length zero"), and the DaCe decoration's first-call detection is exception-driven (decoration.py: aTypeErrorfrom iterating theNoneargument vector) — with zero arguments the loop never touches it, so construction is silently skipped andfast_callhitsAssertionError: Argument vector was not set properly.Possible directions
Option A — skip emission: generate no
SetAtfor an emptyoutinpast_to_itirand make the later stages tolerate a statement-less program (default grid type when no domains exist, non-empty roundtrip function body, zero-length unpack in the DaCe bindings, explicit first-call detection in the DaCe decoration). A working implementation of exactly this exists and passed the fulltest_tuples.pybackend matrix; it was deliberately kept out of #2833 as an unrelated toolchain concern.Option B — reject with a diagnostic: refuse an empty
outat call time with a proper located error instead of supporting the no-op program.Independent of the choice, the DaCe first-call detection issue in (4) is arguably worth fixing on its own — the exception-driven control flow is fragile by design and the code's own TODO asks for a refactor.
Current state
test_var_len_tuple_comprehension_emptyintest_tuples.py(on the #2833 branch) pins the working embedded behavior and xfails on all compiled backends, pointing here. The type-level acceptance is pinned bytest_is_concretizable_vararg_to_tupleintest_type_info.py.Disclaimer: This issue was written largely with the help of AI.