Skip to content

Native classes (Dirent, Readable, Writable) returned with no instance methods on prototype #631

Description

@proggeramlug

Repro

import * as fs from "node:fs";
import { Readable } from "node:stream";

// fs.readdirSync withFileTypes:true should return Dirent[]
const ents = fs.readdirSync("/tmp", { withFileTypes: true });
console.log("entry[0] typeof:", typeof ents[0]);
console.log("entry[0] is string:", typeof ents[0] === "string");
console.log("entry[0].isFile type:", typeof (ents[0] as any).isFile);

// Readable.from(...) should return a Readable with .on / .read / .pause
const r = Readable.from([1, 2, 3]);
console.log("Readable.from typeof:", typeof r);
console.log("Readable.from .on type:", typeof (r as any)?.on);
console.log("Readable.from .pipe type:", typeof (r as any)?.pipe);

// new Readable({read}) similarly
const r2 = new Readable({ read() {} });
console.log("new Readable .on type:", typeof (r2 as any)?.on);
console.log("new Readable .pipe type:", typeof (r2 as any)?.pipe);

Actual (Perry)

entry[0] typeof: string
entry[0] is string: true
entry[0].isFile type: undefined
Readable.from typeof: undefined
Readable.from .on type: undefined
Readable.from .pipe type: undefined
new Readable .on type: undefined
new Readable .pipe type: undefined

Expected (Node)

entry[0] typeof: object
entry[0] is string: false
entry[0].isFile type: function
Readable.from typeof: object
Readable.from .on type: function
Readable.from .pipe type: function
new Readable .on type: function
new Readable .pipe type: function

Impact

Two related symptoms:

  1. fs.readdirSync(path, { withFileTypes: true }) silently ignores the option and returns string entries instead of Dirent objects. Code patterns like const dirs = entries.filter(e => e.isDirectory()) crash with (string).isDirectory is not a function.

  2. stream.Readable.from(...) and new stream.Readable({read}) return undefined (or an object with no prototype methods) — the entire Readable / Writable / Duplex / Transform class hierarchy is unusable downstream, even though the constructors exist. This was the dominant cause of test-files/test_parity_stream.ts reaching 97% divergence: nothing built on top of these primitives works.

The grouping of these as "one issue" is the architectural shape: native Perry classes that wrap runtime objects need their prototype-method dispatch wired to the underlying instance (the FFI registry / handle table). Without that wiring, the constructor returns either a raw handle (Dirent → string) or undefined (Readable.from), and downstream property access fails.

Acceptance

The repro above prints output byte-identical to Node.

Related

  • Surfaced via test-files/test_parity_fs.ts (Stats / Dirent / fsPromises FileHandle methods all undefined) and test-files/test_parity_stream.ts (Readable / Writable / Duplex / Transform instance methods all undefined).
  • May share root cause with typeof process returns "number" instead of "object" (default-import + global) #623 (typeof process numeric tag) — a class-instance value getting a wrong NaN-box tag at the construction boundary so downstream property lookups fall through.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions