mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-04 08:04:37 +08:00
Resolve builtin classes through const aliases (#1327)
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { type Lowerer } from "../lowerer.js";
|
||||
import { type ClassInfo } from "../lower-classes.js";
|
||||
import { isJsSourceFile } from "../../program.js";
|
||||
import { builtinMemberRequireDecl } from "./module-bindings.js";
|
||||
import { stripTypeCasts } from "./arguments.js";
|
||||
import { streamClassAliasDecl } from "../lower-stream.js";
|
||||
import { zlibClassName, zlibStreamModeOf } from "./compression.js";
|
||||
import { stdlibGlobalNameOf } from "../surfaces.js";
|
||||
|
||||
/** A builtin class an expression names at compile time: the error
|
||||
* classes, node:events EventEmitter, the node:stream classes (each a
|
||||
* runtime ClassInfo), the node:zlib stream classes (by node_zlib_mode),
|
||||
* and the Map and Set constructors. `name` is the class's `name`
|
||||
* property. */
|
||||
export type BuiltinClassRef =
|
||||
| { kind: "error" | "emitter" | "stream"; info: ClassInfo; name: string }
|
||||
| { kind: "zlib"; mode: number; name: string }
|
||||
| { kind: "collection"; which: "Map" | "Set"; name: string };
|
||||
|
||||
/** The builtin class `expr` names: a direct spelling (`EventEmitter`,
|
||||
* `stream.Readable`, `zlib.Gzip`, `Map`, `globalThis.TypeError`) or a
|
||||
* `const` binding of one (`const E = EventEmitter`, an alias of an alias,
|
||||
* an imported alias), through parentheses and type assertions. Null for
|
||||
* anything else. */
|
||||
export function builtinClassRefOf(lowerer: Lowerer, expr: ts.Expression): BuiltinClassRef | null {
|
||||
const e = stripTypeCasts(expr);
|
||||
// Checker lookups only: answering must not run the fences and deferred
|
||||
// diagnostics of a lowered reference (resolveValueSymbol).
|
||||
let symbol: ts.Symbol | undefined;
|
||||
if (ts.isIdentifier(e)) {
|
||||
symbol = lowerer.checker.getSymbolAtLocation(e);
|
||||
} else if (ts.isPropertyAccessExpression(e) && !e.questionDotToken) {
|
||||
symbol = lowerer.checker.getSymbolAtLocation(e.name);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if (!symbol) return null;
|
||||
const alias = builtinClassAliasOf(lowerer, symbol);
|
||||
if (alias !== null) return alias;
|
||||
if (symbol.flags & ts.SymbolFlags.Alias) symbol = lowerer.checker.getAliasedSymbol(symbol);
|
||||
const which = stdlibGlobalNameOf(lowerer, e);
|
||||
if (which === "Map" || which === "Set") return { kind: "collection", which, name: which };
|
||||
const error = lowerer.builtinErrorInfoOf(symbol);
|
||||
if (error) return { kind: "error", info: error, name: runtimeClassName(error) };
|
||||
const emitter = lowerer.builtinEmitterInfoOf(symbol);
|
||||
if (emitter) return { kind: "emitter", info: emitter, name: runtimeClassName(emitter) };
|
||||
const stream = lowerer.builtinStreamInfoOf(symbol);
|
||||
if (stream) return { kind: "stream", info: stream, name: runtimeClassName(stream) };
|
||||
const mode = zlibStreamModeOf(lowerer, symbol);
|
||||
if (mode !== null) return { kind: "zlib", mode, name: zlibClassName(mode) };
|
||||
return null;
|
||||
}
|
||||
|
||||
/** The builtin class a `const` binding names (`const E = EventEmitter`,
|
||||
* `const M = Map`, `const C = zlib.Gzip`, or an alias of such a binding),
|
||||
* or null. `let` and `var` bindings could be reassigned, and annotated
|
||||
* bindings give the value another type, so they are not aliases. Resolved from the declaration, so uses lowered before the
|
||||
* declaration statement (hoisted functions) see the same answer. */
|
||||
export function builtinClassAliasOf(lowerer: Lowerer, symbol: ts.Symbol): BuiltinClassRef | null {
|
||||
const cached = lowerer.builtinClassAliases.get(symbol);
|
||||
if (cached !== undefined) return cached;
|
||||
// An import of an exported alias names the exporting binding.
|
||||
if (symbol.flags & ts.SymbolFlags.Alias) {
|
||||
lowerer.builtinClassAliases.set(symbol, null);
|
||||
const target = lowerer.checker.getAliasedSymbol(symbol);
|
||||
const ref = target === symbol ? null : builtinClassAliasOf(lowerer, target);
|
||||
lowerer.builtinClassAliases.set(symbol, ref);
|
||||
return ref;
|
||||
}
|
||||
const init = constInitializerOf(lowerer, symbol);
|
||||
if (init === null) {
|
||||
lowerer.builtinClassAliases.set(symbol, null);
|
||||
return null;
|
||||
}
|
||||
// Marked first: a cycle through other bindings answers null.
|
||||
lowerer.builtinClassAliases.set(symbol, null);
|
||||
const ref = builtinClassRefOf(lowerer, init);
|
||||
lowerer.builtinClassAliases.set(symbol, ref);
|
||||
return ref;
|
||||
}
|
||||
|
||||
/** True when `symbol` is a builtin class alias whose declaration owns no
|
||||
* runtime slot: new, instanceof, typeof, .name and === lower at each use,
|
||||
* and other uses report SC1090. In JavaScript files, a binding whose
|
||||
* initializer has a value (`const E = EventEmitter`, `const M =
|
||||
* globalThis.Map`, both identity tokens) keeps its slot; only builtin
|
||||
* module members (`zlib.Gzip`) and copies of slotless aliases own none.
|
||||
* The `require("events").EventEmitter` and `stream.Writable` spellings
|
||||
* keep their own declaration handling. */
|
||||
export function builtinClassAliasOwnsNoSlot(lowerer: Lowerer, symbol: ts.Symbol): boolean {
|
||||
if (builtinClassAliasOf(lowerer, symbol) === null) return false;
|
||||
const binding =
|
||||
symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol;
|
||||
const decl = lowerer.checker.valueDeclarationOf(binding);
|
||||
if (decl === undefined || !ts.isVariableDeclaration(decl)) return false;
|
||||
if (builtinMemberRequireDecl(decl.name, decl.initializer)) return false;
|
||||
if (streamClassAliasDecl(lowerer, decl.name, decl.initializer)) return false;
|
||||
if (!isJsSourceFile(decl.getSourceFile())) return true;
|
||||
let init = decl.initializer;
|
||||
while (init !== undefined && ts.isParenthesizedExpression(init)) init = init.expression;
|
||||
if (init === undefined) return true;
|
||||
let source: ts.Symbol | undefined;
|
||||
if (ts.isPropertyAccessExpression(init)) {
|
||||
if (lowerer.builtinNamespaceModuleOf(init.expression) !== null) return true;
|
||||
source = lowerer.checker.getSymbolAtLocation(init.name);
|
||||
} else {
|
||||
source = lowerer.checker.getSymbolAtLocation(init);
|
||||
}
|
||||
return source !== undefined && builtinClassAliasOwnsNoSlot(lowerer, source);
|
||||
}
|
||||
|
||||
/** True for a `const` declaration that is a builtin class alias owning no
|
||||
* runtime slot (see builtinClassAliasOwnsNoSlot): both declaration walks
|
||||
* skip it. */
|
||||
export function registerBuiltinClassAlias(
|
||||
lowerer: Lowerer,
|
||||
nameNode: ts.Node,
|
||||
init: ts.Expression | undefined,
|
||||
): boolean {
|
||||
if (!ts.isIdentifier(nameNode) || init === undefined) return false;
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(nameNode);
|
||||
return symbol !== undefined && builtinClassAliasOwnsNoSlot(lowerer, symbol);
|
||||
}
|
||||
|
||||
/** True when two references name the same builtin class. */
|
||||
export function sameBuiltinClass(a: BuiltinClassRef, b: BuiltinClassRef): boolean {
|
||||
if (a.kind === "zlib") return b.kind === "zlib" && a.mode === b.mode;
|
||||
if (a.kind === "collection") return b.kind === "collection" && a.which === b.which;
|
||||
return b.kind === a.kind && b.info === a.info;
|
||||
}
|
||||
|
||||
/** The initializer of an unannotated `const` binding whose initializer
|
||||
* can name a class (an identifier or a property access), or null. */
|
||||
function constInitializerOf(lowerer: Lowerer, symbol: ts.Symbol): ts.Expression | null {
|
||||
if ((symbol.flags & ts.SymbolFlags.BlockScopedVariable) === 0) return null;
|
||||
const decl = lowerer.checker.valueDeclarationOf(symbol);
|
||||
if (
|
||||
decl === undefined ||
|
||||
!ts.isVariableDeclaration(decl) ||
|
||||
!ts.isIdentifier(decl.name) ||
|
||||
decl.type !== undefined
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const list = decl.parent;
|
||||
if (!ts.isVariableDeclarationList(list) || (list.flags & ts.NodeFlags.Const) === 0) return null;
|
||||
let init = decl.initializer;
|
||||
while (init !== undefined && ts.isParenthesizedExpression(init)) init = init.expression;
|
||||
if (init === undefined) return null;
|
||||
if (ts.isIdentifier(init)) return init;
|
||||
if (ts.isPropertyAccessExpression(init) && !init.questionDotToken) return init;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Node's constructor name of a runtime class (`%TypeError` → TypeError). */
|
||||
function runtimeClassName(info: ClassInfo): string {
|
||||
return info.def.name.startsWith("%") ? info.def.name.slice(1) : info.def.name;
|
||||
}
|
||||
@@ -132,8 +132,8 @@ export function zlibStreamModeOf(
|
||||
symbol: ts.Symbol | null | undefined,
|
||||
): number | null {
|
||||
if (!symbol) return null;
|
||||
const aliasMode = zlibClassAliasModeOf(lowerer, symbol, 0);
|
||||
if (aliasMode !== undefined) return aliasMode;
|
||||
const alias = lowerer.builtinClassAliasOf(symbol);
|
||||
if (alias !== null) return alias.kind === "zlib" ? alias.mode : null;
|
||||
const target =
|
||||
symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol;
|
||||
const mode = ZLIB_STREAM_MODES.get(target.name);
|
||||
@@ -499,80 +499,12 @@ export function lowerZlibModuleCall(
|
||||
throw new InternalCompilerError(`unhandled lowered zlib member ${bi.member}`);
|
||||
}
|
||||
|
||||
/** The zlib stream class an expression names (`zlib.Gzip`, an imported
|
||||
* `Gzip`, a `const C = zlib.Gzip` alias), as its node_zlib_mode, or null. */
|
||||
export function zlibClassModeOfExpr(lowerer: Lowerer, expr: ts.Expression): number | null {
|
||||
let e = expr;
|
||||
while (ts.isParenthesizedExpression(e)) e = e.expression;
|
||||
if (ts.isIdentifier(e)) return zlibStreamModeOf(lowerer, lowerer.checker.getSymbolAtLocation(e));
|
||||
if (ts.isPropertyAccessExpression(e) && !e.questionDotToken) {
|
||||
return zlibStreamModeOf(lowerer, lowerer.checker.getSymbolAtLocation(e.name));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** The class name of a zlib stream mode (Node's constructor name). */
|
||||
export function zlibClassName(mode: number): string {
|
||||
for (const [name, m] of ZLIB_STREAM_MODES) if (m === mode) return name;
|
||||
return "Zlib";
|
||||
}
|
||||
|
||||
/** The mode of a `const C = zlib.Gzip` binding (or an alias of such a
|
||||
* binding), null when the symbol is a const binding of something else,
|
||||
* undefined when it is not a const binding. Resolved from the declaration,
|
||||
* so uses lowered before the declaration statement see the same answer. */
|
||||
function zlibClassAliasModeOf(
|
||||
lowerer: Lowerer,
|
||||
symbol: ts.Symbol,
|
||||
depth: number,
|
||||
): number | null | undefined {
|
||||
const cached = lowerer.zlibClassAliases.get(symbol);
|
||||
if (cached !== undefined) return cached;
|
||||
if ((symbol.flags & ts.SymbolFlags.BlockScopedVariable) === 0) return undefined;
|
||||
const decl = lowerer.checker.valueDeclarationOf(symbol);
|
||||
if (decl === undefined || !ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name)) {
|
||||
return undefined;
|
||||
}
|
||||
// const only: a var or let binding could be reassigned
|
||||
const list = decl.parent;
|
||||
if (!ts.isVariableDeclarationList(list) || (list.flags & ts.NodeFlags.Const) === 0) {
|
||||
return undefined;
|
||||
}
|
||||
let init = decl.initializer;
|
||||
while (init !== undefined && ts.isParenthesizedExpression(init)) init = init.expression;
|
||||
if (init === undefined || depth > 16) return null;
|
||||
let mode: number | null = null;
|
||||
if (ts.isIdentifier(init)) {
|
||||
const next = lowerer.checker.getSymbolAtLocation(init);
|
||||
if (next !== undefined) {
|
||||
const nested = zlibClassAliasModeOf(lowerer, next, depth + 1);
|
||||
mode = nested === undefined ? zlibStreamModeOf(lowerer, next) : nested;
|
||||
}
|
||||
} else if (ts.isPropertyAccessExpression(init) && !init.questionDotToken) {
|
||||
mode = zlibStreamModeOf(lowerer, lowerer.checker.getSymbolAtLocation(init.name));
|
||||
}
|
||||
if (mode !== null) lowerer.zlibClassAliases.set(symbol, mode);
|
||||
return mode;
|
||||
}
|
||||
|
||||
/** True for `const C = zlib.Gzip` (or an alias of an alias): a
|
||||
* compile-time zlib class alias that owns no runtime slot, so the
|
||||
* statement lowering skips it. */
|
||||
export function registerZlibClassAlias(
|
||||
lowerer: Lowerer,
|
||||
nameNode: ts.Node,
|
||||
init: ts.Expression | undefined,
|
||||
): boolean {
|
||||
if (!ts.isIdentifier(nameNode) || init === undefined) return false;
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(nameNode);
|
||||
return symbol !== undefined && zlibClassAliasOf(lowerer, symbol) !== null;
|
||||
}
|
||||
|
||||
/** The mode of a `const C = zlib.Gzip` alias symbol, or null. */
|
||||
export function zlibClassAliasOf(lowerer: Lowerer, symbol: ts.Symbol): number | null {
|
||||
return zlibClassAliasModeOf(lowerer, symbol, 0) ?? null;
|
||||
}
|
||||
|
||||
/** `value instanceof zlib.Gzip` (any spelling of the class, aliases
|
||||
* included). zlib streams are runtime Transforms: a Transform operand asks
|
||||
* the runtime for the stream's mode, and an operand typed as one of
|
||||
|
||||
@@ -12,6 +12,11 @@ import { InternalCompilerError } from "../../errors.js";
|
||||
import { fsStreamClassOf, lowerFsStreamNew } from "./builtins/fs-streams.js";
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import { lowerZlibStreamNew, zlibClassName, zlibStreamModeOf } from "./builtins/compression.js";
|
||||
import {
|
||||
type BuiltinClassRef,
|
||||
builtinClassAliasOf,
|
||||
builtinClassRefOf,
|
||||
} from "./builtins/class-values.js";
|
||||
import type { FnCtx, Lowerer } from "./lowerer.js";
|
||||
import {
|
||||
BOOL,
|
||||
@@ -924,12 +929,15 @@ export function registerBuiltinStreamClasses(lowerer: Lowerer): void {
|
||||
* "stream" ambient module, EXCLUDING @types/node's (whose stream.Readable
|
||||
* also types child stdio — under @types/node the childStream mapping
|
||||
* keeps priority and the static stream classes stand down; the shipped
|
||||
* fallback declarations are the supported surface). */
|
||||
* fallback declarations are the supported surface). A const binding of
|
||||
* one (`const R = Readable`) names the same class (builtinClassAliasOf). */
|
||||
export function builtinStreamInfoOf(
|
||||
lowerer: Lowerer,
|
||||
symbol: ts.Symbol | null | undefined,
|
||||
): ClassInfo | null {
|
||||
if (!symbol) return null;
|
||||
const alias = builtinClassAliasOf(lowerer, symbol);
|
||||
if (alias !== null) return alias.kind === "stream" ? alias.info : null;
|
||||
if (!lowerer.isStdlibSymbol(symbol)) {
|
||||
// A const ALIAS of a namespace member (`const Writable =
|
||||
// stream.Writable` — the two-step spelling; the one-step
|
||||
@@ -1284,12 +1292,16 @@ function undefArmedFieldType(lowerer: Lowerer, p: ts.Symbol): IrType | null {
|
||||
* `extends EventEmitter`, `x instanceof EventEmitter`) — any import
|
||||
* spelling (named/default/namespace member, CJS require) resolves to
|
||||
* the ambient class. Provenance-checked like the error classes: only a
|
||||
* stdlib-file declaration inside the "events" ambient module counts. */
|
||||
* stdlib-file declaration inside the "events" ambient module counts. A
|
||||
* const binding of it (`const E = EventEmitter`) names the same class
|
||||
* (builtinClassAliasOf). */
|
||||
export function builtinEmitterInfoOf(
|
||||
lowerer: Lowerer,
|
||||
symbol: ts.Symbol | null | undefined,
|
||||
): ClassInfo | null {
|
||||
if (!symbol) return null;
|
||||
const alias = builtinClassAliasOf(lowerer, symbol);
|
||||
if (alias !== null) return alias.kind === "emitter" ? alias.info : null;
|
||||
if (!lowerer.isStdlibSymbol(symbol)) {
|
||||
// A const ALIAS of the emitter class member (`const EventEmitter =
|
||||
// require('node:events').EventEmitter` — commander's spelling; the
|
||||
@@ -1330,12 +1342,17 @@ export function builtinEmitterInfoOf(
|
||||
/** The builtin error ClassInfo a VALUE symbol refers to (`new Error`,
|
||||
* `extends TypeError`, `x instanceof RangeError`), or null. Provenance-
|
||||
* checked: only the standard library's declarations count — a user's own
|
||||
* `class Error` resolves through classBySymbol instead. */
|
||||
* `class Error` resolves through classBySymbol instead. A const binding
|
||||
* of one (`const E = TypeError`) names the same class
|
||||
* (builtinClassAliasOf). */
|
||||
export function builtinErrorInfoOf(
|
||||
lowerer: Lowerer,
|
||||
symbol: ts.Symbol | null | undefined,
|
||||
): ClassInfo | null {
|
||||
if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null;
|
||||
if (!symbol) return null;
|
||||
const alias = builtinClassAliasOf(lowerer, symbol);
|
||||
if (alias !== null) return alias.kind === "error" ? alias.info : null;
|
||||
if (!lowerer.isStdlibSymbol(symbol)) return null;
|
||||
for (const [irName, rec] of RUNTIME_ERROR_CLASSES) {
|
||||
if (rec.lib === symbol.name) return lowerer.classes.get(irName) ?? null;
|
||||
}
|
||||
@@ -8443,6 +8460,12 @@ function assignedThisFieldType(lowerer: Lowerer, expr: ts.NewExpression): IrType
|
||||
return lowerer.currentClass?.fields.get(parent.left.name.text) ?? null;
|
||||
}
|
||||
|
||||
/** Map, Set and the error classes construct in lowerNew's identifier
|
||||
* branch, which a module member alias of one (`new mod.M()`) also takes. */
|
||||
function isCollectionOrErrorClass(ref: BuiltinClassRef | null): boolean {
|
||||
return ref?.kind === "collection" || ref?.kind === "error";
|
||||
}
|
||||
|
||||
export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
const loc = locOf(expr);
|
||||
const worker = lowerWorkerNew(lowerer, expr);
|
||||
@@ -8737,7 +8760,10 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
ts.isIdentifier(expr.expression) ||
|
||||
(ts.isPropertyAccessExpression(expr.expression) &&
|
||||
ts.isIdentifier(expr.expression.name) &&
|
||||
lowerer.isStdlibGlobal(expr.expression.expression, "globalThis"))
|
||||
(lowerer.isStdlibGlobal(expr.expression.expression, "globalThis") ||
|
||||
// `new mod.M()` over an exported `const M = Map` or `const E =
|
||||
// TypeError`
|
||||
isCollectionOrErrorClass(builtinClassRefOf(lowerer, expr.expression))))
|
||||
) {
|
||||
// `import C = N.C; new C()` — the alias's own source-order guards
|
||||
// (a no-op for every non-import= binding).
|
||||
@@ -9102,7 +9128,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
if (symbol?.name === "Array" && lowerer.isStdlibSymbol(symbol)) {
|
||||
return lowerArrayConstructor(lowerer, expr, expr.arguments ?? []);
|
||||
}
|
||||
if (symbol?.name === "Map" && lowerer.isStdlibSymbol(symbol)) {
|
||||
// A const alias (`const M = Map`) constructs the same Map.
|
||||
if (ts.isIdentifier(identifier) && lowerer.isStdlibGlobal(identifier, "Map")) {
|
||||
const seedArg = (expr.arguments?.length ?? 0) === 1 ? expr.arguments![0]! : null;
|
||||
const isPairLit = (el: ts.Expression): el is ts.ArrayLiteralExpression =>
|
||||
ts.isArrayLiteralExpression(el) &&
|
||||
@@ -9234,7 +9261,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
}
|
||||
// `new Set<T>()`: construct + bulk add from supported values,
|
||||
// including live collection cursors. Duplicates collapse in insertion order.
|
||||
if (symbol?.name === "Set" && lowerer.isStdlibSymbol(symbol)) {
|
||||
if (ts.isIdentifier(identifier) && lowerer.isStdlibGlobal(identifier, "Set")) {
|
||||
const tsType = lowerer.typeOf(expr);
|
||||
let mapped = lowerer.mapTypeOf(tsType);
|
||||
const fieldType = assignedThisFieldType(lowerer, expr);
|
||||
|
||||
@@ -35,10 +35,12 @@ import {
|
||||
isZlibStreamMember,
|
||||
lowerZlibInstanceOf,
|
||||
lowerZlibStreamBytesWritten,
|
||||
zlibClassAliasOf,
|
||||
zlibClassModeOfExpr,
|
||||
zlibClassName,
|
||||
} from "./builtins/compression.js";
|
||||
import {
|
||||
builtinClassAliasOwnsNoSlot,
|
||||
builtinClassRefOf,
|
||||
sameBuiltinClass,
|
||||
} from "./builtins/class-values.js";
|
||||
import { constituentTypes } from "../ts7/checker.js";
|
||||
import { dirname } from "node:path";
|
||||
import * as posix from "node:path/posix";
|
||||
@@ -616,8 +618,9 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
if (isNodeModuleValue(lowerer, expr.expression)) {
|
||||
return { kind: "strLit", value: "object", type: STRING, loc };
|
||||
}
|
||||
// `typeof zlib.Gzip`, `typeof C` over a zlib class alias.
|
||||
if (zlibClassModeOfExpr(lowerer, expr.expression) !== null) {
|
||||
// `typeof EventEmitter`, `typeof zlib.Gzip`, `typeof C` over a
|
||||
// builtin class alias.
|
||||
if (builtinClassRefOf(lowerer, expr.expression) !== null) {
|
||||
return { kind: "strLit", value: "function", type: STRING, loc };
|
||||
}
|
||||
if (ts.isPropertyAccessExpression(expr.expression)) {
|
||||
@@ -902,13 +905,13 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
`a builtin function alias as an escaping value (call '${expr.text}' directly)`,
|
||||
);
|
||||
}
|
||||
// `const C = zlib.Gzip` owns no runtime slot: construction,
|
||||
// `const E = EventEmitter` owns no runtime slot: construction,
|
||||
// instanceof, typeof and .name lower at their use sites.
|
||||
if (sym && zlibClassAliasOf(lowerer, sym) !== null) {
|
||||
if (sym && builtinClassAliasOwnsNoSlot(lowerer, sym)) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
expr,
|
||||
`a zlib class alias as an escaping value (use '${expr.text}' with new, instanceof, typeof or .name)`,
|
||||
`a builtin class alias as an escaping value (use '${expr.text}' with new, instanceof, typeof or .name)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1849,10 +1852,11 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
const table = lowerInternTableProperty(lowerer, expr);
|
||||
if (table) return table;
|
||||
}
|
||||
// `zlib.Gzip.name`, `C.name` over a zlib class alias.
|
||||
// `EventEmitter.name`, `zlib.Gzip.name`, `C.name` over a builtin
|
||||
// class alias.
|
||||
if (expr.name.text === "name" && !expr.questionDotToken) {
|
||||
const mode = zlibClassModeOfExpr(lowerer, expr.expression);
|
||||
if (mode !== null) return { kind: "strLit", value: zlibClassName(mode), type: STRING, loc };
|
||||
const ref = builtinClassRefOf(lowerer, expr.expression);
|
||||
if (ref !== null) return { kind: "strLit", value: ref.name, type: STRING, loc };
|
||||
}
|
||||
// Expando statics may still be absent even when JS inference sees
|
||||
// the eventual assignment's scalar type. Preserve the checked value.
|
||||
@@ -10266,17 +10270,18 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
|
||||
lowerUnionTypeofTest(lowerer, expr, loc);
|
||||
if (test) return test;
|
||||
}
|
||||
// `C === zlib.Gzip` between two zlib class references: the classes
|
||||
// are compile-time values, so the answer folds.
|
||||
// `C === EventEmitter` between two builtin class references: the
|
||||
// classes are compile-time values, so the answer folds.
|
||||
if (
|
||||
op === ts.SyntaxKind.EqualsEqualsEqualsToken ||
|
||||
op === ts.SyntaxKind.ExclamationEqualsEqualsToken
|
||||
) {
|
||||
const leftClass = zlibClassModeOfExpr(lowerer, expr.left);
|
||||
const rightClass = leftClass === null ? null : zlibClassModeOfExpr(lowerer, expr.right);
|
||||
const leftClass = builtinClassRefOf(lowerer, expr.left);
|
||||
const rightClass = leftClass === null ? null : builtinClassRefOf(lowerer, expr.right);
|
||||
if (leftClass !== null && rightClass !== null) {
|
||||
const negated = op === ts.SyntaxKind.ExclamationEqualsEqualsToken;
|
||||
return { kind: "boolLit", value: (leftClass === rightClass) !== negated, type: BOOL, loc };
|
||||
const same = sameBuiltinClass(leftClass, rightClass);
|
||||
return { kind: "boolLit", value: same !== negated, type: BOOL, loc };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12352,11 +12357,11 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc
|
||||
// h2 compat 'connect' narrowing (lower-server.ts): a union tag test.
|
||||
const sockTest = lowerSocketInstanceOf(lowerer, expr, loc);
|
||||
if (sockTest !== null) return sockTest;
|
||||
const zlibMode = zlibClassModeOfExpr(lowerer, expr.right);
|
||||
if (zlibMode !== null) return lowerZlibInstanceOf(lowerer, expr, zlibMode, loc);
|
||||
const rightClass = builtinClassRefOf(lowerer, expr.right);
|
||||
if (rightClass?.kind === "zlib") return lowerZlibInstanceOf(lowerer, expr, rightClass.mode, loc);
|
||||
|
||||
if (lowerer.isStdlibGlobal(expr.right, "Set") || lowerer.isStdlibGlobal(expr.right, "Map")) {
|
||||
const collection = lowerer.isStdlibGlobal(expr.right, "Map") ? "map" : "set";
|
||||
if (rightClass?.kind === "collection") {
|
||||
const collection = rightClass.which === "Map" ? "map" : "set";
|
||||
const value = lowerer.lowerExpr(expr.left);
|
||||
if (value.type.kind === "union") {
|
||||
const union = lowerer.unions.get(value.type.unionId);
|
||||
|
||||
@@ -80,7 +80,7 @@ import {
|
||||
createRequireProgramModuleDecl,
|
||||
} from "./builtins/module-bindings.js";
|
||||
import { isPromisifyCall, registerBuiltinCallableAlias } from "./builtins/callable-aliases.js";
|
||||
import { registerZlibClassAlias } from "./builtins/compression.js";
|
||||
import { registerBuiltinClassAlias } from "./builtins/class-values.js";
|
||||
import { stripTypeCasts } from "./builtins/arguments.js";
|
||||
import {
|
||||
bindingContextualGenericFnNodeOf,
|
||||
@@ -1701,7 +1701,7 @@ export function collectGlobals(
|
||||
// resolution and value reads reuse the source's interned closure.
|
||||
if (isConst && registerOverloadedCallableAlias(lowerer, decl)) continue;
|
||||
if (isConst && registerBuiltinCallableAlias(lowerer, decl.name, decl.initializer)) continue;
|
||||
if (registerZlibClassAlias(lowerer, decl.name, decl.initializer)) continue;
|
||||
if (registerBuiltinClassAlias(lowerer, decl.name, decl.initializer)) continue;
|
||||
// `const f = (x) => ...` at file scope of an npm-static JS file
|
||||
// whose params are implicit-any: the implicit-monomorphization
|
||||
// twin of the generic binding above — no global exists; calls and
|
||||
|
||||
@@ -148,7 +148,7 @@ import {
|
||||
} from "./builtins/module-bindings.js";
|
||||
import { lowerNodeModuleCall } from "./builtins/module-resolution.js";
|
||||
import { registerBuiltinCallableAlias } from "./builtins/callable-aliases.js";
|
||||
import { registerZlibClassAlias } from "./builtins/compression.js";
|
||||
import { builtinClassAliasOwnsNoSlot, registerBuiltinClassAlias } from "./builtins/class-values.js";
|
||||
import { lowerEnumDeclaration } from "./lower-enums.js";
|
||||
import {
|
||||
abstractPropertyDeclOf,
|
||||
@@ -853,6 +853,9 @@ export function predeclareForwardCapture(lowerer: Lowerer, symbol: ts.Symbol): b
|
||||
if (!decl.initializer) return false;
|
||||
const flags = ts.getCombinedNodeFlags(decl);
|
||||
if ((flags & ts.NodeFlags.BlockScoped) === 0 || (flags & ts.NodeFlags.Using) !== 0) return false;
|
||||
// `const E = EventEmitter` has no storage to box: uses resolve at
|
||||
// compile time (builtinClassAliasOf).
|
||||
if (builtinClassAliasOwnsNoSlot(lowerer, symbol)) return false;
|
||||
// MODULE-scope bindings are pre-registered globals (collectGlobals):
|
||||
// references resolve through globalOf after the local search fails, so
|
||||
// a TDZ box here would SHADOW the global and never fill (the top-level
|
||||
@@ -4429,7 +4432,7 @@ export function lowerVarDecl(
|
||||
// reads reuse its interned closure identity.
|
||||
if (!isLet && registerOverloadedCallableAlias(lowerer, decl)) return null;
|
||||
if (!isLet && registerBuiltinCallableAlias(lowerer, decl.name, decl.initializer)) return null;
|
||||
if (registerZlibClassAlias(lowerer, decl.name, decl.initializer)) return null;
|
||||
if (registerBuiltinClassAlias(lowerer, decl.name, decl.initializer)) return null;
|
||||
|
||||
// `const knownBy = (cmd) => ...` — an IMPLICIT-ANY function-value
|
||||
// binding in npm-static JS (function-scope bindings register here, in
|
||||
|
||||
@@ -261,6 +261,7 @@ import {
|
||||
stdlibGlobalMember,
|
||||
nodeTypesOnlySymbol,
|
||||
} from "./surfaces.js";
|
||||
import { type BuiltinClassRef, builtinClassAliasOf } from "./builtins/class-values.js";
|
||||
import {
|
||||
type FileParts,
|
||||
splitFiles,
|
||||
@@ -1858,11 +1859,11 @@ export class Lowerer {
|
||||
* typeParamBindings — the checker has no `T` to substitute, so the
|
||||
* binding rides the node-type accessor instead of mapType. */
|
||||
implicitParamTypes: Map<ts.Symbol, ts.Type> | null = null;
|
||||
/** Immutable bindings that name a zlib stream class (`const C =
|
||||
* zlib.Gzip`), keyed by the binding's symbol: the class's
|
||||
* node_zlib_mode. No runtime slot exists: new, typeof, .name and ===
|
||||
* resolve at compile time, and instanceof checks the stream's mode. */
|
||||
readonly zlibClassAliases = new Map<ts.Symbol, number>();
|
||||
/** The builtin class each binding names (`const E = EventEmitter`,
|
||||
* `const C = zlib.Gzip`), keyed by the binding's symbol; null for
|
||||
* bindings that name none (builtinClassAliasOf). new, instanceof,
|
||||
* typeof, .name and === resolve at compile time. */
|
||||
readonly builtinClassAliases = new Map<ts.Symbol, BuiltinClassRef | null>();
|
||||
/** Whether some net.Socket in this program may get an encoding: a
|
||||
* setEncoding call whose receiver is a socket or an untyped value.
|
||||
* Computed once on first use (null until then). */
|
||||
@@ -3073,6 +3074,10 @@ export class Lowerer {
|
||||
return builtinStreamInfoOf(this, symbol);
|
||||
}
|
||||
|
||||
builtinClassAliasOf(symbol: ts.Symbol): BuiltinClassRef | null {
|
||||
return builtinClassAliasOf(this, symbol);
|
||||
}
|
||||
|
||||
/** Program-wide qualified name for a top-level declaration. */
|
||||
qualify(sf: ts.SourceFile, name: string): string {
|
||||
return `${this.fileTag.get(sf) ?? ""}${name}`;
|
||||
|
||||
@@ -3207,7 +3207,9 @@ export function isStdlibSymbol(lowerer: Lowerer, symbol: ts.Symbol | undefined):
|
||||
* `global.process`) — `declare var` globals ARE properties of
|
||||
* `typeof globalThis`, same symbol either way;
|
||||
* - a local alias binding (`const process = globalThis.process`, the
|
||||
* tamper-guard prologue) registered in stdlibGlobalAliases. */
|
||||
* tamper-guard prologue) registered in stdlibGlobalAliases;
|
||||
* - a const binding of the Map or Set constructor (`const M = Map`,
|
||||
* builtinClassAliasOf). */
|
||||
export function stdlibGlobalNameOf(lowerer: Lowerer, expr: ts.Expression): string | null {
|
||||
if (ts.isParenthesizedExpression(expr)) return stdlibGlobalNameOf(lowerer, expr.expression);
|
||||
if (ts.isIdentifier(expr)) {
|
||||
@@ -3225,6 +3227,8 @@ export function stdlibGlobalNameOf(lowerer: Lowerer, expr: ts.Expression): strin
|
||||
if (!symbol) return null;
|
||||
const alias = lowerer.stdlibGlobalAliases.get(symbol);
|
||||
if (alias !== undefined) return alias;
|
||||
const classAlias = lowerer.builtinClassAliasOf(symbol);
|
||||
if (classAlias?.kind === "collection") return classAlias.which;
|
||||
const decl = lowerer.checker.declarationsOf(symbol)[0];
|
||||
if (decl !== undefined && ts.isImportClause(decl) && decl.name !== undefined) {
|
||||
const importDecl = decl.parent;
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5976-builtin-class-aliases.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5977-builtin-class-aliases.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5978-builtin-class-alias-modules/classes.ts",
|
||||
"<repo>/tests/corpus/5978-builtin-class-alias-modules/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/builtin-class-alias-escape.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Builtin classes through const aliases: EventEmitter, the stream classes,
|
||||
// the error classes, Map and Set construct, extend and test instanceof
|
||||
// through an alias, and typeof, .name and === answer as in Node for the
|
||||
// aliases and the direct spellings.
|
||||
import { EventEmitter } from "node:events";
|
||||
import * as events from "node:events";
|
||||
import { PassThrough, Readable, Transform } from "node:stream";
|
||||
import * as stream from "node:stream";
|
||||
import { AssertionError } from "node:assert";
|
||||
import * as assert from "node:assert";
|
||||
|
||||
function lateEmitter(): boolean {
|
||||
return new LateEmitter() instanceof EventEmitter && typeof LateMap === "function";
|
||||
}
|
||||
|
||||
function localHoist(): string {
|
||||
function inner(): string {
|
||||
return `${typeof LocalMap} ${LocalMap.name} ${new LocalMap<string, number>().size}`;
|
||||
}
|
||||
const LocalMap = Map;
|
||||
return inner();
|
||||
}
|
||||
|
||||
const Emitter = EventEmitter;
|
||||
const NsEmitter = events.EventEmitter;
|
||||
const Again = Emitter;
|
||||
const LateEmitter = events.EventEmitter;
|
||||
const LateMap = Map;
|
||||
|
||||
const e = new Emitter();
|
||||
e.on("ping", (n: number) => console.log("ping", n));
|
||||
e.emit("ping", 1);
|
||||
console.log("emitter", e instanceof Emitter, e instanceof NsEmitter, e instanceof Again);
|
||||
console.log("hoisted use", lateEmitter(), localHoist());
|
||||
|
||||
class Bus extends Again {
|
||||
send(text: string): void {
|
||||
this.emit("text", text);
|
||||
}
|
||||
}
|
||||
const bus = new Bus();
|
||||
bus.on("text", (text: string) => console.log("bus", text));
|
||||
bus.send("hello");
|
||||
console.log("subclass", bus instanceof Emitter, bus instanceof Bus);
|
||||
|
||||
const Pass = PassThrough;
|
||||
const R = Readable;
|
||||
const T = stream.Transform;
|
||||
const pass = new Pass();
|
||||
console.log("stream", pass instanceof Pass, pass instanceof R, pass instanceof T);
|
||||
console.log("readable", new R({ read() {} }) instanceof Pass, e instanceof R);
|
||||
|
||||
class Upper extends T {
|
||||
override _transform(
|
||||
chunk: Buffer,
|
||||
_encoding: BufferEncoding,
|
||||
callback: (error: Error | null, data?: string) => void,
|
||||
): void {
|
||||
callback(null, chunk.toString().toUpperCase());
|
||||
}
|
||||
}
|
||||
const upper = new Upper();
|
||||
upper.on("data", (chunk: Buffer) => console.log("upper", chunk.toString()));
|
||||
upper.end("quiet");
|
||||
|
||||
const TE = TypeError;
|
||||
const RE = RangeError;
|
||||
class LimitError extends RE {
|
||||
constructor(limit: number) {
|
||||
super(`over ${limit}`);
|
||||
this.name = "LimitError";
|
||||
}
|
||||
}
|
||||
try {
|
||||
throw new TE("bad input");
|
||||
} catch (err) {
|
||||
console.log("error", err instanceof TE, err instanceof RE, err instanceof Error, (err as Error).message);
|
||||
}
|
||||
try {
|
||||
throw new LimitError(3);
|
||||
} catch (err) {
|
||||
console.log("subclass error", err instanceof RE, err instanceof LimitError, (err as Error).message);
|
||||
}
|
||||
const AE = AssertionError;
|
||||
try {
|
||||
assert.strictEqual(1, 2);
|
||||
} catch (err) {
|
||||
console.log("assertion", err instanceof AE, err instanceof TE);
|
||||
}
|
||||
const viaNamespace = new assert.AssertionError({ message: "custom" });
|
||||
console.log("namespace error", viaNamespace.message, viaNamespace instanceof AE);
|
||||
|
||||
const M = Map;
|
||||
const S = Set;
|
||||
const m = new M<string, number>([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
]);
|
||||
m.set("c", 3);
|
||||
const s = new S([1, 2, 2, 3]);
|
||||
console.log("collections", m.get("b"), m.size, s.size, m instanceof M, s instanceof S, s instanceof M);
|
||||
const groups = M.groupBy([1, 2, 3, 4, 5], (n: number) => (n % 2 === 0 ? "even" : "odd"));
|
||||
console.log("groupBy", groups.get("even"), groups.get("odd"));
|
||||
|
||||
console.log("typeof", typeof Emitter, typeof R, typeof TE, typeof M, typeof EventEmitter, typeof Readable);
|
||||
console.log("name", Emitter.name, NsEmitter.name, Pass.name, T.name, TE.name, AE.name, M.name, S.name);
|
||||
console.log("direct name", EventEmitter.name, stream.Duplex.name, Error.name, RangeError.name, Map.name, Set.name);
|
||||
console.log("identity", Emitter === EventEmitter, Again === NsEmitter, T === Transform, R === stream.Readable);
|
||||
console.log("other class", (Pass as unknown) === (R as unknown), (M as unknown) !== (S as unknown), (TE as unknown) === (RE as unknown));
|
||||
@@ -0,0 +1,45 @@
|
||||
// Builtin classes through const aliases in a CommonJS file. A copy of a
|
||||
// name (or of a globalThis property) keeps its value, so it can be stored
|
||||
// and compared; construction, instanceof, typeof and .name resolve to the
|
||||
// class. Builtin module members construct through the alias.
|
||||
const { EventEmitter } = require("node:events");
|
||||
const events = require("node:events");
|
||||
const zlib = require("node:zlib");
|
||||
const { PassThrough } = require("node:stream");
|
||||
|
||||
const Emitter = EventEmitter;
|
||||
const NsEmitter = events.EventEmitter;
|
||||
const Gz = zlib.Gzip;
|
||||
const Gz2 = Gz;
|
||||
const M = Map;
|
||||
const GlobalSet = globalThis.Set;
|
||||
const Pass = PassThrough;
|
||||
const TE = TypeError;
|
||||
|
||||
const e = new Emitter();
|
||||
e.on("ping", (n) => console.log("ping", n));
|
||||
e.emit("ping", 2);
|
||||
console.log("emitter", e instanceof Emitter, e instanceof NsEmitter, e instanceof EventEmitter);
|
||||
|
||||
const m = new M();
|
||||
m.set("k", 1);
|
||||
const s = new GlobalSet(["a", "b", "a"]);
|
||||
console.log("collections", m.get("k"), s.size, m instanceof M, s instanceof GlobalSet, m instanceof GlobalSet);
|
||||
|
||||
const gz = new Gz2();
|
||||
console.log("zlib", gz instanceof Gz, gz instanceof zlib.Gunzip, Gz2 === zlib.Gzip);
|
||||
|
||||
const pass = new Pass();
|
||||
console.log("stream", pass instanceof Pass, pass instanceof EventEmitter);
|
||||
|
||||
try {
|
||||
throw new TE("typed");
|
||||
} catch (err) {
|
||||
console.log("error", err instanceof TE, err.message);
|
||||
}
|
||||
|
||||
const stored = [Emitter, M, GlobalSet, Pass];
|
||||
console.log("stored", stored.length, stored[0] === EventEmitter, stored[1] === Map, stored[2] === Set);
|
||||
console.log("typeof", typeof Emitter, typeof NsEmitter, typeof Gz2, typeof M, typeof GlobalSet, typeof TE);
|
||||
console.log("name", Emitter.name, NsEmitter.name, Gz2.name, M.name, GlobalSet.name, Pass.name, TE.name);
|
||||
console.log("identity", Emitter === NsEmitter, M === Map, GlobalSet === Set, M === GlobalSet);
|
||||
@@ -0,0 +1,8 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import { PassThrough } from "node:stream";
|
||||
|
||||
export const Emitter = EventEmitter;
|
||||
export const Pass = PassThrough;
|
||||
export const Dict = Map;
|
||||
export const Bag = Set;
|
||||
export const Failure = TypeError;
|
||||
@@ -0,0 +1,33 @@
|
||||
// Builtin class aliases exported from one module and used through named
|
||||
// and namespace imports: construction, instanceof, typeof, .name and ===
|
||||
// resolve to the builtin class in the importing module.
|
||||
import { EventEmitter } from "node:events";
|
||||
import { Emitter, Pass, Dict, Bag, Failure } from "./classes.ts";
|
||||
import * as classes from "./classes.ts";
|
||||
|
||||
const e = new Emitter();
|
||||
e.on("tick", (n: number) => console.log("tick", n));
|
||||
e.emit("tick", 7);
|
||||
console.log("emitter", e instanceof Emitter, e instanceof classes.Emitter, e instanceof EventEmitter);
|
||||
|
||||
const viaNs = new classes.Emitter();
|
||||
console.log("namespace emitter", viaNs instanceof Emitter, Emitter === classes.Emitter);
|
||||
|
||||
const pass = new classes.Pass();
|
||||
console.log("stream", pass instanceof Pass, pass instanceof classes.Emitter);
|
||||
|
||||
const dict = new Dict<string, number>([["k", 1]]);
|
||||
const viaNsDict = new classes.Dict<string, string>();
|
||||
viaNsDict.set("a", "b");
|
||||
const bag = new classes.Bag<number>([1, 1, 2]);
|
||||
console.log("collections", dict.get("k"), viaNsDict.get("a"), bag.size, dict instanceof classes.Dict, bag instanceof Bag);
|
||||
|
||||
try {
|
||||
throw new classes.Failure("wrong type");
|
||||
} catch (err) {
|
||||
console.log("error", err instanceof Failure, err instanceof classes.Failure, (err as Error).message);
|
||||
}
|
||||
|
||||
console.log("typeof", typeof Emitter, typeof classes.Pass, typeof Dict, typeof classes.Bag, typeof Failure);
|
||||
console.log("name", Emitter.name, classes.Pass.name, Dict.name, classes.Bag.name, Failure.name);
|
||||
console.log("identity", Dict === Map, classes.Bag === Set, Failure === TypeError, (Dict as unknown) === (Bag as unknown));
|
||||
@@ -0,0 +1,15 @@
|
||||
// A builtin class alias owns no runtime value in a TypeScript file:
|
||||
// construction, instanceof, typeof and .name compile, other uses report
|
||||
// at the use. A let binding of a class is not an alias.
|
||||
import { EventEmitter } from "node:events";
|
||||
|
||||
const Emitter = EventEmitter;
|
||||
const kinds = [Emitter];
|
||||
console.log(kinds.length);
|
||||
|
||||
const M = Map;
|
||||
const boxed: unknown = M;
|
||||
console.log(boxed !== undefined);
|
||||
|
||||
let Mutable = EventEmitter;
|
||||
console.log(typeof Mutable);
|
||||
@@ -18,8 +18,10 @@ class Unrelated {
|
||||
static tag = "unrelated";
|
||||
}
|
||||
|
||||
// Builtin classes as values: their construction is libCall-shaped.
|
||||
// Builtin classes as values: a const alias resolves at compile time, and
|
||||
// an array of builtin classes has no element type.
|
||||
const E = Error;
|
||||
const errorClasses = [E];
|
||||
|
||||
// Classes extending builtin bases as values (the error-message completion
|
||||
// rule has no thunk ABI).
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
builtin-class-alias-escape.ts:7:16 - error SC1090: a builtin class alias as an escaping value (use 'Emitter' with new, instanceof, typeof or .name) is not supported yet
|
||||
|
||||
6 | const Emitter = EventEmitter;
|
||||
7 | const kinds = [Emitter];
|
||||
| ^~~~~~~
|
||||
8 | console.log(kinds.length);
|
||||
|
||||
builtin-class-alias-escape.ts:11:24 - error SC1090: a builtin class alias as an escaping value (use 'M' with new, instanceof, typeof or .name) is not supported yet
|
||||
|
||||
10 | const M = Map;
|
||||
11 | const boxed: unknown = M;
|
||||
| ^
|
||||
12 | console.log(boxed !== undefined);
|
||||
|
||||
builtin-class-alias-escape.ts:14:15 - error SC2020: 'events.EventEmitter' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
13 |
|
||||
14 | let Mutable = EventEmitter;
|
||||
| ^~~~~~~~~~~~
|
||||
15 | console.log(typeof Mutable);
|
||||
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
@@ -1,52 +1,52 @@
|
||||
class-values.ts:22:7 - error SC2020: 'ErrorConstructor' is part of the standard library types but has no scriptc lowering yet
|
||||
class-values.ts:24:7 - error SC2009: values of type 'ErrorConstructor[]' cannot be compiled: the array shape is supported, but its element type 'ErrorConstructor' does not compile
|
||||
|
||||
21 | // Builtin classes as values: their construction is libCall-shaped.
|
||||
22 | const E = Error;
|
||||
| ^
|
||||
23 |
|
||||
23 | const E = Error;
|
||||
24 | const errorClasses = [E];
|
||||
| ^~~~~~~~~~~~
|
||||
25 |
|
||||
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
class-values.ts:22:11 - error SC2020: 'Error' is part of the standard library types but has no scriptc lowering yet
|
||||
class-values.ts:24:22 - error SC2009: values of type 'ErrorConstructor[]' cannot be compiled: the array shape is supported, but its element type 'ErrorConstructor' does not compile
|
||||
|
||||
21 | // Builtin classes as values: their construction is libCall-shaped.
|
||||
22 | const E = Error;
|
||||
| ^~~~~
|
||||
23 |
|
||||
23 | const E = Error;
|
||||
24 | const errorClasses = [E];
|
||||
| ^~~
|
||||
25 |
|
||||
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown')
|
||||
|
||||
class-values.ts:44:1 - error SC1090: assigning the static 'tag' through a class value (JS creates an OWN property on the runtime class — assign through the declaring class's name, which subclasses here) is not supported yet
|
||||
class-values.ts:46:1 - error SC1090: assigning the static 'tag' through a class value (JS creates an OWN property on the runtime class — assign through the declaring class's name, which subclasses here) is not supported yet
|
||||
|
||||
43 | let someClass: typeof Base = Base;
|
||||
44 | someClass.tag = "write";
|
||||
45 | let someClass: typeof Base = Base;
|
||||
46 | someClass.tag = "write";
|
||||
| ^~~~~~~~~~~~~
|
||||
45 |
|
||||
47 |
|
||||
|
||||
class-values.ts:48:13 - error SC1090: reading the static member 'tag' through a class value (a subclass of 'Base' redeclares it, so the runtime class decides which declaration answers) is not supported yet
|
||||
class-values.ts:50:13 - error SC1090: reading the static member 'tag' through a class value (a subclass of 'Base' redeclares it, so the runtime class decides which declaration answers) is not supported yet
|
||||
|
||||
47 | // runtime class decides which declaration answers.
|
||||
48 | console.log(someClass.tag);
|
||||
49 | // runtime class decides which declaration answers.
|
||||
50 | console.log(someClass.tag);
|
||||
| ^~~~~~~~~~~~~
|
||||
49 |
|
||||
51 |
|
||||
|
||||
class-values.ts:52:10 - error SC1090: function-local classes with statics, decorators, or generic members are not supported yet
|
||||
class-values.ts:54:10 - error SC1090: function-local classes with statics, decorators, or generic members are not supported yet
|
||||
|
||||
51 | function make(): unknown {
|
||||
52 | return class { static value = 1; };
|
||||
53 | function make(): unknown {
|
||||
54 | return class { static value = 1; };
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
53 | }
|
||||
55 | }
|
||||
|
||||
class-values.ts:59:18 - error SC1090: class expressions with static initializers or static blocks outside a whole-initializer position (their declaration-time code must run exactly where the expression evaluates — bind the class in its own top-level `const C = class …` statement) is not supported yet
|
||||
class-values.ts:61:18 - error SC1090: class expressions with static initializers or static blocks outside a whole-initializer position (their declaration-time code must run exactly where the expression evaluates — bind the class in its own top-level `const C = class …` statement) is not supported yet
|
||||
|
||||
58 | // evaluates.
|
||||
59 | const pair = [1, class WithStatic { static x = 1; }] as const;
|
||||
60 | // evaluates.
|
||||
61 | const pair = [1, class WithStatic { static x = 1; }] as const;
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
60 |
|
||||
62 |
|
||||
|
||||
class-values.ts:79:56 - error SC1090: receiver-dependent static methods through a polymorphic class value is not supported yet
|
||||
class-values.ts:81:56 - error SC1090: receiver-dependent static methods through a polymorphic class value is not supported yet
|
||||
|
||||
78 | class OtherThis extends UsesThis {}
|
||||
79 | function self(value: typeof UsesThis): string { return value.self(); }
|
||||
80 | class OtherThis extends UsesThis {}
|
||||
81 | function self(value: typeof UsesThis): string { return value.self(); }
|
||||
| ^~~~~~~~~~
|
||||
80 | console.log(self(OtherThis));
|
||||
82 | console.log(self(OtherThis));
|
||||
@@ -1,4 +1,4 @@
|
||||
zlib-class-alias-escape.ts:7:18 - error SC1090: a zlib class alias as an escaping value (use 'Gz' with new, instanceof, typeof or .name) is not supported yet
|
||||
zlib-class-alias-escape.ts:7:18 - error SC1090: a builtin class alias as an escaping value (use 'Gz' with new, instanceof, typeof or .name) is not supported yet
|
||||
|
||||
6 | const Gz = zlib.Gzip;
|
||||
7 | const classes = [Gz];
|
||||
|
||||
@@ -2190,6 +2190,9 @@
|
||||
"5973-large-closure-environment.ts": 4499,
|
||||
"5974-typeof-module-exports.cjs": 2000,
|
||||
"5975-zlib-class-values.ts": 2800,
|
||||
"5976-builtin-class-aliases.ts": 3100,
|
||||
"5977-builtin-class-aliases.cjs": 2950,
|
||||
"5978-builtin-class-alias-modules/main.ts": 2750,
|
||||
"5990-worker-uncloneable.mjs": 3283,
|
||||
"5991-worker-async-dispose.mjs": 5699,
|
||||
"5992-worker-thread-name-lifetime.mjs": 2687,
|
||||
@@ -5226,6 +5229,9 @@
|
||||
"5973-large-closure-environment.ts": 5145,
|
||||
"5974-typeof-module-exports.cjs": 2500,
|
||||
"5975-zlib-class-values.ts": 4200,
|
||||
"5976-builtin-class-aliases.ts": 5450,
|
||||
"5977-builtin-class-aliases.cjs": 4300,
|
||||
"5978-builtin-class-alias-modules/main.ts": 4450,
|
||||
"5990-worker-uncloneable.mjs": 22432,
|
||||
"5991-worker-async-dispose.mjs": 25768,
|
||||
"5992-worker-thread-name-lifetime.mjs": 28932,
|
||||
|
||||
Reference in New Issue
Block a user