/** * ArgumentsBinder: Stateless argument-to-parameter binding * * Produces a BoundArguments map from a list of argument nodes and a callable's * parameter metadata. This is Phase 1 of the two-phase invocation system. * Phase 2 (marshalling) runs via marshalArgs in callable.ts and remains unchanged. * * ## Responsibilities * - Detect spread arguments (single owner of spread detection) * - Validate spread constraints (untyped builtin, extra positional, null source) * - Produce BoundArguments map for downstream marshalling * - Return pre-allocated empty sentinel for zero-arg or non-spread calls * * ## Error Cases * - Spread on untyped builtin → RILL-R001 (message matches closures.ts:1079) * - Extra positional argument → RILL-R001 (message matches closures.ts:1893) * - Null spread source → RILL-R001 (message matches closures.ts:1912) * - Spread value not tuple/dict/ordered → RILL-R001 * - Dict spread key matches no parameter → RILL-R001 * - Ordered spread key-order mismatch → RILL-R001 * - Duplicate binding → RILL-R001 * - ApplicationCallable with undefined params → RILL-R001 * * ## Implementation Notes * * [SPEC] Signature omits the evaluateExpression callback. * Spec: `bind(args, callable, pipeInput, location) => Promise` * Actual: `bind(args, callable, pipeInput, evaluate, location) => Promise` * Rationale: args are AST nodes requiring evaluation. A stateless class with no context * dependency must receive evaluateExpression as an explicit callback parameter. The pipeInput * parameter is kept as specified; the caller is responsible for applying it to ctx before * constructing the evaluate callback, preserving save/restore semantics. * * @internal */ import type { ExpressionNode, SpreadArgNode, SourceLocation } from '../../../../types.js'; import type { RillCallable } from '../../callable.js'; import type { RillValue } from '../../types/structures.js'; /** * Result of ArgumentsBinder.bind: parameter names mapped to evaluated values. * * Only explicitly bound parameters are present (positional, tuple, ordered, * or dict spread). Missing parameters are absent from the map; callers pass * undefined for them, and marshalArgs handles defaults and required checks. * * Phase 2 integration: callers convert this to a positional RillValue[] via * params.map(p => bound.params.get(p.name)!) * and pass that to marshalArgs stages 2-3 (skipping stage 1 excess check, * which bind handles differently via per-source arity validation). */ export interface BoundArguments { readonly params: ReadonlyMap; } /** * Stateless argument binder. * * No instance fields. No context dependency. All state is passed via parameters. * Instantiate once and reuse across calls. */ export declare class ArgumentsBinder { /** * Returns true when any argument in the list is a spread arg. * * Single owner of `args.some(a => a.type === 'SpreadArg')`. * Phase 2 callers use this to decide whether to invoke bind. */ hasSpread(args: (ExpressionNode | SpreadArgNode)[]): boolean; /** * Bind arguments to callable parameters. * * Returns the pre-allocated empty sentinel when: * - args.length === 0 * - No spread is present (non-spread path; normal marshalArgs handles the rest) * * Allocates and populates BoundArguments only when spread is present. * * @param args - Argument nodes from the call site * @param callable - Resolved callable with parameter metadata * @param _pipeInput - Active pipe value ($ in expressions); reserved for future use. * Caller is responsible for applying pipeInput to ctx before constructing evaluate. * @param evaluate - Expression evaluator; caller sets pipeInput on ctx before passing * @param location - Call-site source location for error reporting */ bind(args: (ExpressionNode | SpreadArgNode)[], callable: RillCallable, _pipeInput: RillValue | undefined, evaluate: (node: ExpressionNode) => Promise, location: SourceLocation, sourceId?: string): Promise; }