Skip to content

class X extends Map {} throws 'called on incompatible receiver' when constructed inside a closure (works at top level) — trigger not yet isolated #10637

Description

@proggeramlug

Found while fixing #10623 (PR #10636), confirmed to reproduce on clean main and confirmed not caused or
fixed by that PR (Map is not in the native_parent match table at all, so it never reaches the code that PR
touches — verified by debug trace showing native_parent_is_some=false).

Repro (exactly as tested — see the caveat, it is NOT minimized)

const { EventEmitter } = require("node:events");
const { Readable } = require("node:stream");

function safe(label: string, fn: () => void) {
  try { fn(); } catch (e: any) { console.log(label, "THREW:", e.constructor.name + ":", e.message); }
}

safe("Map implicit:", () => {
  class NoCtorMap extends Map {}
  const m = new NoCtorMap([["a", 1]]);
  console.log("Map implicit OK:", m.get("a"), m.size);
});

Node: Map implicit OK: 1 1
Perry: Map implicit: THREW: TypeError: Method Map.prototype.get called on incompatible receiver #<NoCtorMap>

Set shows the identical shape (Set.prototype.has called on incompatible receiver).

Important caveat — this needs minimizing before it is fixed

The same class and construction at true top level, with no safe() wrapper and no other require() calls in
the file, works correctly
and matches Node (1 1). So something about being inside a closure, or about other
require() calls being present, is the trigger. The reporter did not isolate which — "closure alone, no other
requires" and "other requires present, no closure" were not tested separately.

First step for whoever takes this: run those two variants. Which factor triggers it is likely to name the
mechanism outright, and the answer changes where to look entirely.

Where it lives

Not characterized. The reporter's explicitly-weak, trace-free guess was something scope-specific in how the
Map/Set backing (js_map_set_subclass_init) is attached versus how the constructed value is read back —
possibly a closure-capture or GC-rooting interaction. Treat that as a starting hypothesis, not a finding.

Activity

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

    package-auditFound by the 2026 package audit: compiling real npm packages from source instead of native bindings

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions