Skip to content

Native Node module spread calls such as path.join(...args) fail despite spread calls being documented as supported #7720

Description

@Cruel

Description

Perry documents the spread operator in calls as supported, and node:path / path.join as supported, but spreading a valid string array into path.join() fails at runtime.

I initially encountered this in a larger application, but it reduces to a very small standalone repro.

Minimal reproduction

repro.ts:

import path from 'node:path';

console.log('direct:', path.join('/tmp/x', 'project.json'));

const parts = ['/tmp/x', 'project.json'];
console.log('spread:', path.join(...parts));

Using Perry 0.5.1220:

pnpm add -D @perryts/perry@0.5.1220
pnpm exec perry compile repro.ts --target linux --output repro
./repro

Actual behavior

The direct call succeeds:

direct: /tmp/x/project.json

The equivalent spread call then throws:

TypeError: The "path" argument must be of type string.
    at <anonymous>

and exits nonzero.

Expected behavior

Both forms should be equivalent:

direct: /tmp/x/project.json
spread: /tmp/x/project.json

Node 24.18.0 produces the expected output.

Why I expected this to be supported

The Perry 0.5.1220 README marks:

  • functions/rest parameters as supported;
  • the spread operator in calls and literals as supported;
  • path.join as supported.

The runtime parity documentation also lists path.join([...paths]) as supported without a caveat.

Perry's own parity suite contains a spread invocation of path.join in:

test-parity/node-suite/path/join/type-errors-extra.ts

using:

path.join(...args)

That fixture appears to test invalid argument types, but does not include the corresponding valid-string-array case.

Current main

I also checked current Perry main.

At the time of testing:

commit: 0328a63bbc1b582bfdbb0842a957aac861bd84d7
version: 0.5.1413

I noticed that the current source contains an internal comment in the native-module call lowering code describing:

ns.method(...arr)

as a separate runtime spread gap.

That appears to be the same category as:

path.join(...parts)

So this may already be a known implementation limitation internally.

I could not find a corresponding user-facing compatibility caveat or a specific issue tracking this case, and the public compatibility documentation currently appears to describe both spread calls and path.join as supported.

If this gap is already tracked elsewhere, please feel free to close this as a duplicate.

Additional observation

The problem does not require a rest parameter. This also fails:

const parts = ['/tmp/x', 'project.json'];
path.join(...parts);

while this succeeds:

path.join('/tmp/x', 'project.json');

A wrapper using a rest parameter therefore fails for the same reason:

function joinPath(...parts: string[]): string {
  return path.join(...parts);
}

Workaround

Avoiding a spread into the native module function works:

function joinPath(...parts: string[]): string {
  if (parts.length === 0) {
    return '.';
  }

  let result = parts[0]!;
  for (let index = 1; index < parts.length; index += 1) {
    result = path.join(result, parts[index]!);
  }

  return result;
}

Environment

  • Perry: 0.5.1220
  • Package: @perryts/perry
  • Target: linux
  • Host: Linux x86_64
  • Node used to invoke the Perry toolchain: 24.18.0

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