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.
Found while fixing #10623 (PR #10636), confirmed to reproduce on clean
mainand confirmed not caused orfixed by that PR (Map is not in the
native_parentmatch table at all, so it never reaches the code that PRtouches — verified by debug trace showing
native_parent_is_some=false).Repro (exactly as tested — see the caveat, it is NOT minimized)
Node:
Map implicit OK: 1 1Perry:
Map implicit: THREW: TypeError: Method Map.prototype.get called on incompatible receiver #<NoCtorMap>Setshows 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 otherrequire()calls inthe file, works correctly and matches Node (
1 1). So something about being inside a closure, or about otherrequire()calls being present, is the trigger. The reporter did not isolate which — "closure alone, no otherrequires" 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/Setbacking (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.