Skip to content

Cross-package function using arguments returns undefined (synthetic-arguments param inflates cross-module arity) — #321 gateway #1816

Closed

Description

@proggeramlug

Summary

A cross-package (compilePackages) function that uses arguments returns
undefined for calls with fewer args than the inflated param count.

Perry appends a synthetic arguments param (name: "arguments", is_rest: true)
to any function whose body references arguments (#677). Cross-package, the
importer's param-count collection (compile.rs ~606, func.params.len())
counts that synthetic param as a real one, inflating the arity, and the
cross-module call mishandles it (the synthetic arguments is treated as a
trailing-rest collecting only args after the fixed params, instead of the full
argument list). Under-filled calls then return undefined.

Minimal repro (compilePackages)

node_modules/mypkg/src/index.ts:

export function usesArgs(a: number, b?: number): number {
  return arguments.length === 1 ? a : a + (b as number);
}
export function noArgs(a: number, b?: number): number {
  return b === undefined ? a : a + b;
}

entry (perry.compilePackages: ["mypkg"]):

import { usesArgs, noArgs } from "mypkg";
console.log(usesArgs(5), usesArgs(5, 3), noArgs(5), noArgs(5, 3));
node perry
usesArgs(5) 5 undefined ✗
usesArgs(5,3) 8 8
noArgs(5) 5 5
noArgs(5,3) 8 8

--print-hir on the entry: usesArgs (params: 3) vs noArgs (params: 2) — the
synthetic arguments param inflates usesArgs to 3. Single-file (non-package)
arguments-using functions work; only the cross-package path is broken.

Impact — the #321 (Effect framework) gateway blocker

effect's pipe and dual (the two most-used combinators) both use arguments,
so cross-package they return undefined. This cascades:

  • pipe(a, fn) → undefined
  • Effect.map / Effect.pipe → _op of undefined in the fiber runtime
  • S.Struct({...}) decode → _tag of undefined in ParseResult
  • effect's runtime init: liveServices = pipe(Context.empty(), Context.add(...)…)
    → undefined → currentServices.initial undefined →
    FiberRuntime.refreshRefCache → .unsafeMap of undefined.

The effect DoD (deep import, Schema init, S.Number decode, import { Effect }
barrel Effect.runSync(Effect.succeed(42))) already works (#1804/#1809/#1810/
#1811/#1812/#1815) because those paths don't depend on a pipe/arguments
result. This bug is the gateway to the rest of effect.

Fix direction

The cross-module param count + call must treat the synthetic arguments param
specially: exclude it from the user-visible arity, and populate it with the
full argument list (as the same-module synthetic-arguments path does), not
as a trailing rest. Sites: compile.rs exported-func param-count collection +
the cross-module call lowering's rest/arity handling. Broad blast radius
(all cross-module calls to arguments-using functions) — needs a thorough
regression sweep.

Refs #321, #1758, #677.

Activity

  1. proggeramlug commented on May 28, 2026

    @proggeramlug
    ContributorAuthor

    Verified fixed by PR #1817 (merged 2026-05-26). Reproduced the exact compilePackages shape from the issue body:

    $ /tmp/repro1816_out
    5 8 5 8
    

    All four cases (usesArgs(5), usesArgs(5,3), noArgs(5), noArgs(5,3)) match Node.

    Cross-module shape locked in test-files/test_gap_cross_module_arguments.ts; cross-package shape (compilePackages → node_modules) locked in test-files/issue_1069/. Closing as fixed.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions