Gate thenable adoption on then fields, keep Promise.prototype bindings checked, and cache the gate on the lowerer

This commit is contained in:
Chris Tate
2026-10-20 17:59:43 -05:00
parent 23db1f70fd
commit 450c8ac2a5
4 changed files with 20 additions and 13 deletions
@@ -116,6 +116,7 @@ export function isNativeBuiltinValueInitializer(
["parseInt", "parseFloat"].includes(lowerer.stdlibGlobalMember(expr, "Number") ?? "") ||
lowerer.stdlibGlobalMember(expr, "Buffer") === "isBuffer" ||
lowerer.stdlibGlobalMember(expr, "Array") === "prototype" ||
lowerer.stdlibGlobalMember(expr, "Promise") === "prototype" ||
(ts.isPropertyAccessExpression(expr.expression) &&
lowerer.stdlibGlobalMember(expr.expression, "Array") === "prototype") ||
lowerer.stdlibGlobalMember(expr, "Object") === "assign" ||
@@ -2073,6 +2073,9 @@ export class Lowerer {
* setEncoding call whose receiver is a socket or an untyped value.
* Computed once on first use (null until then). */
socketEncodingReachable: boolean | null = null;
/** Whether some program source can define a thenable
* (programMayDefineThenables). Null until first asked. */
thenablesMayBeDefined: boolean | null = null;
/** Whether some server in this program may listen on a Unix socket: a
* listen call whose first argument is a string. Computed once on first
* use (null until then); address() answers the path only when true. */
@@ -15,22 +15,19 @@ import { thenReader } from "./checked-promise-all.js";
import { builtinGlobalValue } from "./expressions/property-access.js";
import type { Lowerer } from "./lowerer.js";
const mayDefineThenables = new WeakMap<Lowerer, boolean>();
/** Whether any program source can define a thenable: a `then` method or
* function, a `then:` key, an assignment to `.then`, or a quoted `'then'`
* key. `.then(` calls alone do not count. Programs without one keep the
* plain checked-dynamic await. */
* function (generic ones too), a `then:` key, a quoted `'then'` key, or an
* assignment to `then` (a class field such as `then = (resolve) => ...` or
* a `.then` property). `.then(` calls alone do not count. Programs without
* one keep the plain checked-dynamic await. */
export function programMayDefineThenables(lowerer: Lowerer): boolean {
let known = mayDefineThenables.get(lowerer);
if (known === undefined) {
const pattern = /(^|[^.\w$])then\s*\(|\bthen\s*:|['"`]then['"`]|\.then\s*=(?!=)/m;
known = lowerer.program
if (lowerer.thenablesMayBeDefined === null) {
const pattern = /(^|[^.\w$])then\s*[(<]|\bthen\s*:|['"`]then['"`]|(^|[^\w$])then\s*=(?![=>])/m;
lowerer.thenablesMayBeDefined = lowerer.program
.getImplementationSourceFiles()
.some((sf) => !sf.isDeclarationFile && pattern.test(sf.text));
mayDefineThenables.set(lowerer, known);
}
return known;
return lowerer.thenablesMayBeDefined;
}
/** `await v` over a checked-dynamic value: PromiseResolve(v), then the await
+8 -2
View File
@@ -1514,7 +1514,6 @@ describe("builtin constructor values (JS lane)", () => {
["Date.prototype", "Date.prototype"],
["RegExp.prototype", "RegExp.prototype"],
["Set.prototype", "Set.prototype"],
["Promise.prototype", "Promise.prototype"],
["WeakMap.prototype", "WeakMap.prototype"],
["WeakSet.prototype", "WeakSet.prototype"],
["Date.prototype.getYear", "Date.prototype.getYear"],
@@ -1547,6 +1546,8 @@ function report(label, e) {
${reads.map(([label, read]) => `try { const value = ${read}; console.log(${JSON.stringify(label)}, "read", typeof value); } catch (e) { report(${JSON.stringify(label)}, e); }`).join("\n")}
console.log(Date.length, RegExp.length, Promise.length, Object.length, Map.length, Set.length);
console.log(Date.name, Map.name, Map.constructor === Function, Object.constructor === Function);
const promisePrototype = Promise.prototype;
console.log(typeof promisePrototype, typeof promisePrototype.then, promisePrototype.constructor === Promise);
`;
const r = await compileAndRun("builtin-constructor-members", source, "cjs");
expect(r.stderr).toBe("");
@@ -1561,7 +1562,12 @@ console.log(Date.name, Map.name, Map.constructor === Function, Object.constructo
),
),
);
expect(lines.slice(reads.length)).toEqual(["7 2 1 1 0 0", "Date Map true true"]);
// Promise.prototype reads as an object holding the runtime's methods.
expect(lines.slice(reads.length)).toEqual([
"7 2 1 1 0 0",
"Date Map true true",
"object function true",
]);
});
});