Skip to content

A promise passed to function.call's variadic args arrives unresolved #663

Open

Description

@David-Noble-at-work

Found by the first end-to-end test of function.Provider (USER-requested, 2026-08-24).

A promise passed to function.call through its variadic arguments arrives at the callable unresolved, as an Invocation object.

Reproduction

def summarize(paths):
    return ",".join(sorted(paths))

walked  = plan.file.walk_tree(root=root, fn=collector, include_gitignored=True)
summary = plan.function.call(summarize, walked)      # walked is a promise
function.call: sorted: for parameter iterable: got Invocation, want iterable

The graph assembles, the edge is recorded, and the unit dispatches in the right order — the promise simply is not resolved before the callable receives it.

Why it happens

function.call's binding is (callable, *args, **kwargs). Promise resolution operates on declared parameters: a slot has a name and a type, and the executor fills it from the producer's result. A variadic catch-all has neither, so the Invocation passes through verbatim.

Contrast plan.file.write_text(content=walked), where content is a declared slot and the same promise resolves correctly. That asymmetry is the bug: whether a promise resolves depends on the shape of the receiving parameter rather than on the value being a promise.

Why it matters

function.call is the one action whose entire purpose is to invoke author code on a value. Chaining it after another unit is the natural use, and it is exactly the use that does not work. A caller must instead route the value through a provider with a declared slot, which defeats the point.

The likely fix

Resolve any Invocation appearing in a variadic or kwargs position, not only in declared slots. An Invocation in an argument position is a promise by definition — there is no legitimate reason to hand one to a callable as data.

Worth checking whether other variadic surfaces share the gap (shell.run, plan.choose) before fixing it in one place only.

Current coverage

cmd/devlore-test/devloretest/data/test_function_call_walk_tree.star covers function.call with literal arguments and with its result flowing onward through a promise, plus the function resource dispatched as walk_tree's reducer. It deliberately does not cover the promise-into-function.call case, because that would enshrine the defect; extend it here when this is fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Epic:ExecutionGraphThe execution graph epicbugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions