Repository navigation
Description
mongodb 7.5.0's collection.find({}).toArray() fails with TypeError: value is not a function. This is the next CRUD-fixture blocker after #11187 (find().sort()) and #11157. The failing call is CursorResponse.make(bson). make is a static method inherited from MongoDBResponse, and it uses module-scope bindings. When it is called through the subclass, those bindings read wrong values. The instrumented fixture showed typeof isErrorResponse === "object" when make ran as CursorResponse.make and "function" when it ran as MongoDBResponse.make.
It reproduces on origin/main 25ef463 without any package. It also reproduces with PR #11199 (#11187) and PR #11188 applied.
Package-free repro (two CommonJS files + entry)
rel/doc.js:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Doc = void 0;
class Doc {
constructor(bson, offset = 0, isArray = false, elements) { this.cache = Object.create(null); this.bson = bson; this.elements = elements ?? [bson.length]; }
get(name) { return this.cache[name] ?? null; }
}
exports.Doc = Doc;rel/resp.js (reduced by greedy line removal from a mirror of mongodb/lib/cmap/wire_protocol/responses.js):
"use strict";
const lib_1 = { parse(b) { return [b.length]; } };
const doc_1 = require("./doc");
const Off = { a: 0, b: 1 };
function isErr(b, els) { for (let i = 0; i < els.length; i++) { if (els[i] === Off.b + 100) return true; } return b === "err"; }
class Base extends doc_1.Doc {
static make(bson) {
const elements = (0, lib_1.parse)(bson);
const isError = isErr(bson, elements);
return isError ? new Base(bson, 0, false, elements) : new this(bson, 0, false, elements);
}
}
exports.Base = Base;
class Sub extends Base {
constructor() { super(...arguments); this._batch = null; this.iterated = 0; }
get id() { try { return lib_1.parse(this.cursor); } catch (cause) { throw new err_1.E(cause.message); } }
}
exports.Sub = Sub;
class Sub2 extends Sub {
}main.ts:
const r = require("./rel/resp.js");
for (const [l, f] of [["Base.make", () => r.Base.make("abc").constructor.name], ["Sub.make", () => r.Sub.make("abc").constructor.name]] as any[]) {
try { console.log(l, f()); } catch (e: any) { console.log(l, "THREW", e.message); }
}Node 26.5.1:
Base.make Base
Sub.make Sub
Perry (main 25ef463, perry-dev, Linux x64, PERRY_NO_AUTO_OPTIMIZE=1):
Base.make Base
Sub.make THREW value is not a function
The reducer kept every one of these ingredients:
Sub's explicitconstructor() { super(...arguments); ... }.- A getter on
Sub. - A further subclass
Sub2 extends Sub. - A cross-module base,
Base extends doc_1.Doc. - The module-scope bindings that
makereads:lib_1,isErr, andOff, whichisErrreads.
Remove any one of them and the call succeeds. new Sub(...) called directly works, and so does Base.make. Only the inherited static that is invoked with this = Sub fails.
In mongodb
Fixture: mongodb 7.5.0 + bson 7.3.3, auto-optimize on, perry.compilePackages = mongodb and its deps. connect, deleteMany and insertMany succeed. Then await collection.find({}).toArray() fails, with or without .sort(). The throw originates in Connection.sendWire, at (responseType ?? MongoDBResponse).make(bson) with responseType = CursorResponse. Instrumentation inside MongoDBResponse.make printed:
make this MongoDBResponse ... typeof isErrorResponse = function (insert/delete: fine)
make this CursorResponse ... typeof isErrorResponse = object (find: throws calling it)
Possibly the same family as #10911 (a class's capturing static methods read captures as undefined). Here the captures come back as the wrong values, and only when the static is reached through a subclass.
Activity
fix(runtime): an inherited static reads its DECLARING class evaluatio…