Skip to content

compile: Array.prototype.forEach(set.add, set) on an Any destructured-for-of receiver throws 'Set.prototype.add called on incompatible receiver' — pi wall #7 #6658

Description

@proggeramlug

Wall #7 of the pi coding-agent bring-up (tracker #6564; prior: #6593, #6604, #6644, #6649, #6651, #6652 all fixed). With the six fixes applied (current: PR #6656), pi-native --help/--version die during module init with:

TypeError: Method Set.prototype.add called on incompatible receiver
    at pi-bundle.mjs:203827

(line from the refreshed --debug-symbols build dist/pi-native-dbg; the stripped build says at <anonymous>.)

Site: @babel/types/lib/index.js (webpack-wrapped CJS factory), the alias-expansion init loop, pi-bundle.mjs:203827:

for (const { types: e4, set: t4 } of n7.allExpandedTypes) for (const r4 of e4) {
  const e5 = n7.FLIPPED_ALIAS_KEYS[r4];
  e5 ? e5.forEach(t4.add, t4) : t4.add(r4);   // ← line 203827
}

Node: Array.prototype.forEach(callback, thisArg) calls the extracted native t4.add (Set.prototype.add) with this = t4 — works. Perry: the receiver reaching the native method fails the Set brand check.

11-line repro (node: sizes: 3 1; perry: the TypeError above, rc=1):

const FLIPPED: any = { A: ["x", "y"], B: null };
const allExpandedTypes: any[] = [
  { types: ["A", "B"], set: new Set() },
  { types: ["B"], set: new Set() },
];
for (const { types: e4, set: t4 } of allExpandedTypes) {
  for (const r4 of e4) {
    const e5 = FLIPPED[r4];
    e5 ? e5.forEach(t4.add, t4) : t4.add(r4);
  }
}
console.log("sizes:", allExpandedTypes[0].set.size, allExpandedTypes[1].set.size);

Already ruled out (each of these works in isolation, byte-identical to node — the bug needs the combined shape, so it is NOT a plain forEach-thisArg gap):

["a","b"].forEach(s.add, s);                       // typed receiver: OK
const t4: any = new Set(); ["a"].forEach(t4.add, t4); // Any receiver: OK
const add = t4.add; add.call(t4, "q");             // extraction + .call: OK
for (const { set: t4 } of rows) ["a"].forEach(t4.add, t4); // destructured for-of: OK
e5 ? e5.forEach(t4.add, t4) : t4.add("z");         // ternary shape: OK

Suspect subsystem: the interaction of nested for-of + object-pattern loop bindings + the two-armed ternary dispatch on an Any receiver — some path re-materializes/wraps t4.add (or the thisArg) so the native Set method's brand check sees a non-Set receiver. Possibly the same family as the alternating t4.add(r4) direct-call arm specializing the method and poisoning the extracted-callback arm (or vice versa).

Also on the road ahead (NOT this wall, hit while probing): the bundle-shape var GaxiosInterceptorManager = class extends Set {} inside a CJS factory, new exportsObj.GaxiosInterceptorManager() then .add(...)TypeError: add is not a function (builtin-subclass through a class EXPRESSION + property-read new; family of #6232 class extends Array). gaxios instantiates these per Gaxios instance — it will bite once init gets past @babel/types.

Repro binaries: secret-tests/pi-target/dist/pi-native (stripped) and pi-native-dbg (debug symbols, prints the bundle line), both built from PR #6656's branch; pi-native.pre6652 reproduces wall #6 for contrast. HOME=$(mktemp -d) ./pi-native --version.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regressionparityCompatibility gap with Node.js, ECMAScript, or the supported ecosystem

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions