mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
Construct builtins through stored constructors and fix function-local class names and dispatch
This commit is contained in:
@@ -3,4 +3,6 @@
|
||||
Plain objects, arrays, functions, class instances, and Date, RegExp, Map, Set, WeakMap, WeakSet, and Promise values inherit `constructor`, `valueOf`, `toString`, `hasOwnProperty`, and `propertyIsEnumerable` from `Object.prototype` as in Node, so `value.constructor === Object` and `value.valueOf === Object.prototype.valueOf` compare identities exactly.
|
||||
Named reads such as `value.valueOf` answer these inherited members; computed-key reads such as `value[key]` do not.
|
||||
`Object.prototype`'s own members read as values; its mutation and other reflection, such as `isPrototypeOf` and `__proto__`, are unsupported.
|
||||
The constructors `Object`, `Function`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, and `Promise` read as function values; through a stored value only `Object` can be called, and `instanceof` and `new` against them remain unsupported.
|
||||
The constructors `Object`, `Function`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, and `Promise` read as function values; through a stored value only `Object` can be called.
|
||||
`new` through a stored `Object`, `Date`, `Map`, `Set`, `WeakMap`, or `WeakSet` builds the same value as the `new` expression.
|
||||
`new` through a stored `RegExp` or `Promise`, `new` of a stored `Object` with a primitive argument, and `instanceof` against these constructors are unsupported.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- **Local class expressions:** Static fields and static blocks in TypeScript files, decorators, private brands, generic members, and references through the class's internal name are unsupported.
|
||||
- **Local class expressions:** Static fields and static blocks in TypeScript files, decorators, private brands, and generic members are unsupported.
|
||||
Static methods read the enclosing function's bindings and are inherited by subclasses.
|
||||
A named class expression's methods and static members can refer to the class by its name.
|
||||
Supported expressions preserve fresh constructor identity and shared captures across constructors, methods, accessors, and arrow fields.
|
||||
Local inheritance requires a compiled base class.
|
||||
Constructors retain identity, `typeof`, `name`, and `length` through `unknown`; cast back to their exact type before construction.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { dynUndefinedExpr, varRef } from "../../ir/build.js";
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import { BOOL, DYN, STRING, type IrExpr, type IrStmt } from "../../ir/ir.js";
|
||||
import { BOOL, DYN, STRING, type IrExpr, type IrLocal, type IrStmt } from "../../ir/ir.js";
|
||||
import { locOf } from "../program.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import { classValueRef, type ClassInfo } from "./lower-classes.js";
|
||||
@@ -15,9 +15,29 @@ export function hasRuntimeStatics(info: ClassInfo): boolean {
|
||||
|
||||
/** Constructor descriptors preserve inherited getter receivers and static
|
||||
* initialization order. Factory evaluations additionally own independent
|
||||
* fields and closures when the same factory is called repeatedly. */
|
||||
export function initializeRuntimeStatics(lowerer: Lowerer, info: ClassInfo, value: IrExpr): IrExpr {
|
||||
if (!info.runtimeStatics && !info.runtimePrototypeMembers) return value;
|
||||
* fields and closures when the same factory is called repeatedly.
|
||||
*
|
||||
* `binding` is the local that holds the class under its own name (a
|
||||
* function-local class declaration's binding, or the inner name of a
|
||||
* function-local class expression whose body names itself), declared
|
||||
* without a value. It is assigned before any static member is defined, so
|
||||
* static initializers and methods read the class through it. */
|
||||
export function initializeRuntimeStatics(
|
||||
lowerer: Lowerer,
|
||||
info: ClassInfo,
|
||||
value: IrExpr,
|
||||
binding?: IrLocal,
|
||||
): IrExpr {
|
||||
if (!info.runtimeStatics && !info.runtimePrototypeMembers) {
|
||||
if (!binding) return value;
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [{ kind: "assign", localId: binding.id, value, loc: value.loc }],
|
||||
result: varRef(binding.id, binding.type, value.loc),
|
||||
type: binding.type,
|
||||
loc: value.loc,
|
||||
};
|
||||
}
|
||||
const loc = value.loc;
|
||||
const local = lowerer.declareHiddenLocal("%staticClass", value.type);
|
||||
const receiver = varRef(local.id, value.type, loc);
|
||||
@@ -28,6 +48,16 @@ export function initializeRuntimeStatics(lowerer: Lowerer, info: ClassInfo, valu
|
||||
: dynUndefinedExpr(loc);
|
||||
const statements: IrStmt[] = [
|
||||
{ kind: "varDecl", localId: local.id, init: value, loc },
|
||||
...(binding
|
||||
? [
|
||||
{
|
||||
kind: "assign" as const,
|
||||
localId: binding.id,
|
||||
value: lowerer.coerceToExpected(receiver, binding.type),
|
||||
loc,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
kind: "exprStmt",
|
||||
expr: { kind: "libCall", fn: "dyn.classInherit", args: [boxed, base], type: DYN, loc },
|
||||
|
||||
@@ -69,6 +69,7 @@ import {
|
||||
builtinModuleConstOf,
|
||||
builtinModuleFnOf,
|
||||
builtinModulesArrayLit,
|
||||
identifierReadSymbol,
|
||||
stdlibGlobalNameOf,
|
||||
} from "../surfaces.js";
|
||||
import { builtinConstantsObject } from "../lower-builtin-values.js";
|
||||
@@ -753,7 +754,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc:
|
||||
return lowerGlobalValue(lowerer, expr);
|
||||
}
|
||||
{
|
||||
const sym = lowerer.checker.getSymbolAtLocation(expr);
|
||||
const sym = identifierReadSymbol(lowerer, expr);
|
||||
// The temporal global is typed (TS 7's lib slice) but Node 24 does
|
||||
// not define it: the read throws Node's ReferenceError.
|
||||
if (isTemporalGlobalRoot(lowerer, expr, sym)) {
|
||||
|
||||
@@ -3262,6 +3262,9 @@ export function lowerShorthandValue(
|
||||
}
|
||||
return lowerer.classValueRef(classInfo, propName);
|
||||
}
|
||||
// A standard-library global (`{ Date }`): the value the long form
|
||||
// `{ Date: Date }` reads.
|
||||
if (lowerer.isStdlibSymbol(resolved)) return lowerer.lowerExpr(propName);
|
||||
}
|
||||
lowerer.rejectUnresolvedSymbol(
|
||||
symbol
|
||||
|
||||
@@ -6034,6 +6034,31 @@ export function lowerClassExpressionInfo(
|
||||
return info;
|
||||
}
|
||||
|
||||
/** True when a named class expression's body refers to the class value by
|
||||
* its own name (`class Box { static of() { return new Box(); } }`); type
|
||||
* annotations naming it (`again(): Box`) do not count. */
|
||||
function classExpressionNamesItself(lowerer: Lowerer, expr: ts.ClassExpression): boolean {
|
||||
const name = expr.name;
|
||||
if (!name) return false;
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(name);
|
||||
if (!symbol) return false;
|
||||
let found = false;
|
||||
for (const member of expr.members.nodes) {
|
||||
ts.walkPreorder(member, (n) => {
|
||||
if (found || ts.isTypeNode(n)) return "skip";
|
||||
if (
|
||||
ts.isIdentifier(n) &&
|
||||
n.text === name.text &&
|
||||
lowerer.checker.getSymbolAtLocation(n) === symbol
|
||||
)
|
||||
found = true;
|
||||
return undefined;
|
||||
});
|
||||
if (found) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** `class {…}` in expression position: a class definition bound to no
|
||||
* statement — once the static side is a value, the expression IS the
|
||||
* definition plus a classRef over it. A DECORATED class expression
|
||||
@@ -6041,8 +6066,16 @@ export function lowerClassExpressionInfo(
|
||||
* mints a class at all: evaluating the expression IS the
|
||||
* ReferenceError, so it lowers to exactly that read — every evaluation
|
||||
* throws identically, which is why the once-evaluated restriction and
|
||||
* the member fences don't apply. */
|
||||
export function lowerClassExpression(lowerer: Lowerer, expr: ts.ClassLikeDeclaration): IrExpr {
|
||||
* the member fences don't apply.
|
||||
*
|
||||
* `binding` is the local of a function-local class declaration's name,
|
||||
* declared without a value; the class value assigns it before the class's
|
||||
* static members are defined (see initializeRuntimeStatics). */
|
||||
export function lowerClassExpression(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.ClassLikeDeclaration,
|
||||
binding?: IrLocal,
|
||||
): IrExpr {
|
||||
if (
|
||||
decoratorNodesOf(expr).length > 0 ||
|
||||
expr.members.nodes.some((m) => decoratorNodesOf(m).length > 0)
|
||||
@@ -6058,6 +6091,41 @@ export function lowerClassExpression(lowerer: Lowerer, expr: ts.ClassLikeDeclara
|
||||
}
|
||||
}
|
||||
const info = lowerClassExpressionInfo(lowerer, expr);
|
||||
// A function-local class expression's own name is a binding of its body:
|
||||
// declared before the members lower, so their references to the name
|
||||
// resolve to it. The class value captures it before it is assigned, as
|
||||
// it captures a class declaration's binding.
|
||||
if (
|
||||
!binding &&
|
||||
info.localClass &&
|
||||
ts.isClassExpression(expr) &&
|
||||
expr.name &&
|
||||
classExpressionNamesItself(lowerer, expr)
|
||||
) {
|
||||
const own = lowerer.declareLocal(
|
||||
expr.name,
|
||||
expr.name.text,
|
||||
{ kind: "classval", className: info.def.name },
|
||||
true,
|
||||
);
|
||||
const value = lowerClassValue(lowerer, expr, info, own);
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [{ kind: "varDecl", localId: own.id, init: null, loc: value.loc }],
|
||||
result: value,
|
||||
type: value.type,
|
||||
loc: value.loc,
|
||||
};
|
||||
}
|
||||
return lowerClassValue(lowerer, expr, info, binding);
|
||||
}
|
||||
|
||||
function lowerClassValue(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.ClassLikeDeclaration,
|
||||
info: ClassInfo,
|
||||
binding: IrLocal | undefined,
|
||||
): IrExpr {
|
||||
if (
|
||||
!info.localClass &&
|
||||
!info.expressionInitializationEmitted &&
|
||||
@@ -6104,7 +6172,7 @@ export function lowerClassExpression(lowerer: Lowerer, expr: ts.ClassLikeDeclara
|
||||
const clsRef: IrExpr = { kind: "varRef", localId: clsLocal.id, type: value.type, loc };
|
||||
const boxed: IrExpr = { kind: "dynFrom", value: clsRef, type: DYN, loc };
|
||||
info.localPrototypeData = true;
|
||||
return initializeRuntimeStatics(lowerer, info, {
|
||||
const built: IrExpr = {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
{ kind: "varDecl", localId: baseLocal.id, init: base, loc },
|
||||
@@ -6137,7 +6205,8 @@ export function lowerClassExpression(lowerer: Lowerer, expr: ts.ClassLikeDeclara
|
||||
result: clsRef,
|
||||
type: value.type,
|
||||
loc,
|
||||
});
|
||||
};
|
||||
return initializeRuntimeStatics(lowerer, info, built, binding);
|
||||
}
|
||||
if (info.localClass?.baseExpression && info.base && value.kind === "classRef") {
|
||||
const loc = locOf(expr);
|
||||
@@ -6152,7 +6221,7 @@ export function lowerClassExpression(lowerer: Lowerer, expr: ts.ClassLikeDeclara
|
||||
const clsRef: IrExpr = { kind: "varRef", localId: clsLocal.id, type: value.type, loc };
|
||||
const basePrototype = classPrototypeData(lowerer, info.base, loc, baseRef)!;
|
||||
info.localPrototypeData = true;
|
||||
return initializeRuntimeStatics(lowerer, info, {
|
||||
const built: IrExpr = {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
{ kind: "varDecl", localId: baseLocal.id, init: base, loc },
|
||||
@@ -6191,13 +6260,15 @@ export function lowerClassExpression(lowerer: Lowerer, expr: ts.ClassLikeDeclara
|
||||
result: clsRef,
|
||||
type: value.type,
|
||||
loc,
|
||||
});
|
||||
};
|
||||
return initializeRuntimeStatics(lowerer, info, built, binding);
|
||||
}
|
||||
if (!info.localClass) return value;
|
||||
const classValue = initializeRuntimeStatics(
|
||||
lowerer,
|
||||
info,
|
||||
value.kind === "classRef" ? { ...value, captures: info.localClass.captureSources } : value,
|
||||
binding,
|
||||
);
|
||||
// The base resolves statically here, but Node still evaluates the
|
||||
// heritage expression each time the class definition runs, first.
|
||||
@@ -7124,12 +7195,102 @@ export function inHierarchy(lowerer: Lowerer, info: ClassInfo): boolean {
|
||||
* dynamically. Abstract re-declarations don't count (they carry no
|
||||
* implementation; the concrete ones below them do, via the recursion). */
|
||||
export function overrideBelow(lowerer: Lowerer, info: ClassInfo, name: string): boolean {
|
||||
if (info.decl !== null && localOverrideBelow(lowerer, info.decl, name)) return true;
|
||||
return info.subclasses.some((s) => {
|
||||
const m = s.methods.get(name);
|
||||
return (m !== undefined && m.abstract !== true) || lowerer.overrideBelow(s, name);
|
||||
});
|
||||
}
|
||||
|
||||
/** The class node an `extends` clause names: a class declaration, or the
|
||||
* class expression a variable is initialized with. */
|
||||
function extendedClassNode(lowerer: Lowerer, decl: ts.ClassLikeDeclaration): ts.Node | null {
|
||||
const heritage = decl.heritageClauses?.nodes.find(
|
||||
(clause) => clause.token === ts.SyntaxKind.ExtendsKeyword,
|
||||
)?.types?.nodes[0]?.expression;
|
||||
if (!heritage || !ts.isIdentifier(heritage)) return null;
|
||||
let symbol = lowerer.checker.getSymbolAtLocation(heritage);
|
||||
if (symbol && symbol.flags & ts.SymbolFlags.Alias)
|
||||
symbol = lowerer.checker.getAliasedSymbol(symbol);
|
||||
const target = symbol?.valueDeclaration;
|
||||
if (!target) return null;
|
||||
if (ts.isClassDeclaration(target) || ts.isClassExpression(target)) return target;
|
||||
if (ts.isVariableDeclaration(target) && target.initializer) {
|
||||
let init: ts.Expression = target.initializer;
|
||||
while (ts.isParenthesizedExpression(init)) init = init.expression;
|
||||
if (ts.isClassExpression(init)) return init;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** The function-local classes that extend the class node `base`. Function
|
||||
* bodies lower on demand, after code that may already call methods of a
|
||||
* class their local classes extend, so such a subclass is not yet in its
|
||||
* base's `subclasses` when those calls lower. */
|
||||
function localSubclassesOf(lowerer: Lowerer, base: ts.Node): readonly ts.ClassLikeDeclaration[] {
|
||||
let index = lowerer.localSubclassIndex;
|
||||
if (index === null) {
|
||||
index = new Map();
|
||||
const files = lowerer.moduleOrder.length > 0 ? lowerer.moduleOrder : [lowerer.entry];
|
||||
for (const sf of files) {
|
||||
if (!sf.text.includes("extends")) continue;
|
||||
ts.walkPreorder(sf, (node) => {
|
||||
if (
|
||||
(ts.isClassDeclaration(node) || ts.isClassExpression(node)) &&
|
||||
node.heritageClauses !== undefined &&
|
||||
functionLocalClass(node)
|
||||
) {
|
||||
const extended = extendedClassNode(lowerer, node);
|
||||
if (extended) {
|
||||
const list = index!.get(extended);
|
||||
if (list) list.push(node);
|
||||
else index!.set(extended, [node]);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
lowerer.localSubclassIndex = index;
|
||||
}
|
||||
return index.get(base) ?? [];
|
||||
}
|
||||
|
||||
/** True when a function-local class below the class node `base` declares
|
||||
* a concrete instance method or accessor `name` (accessors are keyed
|
||||
* `get:x` and `set:x`). A call on `base`'s class then dispatches on the
|
||||
* receiver; devirtualization turns it back into a direct call when no such
|
||||
* override is reachable. */
|
||||
function localOverrideBelow(
|
||||
lowerer: Lowerer,
|
||||
base: ts.Node,
|
||||
name: string,
|
||||
visited?: Set<ts.Node>,
|
||||
): boolean {
|
||||
const subclasses = localSubclassesOf(lowerer, base);
|
||||
if (subclasses.length === 0) return false;
|
||||
visited ??= new Set();
|
||||
if (visited.has(base)) return false;
|
||||
visited.add(base);
|
||||
const accessor = name.startsWith("get:") ? "get" : name.startsWith("set:") ? "set" : null;
|
||||
const member = accessor ? name.slice(4) : name;
|
||||
return subclasses.some(
|
||||
(sub) =>
|
||||
sub.members.nodes.some(
|
||||
(m) =>
|
||||
(accessor === null
|
||||
? ts.isMethodDeclaration(m)
|
||||
: accessor === "get"
|
||||
? ts.isGetAccessorDeclaration(m)
|
||||
: ts.isSetAccessorDeclaration(m)) &&
|
||||
m.body !== undefined &&
|
||||
m.name !== undefined &&
|
||||
(ts.isIdentifier(m.name) || ts.isStringLiteral(m.name)) &&
|
||||
m.name.text === member &&
|
||||
!ts.getModifiers(m)?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword),
|
||||
) || localOverrideBelow(lowerer, sub, name, visited),
|
||||
);
|
||||
}
|
||||
|
||||
/** The nearest GENERIC-method declaration of `name` at/above `info` —
|
||||
* findMethodOn's twin over the genericMethods tables. */
|
||||
export function findGenericMethodOn(
|
||||
|
||||
@@ -1523,6 +1523,14 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt
|
||||
const info = lowerer.lowerClassExpressionInfo(stmt);
|
||||
const type: IrType = { kind: "classval", className: info.def.name };
|
||||
const local = lowerer.declareLocal(stmt.name, stmt.name.text, type, true);
|
||||
// Static members of a function-local class run while the class value is
|
||||
// built and may read the class by name: the class value assigns the
|
||||
// binding before them.
|
||||
if (info.localClass && (info.runtimeStatics || info.runtimePrototypeMembers))
|
||||
return [
|
||||
{ kind: "varDecl", localId: local.id, init: null, loc: locOf(stmt) },
|
||||
{ kind: "exprStmt", expr: lowerer.lowerClassExpression(stmt, local), loc: locOf(stmt) },
|
||||
];
|
||||
const init = lowerer.lowerClassExpression(stmt);
|
||||
return { kind: "varDecl", localId: local.id, init, loc: locOf(stmt) };
|
||||
}
|
||||
|
||||
@@ -3303,6 +3303,9 @@ export class Lowerer {
|
||||
* the class and constructed-class counts, it tells caches over the class
|
||||
* graph when the graph changed. */
|
||||
subclassLinks = 0;
|
||||
/** Function-local classes by the class node they extend, built on first
|
||||
* use (localSubclassesOf in lower-classes.ts). */
|
||||
localSubclassIndex: Map<ts.Node, ts.ClassLikeDeclaration[]> | null = null;
|
||||
/** Set while a subclass constructor's super() call notes its base
|
||||
* constructor edge: that edge does not instantiate the base exactly. */
|
||||
private superConstructorEdge = false;
|
||||
@@ -11377,8 +11380,8 @@ export class Lowerer {
|
||||
}
|
||||
}
|
||||
|
||||
lowerClassExpression(expr: ts.ClassLikeDeclaration): IrExpr {
|
||||
return lowerClassExpression(this, expr);
|
||||
lowerClassExpression(expr: ts.ClassLikeDeclaration, binding?: IrLocal): IrExpr {
|
||||
return lowerClassExpression(this, expr, binding);
|
||||
}
|
||||
|
||||
/* ── the class graph (single inheritance) ─────────────────────────── */
|
||||
|
||||
@@ -3366,6 +3366,17 @@ export function isStdlibSymbol(lowerer: Lowerer, symbol: ts.Symbol | undefined):
|
||||
* tamper-guard prologue) registered in stdlibGlobalAliases;
|
||||
* - a const binding of the Map or Set constructor (`const M = Map`,
|
||||
* builtinClassAliasOf). */
|
||||
/** The symbol an identifier reads. A shorthand property's name (`{ Date }`)
|
||||
* reads the binding of the same name, not the property it declares. */
|
||||
export function identifierReadSymbol(lowerer: Lowerer, expr: ts.Identifier): ts.Symbol | undefined {
|
||||
if (expr.parent && ts.isShorthandPropertyAssignment(expr.parent) && expr.parent.name === expr)
|
||||
return (
|
||||
lowerer.checker.getShorthandAssignmentValueSymbol(expr.parent) ??
|
||||
lowerer.checker.getSymbolAtLocation(expr)
|
||||
);
|
||||
return lowerer.checker.getSymbolAtLocation(expr);
|
||||
}
|
||||
|
||||
export function stdlibGlobalNameOf(lowerer: Lowerer, expr: ts.Expression): string | null {
|
||||
if (ts.isParenthesizedExpression(expr)) return stdlibGlobalNameOf(lowerer, expr.expression);
|
||||
// The global-object detection chain (`typeof global < "u" ? global :
|
||||
@@ -3377,7 +3388,7 @@ export function stdlibGlobalNameOf(lowerer: Lowerer, expr: ts.Expression): strin
|
||||
return stdlibGlobalNameOf(lowerer, taken ? expr.whenTrue : expr.whenFalse);
|
||||
}
|
||||
if (ts.isIdentifier(expr)) {
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(expr);
|
||||
const symbol = identifierReadSymbol(lowerer, expr);
|
||||
// `globalThis` itself: a reserved intrinsic — tsc rejects user
|
||||
// global bindings, but local parameters can shadow it. Its special
|
||||
// symbol has no ordinary declarations for the provenance check.
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6913-builtin-constructor-shorthand.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6914-dynamic-builtin-construct.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6915-dynamic-builtin-construct.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6916-local-class-static-self-reference.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6917-local-class-expression-self-reference.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6918-local-class-expression-self-reference.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6919-local-class-inspect-name.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6921-local-subclass-override-dispatch.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6922-local-subclass-override-dispatch.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -659,6 +659,9 @@ ScrDyn *scr_dyn_native_set_new(const ScrDyn *seed);
|
||||
ScrDyn *scr_dyn_native_set(ScrMap *value); /* borrowed backing, +1 box */
|
||||
bool scr_dyn_native_set_is(const ScrDyn *value);
|
||||
ScrDyn *scr_dyn_native_map(ScrMap *value); /* borrowed backing, +1 box */
|
||||
/* `new Map(entries)` over a checked-dynamic seed: +1, or NULL with the
|
||||
* exception pending. */
|
||||
ScrDyn *scr_dyn_native_map_new(const ScrDyn *seed);
|
||||
bool scr_dyn_native_map_is(const ScrDyn *value);
|
||||
void scr_regex_reset_last_index(ScrRegex *re, double index);
|
||||
ScrRegex *scr_regex_new_checked(const ScrDyn *pattern, const ScrDyn *flags);
|
||||
@@ -879,6 +882,12 @@ bool scr_bytes_instanceof(const ScrDyn *value, const ScrDyn *callee);
|
||||
bool scr_dyn_data_view_is(const ScrDyn *value);
|
||||
ScrDyn *scr_dyn_construct(const ScrDyn *callee, const ScrDyn *args, const ScrStr *what);
|
||||
ScrDyn *scr_bytes_construct(const ScrDyn *callee, const ScrDyn *args, const ScrStr *what);
|
||||
/* Installed by scr_dyn_invoke.c when it interns a builtin constructor value
|
||||
* (scr_dyn_global_constructor): false when `callee` is not one of them;
|
||||
* otherwise true with `new callee(...args)` in *result (+1, or NULL with
|
||||
* the exception pending). */
|
||||
extern SCR_TL bool (*scr_dyn_global_construct_fn)(const ScrDyn *callee, const ScrDyn *args,
|
||||
ScrDyn **result);
|
||||
ScrDyn *scr_dyn_bytes_key_get(const ScrDyn *value, const ScrStr *key);
|
||||
/* The Buffer-flavored twin (stream chunks): string coercion/toString
|
||||
* decode utf8 instead of joining elements. */
|
||||
|
||||
@@ -1854,6 +1854,8 @@ static const char dyn_s29[] SCR_DYN_COMPAT_STR = "Date";
|
||||
static const char dyn_s30[] SCR_DYN_COMPAT_STR = "Map";
|
||||
static const char dyn_s31[] SCR_DYN_COMPAT_STR = "Set";
|
||||
static const char dyn_s32[] SCR_DYN_COMPAT_STR = "";
|
||||
static const char dyn_s33[] SCR_DYN_COMPAT_STR = "constructing %s%s through a stored value is not supported yet";
|
||||
static const char dyn_s34[] SCR_DYN_COMPAT_STR = " from a primitive";
|
||||
|
||||
static const char *const dyn_global_ctor_names[] = {
|
||||
dyn_s26, dyn_s29, dyn_s25, dyn_s30, dyn_s31, dyn_s21, dyn_s22, dyn_s24,
|
||||
@@ -1887,12 +1889,42 @@ static ScrDyn *dyn_global_ctor_call(ScrClosure *closure, ScrDyn *const *args, si
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* `new C(...args)` through a stored Object, Date, Map, Set, WeakMap or
|
||||
* WeakSet builds what the `new` expression builds. RegExp, Promise, and
|
||||
* Object over a primitive (a wrapper object) are fenced. */
|
||||
static bool dyn_global_construct(const ScrDyn *callee, const ScrDyn *args, ScrDyn **result) {
|
||||
if (callee->kind != SCR_DYN_FUNC || callee->v.fn.thunk != dyn_global_ctor_call) return false;
|
||||
const char *name = callee->v.fn.clo->fn;
|
||||
ScrDyn *const *items = args->v.arr.items;
|
||||
size_t argc = args->v.arr.len;
|
||||
const ScrDyn *first = argc ? items[0] : scr_dyn_undefined();
|
||||
bool primitive = first->kind == SCR_DYN_NUM || first->kind == SCR_DYN_STR || first->kind == SCR_DYN_BOOL ||
|
||||
first->kind == SCR_DYN_BIGINT || first->kind == SCR_DYN_SYMBOL;
|
||||
if (strcmp(name, dyn_s26) == 0 && !primitive) *result = dyn_global_ctor_call(callee->v.fn.clo, items, argc);
|
||||
else if (strcmp(name, dyn_s29) == 0)
|
||||
*result = argc == 0 ? scr_dyn_native_date_new(args)
|
||||
: argc == 1 ? scr_dyn_native_date_new_value(args)
|
||||
: scr_dyn_native_date_new_fields(args);
|
||||
else if (strcmp(name, dyn_s30) == 0) *result = scr_dyn_native_map_new(first);
|
||||
else if (strcmp(name, dyn_s31) == 0) *result = scr_dyn_native_set_new(first);
|
||||
else if (strcmp(name, dyn_s22) == 0) *result = scr_weak_map_new((ScrDyn *)first);
|
||||
else if (strcmp(name, dyn_s24) == 0) *result = scr_weak_set_new((ScrDyn *)first);
|
||||
else {
|
||||
char message[128];
|
||||
int length = snprintf(message, sizeof message, dyn_s33, name, strcmp(name, dyn_s26) == 0 ? dyn_s34 : dyn_s32);
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)length, dyn_s27);
|
||||
*result = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static ScrDyn *dyn_global_ctor(const char *name) {
|
||||
for (size_t i = 0; i < DYN_GLOBAL_CTORS; i++) {
|
||||
if (strcmp(dyn_global_ctor_names[i], name) != 0) continue;
|
||||
if (!dyn_global_ctors_registered) {
|
||||
dyn_global_ctors_registered = true;
|
||||
scr_atexit(dyn_global_ctors_cleanup);
|
||||
scr_dyn_global_construct_fn = dyn_global_construct;
|
||||
}
|
||||
if (!dyn_global_ctors[i]) {
|
||||
/* Each constructor's own length: Object 1, Date 7, RegExp 2, Promise 1,
|
||||
|
||||
@@ -4017,6 +4017,8 @@ bool scr_bytes_instanceof(const ScrDyn *value, const ScrDyn *callee) {
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC1090");
|
||||
return false;
|
||||
}
|
||||
SCR_TL bool (*scr_dyn_global_construct_fn)(const ScrDyn *callee, const ScrDyn *args, ScrDyn **result) = NULL;
|
||||
|
||||
ScrDyn *scr_bytes_construct(const ScrDyn *callee, const ScrDyn *args, const ScrStr *what) {
|
||||
if (callee->kind == SCR_DYN_FUNC && scr_array_ctor && callee->v.fn.clo == scr_array_ctor->v.fn.clo)
|
||||
return scr_array_ctor_call(callee->v.fn.clo, args->v.arr.items, args->v.arr.len);
|
||||
@@ -4033,6 +4035,9 @@ ScrDyn *scr_bytes_construct(const ScrDyn *callee, const ScrDyn *args, const ScrS
|
||||
scr_bytes_release(bytes);
|
||||
return result;
|
||||
}
|
||||
ScrDyn *constructed = NULL;
|
||||
if (scr_dyn_global_construct_fn && scr_dyn_global_construct_fn(callee, args, &constructed))
|
||||
return constructed;
|
||||
ScrJsonBuf message;
|
||||
scr_jb_init(&message);
|
||||
scr_jb_write(&message, what->data, what->len);
|
||||
@@ -4180,6 +4185,13 @@ ScrDyn *scr_dyn_class_prototype(ScrDyn *value, ScrDyn *base) {
|
||||
if (!cls->prototype_data) {
|
||||
ScrDyn *data = base->kind == SCR_DYN_UNDEF ? scr_dyn_new_obj() : scr_dyn_obj_create(base);
|
||||
if (!data) return NULL;
|
||||
/* util.inspect prints an instance with its class's name. */
|
||||
const char *name = value->v.fn.name;
|
||||
if (name && name[0]) {
|
||||
ScrStr *class_name = scr_str_new(name, strlen(name));
|
||||
scr_dyn_name_class_prototype(data, class_name);
|
||||
scr_str_release(class_name);
|
||||
}
|
||||
cls->prototype_data = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v);
|
||||
scr_box_set_ref(cls->prototype_data, data);
|
||||
}
|
||||
@@ -14939,6 +14951,27 @@ ScrDyn *scr_dyn_native_map(ScrMap *value) {
|
||||
return scr_dyn_new_handle(value, SCR_DYNH_MAP);
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_native_map_new(const ScrDyn *seed) {
|
||||
ScrDyn *entries = scr_dyn_map_seed_entries(seed);
|
||||
if (!entries) return NULL;
|
||||
ScrMap *map = scr_map_new(SCR_MAP_KEY_DYN, SCR_MAP_VAL_REF, &scr_map_dyn_map_ops);
|
||||
for (size_t i = 0; i < entries->v.arr.len; i++) {
|
||||
ScrDyn *entry = scr_dyn_map_seed_entry(entries->v.arr.items[i]);
|
||||
if (!entry) break;
|
||||
ScrDyn *key = scr_dyn_entry_read(entry, 0);
|
||||
ScrDyn *value = scr_exc_pending() ? NULL : scr_dyn_entry_read(entry, 1);
|
||||
scr_dyn_release(entry);
|
||||
if (value) scr_map_dyn_set(map, key, value, false);
|
||||
scr_dyn_release(key);
|
||||
scr_dyn_release(value);
|
||||
if (scr_exc_pending()) break;
|
||||
}
|
||||
scr_dyn_release(entries);
|
||||
ScrDyn *result = scr_exc_pending() ? NULL : scr_dyn_native_map(map);
|
||||
scr_map_release(map);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool scr_dyn_native_map_is(const ScrDyn *value) {
|
||||
return value && value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_MAP;
|
||||
}
|
||||
|
||||
@@ -1527,7 +1527,7 @@ ScrMap *scr_map_clone(const ScrMap *source, bool keys_only, const ScrMapOps *ops
|
||||
const ScrMapOps scr_map_dyn_set_ops = {
|
||||
scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v, NULL, NULL, NULL, NULL
|
||||
};
|
||||
static const ScrMapOps scr_map_dyn_map_ops = {
|
||||
const ScrMapOps scr_map_dyn_map_ops = {
|
||||
scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v,
|
||||
scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v, NULL
|
||||
};
|
||||
|
||||
@@ -214,8 +214,10 @@ void *scr_map_get_span_ref(const ScrMap *m, const char *bytes, size_t length);
|
||||
* adapters); NULL keeps the source's descriptor. */
|
||||
ScrMap *scr_map_clone(const ScrMap *source, bool keys_only, const ScrMapOps *ops);
|
||||
ScrMap *scr_map_clone_dyn(const ScrMap *source, bool keys_only);
|
||||
/* The descriptor of a Set of checked values (SCR_MAP_KEY_DYN keys). */
|
||||
/* The descriptors of a Set and a Map of checked values (SCR_MAP_KEY_DYN
|
||||
* keys; the Map's values are checked values too). */
|
||||
extern const ScrMapOps scr_map_dyn_set_ops;
|
||||
extern const ScrMapOps scr_map_dyn_map_ops;
|
||||
void scr_map_values_into(ScrMap *set, const ScrMap *source, bool checked);
|
||||
|
||||
void scr_map_dyn_attach(ScrMap *map, const ScrMapDynOps *ops);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Builtin constructors in shorthand properties are the constructors themselves.
|
||||
const direct: Record<string, unknown> = { Date: Date, Map: Map, Set: Set };
|
||||
const shorthand: Record<string, unknown> = { Date, Map, Set, Object, Promise, WeakMap };
|
||||
console.log(shorthand.Date === Date, shorthand.Map === direct.Map, shorthand.Set === direct.Set);
|
||||
console.log(shorthand.Object === Object, shorthand.Promise === Promise, shorthand.WeakMap === WeakMap);
|
||||
console.log(typeof shorthand.Date, typeof shorthand.Map);
|
||||
function pick(table: Record<string, unknown>, name: string): unknown {
|
||||
return table[name];
|
||||
}
|
||||
console.log(pick({ Map, Set }, "Set") === Set, pick({ Date }, "Date") === pick(direct, "Date"));
|
||||
@@ -0,0 +1,54 @@
|
||||
// `new` through values that hold builtin constructors.
|
||||
const ctors: Record<string, unknown> = { Date, Map, Set, Object, WeakMap, WeakSet };
|
||||
function construct(name: string, ...args: unknown[]): unknown {
|
||||
const C = ctors[name] as any;
|
||||
return new C(...args);
|
||||
}
|
||||
function make(C: any, ...args: unknown[]): unknown {
|
||||
return new C(...args);
|
||||
}
|
||||
const tag = (value: unknown) => Object.prototype.toString.call(value);
|
||||
|
||||
const d = construct("Date", 0) as Date;
|
||||
console.log(tag(d), d.getTime(), d.toISOString());
|
||||
const d2 = make(Date, 2020, 1, 3, 4, 5, 6, 7) as Date;
|
||||
console.log(d2.getFullYear(), d2.getMonth(), d2.getDate(), d2.getHours(), d2.getMilliseconds());
|
||||
const d3 = make(Date, "2001-02-03T04:05:06Z") as Date;
|
||||
const d4 = make(Date, d3) as Date;
|
||||
console.log(d3.getTime(), d4.getTime());
|
||||
console.log(typeof make(Date), Number.isNaN((make(Date, "nope") as Date).getTime()));
|
||||
|
||||
const m = construct("Map", [["a", 1], ["b", 2]]) as Map<unknown, unknown>;
|
||||
console.log(tag(m), m.size, m.get("b"), [...m.keys()].join());
|
||||
const m2 = make(Map, m) as Map<unknown, unknown>;
|
||||
m2.set("c", 3);
|
||||
console.log(m.size, m2.size, (make(Map) as Map<unknown, unknown>).size, (make(Map, null) as Map<unknown, unknown>).size);
|
||||
|
||||
const s = construct("Set", [1, 2, 2, 3]) as Set<unknown>;
|
||||
console.log(tag(s), s.size, s.has(3), (make(Set, "abca") as Set<unknown>).size);
|
||||
|
||||
const o = construct("Object") as Record<string, unknown>;
|
||||
o.x = 1;
|
||||
const same = { y: 2 };
|
||||
console.log(tag(o), JSON.stringify(o), make(Object, same) === same, tag(make(Object, null)));
|
||||
|
||||
const key = {};
|
||||
const w = construct("WeakMap", [[key, "v"]]) as WeakMap<object, unknown>;
|
||||
console.log(tag(w), w.get(key), w.has({}));
|
||||
const ws = make(WeakSet, [key]) as WeakSet<object>;
|
||||
console.log(tag(ws), ws.has(key));
|
||||
|
||||
try {
|
||||
make(Map, [1]);
|
||||
} catch (e) {
|
||||
console.log((e as Error).name, (e as Error).message);
|
||||
}
|
||||
try {
|
||||
make(WeakMap, [[1, 2]]);
|
||||
} catch (e) {
|
||||
console.log((e as Error).name, (e as Error).message);
|
||||
}
|
||||
|
||||
const DateSlot: new () => unknown = Date;
|
||||
const MapSlot: new (entries: [string, number][]) => unknown = Map;
|
||||
console.log(tag(new DateSlot()), (new MapSlot([["k", 1]]) as Map<unknown, unknown>).get("k"));
|
||||
@@ -0,0 +1,16 @@
|
||||
// `new` through stored builtin constructors in JavaScript.
|
||||
const table = [Date, Map, Set, Object, WeakMap, WeakSet];
|
||||
for (const C of table) {
|
||||
const value = new C();
|
||||
console.log(C.name, typeof value, Object.prototype.toString.call(value), value.constructor === C);
|
||||
}
|
||||
const Ctor = table[0];
|
||||
console.log(new Ctor(86400000).toISOString(), new Ctor(2024, 0, 31).getDate());
|
||||
const MapCtor = table[1];
|
||||
const map = new MapCtor(Object.entries({ a: 1, b: 2 }));
|
||||
map.set("c", 3);
|
||||
console.log(map.size, map.get("a"), [...map.entries()].length);
|
||||
const SetCtor = table[2];
|
||||
console.log(new SetCtor([...map.keys()]).size, new SetCtor([NaN, NaN, 0, -0]).size);
|
||||
const named = { Date, Map, Set };
|
||||
console.log(named.Date === Date, named.Map === table[1], named.Set === table[2]);
|
||||
@@ -0,0 +1,52 @@
|
||||
// Static fields, static blocks and static methods of function-local classes
|
||||
// that read the class by its name while the class is being defined.
|
||||
function registry(prefix) {
|
||||
class Registry {
|
||||
static label = Registry.name + ":" + prefix;
|
||||
static instances = [];
|
||||
static {
|
||||
Registry.ready = Registry.instances.length === 0;
|
||||
}
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
Registry.instances.push(this);
|
||||
}
|
||||
static create(id) {
|
||||
return new Registry(id);
|
||||
}
|
||||
}
|
||||
return Registry;
|
||||
}
|
||||
const R = registry("main");
|
||||
R.create(1);
|
||||
R.create(2);
|
||||
console.log(R.label, R.ready, R.instances.map((r) => r.id));
|
||||
const R2 = registry("other");
|
||||
console.log(R2.label, R2.instances.length, R2 === R, R.instances.length);
|
||||
|
||||
function counter(step) {
|
||||
class Counter {
|
||||
static zero = new Counter(0);
|
||||
static one = Counter.zero.next();
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
next() {
|
||||
return new Counter(this.value + step);
|
||||
}
|
||||
}
|
||||
return Counter;
|
||||
}
|
||||
const C = counter(5);
|
||||
console.log(C.zero.value, C.one.value, C.one.next().value, C.one instanceof C);
|
||||
|
||||
function described() {
|
||||
class Shape {
|
||||
static kinds = ["circle", "square"];
|
||||
static describe() {
|
||||
return Shape.kinds.map((kind) => kind + "@" + Shape.name).join(",");
|
||||
}
|
||||
}
|
||||
return Shape.describe();
|
||||
}
|
||||
console.log(described());
|
||||
@@ -0,0 +1,41 @@
|
||||
// Function-local class expressions that refer to themselves by their own name.
|
||||
function counter(start: number) {
|
||||
const C = class Counter {
|
||||
value: number;
|
||||
constructor(value: number) { this.value = value; }
|
||||
next(): Counter { return new Counter(this.value + 1); }
|
||||
static first(): Counter { return new Counter(start); }
|
||||
static isSelf(value: unknown): boolean { return value === Counter; }
|
||||
};
|
||||
return C;
|
||||
}
|
||||
const Counter1 = counter(10);
|
||||
console.log(Counter1.first().next().next().value);
|
||||
console.log(Counter1.isSelf(Counter1), Counter1.isSelf(counter(1)), Counter1.name);
|
||||
|
||||
function box<T>(seed: T) {
|
||||
return class Box {
|
||||
value: T;
|
||||
constructor(value: T) { this.value = value; }
|
||||
again(): Box { return new Box(this.value); }
|
||||
static of(): Box { return new Box(seed); }
|
||||
};
|
||||
}
|
||||
const StringBox = box("seed");
|
||||
console.log(new StringBox("x").again().value, StringBox.of().value);
|
||||
const NumberBox = box(7);
|
||||
console.log(NumberBox.of().again().value + 1);
|
||||
|
||||
function nested(n: number) {
|
||||
const Outer = class Tree {
|
||||
children: Tree[] = [];
|
||||
static build(depth: number): Tree {
|
||||
const tree = new Tree();
|
||||
if (depth > 0) tree.children.push(Tree.build(depth - 1), Tree.build(depth - 1));
|
||||
return tree;
|
||||
}
|
||||
size(): number { return 1 + this.children.reduce((total, child) => total + child.size(), 0); }
|
||||
};
|
||||
return Outer.build(n).size();
|
||||
}
|
||||
console.log(nested(3));
|
||||
@@ -0,0 +1,50 @@
|
||||
// Function-local class expressions in JavaScript whose static members and
|
||||
// methods read the class by its own name.
|
||||
function point(scale) {
|
||||
const P = class Point {
|
||||
static origin = new Point(0, 0);
|
||||
static unit = Point.scaled(1);
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
static scaled(n) {
|
||||
return new Point(n * scale, n * scale);
|
||||
}
|
||||
plus(other) {
|
||||
return new Point(this.x + other.x, this.y + other.y);
|
||||
}
|
||||
};
|
||||
return P;
|
||||
}
|
||||
const P = point(3);
|
||||
const sum = P.origin.plus(P.unit).plus(P.unit);
|
||||
console.log(sum.x, sum.y, P.unit instanceof P, P.name);
|
||||
|
||||
function selfName() {
|
||||
const K = class Named {
|
||||
static who() {
|
||||
return Named === this;
|
||||
}
|
||||
};
|
||||
return [K.who(), K.name, typeof K];
|
||||
}
|
||||
console.log(selfName());
|
||||
|
||||
function factory(tag) {
|
||||
return class Tagged {
|
||||
static count = 0;
|
||||
constructor() {
|
||||
Tagged.count++;
|
||||
}
|
||||
static make() {
|
||||
return new Tagged();
|
||||
}
|
||||
get tag() {
|
||||
return tag + Tagged.count;
|
||||
}
|
||||
};
|
||||
}
|
||||
const T = factory("t");
|
||||
T.make();
|
||||
console.log(T.make().tag, T.count, factory("u").count);
|
||||
@@ -0,0 +1,51 @@
|
||||
// console.log prints instances of function-local classes with prototype
|
||||
// methods under their class names.
|
||||
function point() {
|
||||
class Point {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
plus(other) {
|
||||
return new Point(this.x + other.x, this.y + other.y);
|
||||
}
|
||||
}
|
||||
return Point;
|
||||
}
|
||||
const Point = point();
|
||||
const a = new Point(1, 2);
|
||||
console.log(a, a.plus(a));
|
||||
console.log([new Point(0, 0)], { p: new Point(5, 6) });
|
||||
|
||||
function animals() {
|
||||
class Animal {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
speak() {
|
||||
return this.name + " makes a sound";
|
||||
}
|
||||
}
|
||||
class Dog extends Animal {
|
||||
speak() {
|
||||
return this.name + " barks";
|
||||
}
|
||||
}
|
||||
return [new Animal("cat"), new Dog("rex")];
|
||||
}
|
||||
const [cat, dog] = animals();
|
||||
console.log(cat, dog);
|
||||
|
||||
function anonymous() {
|
||||
const Named = class {
|
||||
value() {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
return [new Named(), new (class {
|
||||
value() {
|
||||
return 2;
|
||||
}
|
||||
})()];
|
||||
}
|
||||
console.log(anonymous());
|
||||
@@ -0,0 +1,51 @@
|
||||
// Overrides declared by function-local subclasses dispatch on the receiver
|
||||
// when the call site lowers before the function that declares them.
|
||||
class Shape {
|
||||
area(): number { return 0; }
|
||||
get label(): string { return "shape"; }
|
||||
describe(): string { return this.label + ":" + this.area(); }
|
||||
}
|
||||
|
||||
function square(side: number): Shape {
|
||||
class Square extends Shape {
|
||||
area(): number { return side * side; }
|
||||
get label(): string { return "square"; }
|
||||
}
|
||||
return new Square();
|
||||
}
|
||||
|
||||
function scaled(factor: number): Shape {
|
||||
class Scaled extends Shape {
|
||||
area(): number { return factor; }
|
||||
}
|
||||
class Doubled extends Scaled {
|
||||
area(): number { return super.area() * 2; }
|
||||
}
|
||||
return new Doubled();
|
||||
}
|
||||
|
||||
const shapes: Shape[] = [new Shape(), square(3), scaled(5)];
|
||||
for (const shape of shapes) console.log(shape.area(), shape.label, shape.describe());
|
||||
|
||||
function pair() {
|
||||
class Base {
|
||||
name(): string { return "base"; }
|
||||
}
|
||||
class Derived extends Base {
|
||||
name(): string { return "derived"; }
|
||||
}
|
||||
return [new Base(), new Derived()];
|
||||
}
|
||||
const [first, second] = pair();
|
||||
console.log(first.name(), second.name());
|
||||
|
||||
function counter() {
|
||||
const Tally = class {
|
||||
count(): number { return 1; }
|
||||
};
|
||||
const Double = class extends Tally {
|
||||
count(): number { return 2; }
|
||||
};
|
||||
return new Double();
|
||||
}
|
||||
console.log(counter().count());
|
||||
@@ -0,0 +1,51 @@
|
||||
// Overrides declared by function-local subclasses in JavaScript dispatch on
|
||||
// the receiver when the call site lowers before the declaring function.
|
||||
class Animal {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
speak() {
|
||||
return this.name + " makes a sound";
|
||||
}
|
||||
}
|
||||
|
||||
function dog(name) {
|
||||
class Dog extends Animal {
|
||||
speak() {
|
||||
return this.name + " barks";
|
||||
}
|
||||
}
|
||||
return new Dog(name);
|
||||
}
|
||||
|
||||
function animals() {
|
||||
class Bird extends Animal {
|
||||
speak() {
|
||||
return this.name + " sings";
|
||||
}
|
||||
}
|
||||
return [new Animal("cat"), new Bird("robin"), dog("rex")];
|
||||
}
|
||||
|
||||
for (const animal of animals()) console.log(animal.speak());
|
||||
const speak = (animal) => animal.speak();
|
||||
console.log(speak(dog("fido")));
|
||||
|
||||
function pets() {
|
||||
class Pet {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
greet() {
|
||||
return this.name + " waves";
|
||||
}
|
||||
}
|
||||
class Cat extends Pet {
|
||||
greet() {
|
||||
return this.name + " purrs";
|
||||
}
|
||||
}
|
||||
return [new Pet("ann"), new Cat("tom")];
|
||||
}
|
||||
const [pet, cat] = pets();
|
||||
console.log(pet.greet(), cat.greet());
|
||||
@@ -1,10 +1,8 @@
|
||||
function privateBrand() { return class { #value = 1; has(other: object): boolean { return #value in other; } }; }
|
||||
function selfName() { return class Local { copy() { return new Local(); } }; }
|
||||
class Base {}
|
||||
function derived() { return class extends Base {}; }
|
||||
function generic() { return class { read<T>(value: T): T { return value; } }; }
|
||||
privateBrand();
|
||||
selfName();
|
||||
derived();
|
||||
generic();
|
||||
// End of unsupported local-class forms.
|
||||
|
||||
@@ -2,18 +2,11 @@ local-class-boundaries.ts:1:34 - error SC1090: function-local classes with priva
|
||||
|
||||
1 | function privateBrand() { return class { #value = 1; has(other: object): boolean { return #value in other; } }; }
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
2 | function selfName() { return class Local { copy() { return new Local(); } }; }
|
||||
2 | class Base {}
|
||||
|
||||
local-class-boundaries.ts:2:64 - error SC1090: named self references inside function-local classes are not supported yet
|
||||
local-class-boundaries.ts:4:29 - error SC1090: function-local classes with statics, decorators, or generic members are not supported yet
|
||||
|
||||
1 | function privateBrand() { return class { #value = 1; has(other: object): boolean { return #value in other; } }; }
|
||||
2 | function selfName() { return class Local { copy() { return new Local(); } }; }
|
||||
| ^~~~~
|
||||
3 | class Base {}
|
||||
|
||||
local-class-boundaries.ts:5:29 - error SC1090: function-local classes with statics, decorators, or generic members are not supported yet
|
||||
|
||||
4 | function derived() { return class extends Base {}; }
|
||||
5 | function generic() { return class { read<T>(value: T): T { return value; } }; }
|
||||
3 | function derived() { return class extends Base {}; }
|
||||
4 | function generic() { return class { read<T>(value: T): T { return value; } }; }
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
6 | privateBrand();
|
||||
5 | privateBrand();
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3202, "san": 4630 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3064, "san": 5586 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2821, "san": 4440 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3135, "san": 4365 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3074, "san": 4624 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3050, "san": 4393 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3058, "san": 4846 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2459, "san": 4630 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3216, "san": 5042 }
|
||||
Reference in New Issue
Block a user