diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 5f5bc3ff3..3062ff06b 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -678,6 +678,30 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { if (!sym) { return { kind: "strLit", value: "undefined", type: STRING, loc }; } + // `const { inspect } = require("util")` / `import { inspect } from + // "node:util"`: a binding of a builtin module's exported function, + // class or object. JavaScript sources represent such a value without + // its own lowering as an identity token (a string), so the operand's + // representation cannot answer. Node's answer follows the export's + // kind, and the binding read has no effects. + const imported = isJsSourceFile(tested.getSourceFile()) + ? lowerer.builtinImportOf(tested) + : null; + if ( + imported && + builtinModuleConstOf(lowerer, imported.module, imported.member) === undefined + ) { + const t = lowerer.typeOf(tested); + if ( + lowerer.checker.getCallSignatures(t).length > 0 || + lowerer.checker.getConstructSignatures(t).length > 0 + ) { + return { kind: "strLit", value: "function", type: STRING, loc }; + } + if ((t.flags & ts.TypeFlags.Object) !== 0) { + return { kind: "strLit", value: "object", type: STRING, loc }; + } + } if (lowerer.isStdlibSymbol(sym)) { const t = lowerer.typeOf(expr.expression); if ( diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6103-typeof-builtin-bindings.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6103-typeof-builtin-bindings.cjs.json new file mode 100644 index 000000000..008914d85 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6103-typeof-builtin-bindings.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6103-typeof-builtin-bindings.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6104-typeof-builtin-bindings.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6104-typeof-builtin-bindings.mjs.json new file mode 100644 index 000000000..b6a8a48e2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6104-typeof-builtin-bindings.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6104-typeof-builtin-bindings.mjs" + ], + "diags": [] +} diff --git a/tests/corpus/6103-typeof-builtin-bindings.cjs b/tests/corpus/6103-typeof-builtin-bindings.cjs new file mode 100644 index 000000000..ecef94dd3 --- /dev/null +++ b/tests/corpus/6103-typeof-builtin-bindings.cjs @@ -0,0 +1,15 @@ +// typeof on bindings destructured from Node builtin modules: Node answers by +// the export's kind ("function" for functions and classes, "object" for +// namespace objects), never by how the binding is represented. +const { inspect, format, promisify, deprecate, isDeepStrictEqual } = require("util"); +const { join, resolve } = require("path"); +const { readFileSync, promises } = require("fs"); +const { EventEmitter } = require("events"); +const { createHash } = require("crypto"); +const util = require("node:util"); +const { inspect: aliased } = util; +console.log(typeof inspect, typeof format, typeof promisify, typeof deprecate, typeof isDeepStrictEqual); +console.log(typeof join, typeof resolve, typeof readFileSync, typeof promises); +console.log(typeof EventEmitter, typeof createHash, typeof aliased, typeof util.inspect); +console.log(typeof inspect === "function" ? "callable" : "not callable"); +console.log(join("a", "b"), format("%s=%d", "x", 1), inspect({ a: [1, 2] })); diff --git a/tests/corpus/6104-typeof-builtin-bindings.mjs b/tests/corpus/6104-typeof-builtin-bindings.mjs new file mode 100644 index 000000000..dd2976775 --- /dev/null +++ b/tests/corpus/6104-typeof-builtin-bindings.mjs @@ -0,0 +1,11 @@ +// typeof on named, default-destructured and namespace-destructured builtin +// bindings in an ES module: Node answers by the export's kind. +import util, { inspect as namedInspect, format } from "node:util"; +import * as path from "node:path"; +import { createHash } from "node:crypto"; +import { promises } from "node:fs"; +const { inspect, promisify } = util; +const { join } = path; +console.log(typeof inspect, typeof promisify, typeof namedInspect, typeof format); +console.log(typeof join, typeof createHash, typeof util.inspect, typeof promises); +console.log(namedInspect("s"), format("%d", 2), join("x", "y"));