diff --git a/packages/compiler/src/frontend/lowering/lower-builtin-values.ts b/packages/compiler/src/frontend/lowering/lower-builtin-values.ts index 5e04ee83c..e5ebc6d8e 100644 --- a/packages/compiler/src/frontend/lowering/lower-builtin-values.ts +++ b/packages/compiler/src/frontend/lowering/lower-builtin-values.ts @@ -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" || diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index e2236f592..a9759b82f 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -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. */ diff --git a/packages/compiler/src/frontend/lowering/thenables.ts b/packages/compiler/src/frontend/lowering/thenables.ts index 7c0072ae3..54854ae75 100644 --- a/packages/compiler/src/frontend/lowering/thenables.ts +++ b/packages/compiler/src/frontend/lowering/thenables.ts @@ -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(); - /** 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 diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index c35022f51..7290bc362 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -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", + ]); }); });