mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-07 08:33:50 +08:00
Compile classes that extend Array (#2059)
This commit is contained in:
@@ -52,6 +52,8 @@ title: "Limitations"
|
||||
|
||||
<include>../limitations/types-and-class-definitions/static-field-updates.mdx</include>
|
||||
|
||||
<include>../limitations/types-and-class-definitions/array-subclasses.mdx</include>
|
||||
|
||||
<include>../limitations/types-and-class-definitions/local-class-expressions.mdx</include>
|
||||
|
||||
<include>../limitations/types-and-class-definitions/constructor-function-inheritance.mdx</include>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
- **Array subclasses:** A class that extends `Array` compiles: its instances are arrays, and `map`, `filter`, `slice` and the other methods that create arrays return instances of the subclass, as in Node.js.
|
||||
A generic subclass (`class Bag<T> extends Array<T>`), a class that extends such a subclass, and a subclass that declares `Symbol.species` are unsupported.
|
||||
A method whose result the subclass's constructor cannot construct throws a `TypeError`: the constructor's parameters must take the one length argument, and a `map` to another element type needs a constructor that adds no elements.
|
||||
@@ -146,6 +146,10 @@ function analyzeCycles(
|
||||
...(c.localCaptures !== undefined
|
||||
? [{ name: "<class>", type: { kind: "classval" as const, className: c.name } }]
|
||||
: []),
|
||||
// An instance of a class that extends Array holds its elements.
|
||||
...(c.arrayElem !== undefined
|
||||
? [{ name: "<elements>", type: { kind: "array" as const, elem: c.arrayElem } }]
|
||||
: []),
|
||||
],
|
||||
})),
|
||||
...(mod.records ?? []).map((r) => ({
|
||||
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
traceAdapter,
|
||||
type ShapeHost,
|
||||
youngEntryLines,
|
||||
arrElemInit,
|
||||
} from "./shapes.js";
|
||||
|
||||
/** One virtual method slot of a hierarchy: the ROOT-MOST declaring class
|
||||
@@ -371,11 +372,34 @@ function runtimePrefixOf(meta: LlClassMeta): RuntimePrefix | null {
|
||||
* on classes rooted at a runtime base, then the stream-state slot on
|
||||
* stream-rooted ones. */
|
||||
function headerEnd(meta: LlClassMeta): number {
|
||||
if (meta.def.arrayElem !== undefined) return ARRAY_PREFIX.length + 1;
|
||||
if (!meta.hierarchy || meta.idInRc) return 1;
|
||||
if (streamRooted(meta)) return 5;
|
||||
return runtimePrefixOf(meta) ? 4 : 2;
|
||||
}
|
||||
|
||||
/** The array header a class declared `extends Array` begins with, after
|
||||
* the size_t reference count: %ScrArr's len, cap, elem, cyc, spare, filled
|
||||
* and data (scr_array.h). */
|
||||
const ARRAY_PREFIX = ["i32", "i32", "i8", "i8", "i16", "i32", "ptr"];
|
||||
|
||||
/** LLVM `c"..."` text of a string's UTF-8 bytes and a terminating NUL. */
|
||||
function nulTerminatedBytes(text: string): string {
|
||||
let out = "";
|
||||
for (const byte of Buffer.from(text, "utf8"))
|
||||
out +=
|
||||
byte >= 0x20 && byte < 0x7f && byte !== 0x22 && byte !== 0x5c
|
||||
? String.fromCharCode(byte)
|
||||
: `\\${byte.toString(16).padStart(2, "0").toUpperCase()}`;
|
||||
return out + "\\00";
|
||||
}
|
||||
|
||||
/** The class descriptor (ScrArrClass: name, destroy) of a class declared
|
||||
* `extends Array`. */
|
||||
export function arrayClassDescriptor(className: string): string {
|
||||
return `@${mangleClassStruct(className)}.arrcls`;
|
||||
}
|
||||
|
||||
/** The GEP index of the ThreadSlots slot word (an i32) in a class whose
|
||||
* hierarchy holds ThreadSlots keys: right after the header words, so every
|
||||
* member of the hierarchy has it at the same offset. */
|
||||
@@ -473,7 +497,11 @@ export interface ClassHost extends ShapeHost {
|
||||
* hierarchy folds away. The cycle analysis leaves the hierarchy untraced, so
|
||||
* no trace adapter ever visits an instance (as for every untraced class). */
|
||||
export function programLifetimeClass(host: ClassHost, meta: LlClassMeta): boolean {
|
||||
return host.programLifetime?.has(meta.root.def.name) === true && meta.root.def.runtime !== true;
|
||||
return (
|
||||
host.programLifetime?.has(meta.root.def.name) === true &&
|
||||
meta.root.def.runtime !== true &&
|
||||
meta.root.def.arrayElem === undefined
|
||||
);
|
||||
}
|
||||
|
||||
/** The reference count a program-lifetime instance is created with: the
|
||||
@@ -733,7 +761,12 @@ export function assignCompactLayouts(
|
||||
): void {
|
||||
const isCompact = (className: string): boolean => {
|
||||
const target = metaMap.get(className);
|
||||
return target !== undefined && target.def.runtime !== true && compact(target.root);
|
||||
return (
|
||||
target !== undefined &&
|
||||
target.def.runtime !== true &&
|
||||
target.root.def.arrayElem === undefined &&
|
||||
compact(target.root)
|
||||
);
|
||||
};
|
||||
const byPre = [...metaMap.values()].sort((a, b) => a.pre - b.pre);
|
||||
for (const meta of byPre) {
|
||||
@@ -908,13 +941,15 @@ export function emitClassShapes(
|
||||
// usual pointer reinterprets.
|
||||
const runtimePrefix = runtimePrefixOf(meta);
|
||||
const prefix =
|
||||
meta.hierarchy && !meta.idInRc
|
||||
? streamRooted(meta)
|
||||
? ["ptr", "ptr", "ptr", "ptr"]
|
||||
: runtimePrefix
|
||||
? ["ptr", "ptr", "ptr"]
|
||||
: ["ptr"]
|
||||
: [];
|
||||
cls.arrayElem !== undefined
|
||||
? ARRAY_PREFIX
|
||||
: meta.hierarchy && !meta.idInRc
|
||||
? streamRooted(meta)
|
||||
? ["ptr", "ptr", "ptr", "ptr"]
|
||||
: runtimePrefix
|
||||
? ["ptr", "ptr", "ptr"]
|
||||
: ["ptr"]
|
||||
: [];
|
||||
const members = [...prefix, ...(meta.threadSlot ? ["i32"] : []), ...fieldTys];
|
||||
typeDefs.push(
|
||||
`%${mangleClassStruct(cls.name)} = type { ${host.sizeType}${members.length ? ", " + members.join(", ") : ""} } ` +
|
||||
@@ -1001,6 +1036,7 @@ export function emitClassShapes(
|
||||
const types = ownedFields(cls).map(({ field }) => storedFieldType(cls, field));
|
||||
for (const t of types) fieldTypes.push(t);
|
||||
if (programLifetimeClass(host, meta)) continue;
|
||||
if (cls.arrayElem !== undefined) continue;
|
||||
if (meta.hierarchy && (meta.children.length !== 0 || meta.root.def.runtime === true)) continue;
|
||||
if (!types.some((t) => isRefCounted(t) && needsBoundedRelease(t))) continue;
|
||||
const release = mangleClassRelease(cls.name);
|
||||
@@ -1017,6 +1053,12 @@ export function emitClassShapes(
|
||||
}
|
||||
const directTargets = usedDirectTargets(host, candidates, fieldTypes);
|
||||
|
||||
// The descriptor entries of a class declared `extends Array`: a helper
|
||||
// the module defines, or null (a class that is never constructed has
|
||||
// neither).
|
||||
const defined = new Set(mod.functions.map((fn) => fn.name));
|
||||
const arrayClassFn = (name: string | undefined): string =>
|
||||
name !== undefined && defined.has(name) ? `@${mangleFunction(name)}` : "null";
|
||||
for (const cls of emitted) {
|
||||
const meta = metaMap.get(cls.name)!;
|
||||
const struct = mangleClassStruct(cls.name);
|
||||
@@ -1098,6 +1140,7 @@ export function emitClassShapes(
|
||||
const destroy = `${meta.hierarchy ? mangleClassReleaseDirect(cls.name) : mangleClassRelease(cls.name)}_destroy`;
|
||||
const segmented =
|
||||
chained &&
|
||||
cls.arrayElem === undefined &&
|
||||
!meta.compact &&
|
||||
runtimePrefix === null &&
|
||||
!isStreamRooted &&
|
||||
@@ -1113,7 +1156,12 @@ export function emitClassShapes(
|
||||
// members). Runs at rc == 0.
|
||||
const teardown = (lines: string[]): void => {
|
||||
const release = (prefix: string, f: (typeof refFields)[number], t: number): string[] => {
|
||||
const direct = bounded ? directDestroyTarget(host, f.type, directTargets) : null;
|
||||
// An Array subclass instance's destroy releases its fields plainly
|
||||
// (it takes no chain length).
|
||||
const direct =
|
||||
bounded && cls.arrayElem === undefined
|
||||
? directDestroyTarget(host, f.type, directTargets)
|
||||
: null;
|
||||
if (direct === null)
|
||||
return [
|
||||
` call void ${releaseSym(host, f.type)}(ptr %v${t}) ; ${llvmCommentText(f.name)}`,
|
||||
@@ -1244,6 +1292,45 @@ export function emitClassShapes(
|
||||
reld.push(`done:`, ` ret void`, `}`, ``);
|
||||
defs.push(...reld);
|
||||
}
|
||||
} else if (cls.arrayElem !== undefined) {
|
||||
// An instance is an array: its release is the array's, which reaches
|
||||
// the class's destroy (the descriptor's) when the count reaches zero.
|
||||
host.declare(`declare void @scr_arr_release(ptr)`);
|
||||
host.declare(`declare void @scr_arr_subclass_drop(ptr)`);
|
||||
const destroy = `${mangleClassRelease(cls.name)}.arrdestroy`;
|
||||
const destroyBody: string[] = [
|
||||
`define internal void @${destroy}(ptr %o) ${FN_ATTRS} { ; destroy ${cls.name} (an Array subclass instance)`,
|
||||
`entry:`,
|
||||
];
|
||||
const fieldsTeardown: string[] = [];
|
||||
teardown(fieldsTeardown);
|
||||
const free = emitObjectFree(host, traced);
|
||||
destroyBody.push(
|
||||
...fieldsTeardown.slice(0, fieldsTeardown.length - free.length),
|
||||
` call void @scr_arr_subclass_drop(ptr %o)`,
|
||||
...free,
|
||||
` ret void`,
|
||||
`}`,
|
||||
``,
|
||||
);
|
||||
const displayName = cls.name.includes(".")
|
||||
? cls.name.slice(cls.name.lastIndexOf(".") + 1)
|
||||
: cls.name;
|
||||
const jsName = cls.jsName ?? displayName;
|
||||
defs.push(
|
||||
...destroyBody,
|
||||
// The name is the class's own copy: its address identifies the
|
||||
// class in dyn snapshots of an instance (scr_arr_copy_metadata).
|
||||
`${arrayClassDescriptor(cls.name)}.name = private constant [${Buffer.byteLength(jsName, "utf8") + 1} x i8] c"${nulTerminatedBytes(jsName)}"`,
|
||||
`${arrayClassDescriptor(cls.name)} = internal constant { ptr, ptr, ptr, ptr } { ptr ${arrayClassDescriptor(cls.name)}.name, ptr @${destroy}, ptr ${arrayClassFn(cls.arrayFields)}, ptr ${arrayClassFn(cls.arraySpecies)} }`,
|
||||
``,
|
||||
`define internal void @${mangleClassRelease(cls.name)}(ptr %o) ${FN_ATTRS} { ; release ${cls.name} (the array's)`,
|
||||
`entry:`,
|
||||
` call void @scr_arr_release(ptr %o)`,
|
||||
` ret void`,
|
||||
`}`,
|
||||
``,
|
||||
);
|
||||
} else {
|
||||
const freeBody: string[] = [];
|
||||
teardown(freeBody);
|
||||
@@ -1331,6 +1418,13 @@ export function emitClassShapes(
|
||||
nw.push(
|
||||
` store ${host.sizeType} ${lifetime ? programLifetimeRc(host.sizeType) : 1}, ptr %o`,
|
||||
);
|
||||
if (cls.arrayElem !== undefined) {
|
||||
const init = arrElemInit(host, cls.arrayElem);
|
||||
host.declare(`declare void @scr_arr_subclass_init(ptr, i8, ptr, ptr, i1 zeroext)`);
|
||||
nw.push(
|
||||
` call void @scr_arr_subclass_init(ptr %o, i8 ${init.kind}, ptr ${init.ops}, ptr ${arrayClassDescriptor(cls.name)}, i1 ${traced})`,
|
||||
);
|
||||
}
|
||||
if (lifetime && audit) {
|
||||
// --sanitize builds: the RC audit skips its leak report when the
|
||||
// program keeps program-lifetime objects (scr_console.c), and
|
||||
@@ -1367,7 +1461,12 @@ export function emitClassShapes(
|
||||
// Young instances: program-owned traced classes. Classes rooted at a
|
||||
// runtime base (errors, emitters, events, streams) carry runtime state
|
||||
// and stay with trial deletion, like every runtime family.
|
||||
if (traced && !lifetime && (!meta.hierarchy || meta.root.def.runtime !== true))
|
||||
if (
|
||||
traced &&
|
||||
!lifetime &&
|
||||
(!meta.hierarchy || meta.root.def.runtime !== true) &&
|
||||
cls.arrayElem === undefined
|
||||
)
|
||||
nw.push(...youngEntryLines(host));
|
||||
nw.push(` ret ptr ${meta.hierarchy ? "%o.vt" : "%o"}`, `}`, ``);
|
||||
defs.push(...nw);
|
||||
@@ -1390,6 +1489,12 @@ export function emitClassShapes(
|
||||
tracedFields.forEach((f, i) => {
|
||||
tr.push(...traceFieldVisitLines(struct, f.index, i, llvmCommentText(f.name)));
|
||||
});
|
||||
// An Array subclass instance's elements, when its current element
|
||||
// storage is cycle-capable (a species result can retype it).
|
||||
if (cls.arrayElem !== undefined) {
|
||||
host.declare(`declare void @scr_arr_subclass_trace(ptr, ptr, ptr)`);
|
||||
tr.push(` call void @scr_arr_subclass_trace(ptr %o, ptr %visit, ptr %ctx) ; the elements`);
|
||||
}
|
||||
tr.push(` ret void`, `}`, ``);
|
||||
defs.push(...tr);
|
||||
|
||||
@@ -1407,6 +1512,12 @@ export function emitClassShapes(
|
||||
);
|
||||
});
|
||||
if (meta.threadSlot) gf.push(...slotForgetLines(host, struct, meta, "gfw"));
|
||||
if (cls.arrayElem !== undefined) {
|
||||
// The elements: traced ones are the collector's, the others are
|
||||
// released with the storage (the same test as the trace's).
|
||||
host.declare(`declare void @scr_arr_subclass_gc_drop(ptr)`);
|
||||
gf.push(` call void @scr_arr_subclass_gc_drop(ptr %o)`);
|
||||
}
|
||||
gf.push(...emitObjectFree(host, true), ` ret void`, `}`, ``);
|
||||
defs.push(...gf);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* Focused LLVM expression emission extracted from emitter.ts. */
|
||||
import { arrayClassDescriptor } from "./classes.js";
|
||||
import { InternalCompilerError } from "../../errors.js";
|
||||
import { newValueMayThrow } from "../../ir/analysis.js";
|
||||
import {
|
||||
@@ -1235,6 +1236,17 @@ export function emitCallExpr(
|
||||
return host.own({ name: v.name, type: e.type });
|
||||
}
|
||||
case "instanceOf": {
|
||||
// An array is an instance of a class declared `extends Array` when
|
||||
// its ext names that class (scr_arr_class_of).
|
||||
if (e.value.type.kind === "array") {
|
||||
const v = emitBorrowedInputs(host, [e.value])[0]!;
|
||||
host.declare(`declare ptr @scr_arr_class_of(ptr)`);
|
||||
const cls = host.B.tmp();
|
||||
const result = host.B.tmp();
|
||||
host.B.line(`${cls} = call ptr @scr_arr_class_of(ptr ${v.name})`);
|
||||
host.B.line(`${result} = icmp eq ptr ${cls}, ${arrayClassDescriptor(e.className)}`);
|
||||
return { name: result, type: e.type };
|
||||
}
|
||||
// O(1) preorder-interval test against the vtable the object
|
||||
// carries; the target's interval is a compile-time constant.
|
||||
if (e.value.type.kind !== "object")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { arrayClassDescriptor } from "./classes.js";
|
||||
import { emitBigIntWrap } from "./bigint-wrap.js";
|
||||
import { emitBorrowedInput, emitBorrowedInputs } from "./borrowed-inputs.js";
|
||||
import { borrowsIteratorInputs, borrowsJsonInputs } from "./checked-value-lifetimes.js";
|
||||
@@ -278,6 +279,82 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
host.dyn.emitRecordSourceSpread(B, built.name, value.name, type.shapeId);
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
if (e.fn === "array.speciesCreate") {
|
||||
// The receiver is borrowed; the call answers a new reference.
|
||||
const receiver = emitBorrowedInput(host, e.args[0]!);
|
||||
const length = host.emitExpr(e.args[1]!);
|
||||
host.declare(`declare ptr @scr_arr_species_create(ptr, double)`);
|
||||
const out = B.tmp();
|
||||
B.line(
|
||||
`${out} = call ptr @scr_arr_species_create(ptr ${receiver.name}, double ${length.name})`,
|
||||
);
|
||||
const value = host.own({ name: out, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return value;
|
||||
}
|
||||
if (e.fn === "array.speciesFinish") {
|
||||
// The instance is borrowed; the method's result moves into the call,
|
||||
// which answers it or the instance that holds its elements.
|
||||
const instance = emitBorrowedInput(host, e.args[0]!);
|
||||
const result = host.emitExpr(e.args[1]!);
|
||||
host.toHeap(result);
|
||||
const setLength = host.emitExpr(e.args[2]!);
|
||||
host.declare(`declare ptr @scr_arr_species_finish(ptr, ptr, i1 zeroext)`);
|
||||
const out = B.tmp();
|
||||
B.line(
|
||||
`${out} = call ptr @scr_arr_species_finish(ptr ${instance.name}, ptr ${result.name}, i1 zeroext ${setLength.name})`,
|
||||
);
|
||||
const value = host.own({ name: out, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return value;
|
||||
}
|
||||
if (e.fn === "array.speciesAdopt") {
|
||||
// The receiver is borrowed; the method's result moves into the call,
|
||||
// which answers it or the species instance that replaces it.
|
||||
const receiver = emitBorrowedInput(host, e.args[0]!);
|
||||
const result = host.emitExpr(e.args[1]!);
|
||||
host.toHeap(result);
|
||||
const length = host.emitExpr(e.args[2]!);
|
||||
const setLength = host.emitExpr(e.args[3]!);
|
||||
host.declare(`declare ptr @scr_arr_species_adopt(ptr, ptr, double, i1 zeroext)`);
|
||||
const out = B.tmp();
|
||||
B.line(
|
||||
`${out} = call ptr @scr_arr_species_adopt(ptr ${receiver.name}, ptr ${result.name}, double ${length.name}, i1 zeroext ${setLength.name})`,
|
||||
);
|
||||
const value = host.own({ name: out, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return value;
|
||||
}
|
||||
if (e.fn === "array.dynIsClass") {
|
||||
const className = e.args[1]!.kind === "strLit" ? e.args[1]!.value : null;
|
||||
if (className === null)
|
||||
throw new InternalCompilerError("llvm emitter bug: array.dynIsClass needs a class name");
|
||||
const value = emitBorrowedInput(host, e.args[0]!);
|
||||
host.declare(`declare zeroext i1 @scr_dyn_is_array_class(ptr, ptr)`);
|
||||
const out = B.tmp();
|
||||
B.line(
|
||||
`${out} = call zeroext i1 @scr_dyn_is_array_class(ptr ${value.name}, ptr ${arrayClassDescriptor(className)})`,
|
||||
);
|
||||
return { name: out, type: e.type };
|
||||
}
|
||||
if (e.fn === "array.classConstructAdopt") {
|
||||
const className = e.args[0]!.kind === "strLit" ? e.args[0]!.value : null;
|
||||
if (className === null)
|
||||
throw new InternalCompilerError(
|
||||
"llvm emitter bug: array.classConstructAdopt needs a class name",
|
||||
);
|
||||
const result = host.emitExpr(e.args[1]!);
|
||||
host.toHeap(result);
|
||||
const length = host.emitExpr(e.args[2]!);
|
||||
host.declare(`declare ptr @scr_arr_class_construct_adopt(ptr, ptr, double)`);
|
||||
const out = B.tmp();
|
||||
B.line(
|
||||
`${out} = call ptr @scr_arr_class_construct_adopt(ptr ${arrayClassDescriptor(className)}, ptr ${result.name}, double ${length.name})`,
|
||||
);
|
||||
const value = host.own({ name: out, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return value;
|
||||
}
|
||||
if (e.fn === "dyn.typedRefIs" || e.fn === "dyn.typedRefKeyIs") {
|
||||
const literal = e.args[1]!.kind === "strLit" ? e.args[1]!.value : null;
|
||||
if (e.fn === "dyn.typedRefKeyIs" && literal === null)
|
||||
|
||||
@@ -1457,6 +1457,13 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"http.reqPipeSock": "scr_http_req_pipe_sock",
|
||||
"http.reqResume": "scr_http_req_resume",
|
||||
"http.reqPause": "scr_http_req_pause",
|
||||
"array.subclassConstruct": "scr_arr_subclass_construct",
|
||||
"array.subclassConstructDyn": "scr_arr_subclass_construct_dyn",
|
||||
"array.speciesAdopt": "scr_arr_species_adopt",
|
||||
"array.speciesCreate": "scr_arr_species_create",
|
||||
"array.speciesFinish": "scr_arr_species_finish",
|
||||
"array.classConstructAdopt": "scr_arr_class_construct_adopt",
|
||||
"array.dynIsClass": "scr_dyn_is_array_class",
|
||||
"http.reqDestroy": "scr_http_req_destroy",
|
||||
"http.reqSetEncoding": "scr_http_req_set_encoding",
|
||||
"http.resSetHeader": "scr_http_res_set_header",
|
||||
|
||||
@@ -1198,7 +1198,33 @@ export function arrNewCall(
|
||||
* live in its own block): capText must be "0". */
|
||||
small = false,
|
||||
): string {
|
||||
const useRef =
|
||||
const useRef = arrUsesRef(host, elem);
|
||||
if (!useRef) {
|
||||
if (small) {
|
||||
host.declare(`declare ptr @scr_arr_new_small(i32)`);
|
||||
return `call ptr @scr_arr_new_small(i32 ${elemKindNum(elem)})`;
|
||||
}
|
||||
host.declare(`declare ptr @scr_arr_new(i32, ${host.sizeType})`);
|
||||
return `call ptr @scr_arr_new(i32 ${elemKindNum(elem)}, ${host.sizeType} ${capText})`;
|
||||
}
|
||||
if (small) {
|
||||
host.declare(`declare ptr @scr_arr_new_ops_small(ptr)`);
|
||||
return `call ptr @scr_arr_new_ops_small(ptr ${arrOps(host, elem)})`;
|
||||
}
|
||||
host.declare(`declare ptr @scr_arr_new_ops(ptr, ${host.sizeType})`);
|
||||
return `call ptr @scr_arr_new_ops(ptr ${arrOps(host, elem)}, ${host.sizeType} ${capText})`;
|
||||
}
|
||||
|
||||
/** The ScrElemKind and ops descriptor (or `null`) an array of `elem`
|
||||
* starts with: the arrays arrNewCall constructs, for the instances of a
|
||||
* class that extends Array (scr_arr_subclass_init). */
|
||||
export function arrElemInit(host: ShapeHost, elem: IrType): { kind: number; ops: string } {
|
||||
if (!arrUsesRef(host, elem)) return { kind: elemKindNum(elem), ops: "null" };
|
||||
return { kind: host.compactArrayElem?.(elem) === true ? 6 : 5, ops: arrOps(host, elem) };
|
||||
}
|
||||
|
||||
function arrUsesRef(host: ShapeHost, elem: IrType): boolean {
|
||||
return (
|
||||
elem.kind === "record" ||
|
||||
elem.kind === "object" ||
|
||||
elem.kind === "union" ||
|
||||
@@ -1215,21 +1241,8 @@ export function arrNewCall(
|
||||
elem.kind === "dyn" || // native collection seeds/drains: traced REF
|
||||
elem.kind === "regex" || // RegExp values: scr_regex_* adapters, no trace (no refs inside)
|
||||
elem.kind === "date" || // Date objects: scr_date_* adapters, no trace (no refs inside)
|
||||
(elem.kind === "array" && traceAdapter(host, elem) !== null);
|
||||
if (!useRef) {
|
||||
if (small) {
|
||||
host.declare(`declare ptr @scr_arr_new_small(i32)`);
|
||||
return `call ptr @scr_arr_new_small(i32 ${elemKindNum(elem)})`;
|
||||
}
|
||||
host.declare(`declare ptr @scr_arr_new(i32, ${host.sizeType})`);
|
||||
return `call ptr @scr_arr_new(i32 ${elemKindNum(elem)}, ${host.sizeType} ${capText})`;
|
||||
}
|
||||
if (small) {
|
||||
host.declare(`declare ptr @scr_arr_new_ops_small(ptr)`);
|
||||
return `call ptr @scr_arr_new_ops_small(ptr ${arrOps(host, elem)})`;
|
||||
}
|
||||
host.declare(`declare ptr @scr_arr_new_ops(ptr, ${host.sizeType})`);
|
||||
return `call ptr @scr_arr_new_ops(ptr ${arrOps(host, elem)}, ${host.sizeType} ${capText})`;
|
||||
(elem.kind === "array" && traceAdapter(host, elem) !== null)
|
||||
);
|
||||
}
|
||||
|
||||
/** The %ScrArrOps descriptor (retain, release, trace, registry id) of a REF
|
||||
|
||||
@@ -0,0 +1,757 @@
|
||||
/* Classes declared `extends Array`. An instance is an array of the class's
|
||||
* element type: its struct begins with the runtime's array header (the
|
||||
* reference count is the array's) and the class's own fields follow it, so
|
||||
* every array operation applies to the instance unchanged and passing it
|
||||
* where a plain array is expected passes the same object. The class's
|
||||
* TypeScript type maps to its element array type; the class's own members,
|
||||
* construction and `instanceof` view the array as the class object
|
||||
* (arrayClassView). */
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { type Lowerer } from "../lowerer.js";
|
||||
import { isJsSourceFile, locOf } from "../../program.js";
|
||||
import { stripTypeWrappers } from "../typed-arguments.js";
|
||||
import { dynUndefinedExpr, nodeThrowExpr } from "../../../ir/build.js";
|
||||
import { lowerArrayFromCall, lowerArrayOfCall } from "../containers/array-construction.js";
|
||||
import { boundedArrayHofHelper } from "../containers/array-callbacks.js";
|
||||
import { storedClassValueType, type ClassInfo } from "../lower-classes.js";
|
||||
import {
|
||||
BOOL,
|
||||
DYN,
|
||||
F64,
|
||||
STRING,
|
||||
UNDEFINED_T,
|
||||
arrayOf,
|
||||
typeEquals,
|
||||
type IrExpr,
|
||||
type IrFunction,
|
||||
type IrLocal,
|
||||
type IrStmt,
|
||||
type IrType,
|
||||
type SrcLoc,
|
||||
} from "../../../ir/ir.js";
|
||||
|
||||
/** The element type of `class X extends Array<T>` (`extends Array` in
|
||||
* JavaScript reads `any[]`, whose elements are checked-dynamic). */
|
||||
export function arrayClassBase(
|
||||
lowerer: Lowerer,
|
||||
heritage: ts.ExpressionWithTypeArguments,
|
||||
): { elem: IrType } {
|
||||
const decl = heritage.parent?.parent;
|
||||
const self =
|
||||
decl && ts.isClassLikeDeclaration(decl)
|
||||
? lowerer.checker.getSymbolAtLocation(decl.name ?? decl)
|
||||
: undefined;
|
||||
const element = heritage.typeArguments?.nodes[0];
|
||||
if (
|
||||
self &&
|
||||
element &&
|
||||
mentionsClass(lowerer, lowerer.checker.getTypeAtLocation(element), self, new Set())
|
||||
)
|
||||
lowerer.unsupported("SC1090", heritage, "classes that extend an Array of their own instances");
|
||||
const mapped = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(heritage));
|
||||
// `extends Array` in JavaScript reads `any[]`: dyn elements.
|
||||
if (isJsSourceFile(ts.getSourceFileOfNode(heritage)) && mapped?.kind !== "array")
|
||||
return { elem: DYN };
|
||||
if (mapped?.kind !== "array")
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
heritage,
|
||||
"extending Array with an element type that does not compile",
|
||||
);
|
||||
return { elem: mapped!.kind === "array" ? mapped!.elem : DYN };
|
||||
}
|
||||
|
||||
/** Whether `type` is the class `self`'s instance type, or holds it through
|
||||
* unions, intersections or type arguments (`Group | null`, `Group[]`,
|
||||
* `Map<string, Group>`): the class's array type would contain itself.
|
||||
* Another class's members do not count, its instances are references. */
|
||||
function mentionsClass(
|
||||
lowerer: Lowerer,
|
||||
type: ts.Type,
|
||||
self: ts.Symbol,
|
||||
seen: Set<ts.Type>,
|
||||
): boolean {
|
||||
if (seen.has(type)) return false;
|
||||
seen.add(type);
|
||||
if (type.getSymbol() === self) return true;
|
||||
if (type.isUnionType() || type.isIntersectionType())
|
||||
return ts.constituentTypes(type).some((part) => mentionsClass(lowerer, part, self, seen));
|
||||
return lowerer.checker
|
||||
.getTypeArguments(type as ts.TypeReference)
|
||||
.some((argument) => mentionsClass(lowerer, argument, self, seen));
|
||||
}
|
||||
|
||||
/** The class declared `extends Array` at the root of `info`'s chain, or null. */
|
||||
export function arrayRootOf(info: ClassInfo | null | undefined): ClassInfo | null {
|
||||
for (let c = info ?? null; c; c = c.base) if (c.extendsArray) return c;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** The array a class instance is (a reinterpret: the same object). */
|
||||
export function arrayOfClassInstance(info: ClassInfo, value: IrExpr, loc: SrcLoc): IrExpr {
|
||||
const root = arrayRootOf(info)!;
|
||||
return { kind: "upcast", value, type: arrayOf(root.extendsArray!.elem), loc };
|
||||
}
|
||||
|
||||
/** The class object an instance of `info` is, viewed from its array type
|
||||
* (a reinterpret; the value is known to be an instance of the class). */
|
||||
export function arrayClassView(info: ClassInfo, value: IrExpr, loc: SrcLoc): IrExpr {
|
||||
return { kind: "downcast", value, type: { kind: "object", className: info.def.name }, loc };
|
||||
}
|
||||
|
||||
/** The receiver of a member that `info` declares, read on `value` (the
|
||||
* lowered `node`): an array value is viewed as the class object. An array
|
||||
* whose elements are not the class's (a species result of another element
|
||||
* type, narrowed back to the class by `instanceof`) is refused. */
|
||||
export function arrayClassReceiver(
|
||||
lowerer: Lowerer,
|
||||
info: ClassInfo,
|
||||
value: IrExpr,
|
||||
node: ts.Node,
|
||||
): IrExpr {
|
||||
if (value.type.kind !== "array") return value;
|
||||
const root = arrayRootOf(info);
|
||||
if (root?.extendsArray === undefined || !typeEquals(value.type.elem, root.extendsArray.elem))
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
node,
|
||||
"members of an Array subclass on an array whose elements are another type (a species result narrowed back to the class)",
|
||||
);
|
||||
return arrayClassView(info, value, locOf(node));
|
||||
}
|
||||
|
||||
function thisArray(info: ClassInfo, thisLocal: IrLocal, loc: SrcLoc): IrExpr {
|
||||
return arrayOfClassInstance(
|
||||
info,
|
||||
{
|
||||
kind: "varRef",
|
||||
localId: thisLocal.id,
|
||||
type: { kind: "object", className: info.def.name },
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
);
|
||||
}
|
||||
|
||||
/** The implicit constructor's super(...args): `pack` is its dyn argument
|
||||
* array, applied with the Array constructor's length-or-elements rule. */
|
||||
export function arraySuperCall(
|
||||
info: ClassInfo,
|
||||
thisRef: IrExpr,
|
||||
pack: IrExpr,
|
||||
loc: SrcLoc,
|
||||
): IrStmt {
|
||||
const elem = arrayRootOf(info)!.extendsArray!.elem;
|
||||
return {
|
||||
kind: "exprStmt",
|
||||
expr: {
|
||||
kind: "libCall",
|
||||
fn: "array.subclassConstructDyn",
|
||||
args: [
|
||||
arrayOfClassInstance(info, thisRef, loc),
|
||||
pack,
|
||||
{ kind: "boolLit", value: elem.kind === "dyn", type: BOOL, loc },
|
||||
],
|
||||
type: { kind: "void" },
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** An explicit super(...) in a class declared `extends Array`: one number
|
||||
* argument sets the length; otherwise the arguments (spreads included)
|
||||
* become the elements, with the same one-number rule at run time. */
|
||||
export function lowerArraySuperCall(
|
||||
lowerer: Lowerer,
|
||||
info: ClassInfo,
|
||||
thisLocal: IrLocal,
|
||||
superCall: ts.CallExpression,
|
||||
): IrStmt[] {
|
||||
const loc = locOf(superCall);
|
||||
const elem = arrayRootOf(info)!.extendsArray!.elem;
|
||||
const args = superCall.arguments.nodes;
|
||||
if (args.length === 0) return [];
|
||||
const only = args.length === 1 && !ts.isSpreadElement(args[0]!) ? args[0]! : null;
|
||||
if (only !== null && (lowerer.typeOf(only).flags & ts.TypeFlags.NumberLike) !== 0) {
|
||||
return [
|
||||
{
|
||||
kind: "arraySetLength",
|
||||
arr: thisArray(info, thisLocal, loc),
|
||||
length: lowerer.lowerExprExpecting(only, { kind: "f64" }),
|
||||
loc,
|
||||
},
|
||||
];
|
||||
}
|
||||
const arrT = arrayOf(elem);
|
||||
const elements: IrExpr[] = [];
|
||||
const spreads: number[] = [];
|
||||
for (const arg of args) {
|
||||
if (ts.isSpreadElement(arg)) {
|
||||
spreads.push(elements.length);
|
||||
elements.push(lowerer.lowerExprExpecting(arg.expression, arrT));
|
||||
} else elements.push(lowerer.lowerExprExpecting(arg, elem));
|
||||
}
|
||||
const pack: IrExpr = {
|
||||
kind: "arrayLit",
|
||||
elems: elements,
|
||||
...(spreads.length > 0 ? { spreads } : {}),
|
||||
type: arrT,
|
||||
loc,
|
||||
} as IrExpr;
|
||||
return [
|
||||
{
|
||||
kind: "exprStmt",
|
||||
expr: {
|
||||
kind: "libCall",
|
||||
fn: "array.subclassConstruct",
|
||||
args: [
|
||||
thisArray(info, thisLocal, loc),
|
||||
pack,
|
||||
{ kind: "boolLit", value: elem.kind === "dyn", type: BOOL, loc },
|
||||
],
|
||||
type: { kind: "void" },
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** The ClassInfo of a class declared `extends Array`, by its declaration. */
|
||||
export function arrayClassOfDecl(
|
||||
lowerer: Lowerer,
|
||||
decl: ts.ClassLikeDeclaration,
|
||||
): ClassInfo | null {
|
||||
const symbol = decl.name ? lowerer.checker.getSymbolAtLocation(decl.name) : undefined;
|
||||
let info = symbol ? lowerer.classBySymbol.get(symbol) : undefined;
|
||||
if (!info)
|
||||
for (const candidate of lowerer.classes.values()) if (candidate.decl === decl) info = candidate;
|
||||
return info && arrayRootOf(info) ? info : null;
|
||||
}
|
||||
|
||||
/** The class that declares the member `access` names when that class is
|
||||
* declared `extends Array` (its own fields, methods and accessors), or null
|
||||
* for the Array members it inherits and every other access. */
|
||||
export function arrayClassMemberOwner(
|
||||
lowerer: Lowerer,
|
||||
access: ts.PropertyAccessExpression,
|
||||
): ClassInfo | null {
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(access.name);
|
||||
const decl = symbol?.declarations?.[0];
|
||||
const owner = decl?.parent;
|
||||
if (owner === undefined || !ts.isClassLikeDeclaration(owner)) return null;
|
||||
const declarer = arrayClassOfDecl(lowerer, owner);
|
||||
if (declarer === null) return null;
|
||||
// The receiver's own class (a subclass member reached through a base
|
||||
// spelling is the same instance).
|
||||
const receiver = lowerer.typeOf(access.expression).getSymbol()?.valueDeclaration;
|
||||
if (receiver !== undefined && ts.isClassLikeDeclaration(receiver))
|
||||
return arrayClassOfDecl(lowerer, receiver) ?? declarer;
|
||||
return declarer;
|
||||
}
|
||||
|
||||
/** `x instanceof Array` on a static array (always true: every array value,
|
||||
* subclass instances included, is an Array) and `x instanceof X` for a
|
||||
* class X declared `extends Array`: the array's class, read from its ext
|
||||
* at run time. Null for every other instanceof. */
|
||||
export function lowerArrayClassInstanceOf(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.BinaryExpression,
|
||||
loc: SrcLoc,
|
||||
): IrExpr | null {
|
||||
if (!ts.isIdentifier(expr.right)) return null;
|
||||
const leftType = lowerer.mapTypeOf(lowerer.typeOf(expr.left));
|
||||
if (lowerer.isStdlibGlobal(expr.right, "Array")) {
|
||||
// A checked-dynamic value: the Array.isArray test (one realm).
|
||||
if (leftType?.kind === "dyn")
|
||||
return {
|
||||
kind: "dynTest",
|
||||
test: "array",
|
||||
value: lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN),
|
||||
type: BOOL,
|
||||
loc,
|
||||
} as IrExpr;
|
||||
if (leftType?.kind !== "array") return null;
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [{ kind: "exprStmt", expr: lowerer.lowerExpr(expr.left), loc }],
|
||||
result: { kind: "boolLit", value: true, type: BOOL, loc },
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
const symbol = lowerer.resolveValueSymbol(expr.right);
|
||||
const decl = symbol?.valueDeclaration;
|
||||
let info: ClassInfo | null = null;
|
||||
if (decl !== undefined && ts.isClassLikeDeclaration(decl)) {
|
||||
info = arrayClassOfDecl(lowerer, decl);
|
||||
} else {
|
||||
// A binding that holds a class value (`const C = class extends Array
|
||||
// {}`): its static class, the only class it can hold while nothing
|
||||
// extends that class.
|
||||
const stored = storedClassValueType(lowerer, expr.right);
|
||||
const held = stored?.kind === "classval" ? lowerer.classes.get(stored.className) : undefined;
|
||||
if (held === undefined || arrayRootOf(held) === null) return null;
|
||||
if (held.subclasses.length > 0)
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
expr,
|
||||
"'instanceof' against a binding holding a class that extends Array and has subclasses",
|
||||
);
|
||||
info = held;
|
||||
}
|
||||
if (info === null) return null;
|
||||
const value = lowerer.lowerExpr(expr.left);
|
||||
if (value.type.kind === "array")
|
||||
return { kind: "instanceOf", value, className: info.def.name, type: BOOL, loc };
|
||||
if (value.type.kind === "dyn")
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "array.dynIsClass",
|
||||
args: [value, { kind: "strLit", value: info.def.name, type: STRING, loc }],
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
if (value.type.kind === "object" && value.type.className === info.def.name)
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [{ kind: "exprStmt", expr: value, loc }],
|
||||
result: { kind: "boolLit", value: true, type: BOOL, loc },
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
/** The class declared `extends Array` that a value of `type` is an instance
|
||||
* of, or null. */
|
||||
export function arrayClassOfType(lowerer: Lowerer, type: ts.Type): ClassInfo | null {
|
||||
const decl = type.getSymbol()?.valueDeclaration;
|
||||
return decl !== undefined && ts.isClassLikeDeclaration(decl)
|
||||
? arrayClassOfDecl(lowerer, decl)
|
||||
: null;
|
||||
}
|
||||
|
||||
/** `x.constructor` on an instance of a class declared `extends Array`: the
|
||||
* class (the class has no subclasses, so the static type names it). */
|
||||
export function lowerArrayClassConstructorRead(
|
||||
lowerer: Lowerer,
|
||||
access: ts.PropertyAccessExpression,
|
||||
loc: SrcLoc,
|
||||
): IrExpr | null {
|
||||
if (access.name.text !== "constructor") return null;
|
||||
const info = arrayClassOfType(lowerer, lowerer.typeOf(access.expression));
|
||||
if (info === null) return null;
|
||||
const value = lowerer.lowerExpr(access.expression);
|
||||
const cls = lowerer.classValueRef(info, access);
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [{ kind: "exprStmt", expr: value, loc }],
|
||||
result: cls,
|
||||
type: cls.type,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** `%C.arrayFields(a)`: the own fields of an instance of a class declared
|
||||
* `extends Array` as a dyn object, in declaration order (the descriptor's
|
||||
* `fields`, which util.inspect and Object.keys read). Fields whose type
|
||||
* has no dyn form are left out. Null when the class has no such field. */
|
||||
export function arrayClassFieldsFn(lowerer: Lowerer, info: ClassInfo): IrFunction | null {
|
||||
const elem = arrayRootOf(info)!.extendsArray!.elem;
|
||||
const arrT = arrayOf(elem);
|
||||
const loc = info.def.loc;
|
||||
const fields = info.def.fields.filter(
|
||||
(f) => !f.name.startsWith("%") && !f.name.startsWith("#") && lowerer.dynConvertible(f.type),
|
||||
);
|
||||
if (fields.length === 0) return null;
|
||||
const name = `%${info.def.name}.arrayFields`;
|
||||
const self = (): IrExpr =>
|
||||
arrayClassView(info, { kind: "varRef", localId: "a.0", type: arrT, loc }, loc);
|
||||
const value: IrExpr = {
|
||||
kind: "dynObjLit",
|
||||
fields: fields.map((f) => ({
|
||||
key: { kind: "strLit", value: f.name, type: STRING, loc },
|
||||
value: lowerer.coerceToExpected(
|
||||
{
|
||||
kind: "fieldGet",
|
||||
obj: self(),
|
||||
className: info.def.name,
|
||||
field: f.name,
|
||||
type: f.type,
|
||||
loc,
|
||||
},
|
||||
DYN,
|
||||
),
|
||||
})),
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
return {
|
||||
name,
|
||||
params: [{ localId: "a.0", name: "a", type: arrT }],
|
||||
returnType: DYN,
|
||||
locals: [{ id: "a.0", name: "a", type: arrT, mutable: false }],
|
||||
body: [{ kind: "return", value, loc }],
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** `%C.species(n, withLength)`: `new C(n)`, or `new C()` without
|
||||
* `withLength`, as ArraySpeciesCreate, Array.of and Array.from call it (the
|
||||
* descriptor's `species`). Null when the constructor's parameters cannot
|
||||
* take those arguments: they must be none, a forwarding rest (dyn or
|
||||
* number), or a number first parameter whose others may be omitted (whose
|
||||
* no-argument form throws). */
|
||||
/** A constructor parameter the species length argument can fill: a
|
||||
* number, a checked-dynamic value, or a union with a number arm
|
||||
* (`length?: number`). */
|
||||
function takesLength(lowerer: Lowerer, type: IrType): boolean {
|
||||
if (type.kind === "f64" || type.kind === "dyn") return true;
|
||||
if (type.kind !== "union") return false;
|
||||
return lowerer.unions.get(type.unionId)?.arms.some((arm) => arm.kind === "f64") === true;
|
||||
}
|
||||
|
||||
export function arrayClassSpeciesFn(lowerer: Lowerer, info: ClassInfo): IrFunction | null {
|
||||
const elem = arrayRootOf(info)!.extendsArray!.elem;
|
||||
const arrT = arrayOf(elem);
|
||||
const loc = info.def.loc;
|
||||
const n = (): IrExpr => ({ kind: "varRef", localId: "n.0", type: F64, loc });
|
||||
const params = info.ctorParams;
|
||||
// The constructor arguments with the length and without it (null: that
|
||||
// form throws a TypeError).
|
||||
let withLength: IrExpr[] | null = null;
|
||||
let without: IrExpr[] | null = null;
|
||||
if (params.length === 0) {
|
||||
withLength = [];
|
||||
without = [];
|
||||
} else if (params.length === 1 && params[0]!.mode === "dynRest") {
|
||||
withLength = [
|
||||
{
|
||||
kind: "dynArrLit",
|
||||
elems: [{ kind: "dynFrom", value: n(), type: DYN, loc }],
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
];
|
||||
without = [{ kind: "dynArrLit", elems: [], type: DYN, loc }];
|
||||
} else if (
|
||||
params.length === 1 &&
|
||||
params[0]!.mode === "rest" &&
|
||||
params[0]!.type.kind === "array" &&
|
||||
params[0]!.type.elem.kind === "f64"
|
||||
) {
|
||||
withLength = [{ kind: "arrayLit", elems: [n()], type: params[0]!.type, loc } as IrExpr];
|
||||
without = [{ kind: "arrayLit", elems: [], type: params[0]!.type, loc } as IrExpr];
|
||||
} else if (
|
||||
(params[0]!.mode === "required" || params[0]!.mode === "omittable") &&
|
||||
takesLength(lowerer, params[0]!.type) &&
|
||||
params.slice(1).every((p) => p.mode === "omittable")
|
||||
) {
|
||||
// An omitted argument is what a call without it passes: the
|
||||
// parameter's default, or undefined.
|
||||
const blame = info.decl ?? null;
|
||||
const omitted = (p: (typeof params)[number]): IrExpr =>
|
||||
p.callDefault ??
|
||||
(blame
|
||||
? lowerer.undefinedArgFor(p.type, loc, blame)
|
||||
: lowerer.coerceToExpected(
|
||||
{ kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } as IrExpr,
|
||||
p.type,
|
||||
));
|
||||
const rest = params.slice(1).map(omitted);
|
||||
// A dyn first parameter (a JavaScript `constructor(size)`) and an
|
||||
// optional or defaulted one take the length as a number, and their
|
||||
// omitted value in the no-argument form.
|
||||
if (params[0]!.type.kind === "dyn" || params[0]!.mode === "omittable")
|
||||
without = [
|
||||
params[0]!.type.kind === "dyn" ? dynUndefinedExpr(loc) : omitted(params[0]!),
|
||||
...rest,
|
||||
];
|
||||
withLength = [
|
||||
params[0]!.type.kind === "dyn"
|
||||
? { kind: "dynFrom", value: n(), type: DYN, loc }
|
||||
: lowerer.coerceToExpected(n(), params[0]!.type),
|
||||
...params.slice(1).map(omitted),
|
||||
];
|
||||
}
|
||||
if (withLength === null) return null;
|
||||
const construct = (args: IrExpr[]): IrStmt => ({
|
||||
kind: "return",
|
||||
value: arrayOfClassInstance(
|
||||
info,
|
||||
{
|
||||
kind: "new",
|
||||
className: info.def.name,
|
||||
args,
|
||||
type: { kind: "object", className: info.def.name },
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
),
|
||||
loc,
|
||||
});
|
||||
const unsupported: IrStmt = {
|
||||
kind: "exprStmt",
|
||||
expr: nodeThrowExpr(
|
||||
1,
|
||||
"",
|
||||
`constructing ${info.def.jsName ?? info.def.name} without arguments is not supported yet`,
|
||||
DYN,
|
||||
loc,
|
||||
),
|
||||
loc,
|
||||
};
|
||||
return {
|
||||
name: `%${info.def.name}.species`,
|
||||
params: [
|
||||
{ localId: "n.0", name: "n", type: F64 },
|
||||
{ localId: "w.0", name: "withLength", type: BOOL },
|
||||
],
|
||||
returnType: arrT,
|
||||
locals: [
|
||||
{ id: "n.0", name: "n", type: F64, mutable: false },
|
||||
{ id: "w.0", name: "withLength", type: BOOL, mutable: false },
|
||||
],
|
||||
body: [
|
||||
{
|
||||
kind: "if",
|
||||
cond: { kind: "varRef", localId: "w.0", type: BOOL, loc },
|
||||
then: [construct(withLength)],
|
||||
else_: [without !== null ? construct(without) : unsupported],
|
||||
loc,
|
||||
},
|
||||
],
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** Whether the program declares a class that extends Array. Array methods
|
||||
* consult species only then. */
|
||||
export function programDeclaresArrayClass(lowerer: Lowerer): boolean {
|
||||
for (const info of lowerer.classes.values()) if (info.extendsArray) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** The Array methods that create their result through ArraySpeciesCreate:
|
||||
* whether the species constructor receives the result's length (map,
|
||||
* slice, splice; the others pass 0), whether the length is set after the
|
||||
* elements are defined (slice, splice, concat), and whether the method
|
||||
* calls back into the program (map, filter, flatMap), which it does after
|
||||
* the species constructor ran. */
|
||||
const SPECIES_METHODS: ReadonlyMap<
|
||||
string,
|
||||
{ resultLength: boolean; setLength: boolean; callback: boolean }
|
||||
> = new Map([
|
||||
["map", { resultLength: true, setLength: false, callback: true }],
|
||||
["filter", { resultLength: false, setLength: false, callback: true }],
|
||||
["slice", { resultLength: true, setLength: true, callback: false }],
|
||||
["splice", { resultLength: true, setLength: true, callback: false }],
|
||||
["concat", { resultLength: false, setLength: true, callback: false }],
|
||||
["flat", { resultLength: false, setLength: false, callback: false }],
|
||||
["flatMap", { resultLength: false, setLength: false, callback: true }],
|
||||
]);
|
||||
|
||||
/** An Array method call whose result an Array subclass instance creates
|
||||
* through its species (scr_arr_species_adopt): the receiver is evaluated
|
||||
* once into a hidden local that the method's lowering reads, and the
|
||||
* method's plain result passes through the species constructor when the
|
||||
* receiver is a subclass instance. Programs that declare no Array subclass
|
||||
* keep the plain lowering. */
|
||||
export function lowerWithArraySpecies(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
lower: () => IrExpr | null,
|
||||
): IrExpr | null {
|
||||
const mode = SPECIES_METHODS.get(access.name.text);
|
||||
if (mode === undefined || !programDeclaresArrayClass(lowerer)) return lower();
|
||||
const receiverType = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
||||
if (receiverType?.kind !== "array") return lower();
|
||||
const loc = locOf(call);
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
if (receiver.type.kind !== "array") return lower();
|
||||
const local = lowerer.declareHiddenLocal("%speciesReceiver", receiver.type);
|
||||
const key = stripTypeWrappers(access.expression);
|
||||
lowerer.stagedArguments.set(key, { localId: local.id, type: receiver.type });
|
||||
let result: IrExpr | null;
|
||||
try {
|
||||
result = lower();
|
||||
} finally {
|
||||
lowerer.stagedArguments.delete(key);
|
||||
}
|
||||
if (result === null || result.type.kind !== "array") return result;
|
||||
const receiverRef: IrExpr = { kind: "varRef", localId: local.id, type: receiver.type, loc };
|
||||
if (mode.callback) {
|
||||
// The species constructor runs before the first callback: the
|
||||
// arguments are evaluated in order, the receiver's length is read, the
|
||||
// instance is created (with that length for map, 0 otherwise), then
|
||||
// the method runs to that length and its elements move into the
|
||||
// instance.
|
||||
const bounded =
|
||||
result.kind === "call" &&
|
||||
result.args[0]?.kind === "varRef" &&
|
||||
result.args[0].localId === local.id
|
||||
? boundedArrayHofHelper(lowerer, result.callee)
|
||||
: null;
|
||||
if (bounded === null || result.kind !== "call")
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call,
|
||||
`'${access.name.text}' in this form when the program declares a class that extends Array`,
|
||||
);
|
||||
const stmts: IrStmt[] = [{ kind: "varDecl", localId: local.id, init: receiver, loc }];
|
||||
const args: IrExpr[] = [receiverRef];
|
||||
for (const arg of result.args.slice(1)) {
|
||||
const held = lowerer.declareHiddenLocal("%speciesArgument", arg.type);
|
||||
stmts.push({ kind: "varDecl", localId: held.id, init: arg, loc });
|
||||
args.push({ kind: "varRef", localId: held.id, type: arg.type, loc });
|
||||
}
|
||||
const bound = lowerer.declareHiddenLocal("%speciesLength", F64);
|
||||
stmts.push({
|
||||
kind: "varDecl",
|
||||
localId: bound.id,
|
||||
init: {
|
||||
kind: "arrIntrinsic",
|
||||
method: "length",
|
||||
receiver: receiverRef,
|
||||
args: [],
|
||||
type: F64,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
});
|
||||
const boundRef: IrExpr = { kind: "varRef", localId: bound.id, type: F64, loc };
|
||||
args.push(boundRef);
|
||||
const instance = lowerer.declareHiddenLocal("%speciesInstance", receiver.type);
|
||||
const length: IrExpr = mode.resultLength
|
||||
? boundRef
|
||||
: { kind: "numLit", value: 0, type: F64, loc };
|
||||
stmts.push({
|
||||
kind: "varDecl",
|
||||
localId: instance.id,
|
||||
init: {
|
||||
kind: "libCall",
|
||||
fn: "array.speciesCreate",
|
||||
args: [receiverRef, length],
|
||||
type: receiver.type,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
});
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts,
|
||||
result: {
|
||||
kind: "libCall",
|
||||
fn: "array.speciesFinish",
|
||||
args: [
|
||||
{ kind: "varRef", localId: instance.id, type: receiver.type, loc },
|
||||
{ ...result, callee: bounded, args },
|
||||
{ kind: "boolLit", value: mode.setLength, type: BOOL, loc },
|
||||
],
|
||||
type: result.type,
|
||||
loc,
|
||||
},
|
||||
type: result.type,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [{ kind: "varDecl", localId: local.id, init: receiver, loc }],
|
||||
result: {
|
||||
kind: "libCall",
|
||||
fn: "array.speciesAdopt",
|
||||
args: [
|
||||
receiverRef,
|
||||
result,
|
||||
{ kind: "numLit", value: mode.resultLength ? -1 : 0, type: F64, loc },
|
||||
{ kind: "boolLit", value: mode.setLength, type: BOOL, loc },
|
||||
],
|
||||
type: result.type,
|
||||
loc,
|
||||
},
|
||||
type: result.type,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** `C.of(...items)` and `C.from(iterable)` on a class declared `extends
|
||||
* Array` that declares neither: Array.of / Array.from's elements, in an
|
||||
* instance the class constructs (`new C(items.length)` for of, `new C()`
|
||||
* for an iterable's from), as Node's inherited statics do. */
|
||||
export function lowerArrayClassStaticCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
): IrExpr | null {
|
||||
const name = access.name.text;
|
||||
if (name !== "of" && name !== "from") return null;
|
||||
if (!ts.isIdentifier(access.expression)) return null;
|
||||
const symbol = lowerer.resolveValueSymbol(access.expression);
|
||||
const decl = symbol?.valueDeclaration;
|
||||
if (decl === undefined || !ts.isClassLikeDeclaration(decl)) return null;
|
||||
const info = arrayClassOfDecl(lowerer, decl);
|
||||
if (info === null || lowerer.findStaticOn(info, name)) return null;
|
||||
const loc = locOf(call);
|
||||
const args = call.arguments.nodes;
|
||||
let ctorLength: number;
|
||||
if (name === "of") {
|
||||
if (args.some(ts.isSpreadElement))
|
||||
lowerer.unsupported("SC1090", call, "spreading arguments into Array.of on an Array subclass");
|
||||
ctorLength = args.length;
|
||||
} else {
|
||||
const source = args[0] ? lowerer.mapTypeOf(lowerer.typeOf(args[0])) : null;
|
||||
if (
|
||||
source?.kind !== "array" &&
|
||||
source?.kind !== "string" &&
|
||||
source?.kind !== "set" &&
|
||||
source?.kind !== "map"
|
||||
)
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call,
|
||||
"Array.from on an Array subclass over a value that is not an array, string, Set or Map",
|
||||
);
|
||||
ctorLength = -1;
|
||||
}
|
||||
const plain =
|
||||
name === "of"
|
||||
? lowerArrayOfCall(lowerer, call, access, true)
|
||||
: lowerArrayFromCall(lowerer, call, access, true);
|
||||
if (plain === null || plain.type.kind !== "array") return plain;
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "array.classConstructAdopt",
|
||||
args: [
|
||||
{ kind: "strLit", value: info.def.name, type: STRING, loc },
|
||||
plain,
|
||||
{ kind: "numLit", value: ctorLength, type: F64, loc },
|
||||
],
|
||||
type: plain.type,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** A class declared `extends Array` that defines `static [Symbol.species]`
|
||||
* (Node would construct method results through that constructor). */
|
||||
export function refuseArrayClassSpecies(lowerer: Lowerer, decl: ts.ClassLikeDeclaration): void {
|
||||
for (const member of decl.members.nodes) {
|
||||
const name = member.name;
|
||||
if (
|
||||
name !== undefined &&
|
||||
ts.isComputedPropertyName(name) &&
|
||||
ts.isPropertyAccessExpression(name.expression) &&
|
||||
name.expression.name.text === "species" &&
|
||||
ts.isIdentifier(name.expression.expression) &&
|
||||
name.expression.expression.text === "Symbol"
|
||||
)
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
member,
|
||||
"Symbol.species on a class that extends Array (its methods' results construct the class itself)",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3376,3 +3376,33 @@ export function lowerTuplePredicateCall(
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** The `%arr.<method>` helper `name` (map, flatMap or filter) taking its
|
||||
* iteration bound as a last parameter: the loop runs to the bound instead
|
||||
* of the receiver's length when it starts. ArraySpeciesCreate reads the
|
||||
* receiver's length before the species constructor runs, so a constructor
|
||||
* that appends to the receiver does not extend the loop
|
||||
* (lowerWithArraySpecies). Null when `name` is no such helper. */
|
||||
export function boundedArrayHofHelper(lowerer: Lowerer, name: string): string | null {
|
||||
const info = lowerer.arrHofInfo.get(name);
|
||||
if (!info || info.method === "forEach") return null;
|
||||
const bounded = `${name}.bounded`;
|
||||
if (lowerer.liftedFnNamed(bounded)) return bounded;
|
||||
const fn = lowerer.liftedFnNamed(name);
|
||||
if (!fn) return null;
|
||||
let replaced = false;
|
||||
const body = fn.body.map((stmt): IrStmt => {
|
||||
if (replaced || stmt.kind !== "varDecl" || stmt.localId !== "n.0") return stmt;
|
||||
replaced = true;
|
||||
return { ...stmt, init: varRef("bound.0", F64, stmt.loc) };
|
||||
});
|
||||
if (!replaced) return null;
|
||||
lowerer.liftedFns.push({
|
||||
...fn,
|
||||
name: bounded,
|
||||
params: [...fn.params, { localId: "bound.0", name: "bound", type: F64 }],
|
||||
locals: [...fn.locals, { id: "bound.0", name: "bound", type: F64, mutable: false }],
|
||||
body,
|
||||
});
|
||||
return bounded;
|
||||
}
|
||||
|
||||
@@ -560,12 +560,15 @@ export function lowerArrayOfCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
/** The receiver is a class declared `extends Array` (array-classes.ts
|
||||
* constructs the result through it). */
|
||||
arrayClassReceiver = false,
|
||||
): IrExpr | null {
|
||||
if (
|
||||
call.questionDotToken ||
|
||||
access.questionDotToken ||
|
||||
access.name.text !== "of" ||
|
||||
!lowerer.isStdlibGlobal(access.expression, "Array")
|
||||
(!arrayClassReceiver && !lowerer.isStdlibGlobal(access.expression, "Array"))
|
||||
)
|
||||
return null;
|
||||
let result = lowerer.mapTypeOf(lowerer.typeOf(call));
|
||||
@@ -932,9 +935,11 @@ export function lowerArrayFromCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
/** The receiver is a class declared `extends Array` (array-classes.ts). */
|
||||
arrayClassReceiver = false,
|
||||
): IrExpr | null {
|
||||
if (call.questionDotToken || access.questionDotToken) return null;
|
||||
if (!lowerer.isStdlibGlobal(access.expression, "Array")) return null;
|
||||
if (!arrayClassReceiver && !lowerer.isStdlibGlobal(access.expression, "Array")) return null;
|
||||
if (access.name.text !== "from") return null;
|
||||
const loc = locOf(call);
|
||||
const args = call.arguments.nodes;
|
||||
|
||||
@@ -774,6 +774,12 @@ export function ensureString(lowerer: Lowerer, e: IrExpr, node: ts.Node): IrExpr
|
||||
isUnitType(a),
|
||||
) ??
|
||||
false);
|
||||
// Checked-dynamic elements: the runtime's String() over the array's
|
||||
// dyn form (its join, element by element).
|
||||
if (elem.kind === "dyn") {
|
||||
const boxed: IrExpr = { kind: "dynFrom", value: e, type: DYN, loc: e.loc };
|
||||
return { kind: "toString", operand: boxed, type: STRING, loc: e.loc };
|
||||
}
|
||||
if (
|
||||
elem.kind === "f64" ||
|
||||
elem.kind === "string" ||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { widensReducedRecords } from "../function-returns.js";
|
||||
import { widthNarrowHelper } from "../coercions/unions.js";
|
||||
/* Property reads (`a.b`): lowerExprInner's property-access branch, intrinsic and union properties, field reads and targets, and match-group reads. */
|
||||
import {
|
||||
arrayClassMemberOwner,
|
||||
arrayClassReceiver,
|
||||
lowerArrayClassConstructorRead,
|
||||
} from "../builtins/array-classes.js";
|
||||
import { indexedUnionArms, lowerIndexedUnionMember } from "../indexed-unions.js";
|
||||
import { keyedHelperName } from "../stable-names.js";
|
||||
import { builtinNamespaceValue } from "../builtin-namespaces.js";
|
||||
@@ -322,6 +327,8 @@ export function lowerPropertyAccessExpr(
|
||||
expr: ts.PropertyAccessExpression,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
const arrayClassConstructor = lowerArrayClassConstructorRead(lowerer, expr, loc);
|
||||
if (arrayClassConstructor !== null) return arrayClassConstructor;
|
||||
// `value.#x` on a checked-dynamic value, or on a checked extraction of
|
||||
// one: the field of the brand-checked receiver (checkedPrivateValue).
|
||||
if (ts.isPrivateIdentifier(expr.name)) {
|
||||
@@ -3839,6 +3846,32 @@ export function fieldTarget(
|
||||
);
|
||||
}
|
||||
}
|
||||
// A field declared by a class that extends Array, on an instance (whose
|
||||
// type maps to its array): the class object's field.
|
||||
{
|
||||
const owner = arrayClassMemberOwner(lowerer, access);
|
||||
const name = access.name.text;
|
||||
if (
|
||||
owner !== null &&
|
||||
(owner.fields.has(name) ||
|
||||
lowerer.findMethodOn(owner, `get:${name}`) ||
|
||||
lowerer.findMethodOn(owner, `set:${name}`))
|
||||
) {
|
||||
const receiver = arrayClassReceiver(
|
||||
lowerer,
|
||||
owner,
|
||||
lowerer.lowerExpr(access.expression),
|
||||
access.expression,
|
||||
);
|
||||
return classFieldTarget(
|
||||
lowerer,
|
||||
access.expression,
|
||||
{ kind: "object", className: owner.def.name },
|
||||
access.name.text,
|
||||
receiver,
|
||||
);
|
||||
}
|
||||
}
|
||||
const stored = ts.isIdentifier(access.expression)
|
||||
? (lowerer.peekLocal(access.expression)?.type ?? lowerer.globalOf(access.expression)?.type)
|
||||
: undefined;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* instanceof and in, including private-name and runtime-key in. */
|
||||
import { lowerArrayClassInstanceOf } from "../builtins/array-classes.js";
|
||||
import { dateRootOf } from "../builtins/date-classes.js";
|
||||
import { keyedHelperName } from "../stable-names.js";
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
@@ -109,6 +110,8 @@ const PRIMITIVE_WRAPPERS = ["Boolean", "Number", "String", "BigInt", "Symbol"] a
|
||||
* Folding is limited to side-effect-free operands — dropping a computed
|
||||
* operand would skip its effects, so those are rejected instead. */
|
||||
export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc: SrcLoc): IrExpr {
|
||||
const arrayClassTest = lowerArrayClassInstanceOf(lowerer, expr, loc);
|
||||
if (arrayClassTest !== null) return arrayClassTest;
|
||||
const webAssembly = lowerWebAssemblyInstanceof(lowerer, expr);
|
||||
if (webAssembly !== null) return webAssembly;
|
||||
refuseWebAssemblyUse(lowerer, expr.right);
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
arrayClassMemberOwner,
|
||||
arrayClassReceiver,
|
||||
lowerArrayClassStaticCall,
|
||||
} from "./builtins/array-classes.js";
|
||||
import { checkedPrivateMethodReceiver, privateNameClass } from "./private-brands.js";
|
||||
import { assertFunctionAliasOf } from "./lower-assert.js";
|
||||
import { keyedHelperName } from "./stable-names.js";
|
||||
@@ -1737,6 +1742,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
lowerIteratorStaticFence(lowerer, expr, expr.expression) ??
|
||||
lowerObjectStaticCall(lowerer, expr, expr.expression) ??
|
||||
lowerObjectFromEntriesCall(lowerer, expr, expr.expression) ??
|
||||
lowerArrayClassStaticCall(lowerer, expr, expr.expression) ??
|
||||
lowerArrayFromCall(lowerer, expr, expr.expression) ??
|
||||
lowerArrayFromAsyncCall(lowerer, expr, expr.expression) ??
|
||||
lowerArrayOfCall(lowerer, expr, expr.expression) ??
|
||||
@@ -10181,6 +10187,12 @@ export function lowerObjectMethodCall(
|
||||
? probe
|
||||
: null;
|
||||
if (specializedReceiver) mappedReceiver = specializedReceiver.type;
|
||||
// A member declared by a class that extends Array, called on an instance
|
||||
// (whose type maps to its array): the class's own method.
|
||||
const arrayClassOwner =
|
||||
mappedReceiver?.kind === "array" ? arrayClassMemberOwner(lowerer, access) : null;
|
||||
if (arrayClassOwner !== null)
|
||||
mappedReceiver = { kind: "object", className: arrayClassOwner.def.name };
|
||||
if (mappedReceiver?.kind === "union") {
|
||||
const dispatched =
|
||||
lowerUnionObjectMethodCall(lowerer, call, access, mappedReceiver) ??
|
||||
@@ -10214,6 +10226,13 @@ export function lowerObjectMethodCall(
|
||||
: null;
|
||||
if (receiverIr === null) return null;
|
||||
const lowerReceiver = (): IrExpr => {
|
||||
if (arrayClassOwner !== null)
|
||||
return arrayClassReceiver(
|
||||
lowerer,
|
||||
arrayClassOwner,
|
||||
lowerer.lowerExpr(access.expression),
|
||||
access.expression,
|
||||
);
|
||||
const receiver = specializedReceiver ?? lowerer.lowerExpr(access.expression);
|
||||
const optional = lowerer.runtimeOptionalPropertyReceiver(
|
||||
access.expression,
|
||||
|
||||
@@ -7,6 +7,14 @@ import {
|
||||
lowerDateSuperMethodCall,
|
||||
} from "./builtins/date-classes.js";
|
||||
import { predeclareForwardClass } from "./lower-stmts.js";
|
||||
import {
|
||||
arrayClassBase,
|
||||
arrayClassFieldsFn,
|
||||
arrayClassSpeciesFn,
|
||||
arraySuperCall,
|
||||
refuseArrayClassSpecies,
|
||||
lowerArraySuperCall,
|
||||
} from "./builtins/array-classes.js";
|
||||
import { lowerDateStaticCall } from "./builtins/dates.js";
|
||||
import { keyedHelperName } from "./stable-names.js";
|
||||
import { lowerValueReturningConstructorNew } from "./js-dynamic-objects.js";
|
||||
@@ -607,6 +615,10 @@ export interface ClassInfo {
|
||||
* constructor's rules, and the Date methods it does not declare lower
|
||||
* over that value. Subclasses inherit the field. */
|
||||
extendsDate?: true;
|
||||
/** A class declared `extends Array` (builtins/array-classes.ts): its
|
||||
* instances are arrays of `elem` whose own fields follow the array
|
||||
* header, and its TypeScript type maps to `elem[]`. */
|
||||
extendsArray?: { elem: IrType };
|
||||
/** The synthetic LifetimeRegion class (@scriptc/experimental,
|
||||
* lifetime-regions.ts): its constructor is synthesized with the class,
|
||||
* and its methods lower at their call sites. */
|
||||
@@ -1995,6 +2007,7 @@ export function collectClassShapeInner(
|
||||
let genericBase: ts.ExpressionWithTypeArguments | undefined;
|
||||
let callableBase: ClassInfo["callableBase"];
|
||||
let extendsDate = false;
|
||||
let extendsArray: { elem: IrType } | undefined;
|
||||
let factoryBaseExpression: ts.Expression | undefined;
|
||||
const adoptCallableBase = (expression: ts.Expression): boolean => {
|
||||
if (familyMode || inst || mixin) return false;
|
||||
@@ -2195,6 +2208,13 @@ export function collectClassShapeInner(
|
||||
if (t && adoptCallableBase(t.expression)) continue;
|
||||
lowerer.unsupported("SC1090", clause, "extending computed expressions");
|
||||
}
|
||||
if ((familyMode || inst) && lowerer.isStdlibGlobal(t.expression, "Array"))
|
||||
lowerer.unsupported("SC1090", t, "a generic class that extends Array");
|
||||
if (!familyMode && !inst && !mixin && lowerer.isStdlibGlobal(t.expression, "Array")) {
|
||||
extendsArray = arrayClassBase(lowerer, t);
|
||||
refuseArrayClassSpecies(lowerer, decl);
|
||||
continue;
|
||||
}
|
||||
if (!familyMode && !inst && !mixin && lowerer.isStdlibGlobal(t.expression, "Date")) {
|
||||
if (t.typeArguments?.nodes) lowerer.unsupported("SC1090", t, "extending generic classes");
|
||||
extendsDate = true;
|
||||
@@ -2295,6 +2315,12 @@ export function collectClassShapeInner(
|
||||
continue;
|
||||
}
|
||||
if (t.typeArguments?.nodes) lowerer.unsupported("SC1090", t, "extending generic classes");
|
||||
if (named?.extendsArray)
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
t,
|
||||
`extending '${t.expression.text}', a class that extends Array (one level of Array subclass compiles)`,
|
||||
);
|
||||
base = named;
|
||||
if (!base) {
|
||||
if (adoptCallableBase(t.expression)) continue;
|
||||
@@ -4416,7 +4442,7 @@ export function collectClassShapeInner(
|
||||
ctor && !mixinForwarding
|
||||
? lowerer.paramShapes(ctor.parameters.nodes)
|
||||
: (base?.ctorParams ??
|
||||
(callableBase || extendsDate ? [{ type: DYN, mode: "dynRest" }] : []));
|
||||
(callableBase || extendsDate || extendsArray ? [{ type: DYN, mode: "dynRest" }] : []));
|
||||
if (
|
||||
ctor &&
|
||||
!mixinForwarding &&
|
||||
@@ -4482,6 +4508,13 @@ export function collectClassShapeInner(
|
||||
return am.length > 0 ? { abstractMethods: am } : {};
|
||||
})(),
|
||||
...(inst ? { genericOf: inst.family.def.name } : {}),
|
||||
...(extendsArray
|
||||
? {
|
||||
arrayElem: extendsArray.elem,
|
||||
arrayFields: `%${className}.arrayFields`,
|
||||
arraySpecies: `%${className}.species`,
|
||||
}
|
||||
: {}),
|
||||
loc: locOf(decl),
|
||||
},
|
||||
fields,
|
||||
@@ -4498,6 +4531,7 @@ export function collectClassShapeInner(
|
||||
...(genericBase ? { genericBase } : {}),
|
||||
...(callableBase ? { callableBase } : {}),
|
||||
...(extendsDate ? { extendsDate: true as const } : {}),
|
||||
...(extendsArray ? { extendsArray } : {}),
|
||||
...(factoryBaseExpression ? { factoryBaseExpression } : {}),
|
||||
subclasses: [],
|
||||
throwingSetters,
|
||||
@@ -7995,6 +8029,12 @@ export function lowerClassMembers(lowerer: Lowerer, info: ClassInfo): IrFunction
|
||||
const diagsBefore = lowerer.diags.length;
|
||||
try {
|
||||
out.push(lowerer.lowerClassCtor(info));
|
||||
if (info.extendsArray) {
|
||||
const fields = arrayClassFieldsFn(lowerer, info);
|
||||
if (fields) out.push(fields);
|
||||
const species = arrayClassSpeciesFn(lowerer, info);
|
||||
if (species) out.push(species);
|
||||
}
|
||||
} catch (e) {
|
||||
if (!(e instanceof PoisonError)) throw e;
|
||||
if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) {
|
||||
@@ -8043,6 +8083,10 @@ export function lowerClassMembers(lowerer: Lowerer, info: ClassInfo): IrFunction
|
||||
* the base's signature — forward every param to super(), then run own
|
||||
* field initializers. */
|
||||
export function lowerClassCtor(lowerer: Lowerer, info: ClassInfo): IrFunction {
|
||||
if (info.extendsArray) {
|
||||
lowerer.noteEdge(`%${info.def.name}.arrayFields`);
|
||||
lowerer.noteEdge(`%${info.def.name}.species`);
|
||||
}
|
||||
const cached = info.localClass?.bodies.get(`%${info.def.name}.constructor`);
|
||||
if (cached) return cached;
|
||||
return withInstanceBindings(lowerer, info, () => lowerClassCtorInner(lowerer, info));
|
||||
@@ -8110,7 +8154,7 @@ function lowerClassCtorInner(lowerer: Lowerer, info: ClassInfo): IrFunction {
|
||||
lowerer.ctx.argumentsLocal = local;
|
||||
}
|
||||
body.push(...declared.prologue);
|
||||
if (!ctorBase && !info.callableBase && !info.extendsDate) {
|
||||
if (!ctorBase && !info.callableBase && !info.extendsDate && !info.extendsArray) {
|
||||
// Node's base-class order: field initializers run at the start
|
||||
// of construction, the parameter-property assignments open the
|
||||
// constructor body (probed — a field initializer reading a
|
||||
@@ -8122,7 +8166,7 @@ function lowerClassCtorInner(lowerer: Lowerer, info: ClassInfo): IrFunction {
|
||||
body.push(...lowerer.lowerDerivedCtorBody(info, thisLocal));
|
||||
}
|
||||
} else {
|
||||
if (ctorBase || info.callableBase || info.extendsDate) {
|
||||
if (ctorBase || info.callableBase || info.extendsDate || info.extendsArray) {
|
||||
// Synthetic forwarding params (the inherited ABI signature).
|
||||
// Nothing references them by symbol — only the super call below,
|
||||
// which forwards the already-completed values UNCHANGED (defaults
|
||||
@@ -9169,7 +9213,9 @@ export function lowerDerivedCtorBody(
|
||||
loc,
|
||||
},
|
||||
];
|
||||
if (info.callableBase || info.extendsDate) {
|
||||
if (info.extendsArray) {
|
||||
statements.push(...lowerArraySuperCall(lowerer, info, thisLocal, superCall));
|
||||
} else if (info.callableBase || info.extendsDate) {
|
||||
if (superCall.arguments.nodes.some(ts.isSpreadElement))
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
@@ -9362,6 +9408,7 @@ export function superCallStmt(
|
||||
loc,
|
||||
};
|
||||
if (info.extendsDate) return dateSuperCall(thisRef, info, args[0]!, loc);
|
||||
if (info.extendsArray) return arraySuperCall(info, thisRef, args[0]!, loc);
|
||||
if (info.callableBase)
|
||||
return {
|
||||
kind: "exprStmt",
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
arrayClassMemberOwner,
|
||||
arrayOfClassInstance,
|
||||
arrayRootOf,
|
||||
} from "./builtins/array-classes.js";
|
||||
import { keyedHelperName } from "./stable-names.js";
|
||||
import {
|
||||
isNewTarget,
|
||||
@@ -509,7 +514,23 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
// noImplicitThis, so the generic scope walk here is preflight-safe.
|
||||
const local = lowerer.resolveThis(expr);
|
||||
if (local) {
|
||||
const value: IrExpr = { kind: "varRef", localId: local.id, type: local.type, loc };
|
||||
let value: IrExpr = { kind: "varRef", localId: local.id, type: local.type, loc };
|
||||
// `this` in a class declared `extends Array` is the array, except as
|
||||
// the receiver of a member the class declares.
|
||||
if (local.type.kind === "object") {
|
||||
const arrayClass = lowerer.classes.get(local.type.className);
|
||||
const parent = expr.parent;
|
||||
if (
|
||||
arrayClass &&
|
||||
arrayRootOf(arrayClass) &&
|
||||
!(
|
||||
ts.isPropertyAccessExpression(parent) &&
|
||||
parent.expression === expr &&
|
||||
arrayClassMemberOwner(lowerer, parent) !== null
|
||||
)
|
||||
)
|
||||
value = arrayOfClassInstance(arrayClass, value, loc);
|
||||
}
|
||||
// `this.#x` in a function-local class's prototype method, whose `this`
|
||||
// is the call-site value: the field lives on the native instance of
|
||||
// this evaluation (checkedPrivateReceiver).
|
||||
@@ -585,7 +606,24 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
// any other super position (field reads, bare references) stays out.
|
||||
lowerer.unsupported("SC1090", expr, "'super' outside super() and super.method() calls");
|
||||
}
|
||||
if (ts.isNewExpression(expr)) return lowerer.lowerNew(expr);
|
||||
if (ts.isNewExpression(expr)) {
|
||||
const value = lowerer.lowerNew(expr);
|
||||
// `new X()` for a class declared `extends Array` is the array, except
|
||||
// as the receiver of a member the class declares (as `this` above).
|
||||
if (value.type.kind !== "object") return value;
|
||||
const arrayClass = lowerer.classes.get(value.type.className);
|
||||
if (!arrayClass || !arrayRootOf(arrayClass)) return value;
|
||||
let operand: ts.Expression = expr;
|
||||
while (ts.isParenthesizedExpression(operand.parent)) operand = operand.parent;
|
||||
const parent = operand.parent;
|
||||
if (
|
||||
ts.isPropertyAccessExpression(parent) &&
|
||||
parent.expression === operand &&
|
||||
arrayClassMemberOwner(lowerer, parent) !== null
|
||||
)
|
||||
return value;
|
||||
return arrayOfClassInstance(arrayClass, value, loc);
|
||||
}
|
||||
if (ts.isAwaitExpression(expr)) {
|
||||
if (!lowerer.ctx.isAsync) {
|
||||
// A tsc-clean occurrence here is outside both an async function
|
||||
|
||||
@@ -966,6 +966,12 @@ export function moduleArtifacts(
|
||||
visitClass("%Readable");
|
||||
visitClass("%Writable");
|
||||
}
|
||||
// A class declared `extends Array` named by its instanceof test
|
||||
// or by X.of / X.from.
|
||||
if (node.fn === "array.dynIsClass" && node.args[1]?.kind === "strLit")
|
||||
visitClass(node.args[1].value);
|
||||
if (node.fn === "array.classConstructAdopt" && node.args[0]?.kind === "strLit")
|
||||
visitClass(node.args[0].value);
|
||||
break;
|
||||
case "dynFrom":
|
||||
if (boxedChild(node.value.type)) {
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { unionNarrowFailureMessage } from "./union-narrow.js";
|
||||
import {
|
||||
arrayClassFieldsFn,
|
||||
arrayClassSpeciesFn,
|
||||
arrayOfClassInstance,
|
||||
arrayRootOf,
|
||||
lowerWithArraySpecies,
|
||||
} from "./builtins/array-classes.js";
|
||||
import {
|
||||
compactDeclOrigins,
|
||||
inPackageFile,
|
||||
@@ -8954,6 +8961,20 @@ export class Lowerer {
|
||||
lower: () =>
|
||||
info.localClass && !info.localClass.ready ? null : this.lowerClassCtor(info),
|
||||
});
|
||||
// The descriptor helpers of a class that extends Array, reached
|
||||
// from its constructor.
|
||||
if (info.extendsArray) {
|
||||
units.set(`%${cName}.arrayFields`, {
|
||||
order: unitOrder++,
|
||||
roots: [],
|
||||
lower: () => arrayClassFieldsFn(this, info),
|
||||
});
|
||||
units.set(`%${cName}.species`, {
|
||||
order: unitOrder++,
|
||||
roots: [],
|
||||
lower: () => arrayClassSpeciesFn(this, info),
|
||||
});
|
||||
}
|
||||
for (const { mName, member } of this.classMethodMembers(info)) {
|
||||
units.set(`%${cName}.${mName}`, {
|
||||
order: unitOrder++,
|
||||
@@ -9082,6 +9103,10 @@ export class Lowerer {
|
||||
register(`%${cName}.constructor`, classCtorRoots(info), () =>
|
||||
info.localClass && !info.localClass.ready ? null : this.lowerClassCtor(info),
|
||||
);
|
||||
if (info.extendsArray) {
|
||||
register(`%${cName}.arrayFields`, [], () => arrayClassFieldsFn(this, info));
|
||||
register(`%${cName}.species`, [], () => arrayClassSpeciesFn(this, info));
|
||||
}
|
||||
for (const { mName, member } of this.classMethodMembers(info)) {
|
||||
register(`%${cName}.${mName}`, functionRoots(member), () =>
|
||||
info.localClass && !info.localClass.ready
|
||||
@@ -10770,6 +10795,18 @@ export class Lowerer {
|
||||
const result = this.coerceToExpected(expr.result, expected);
|
||||
return { ...expr, result, type: result.type };
|
||||
}
|
||||
// An instance of a class declared `extends Array` is its array (the
|
||||
// same object); an array slot of another element type converts it as
|
||||
// an array.
|
||||
if (expr.type.kind === "object" && expected.kind !== "object") {
|
||||
const arrayClass = this.classes.get(expr.type.className);
|
||||
if (arrayClass && arrayRootOf(arrayClass)) {
|
||||
const asArray = arrayOfClassInstance(arrayClass, expr, expr.loc);
|
||||
return typeEquals(asArray.type, expected)
|
||||
? asArray
|
||||
: this.coerceToExpected(asArray, expected);
|
||||
}
|
||||
}
|
||||
// A destination that holds undefined takes an unassigned deferred-init
|
||||
// field's slot as is (Node passes the undefined along).
|
||||
if (expected.kind === "union" && this.armTag(expected.unionId, UNDEFINED_T) >= 0) {
|
||||
@@ -14768,7 +14805,9 @@ export class Lowerer {
|
||||
call: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
): IrExpr | null {
|
||||
return lowerArrayMethodCall(this, call, access);
|
||||
return lowerWithArraySpecies(this, call, access, () =>
|
||||
lowerArrayMethodCall(this, call, access),
|
||||
);
|
||||
}
|
||||
|
||||
lowerMapMethodCall(call: ts.CallExpression, access: ts.PropertyAccessExpression): IrExpr | null {
|
||||
|
||||
@@ -1810,6 +1810,36 @@ export function isFetchHandleType(
|
||||
return handle;
|
||||
}
|
||||
|
||||
/** The array type a class declared `extends Array` maps to (its base type,
|
||||
* `T[]`), or null for every other class. */
|
||||
function arrayClassBaseType(
|
||||
classDecl: ts.ClassLikeDeclaration,
|
||||
instance: ts.Type,
|
||||
ctx: TypeMapperCtx,
|
||||
): IrType | null {
|
||||
const heritage = classDecl.heritageClauses?.nodes.find(
|
||||
(clause) => clause.token === ts.SyntaxKind.ExtendsKeyword,
|
||||
)?.types?.nodes[0];
|
||||
if (heritage === undefined || !ts.isIdentifier(heritage.expression)) return null;
|
||||
if (heritage.expression.text !== "Array") return null;
|
||||
// The polymorphic `this` type of the class names the class's own type.
|
||||
const symbol = instance.getSymbol();
|
||||
const declared =
|
||||
(instance.flags & ts.TypeFlags.TypeParameter) !== 0 && symbol !== undefined
|
||||
? ctx.checker.getDeclaredTypeOfSymbol(symbol)
|
||||
: instance;
|
||||
if ((declared.flags & ts.TypeFlags.Object) === 0) return null;
|
||||
const target = declared.isTypeReference() ? (declared.getTarget() ?? declared) : declared;
|
||||
const bases = ctx.checker.getBaseTypes(target as ts.InterfaceType);
|
||||
const base = bases[0];
|
||||
if (base === undefined || !ctx.checker.isArrayType(base)) return null;
|
||||
const mapped = mapType(base, ctx);
|
||||
if (mapped?.kind === "array") return mapped;
|
||||
// `extends Array` in JavaScript reads `any[]`: dyn elements (the class's
|
||||
// arrayClassBase).
|
||||
return isJsSourceFile(ts.getSourceFileOfNode(classDecl)) ? { kind: "array", elem: DYN } : null;
|
||||
}
|
||||
|
||||
export function mapType(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
|
||||
if (mapTypeDepth >= MAP_TYPE_MAX_DEPTH) return null;
|
||||
// These intrinsic domains cannot depend on generic bindings, declaration
|
||||
@@ -2615,6 +2645,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
|
||||
return viaMixin;
|
||||
}
|
||||
}
|
||||
// A class declared `extends Array` (frontend/lowering/builtins/
|
||||
// array-classes.ts): its instances are arrays of the base's elements.
|
||||
{
|
||||
const arrayBase = arrayClassBaseType(classDecl, widened, ctx);
|
||||
if (arrayBase !== null) return arrayBase;
|
||||
}
|
||||
if (classExprNeedsContext(classDecl)) {
|
||||
const local = ctx.localClassInstance?.(classDecl, widened);
|
||||
if (local) contextResolutions++;
|
||||
|
||||
@@ -4040,6 +4040,32 @@ function buildLibFnSignatures() {
|
||||
// The string keys `for...in` visits on an array (args[0], any element
|
||||
// type) after its indices. Borrowed; never throws.
|
||||
"array.namedKeys": { argTypes: [null], result: arrayOf(STRING) },
|
||||
/** super(...) in a class declared `extends Array`: the instance, the
|
||||
* arguments (an array of its element type, or the implicit constructor's
|
||||
* dyn argument array) and whether the elements are dyn values. */
|
||||
"array.subclassConstruct": { argTypes: [null, null, BOOL], result: VOID },
|
||||
"array.subclassConstructDyn": { argTypes: [null, DYN, BOOL], result: VOID },
|
||||
/** ArraySpeciesCreate over an Array method's plain result: the receiver
|
||||
* (borrowed), the result (moved), the species constructor's length
|
||||
* argument (-1: the result's length) and whether to set the length. The
|
||||
* result has the second argument's type. */
|
||||
"array.speciesAdopt": { argTypes: [null, null, F64, BOOL], result: VOID },
|
||||
/** ArraySpeciesCreate ahead of a method that calls back into the program
|
||||
* (map, filter, flatMap): the receiver (borrowed) and the species
|
||||
* constructor's length argument. The result has the receiver's type: the
|
||||
* instance, or the receiver itself when it is a plain array. */
|
||||
"array.speciesCreate": { argTypes: [null, F64], result: VOID },
|
||||
/** The method's plain result (moved) defined on the instance
|
||||
* array.speciesCreate answered (borrowed), and whether to set the length.
|
||||
* The result has the second argument's type. */
|
||||
"array.speciesFinish": { argTypes: [null, null, BOOL], result: VOID },
|
||||
/** Array.of / Array.from on a class declared `extends Array`: the class
|
||||
* (its name, a literal), the plain result (moved) and the constructor's
|
||||
* length argument (negative: no argument). */
|
||||
"array.classConstructAdopt": { argTypes: [STRING, null, F64], result: VOID },
|
||||
/** `value instanceof C` for a dyn value and a class that extends Array
|
||||
* (its name, a literal). */
|
||||
"array.dynIsClass": { argTypes: [DYN, STRING], result: BOOL },
|
||||
// The value of the foreign element (SCR_ARR_FOREIGN) at args[1] of an
|
||||
// array (args[0], any element type); callers test the element state
|
||||
// first. Borrowed; never throws.
|
||||
|
||||
@@ -157,6 +157,8 @@ const MODULE_FIELDS: Record<StringFields<Omit<IrModule, "functions">>, Role> = {
|
||||
"classes[].errorNameAfter": "other",
|
||||
"classes[].prototypeDataHelper": "reference",
|
||||
"classes[].instancePrototypeHelper": "reference",
|
||||
"classes[].arrayFields": "reference",
|
||||
"classes[].arraySpecies": "reference",
|
||||
"classes[].stringTagHelper": "reference",
|
||||
"classes[].localCaptures[].name": "other",
|
||||
"classes[].localCaptures[].localId": "other",
|
||||
@@ -285,6 +287,14 @@ const REFERENCE_FIXTURES: Record<string, () => IrModule> = {
|
||||
...module(),
|
||||
classes: [{ name: "%m0.Cell", fields: [], instancePrototypeHelper: TARGET, loc }],
|
||||
}),
|
||||
"classes[].arrayFields": () => ({
|
||||
...module(),
|
||||
classes: [{ name: "%m0.Row", fields: [], arrayElem: F64, arrayFields: TARGET, loc }],
|
||||
}),
|
||||
"classes[].arraySpecies": () => ({
|
||||
...module(),
|
||||
classes: [{ name: "%m0.Row", fields: [], arrayElem: F64, arraySpecies: TARGET, loc }],
|
||||
}),
|
||||
"classes[].stringTagHelper": () => ({
|
||||
...module(),
|
||||
classes: [{ name: "%m0.Cell", fields: [], stringTagHelper: TARGET, loc }],
|
||||
|
||||
@@ -24,6 +24,8 @@ export function moduleFunctionReferences(mod: IrModule): string[] {
|
||||
for (const cls of mod.classes ?? []) {
|
||||
if (cls.prototypeDataHelper !== undefined) names.push(cls.prototypeDataHelper);
|
||||
if (cls.instancePrototypeHelper !== undefined) names.push(cls.instancePrototypeHelper);
|
||||
if (cls.arrayFields !== undefined) names.push(cls.arrayFields);
|
||||
if (cls.arraySpecies !== undefined) names.push(cls.arraySpecies);
|
||||
if (cls.stringTagHelper !== undefined) names.push(cls.stringTagHelper);
|
||||
}
|
||||
return names;
|
||||
|
||||
@@ -1537,6 +1537,17 @@ export interface IrClassDef {
|
||||
* RUNTIME_ERROR_CLASSES). User subclasses are ordinary emitted classes
|
||||
* whose layout prefix embeds ScrError's fields. */
|
||||
runtime?: true;
|
||||
/** A class declared `extends Array`: its instances are arrays of this
|
||||
* element type. The struct begins with the runtime's array header (the
|
||||
* reference count is the array's, field 0) and the class's fields follow
|
||||
* it; the runtime finds the class through the array's ext. */
|
||||
arrayElem?: IrType;
|
||||
/** The function (array) => dyn listing such a class's own fields
|
||||
* (ScrArrClass.fields); absent when it declares none. */
|
||||
arrayFields?: string;
|
||||
/** The function (length) => array constructing such a class for
|
||||
* ArraySpeciesCreate (ScrArrClass.species). */
|
||||
arraySpecies?: string;
|
||||
/** Base class name (single inheritance, `extends`). A class is IN A
|
||||
* HIERARCHY when it has a base or is some class's base; hierarchy classes
|
||||
* carry a vtable word after `rc` (backends), standalone classes are laid
|
||||
|
||||
@@ -140,6 +140,7 @@ export function everyModuleNode(mod: IrModule, visitor: IrModuleVisitor): boolea
|
||||
for (const cls of mod.classes ?? []) {
|
||||
for (const capture of cls.localCaptures ?? []) if (!type(capture.type, cls.loc)) return false;
|
||||
for (const field of cls.fields) if (!type(field.type, cls.loc)) return false;
|
||||
if (cls.arrayElem !== undefined && !type(cls.arrayElem, cls.loc)) return false;
|
||||
}
|
||||
for (const global of mod.globals ?? [])
|
||||
if (!type(global.type, global.source?.loc ?? entryLoc)) return false;
|
||||
|
||||
@@ -3026,6 +3026,18 @@ function validateFunction(
|
||||
}
|
||||
break;
|
||||
}
|
||||
// An instance of a class declared `extends Array` and its array
|
||||
// are the same object: upcast to the array, downcast back.
|
||||
{
|
||||
const instance = e.kind === "upcast" ? e.value.type : e.type;
|
||||
const array = e.kind === "upcast" ? e.type : e.value.type;
|
||||
if (instance.kind === "object" && array.kind === "array") {
|
||||
const elem = classes.get(instance.className)?.arrayElem;
|
||||
if (elem === undefined || !typeEquals(elem, array.elem))
|
||||
err(`${e.kind}: "${instance.className}" is not an array of that element type`, e.loc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (e.type.kind !== "object" || e.value.type.kind !== "object") {
|
||||
err(`${e.kind} between non-class types`, e.loc);
|
||||
break;
|
||||
@@ -3133,6 +3145,15 @@ function validateFunction(
|
||||
case "instanceOf": {
|
||||
checkExpr(e.value);
|
||||
if (e.type.kind !== "bool") err("instanceOf must be bool", e.loc);
|
||||
// An array against a class declared `extends Array` (its ext).
|
||||
if (e.value.type.kind === "array") {
|
||||
if (classes.get(e.className)?.arrayElem === undefined)
|
||||
err(
|
||||
`instanceOf of an array against "${e.className}", which does not extend Array`,
|
||||
e.loc,
|
||||
);
|
||||
break;
|
||||
}
|
||||
if (!hierarchy.has(e.className)) {
|
||||
err(`instanceOf against non-hierarchy class "${e.className}"`, e.loc);
|
||||
}
|
||||
@@ -4223,6 +4244,41 @@ function validateFunction(
|
||||
}
|
||||
|
||||
function checkLibValueCall(e: IrExpr & { kind: "libCall" }): boolean {
|
||||
// ArraySpeciesCreate over an Array method's result, and Array.of /
|
||||
// Array.from on a class that extends Array: the result has the plain
|
||||
// result's array type.
|
||||
if (e.fn === "array.dynIsClass") {
|
||||
const cls = e.args[1]?.kind === "strLit" ? classes.get(e.args[1].value) : undefined;
|
||||
if (cls?.arrayElem === undefined)
|
||||
err("libCall array.dynIsClass must name a class that extends Array", e.loc);
|
||||
return false;
|
||||
}
|
||||
if (e.fn === "array.speciesCreate") {
|
||||
if (e.args[0]?.type.kind !== "array" || !typeEquals(e.type, e.args[0].type))
|
||||
err("libCall array.speciesCreate must have its receiver's array type", e.loc);
|
||||
return true;
|
||||
}
|
||||
if (e.fn === "array.speciesFinish") {
|
||||
const plain = e.args[1];
|
||||
if (plain?.type.kind !== "array" || !typeEquals(e.type, plain.type))
|
||||
err("libCall array.speciesFinish must have its array argument's type", e.loc);
|
||||
if (e.args[0]?.type.kind !== "array")
|
||||
err("libCall array.speciesFinish instance must be an array", e.loc);
|
||||
return true;
|
||||
}
|
||||
if (e.fn === "array.speciesAdopt" || e.fn === "array.classConstructAdopt") {
|
||||
const plain = e.args[1];
|
||||
if (plain?.type.kind !== "array" || !typeEquals(e.type, plain.type))
|
||||
err(`libCall ${e.fn} must have its array argument's type`, e.loc);
|
||||
if (e.fn === "array.speciesAdopt" && e.args[0]?.type.kind !== "array")
|
||||
err("libCall array.speciesAdopt receiver must be an array", e.loc);
|
||||
if (e.fn === "array.classConstructAdopt") {
|
||||
const cls = e.args[0]?.kind === "strLit" ? classes.get(e.args[0].value) : undefined;
|
||||
if (cls?.arrayElem === undefined)
|
||||
err("libCall array.classConstructAdopt must name a class that extends Array", e.loc);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const cryptoPromise = CRYPTO_PROMISE_VALUES[e.fn];
|
||||
if (cryptoPromise) {
|
||||
const inner = e.type.kind === "promise" ? e.type.inner : undefined;
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7720-array-subclass-basics.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7721-array-subclass-species.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7722-array-subclass-zero-array.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7723-array-subclass-as-array.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7724-array-subclass-cycles.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7725-array-subclass-operations.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7726-array-subclass-expressions.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7728-array-subclass-species-order.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7730-array-subclass-retyped-trace.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7731-array-subclass-deep-release.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7732-array-subclass-species-bound.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7733-array-subclass-optional-length.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8333-array-subclass-view-writes.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8334-array-subclass-inspect-depth.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/array-subclass-fences.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/array-subclass-retyped-members.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -1669,8 +1669,8 @@ static void scr_arr_release_foreign(ScrArr *a) {
|
||||
x->foreign = false;
|
||||
}
|
||||
|
||||
static void scr_arr_destroy(void *object) {
|
||||
ScrArr *a = object;
|
||||
/* Releases an array's elements and ext contents (destroy's first half). */
|
||||
static void scr_arr_release_contents(ScrArr *a) {
|
||||
if (scr_arr_ext_of(a)) {
|
||||
scr_dyn_release(scr_arr_ext_of(a)->metadata);
|
||||
scr_arr_ext_of(a)->metadata = NULL;
|
||||
@@ -1737,10 +1737,255 @@ static void scr_arr_destroy(void *object) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void scr_arr_destroy(void *object) {
|
||||
ScrArr *a = object;
|
||||
scr_arr_release_contents(a);
|
||||
if (a->cyc) scr_arr_gc_free(a);
|
||||
else scr_arr_free(a);
|
||||
}
|
||||
|
||||
void scr_arr_subclass_init(ScrArr *a, uint8_t elem, const ScrArrOps *ops, const ScrArrClass *cls,
|
||||
bool cyc) {
|
||||
a->len = 0;
|
||||
a->cap = 0;
|
||||
a->elem = elem;
|
||||
a->cyc = cyc ? 1 : 0;
|
||||
uint32_t id = ops ? scr_arr_ops_id(ops) : 0;
|
||||
a->spare = (uint16_t)(id << SCR_ARR_OPS_SHIFT);
|
||||
a->filled = 0;
|
||||
a->data = NULL;
|
||||
ScrArrExt *x = scr_arr_ext(a);
|
||||
if (id == SCR_ARR_OPS_OVERFLOW) x->ops = ops;
|
||||
x->cls = cls;
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_live_arrays++;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* super(...args) in a class that extends Array, as the Array constructor:
|
||||
* one number argument sets the length (a RangeError for an invalid length),
|
||||
* otherwise the arguments become the elements. `args` has the instance's
|
||||
* element type and is borrowed; `dyn_elems` says its elements are dyn
|
||||
* values, whose number test reads the value. */
|
||||
void scr_arr_subclass_construct(ScrArr *a, ScrArr *args, bool dyn_elems) {
|
||||
if (args->len == 1) {
|
||||
if (args->elem == SCR_ELEM_F64 || args->elem == SCR_ELEM_I32) {
|
||||
scr_arr_set_len(a, scr_arr_get_number(args, 0));
|
||||
return;
|
||||
}
|
||||
if (dyn_elems) {
|
||||
const ScrDyn *d = scr_arr_borrow_ref(args, 0);
|
||||
if (d != NULL && d->kind == SCR_DYN_NUM) {
|
||||
scr_arr_set_len(a, d->v.num);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (args->len > 0) (void)scr_arr_push_spread(a, args);
|
||||
}
|
||||
|
||||
/* The implicit constructor of a class that extends Array forwards its
|
||||
* arguments (a dyn array) to super: the same rule, each argument converted
|
||||
* to the element type. Element types other than numbers, booleans, strings
|
||||
* and dyn values take only the length form. */
|
||||
void scr_arr_subclass_construct_dyn(ScrArr *a, const ScrDyn *args, bool dyn_elems) {
|
||||
if (args == NULL || args->kind != SCR_DYN_ARR) return;
|
||||
size_t n = args->v.arr.len;
|
||||
ScrDyn *const *items = args->v.arr.items;
|
||||
if (n == 1 && items[0] != NULL && items[0]->kind == SCR_DYN_NUM) {
|
||||
scr_arr_set_len(a, items[0]->v.num);
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
ScrDyn *d = items[i];
|
||||
if (dyn_elems) {
|
||||
(void)scr_arr_push_ref(a, scr_dyn_retain(d));
|
||||
continue;
|
||||
}
|
||||
if ((a->elem == SCR_ELEM_F64 || a->elem == SCR_ELEM_I32) && d && d->kind == SCR_DYN_NUM) {
|
||||
(void)scr_arr_push_f64(a, d->v.num);
|
||||
} else if (a->elem == SCR_ELEM_BOOL && d && d->kind == SCR_DYN_BOOL) {
|
||||
(void)scr_arr_push_bool(a, d->v.b);
|
||||
} else if (a->elem == SCR_ELEM_STR && d && d->kind == SCR_DYN_STR) {
|
||||
(void)scr_arr_push_ref(a, scr_str_retain(d->v.str));
|
||||
} else {
|
||||
static const char msg[] =
|
||||
"constructing this Array subclass with element arguments is not supported yet";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void scr_arr_set_slot(ScrArr *a, double i, uint64_t slot);
|
||||
|
||||
static ScrArr *scr_arr_species_fill(const ScrArrClass *cls, ScrArr *result, double ctor_length,
|
||||
bool with_length, bool set_length);
|
||||
static ScrArr *scr_arr_species_move(ScrArr *species, ScrArr *result, bool set_length);
|
||||
|
||||
/* Gives an Array subclass instance holding no elements (only holes) another
|
||||
* element type: its storage is dropped and its length kept. */
|
||||
static void scr_arr_retype_empty(ScrArr *a, uint8_t elem, const ScrArrOps *ops) {
|
||||
uint32_t len = a->len;
|
||||
ScrArrExt *x = scr_arr_ext_of(a);
|
||||
const ScrArrClass *cls = x->cls;
|
||||
scr_arr_free_parts(a);
|
||||
a->data = NULL;
|
||||
a->len = a->cap = a->filled = 0;
|
||||
a->elem = elem;
|
||||
uint32_t id = ops ? scr_arr_ops_id(ops) : 0;
|
||||
a->spare = (uint16_t)(id << SCR_ARR_OPS_SHIFT);
|
||||
x = scr_arr_ext(a);
|
||||
if (id == SCR_ARR_OPS_OVERFLOW) x->ops = ops;
|
||||
x->cls = cls;
|
||||
scr_arr_set_len(a, (double)len);
|
||||
}
|
||||
|
||||
ScrArr *scr_arr_species_adopt(ScrArr *recv, ScrArr *result, double ctor_length, bool set_length) {
|
||||
const ScrArrClass *cls = scr_arr_class_of(recv);
|
||||
if (cls == NULL) return result;
|
||||
if (ctor_length < 0) ctor_length = (double)result->len;
|
||||
return scr_arr_species_fill(cls, result, ctor_length, true, set_length);
|
||||
}
|
||||
|
||||
ScrArr *scr_arr_species_create(ScrArr *recv, double ctor_length) {
|
||||
const ScrArrClass *cls = scr_arr_class_of(recv);
|
||||
if (cls == NULL) return scr_arr_retain(recv);
|
||||
if (cls->species == NULL) {
|
||||
static const char msg[] =
|
||||
"this Array subclass's constructor cannot construct a species result (Symbol.species)";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return scr_arr_retain(recv);
|
||||
}
|
||||
ScrArr *species = cls->species(ctor_length, true);
|
||||
if (scr_exc_pending()) {
|
||||
scr_arr_release(species);
|
||||
return scr_arr_retain(recv);
|
||||
}
|
||||
return species;
|
||||
}
|
||||
|
||||
ScrArr *scr_arr_species_finish(ScrArr *species, ScrArr *result, bool set_length) {
|
||||
if (scr_arr_class_of(species) == NULL) return result;
|
||||
return scr_arr_species_move(scr_arr_retain(species), result, set_length);
|
||||
}
|
||||
|
||||
ScrArr *scr_arr_class_construct_adopt(const ScrArrClass *cls, ScrArr *result, double ctor_length) {
|
||||
return scr_arr_species_fill(cls, result, ctor_length < 0 ? 0 : ctor_length, ctor_length >= 0, true);
|
||||
}
|
||||
|
||||
static ScrArr *scr_arr_species_fill(const ScrArrClass *cls, ScrArr *result, double ctor_length,
|
||||
bool with_length, bool set_length) {
|
||||
if (cls->species == NULL) {
|
||||
static const char msg[] =
|
||||
"this Array subclass's constructor cannot construct a species result (Symbol.species)";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
return result;
|
||||
}
|
||||
ScrArr *species = cls->species(ctor_length, with_length);
|
||||
if (scr_exc_pending()) {
|
||||
scr_arr_release(species);
|
||||
return result;
|
||||
}
|
||||
return scr_arr_species_move(species, result, set_length);
|
||||
}
|
||||
|
||||
/* Defines the present elements of `result` (owned) on the species instance
|
||||
* `species` (owned) and answers the instance, or the result with an error
|
||||
* pending when the instance cannot take them. */
|
||||
static ScrArr *scr_arr_species_move(ScrArr *species, ScrArr *result, bool set_length) {
|
||||
if (species->elem != result->elem || scr_arr_ops(species) != scr_arr_ops(result)) {
|
||||
/* A result of another element type (map, flatMap): an instance the
|
||||
* constructor left without elements takes the result's element type;
|
||||
* its length stays. An instance whose collector header the elements
|
||||
* would need, or that already holds elements, cannot. */
|
||||
const ScrArrOps *ops = scr_arr_ops(result);
|
||||
bool traced = ops != NULL && ops->trace != NULL;
|
||||
bool empty = true;
|
||||
for (size_t i = 0; empty && i < species->len; i++) {
|
||||
uint64_t slot;
|
||||
empty = scr_arr_state_at(species, i, &slot) == SCR_ARR_HOLE;
|
||||
}
|
||||
if (!empty || (traced && !species->cyc)) {
|
||||
static const char msg[] =
|
||||
"an Array subclass's species result of another element type is not supported yet";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
|
||||
scr_arr_release(species);
|
||||
return result;
|
||||
}
|
||||
scr_arr_retype_empty(species, result->elem, ops);
|
||||
}
|
||||
for (size_t i = 0; i < result->len; i++) {
|
||||
uint64_t slot;
|
||||
uint8_t state = scr_arr_state_at(result, i, &slot);
|
||||
if (state == SCR_ARR_VALUE) scr_arr_set_slot(species, (double)i, scr_elem_retain_slot(result, slot));
|
||||
else if (state == SCR_ARR_UNDEFINED) scr_arr_set_undefined(species, (double)i);
|
||||
/* A value written through a dynamic view (scr_arr_set_foreign retains
|
||||
* it); the instance has the result's element type. */
|
||||
else if (state == SCR_ARR_FOREIGN) scr_arr_set_foreign(species, (double)i, scr_slot_to_ptr(slot));
|
||||
}
|
||||
if (set_length) scr_arr_set_len(species, (double)result->len);
|
||||
scr_arr_release(result);
|
||||
return species;
|
||||
}
|
||||
|
||||
bool scr_dyn_is_array_class(const ScrDyn *d, const ScrArrClass *cls) {
|
||||
if (d == NULL) return false;
|
||||
/* A live view of the native instance. */
|
||||
if (d->kind == SCR_DYN_TYPED_REF && d->v.typed_ref.array != NULL && !d->v.typed_ref.record &&
|
||||
!d->v.typed_ref.klass && !d->v.typed_ref.bag_class)
|
||||
return scr_arr_class_of(d->v.typed_ref.ptr) == cls;
|
||||
/* A snapshot: scr_arr_copy_metadata names the class by its name's
|
||||
* address, which each class owns. */
|
||||
if (d->kind == SCR_DYN_ARR) {
|
||||
const ScrDyn *props = d->v.arr.properties;
|
||||
return props != NULL && props->v.obj.class_name == cls->name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const ScrArrClass *scr_arr_class_of(const ScrArr *a) {
|
||||
if (!(a->spare & SCR_ARR_HAS_EXT)) return NULL;
|
||||
return scr_arr_ext_of((ScrArr *)a)->cls;
|
||||
}
|
||||
|
||||
/* Whether an Array subclass instance's elements are the collector's: its
|
||||
* current element ops trace them. A species result can retype an empty
|
||||
* instance's storage (scr_arr_species_move), so this follows the storage,
|
||||
* not the element type the class declares. */
|
||||
static bool scr_arr_subclass_elements_traced(const ScrArr *a) {
|
||||
const ScrArrOps *ops = scr_arr_ops(a);
|
||||
return ops != NULL && ops->trace != NULL;
|
||||
}
|
||||
|
||||
void scr_arr_subclass_trace(void *a, ScrTraceVisit visit, void *ctx) {
|
||||
if (scr_arr_subclass_elements_traced((ScrArr *)a)) scr_arr_trace_v(a, visit, ctx);
|
||||
}
|
||||
|
||||
void scr_arr_subclass_gc_drop(ScrArr *a) {
|
||||
if (!scr_arr_subclass_elements_traced(a)) scr_arr_release_contents(a);
|
||||
scr_arr_free_parts(a);
|
||||
a->data = NULL;
|
||||
a->len = a->cap = a->filled = 0;
|
||||
a->spare &= (uint16_t)~SCR_ARR_HAS_EXT;
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_live_arrays--;
|
||||
#endif
|
||||
}
|
||||
|
||||
void scr_arr_subclass_drop(ScrArr *a) {
|
||||
scr_arr_release_contents(a);
|
||||
scr_arr_free_parts(a);
|
||||
a->data = NULL;
|
||||
a->len = a->cap = a->filled = 0;
|
||||
a->spare &= (uint16_t)~SCR_ARR_HAS_EXT;
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_live_arrays--;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Whether destroying `a`, an acyclic array, releases nothing: it has no ext
|
||||
* and its elements are numbers, booleans or compact references, or
|
||||
* references whose release does nothing (SCR_ARR_OPS_RELEASE_NOTHING). */
|
||||
@@ -1754,6 +1999,12 @@ static inline bool scr_arr_releases_nothing(const ScrArr *a) {
|
||||
void scr_arr_release(ScrArr *a) {
|
||||
if (!a || a->rc == SIZE_MAX) return; /* NULL: an uninitialized `let` local */
|
||||
if (--a->rc == 0) {
|
||||
/* An Array subclass instance: its class tears it down. */
|
||||
if ((a->spare & SCR_ARR_HAS_EXT) && scr_arr_ext_of(a)->cls) {
|
||||
if (a->cyc) scr_cyc_dead(a);
|
||||
scr_rc_destroy(a, scr_arr_ext_of(a)->cls->destroy);
|
||||
return;
|
||||
}
|
||||
/* Nothing to tear down: free the storage without scr_rc_destroy's
|
||||
* queue (no release can recurse) or the element loop. */
|
||||
if (!a->cyc && scr_arr_releases_nothing(a)) {
|
||||
@@ -5711,10 +5962,32 @@ bool scr_arr_numeric_key(const char *key, size_t len, double *out) {
|
||||
|
||||
bool scr_arr_has_properties(const ScrArr *a) {
|
||||
const ScrArrExt *x = scr_arr_ext_of(a);
|
||||
return x && (x->prop_len || x->metadata);
|
||||
return x && (x->prop_len || x->metadata || x->cls);
|
||||
}
|
||||
|
||||
/* The own fields of an Array subclass instance (ScrArrClass.fields), a
|
||||
* fresh dyn object, or NULL. */
|
||||
static ScrDyn *scr_arr_class_fields(ScrArr *a) {
|
||||
const ScrArrClass *cls = scr_arr_class_of(a);
|
||||
if (!cls || !cls->fields) return NULL;
|
||||
return cls->fields(scr_arr_retain(a));
|
||||
}
|
||||
|
||||
void scr_arr_copy_metadata(ScrArr *a, ScrDyn *target) {
|
||||
const ScrArrClass *cls = scr_arr_class_of(a);
|
||||
if (cls) {
|
||||
/* An Array subclass instance: the class name util.inspect prints before
|
||||
* the elements, then the class's own fields as properties. */
|
||||
if (!target->v.arr.properties) target->v.arr.properties = scr_dyn_new_obj();
|
||||
target->v.arr.properties->v.obj.class_name = cls->name;
|
||||
ScrDyn *fields = scr_arr_class_fields(a);
|
||||
if (fields) {
|
||||
for (size_t i = 0; i < fields->v.obj.len; i++)
|
||||
scr_dyn_copy_entry(target, fields, &fields->v.obj.entries[i]);
|
||||
scr_dyn_release(fields);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const ScrDyn *metadata = SCR_AX(a)->metadata;
|
||||
if (metadata && metadata->kind == SCR_DYN_HANDLE && metadata->v.handle.tag == SCR_DYNH_MATCH_SOURCE &&
|
||||
scr_arr_match_copy(a, target))
|
||||
@@ -5733,6 +6006,15 @@ ScrArr *scr_arr_named_keys(ScrArr *a) {
|
||||
ScrArr *keys = scr_arr_new(SCR_ELEM_STR, 0);
|
||||
for (size_t i = 0; i < x->prop_len; i++)
|
||||
scr_arr_push_ref(keys, scr_str_new(x->props[i].key, x->props[i].key_len));
|
||||
ScrDyn *fields = scr_arr_class_fields(a);
|
||||
if (fields) {
|
||||
for (size_t i = 0; i < fields->v.obj.len; i++) {
|
||||
const ScrDynEntry *entry = &fields->v.obj.entries[i];
|
||||
if (entry->enumerable) scr_arr_push_ref(keys, scr_str_new(entry->key, entry->key_len));
|
||||
}
|
||||
scr_dyn_release(fields);
|
||||
return keys;
|
||||
}
|
||||
const ScrDyn *metadata = x->metadata;
|
||||
if (metadata && metadata->kind == SCR_DYN_HANDLE && metadata->v.handle.tag == SCR_DYNH_MATCH_SOURCE) {
|
||||
static const char *const names[] = {"index", "input", "groups"};
|
||||
@@ -5765,6 +6047,13 @@ bool scr_arr_has_named_key(ScrArr *a, ScrStr *key) {
|
||||
|
||||
void scr_arr_adopt_view_properties(ScrArr *a, ScrDyn *view, ScrArr *target) {
|
||||
ScrArrExt *from = scr_arr_ext_of(target);
|
||||
/* An Array subclass instance's class is in its out-of-line storage, which
|
||||
* the commit gives to `a`: the class moves to `a`'s, and so stays with the
|
||||
* instance, while `a` is released as a plain array. */
|
||||
if (from && from->cls) {
|
||||
scr_arr_ext(a)->cls = from->cls;
|
||||
from->cls = NULL;
|
||||
}
|
||||
if (from && from->prop_len) {
|
||||
/* Both arrays have the same element type, so the slots keep their owner. */
|
||||
ScrArrExt *x = scr_arr_ext(a);
|
||||
|
||||
@@ -175,10 +175,34 @@ typedef struct ScrArrExt {
|
||||
/* The element ops of an array whose ops have no registry id
|
||||
* (SCR_ARR_OPS_OVERFLOW). */
|
||||
const ScrArrOps *ops;
|
||||
/* The class of an instance of a program class that extends Array
|
||||
* (scr_arr_subclass_init); NULL for every other array. */
|
||||
const struct ScrArrClass *cls;
|
||||
/* Some slot may hold SCR_ARR_FOREIGN; release and copies walk the states
|
||||
* for their payloads only then. */
|
||||
bool foreign;
|
||||
} ScrArrExt;
|
||||
|
||||
/* A program class that extends Array. Its instances are emitted structs
|
||||
* that begin with a ScrArr (the reference count is the array's) and keep
|
||||
* the class's own fields after it; they always have an ext, whose `cls`
|
||||
* names the class. Every array entry point accepts them as arrays. */
|
||||
typedef struct ScrArrClass {
|
||||
/* The class's name, as constructor.name reads it. */
|
||||
const char *name;
|
||||
/* Destroys an instance whose count reached zero: releases the class's own
|
||||
* fields, then scr_arr_subclass_drop, then frees the instance. */
|
||||
void (*destroy)(void *a);
|
||||
/* The class's own fields as a dyn object in declaration order (the array
|
||||
* is passed owned, +1), or NULL when the class declares none. util.inspect
|
||||
* and Object.keys list them after the elements. */
|
||||
struct ScrDyn *(*fields)(ScrArr *a);
|
||||
/* `new C(length)` (`new C()` without `with_length`): the instance
|
||||
* ArraySpeciesCreate makes for map, filter, slice, splice, concat, flat
|
||||
* and flatMap, and Array.of / Array.from called on the class. NULL when
|
||||
* the class's constructor cannot take that argument. */
|
||||
ScrArr *(*species)(double length, bool with_length);
|
||||
} ScrArrClass;
|
||||
/* The LLVM backend mirrors ScrArr as %ScrArr: its inline retain/release read
|
||||
* `cyc` (field 4, i8) to decide mark-live / candidate buffering exactly like
|
||||
* scr_arr_retain and scr_arr_release; its inline element access uses len and
|
||||
@@ -482,11 +506,59 @@ static inline ScrArr *scr_arr_retain(ScrArr *a) {
|
||||
* NULL-tolerant (uninitialized locals). Cycle-capable arrays (`cyc`) feed
|
||||
* the candidate-root buffer like every headered type. */
|
||||
void scr_arr_release(ScrArr *a);
|
||||
/* Initializes the ScrArr prefix of a zeroed, freshly allocated instance of
|
||||
* an Array subclass (count already 1): no elements, the element kind and
|
||||
* ops, and an ext naming the class. `cyc` says the instance carries the
|
||||
* collector header (its class is cycle-capable). */
|
||||
void scr_arr_subclass_init(ScrArr *a, uint8_t elem, const ScrArrOps *ops, const ScrArrClass *cls,
|
||||
bool cyc);
|
||||
/* The collector's teardown of an Array subclass instance's storage: the
|
||||
* elements are released unless they are the collector's (`traced`). */
|
||||
void scr_arr_subclass_gc_drop(ScrArr *a);
|
||||
/* The collector trace of an Array subclass instance's elements: visits
|
||||
* them when its current element ops trace them. */
|
||||
void scr_arr_subclass_trace(void *a, ScrTraceVisit visit, void *ctx);
|
||||
/* super(...args) in a class that extends Array (the Array constructor's
|
||||
* length-or-elements rule); `args` is borrowed. */
|
||||
void scr_arr_subclass_construct(ScrArr *a, ScrArr *args, bool dyn_elems);
|
||||
/* The implicit constructor's super(...args) over its dyn argument array. */
|
||||
void scr_arr_subclass_construct_dyn(ScrArr *a, const struct ScrDyn *args, bool dyn_elems);
|
||||
/* ArraySpeciesCreate over the result `result` (owned) of an Array method
|
||||
* called on `recv` (borrowed): a plain receiver keeps the result; a
|
||||
* subclass instance's result is `new C(ctor_length)`, whose indices then
|
||||
* receive the result's present elements, and whose length is set to the
|
||||
* result's when `set_length` (slice, splice and concat set it; map,
|
||||
* filter, flat and flatMap do not). A negative `ctor_length` passes the
|
||||
* result's length. Result owned. */
|
||||
ScrArr *scr_arr_species_adopt(ScrArr *recv, ScrArr *result, double ctor_length, bool set_length);
|
||||
/* ArraySpeciesCreate ahead of an Array method that calls back into the
|
||||
* program (map, filter, flatMap), so the species constructor runs before
|
||||
* the first callback: a subclass instance `recv` (borrowed) answers
|
||||
* `new C(ctor_length)`, a plain receiver itself. Result owned. */
|
||||
ScrArr *scr_arr_species_create(ScrArr *recv, double ctor_length);
|
||||
/* The present elements of the method's result `result` (owned) defined on
|
||||
* the instance scr_arr_species_create answered (`species`, borrowed), and
|
||||
* its length set to the result's when `set_length`; a plain receiver's
|
||||
* result as it is. Result owned. */
|
||||
ScrArr *scr_arr_species_finish(ScrArr *species, ScrArr *result, bool set_length);
|
||||
/* Array.of / Array.from called on an Array subclass: `new C(ctor_length)`
|
||||
* (`new C()` when ctor_length is negative), then the elements of `result`
|
||||
* (owned) and its length, as scr_arr_species_adopt. */
|
||||
ScrArr *scr_arr_class_construct_adopt(const ScrArrClass *cls, ScrArr *result, double ctor_length);
|
||||
/* `value instanceof C` for a dyn value and a class that extends Array: a
|
||||
* live view of an instance of C, or a snapshot of one. */
|
||||
bool scr_dyn_is_array_class(const struct ScrDyn *d, const ScrArrClass *cls);
|
||||
/* The class of an Array subclass instance, or NULL for a plain array. */
|
||||
const ScrArrClass *scr_arr_class_of(const ScrArr *a);
|
||||
/* Releases the elements of an Array subclass instance and frees its
|
||||
* storage and ext, leaving the instance itself to its class's destroy. */
|
||||
void scr_arr_subclass_drop(ScrArr *a);
|
||||
void scr_arr_copy_metadata(ScrArr *a, struct ScrDyn *target);
|
||||
/* The commit of a live array view calls this on `a`, the array converted
|
||||
* from `view`, before `target` takes its storage: `a` takes the view's named
|
||||
* (non-index) properties as its metadata and the noncanonical numeric
|
||||
* properties of `target`, so neither is lost when the storage is swapped. */
|
||||
* (non-index) properties as its metadata, the noncanonical numeric
|
||||
* properties of `target` and the class of an Array subclass instance, so
|
||||
* none is lost when the storage is swapped. */
|
||||
void scr_arr_adopt_view_properties(ScrArr *a, struct ScrDyn *view, ScrArr *target);
|
||||
/* True when `key` is the string spelling of a number that is not an array
|
||||
* index ("-1", "1.5", "NaN", "Infinity"), or "undefined", with the number in
|
||||
|
||||
@@ -2499,8 +2499,26 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
|
||||
const ScrDyn *props = d->v.arr.properties;
|
||||
size_t nprops = 0;
|
||||
for (size_t i = 0; props && i < props->v.obj.len; i++) nprops += insp_obj_shows(&props->v.obj.entries[i]);
|
||||
if (len == 0 && nprops == 0 && !g_show_hidden) return scr_str_new("[]", 2);
|
||||
if (recurse > depth) return insp_styled_lit("[Array]", 7, INSP_SPECIAL);
|
||||
/* An instance of a class that extends Array: Node's `Name(length) `
|
||||
* prefix (scr_arr_copy_metadata names the class). */
|
||||
const char *cls = props ? props->v.obj.class_name : NULL;
|
||||
char prefix[160] = "";
|
||||
if (cls) snprintf(prefix, sizeof prefix, "%.120s(%zu) ", cls, len);
|
||||
if (len == 0 && nprops == 0 && !g_show_hidden) {
|
||||
if (!cls) return scr_str_new("[]", 2);
|
||||
InspBuf eb = {0};
|
||||
ib_cstr(&eb, prefix);
|
||||
ib_cstr(&eb, "[]");
|
||||
return ib_take(&eb);
|
||||
}
|
||||
if (recurse > depth) {
|
||||
if (!cls) return insp_styled_lit("[Array]", 7, INSP_SPECIAL);
|
||||
InspBuf eb = {0};
|
||||
ib_char(&eb, '[');
|
||||
ib_cstr(&eb, cls);
|
||||
ib_char(&eb, ']');
|
||||
return insp_styled(ib_take(&eb), INSP_SPECIAL);
|
||||
}
|
||||
scr_insp_begin(recurse + 1);
|
||||
/* Node's maxArrayLength (100) counts output entries; a run of holes
|
||||
* is one `<n empty items>` entry (formatSpecialArray). */
|
||||
@@ -2570,7 +2588,10 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
|
||||
* `[length]` under showHidden, or the last property. */
|
||||
bool last_out = entries > 100;
|
||||
ScrStr *base = scr_str_new("", 0);
|
||||
ScrStr *b0 = scr_str_new("[", 1);
|
||||
InspBuf ob = {0};
|
||||
ib_cstr(&ob, prefix);
|
||||
ib_char(&ob, '[');
|
||||
ScrStr *b0 = ib_take(&ob);
|
||||
ScrStr *b1 = scr_str_new("]", 1);
|
||||
ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, true, last_out);
|
||||
scr_str_release(base);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// A class that extends Array: construction (one number argument sets the
|
||||
// length, other arguments become the elements), length and index writes,
|
||||
// Array.isArray, instanceof, constructor, inherited methods, the subclass's
|
||||
// own fields and methods, and how console.log and JSON show the instance.
|
||||
class Stack extends Array<number> {
|
||||
pushes = 0;
|
||||
|
||||
top(): number | undefined {
|
||||
return this[this.length - 1];
|
||||
}
|
||||
|
||||
add(value: number): this {
|
||||
this.push(value);
|
||||
this.pushes++;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
const s = new Stack();
|
||||
s.add(1).add(2).add(3);
|
||||
console.log(s.length, s.top(), s.pushes);
|
||||
console.log(Array.isArray(s), s instanceof Stack, s instanceof Array, s.constructor === Stack);
|
||||
console.log(s);
|
||||
console.log(JSON.stringify(s));
|
||||
s[5] = 9;
|
||||
console.log(s.length, s[4], s);
|
||||
s.length = 2;
|
||||
console.log(s, s.indexOf(2), s.includes(3), s.join("-"));
|
||||
s.reverse();
|
||||
console.log(s.top(), s);
|
||||
|
||||
const sized = new Stack(3);
|
||||
console.log(sized.length, sized, 0 in sized);
|
||||
const listed = new Stack(4, 5);
|
||||
console.log(listed.length, listed);
|
||||
|
||||
try {
|
||||
new Stack(-1);
|
||||
} catch (e) {
|
||||
console.log((e as Error).name, (e as Error).message);
|
||||
}
|
||||
|
||||
for (const v of listed) console.log("item", v);
|
||||
console.log([...listed, 6], Object.keys(listed));
|
||||
@@ -0,0 +1,37 @@
|
||||
// Array.prototype methods that create a new array construct it through the
|
||||
// subclass (ArraySpeciesCreate): map, filter, slice, splice, concat, flat
|
||||
// and flatMap return an instance of the subclass, built by calling its
|
||||
// constructor with the length the specification passes. Methods that
|
||||
// return the receiver keep it, and Array.of / Array.from inherited as
|
||||
// statics construct the subclass too.
|
||||
class Logged extends Array<number> {
|
||||
constructor(...args: number[]) {
|
||||
super(...args);
|
||||
console.log("construct", args.length === 1 ? `length ${args[0]}` : `items ${args.join(",")}`);
|
||||
}
|
||||
|
||||
sum(): number {
|
||||
let total = 0;
|
||||
for (const v of this) total += v;
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
const base = Logged.of(1, 2, 3, 4);
|
||||
console.log(base instanceof Logged, base);
|
||||
const doubled = base.map((v) => v * 2);
|
||||
console.log(doubled instanceof Logged, (doubled as Logged).sum(), doubled);
|
||||
const even = base.filter((v) => v % 2 === 0);
|
||||
console.log(even instanceof Logged, even);
|
||||
const part = base.slice(1, 3);
|
||||
console.log(part instanceof Logged, part);
|
||||
const removed = base.splice(0, 1);
|
||||
console.log(removed instanceof Logged, removed, base);
|
||||
const joined = base.concat([7, 8]);
|
||||
console.log(joined instanceof Logged, joined.length, joined);
|
||||
const flat = base.flatMap((v) => [v, v]);
|
||||
console.log(flat instanceof Logged, flat.length);
|
||||
const filled = base.fill(0, 0, 1);
|
||||
console.log(filled === base, base);
|
||||
const from = Logged.from([5, 6]);
|
||||
console.log(from instanceof Logged, (from as Logged).sum());
|
||||
@@ -0,0 +1,16 @@
|
||||
// lru-cache's ZeroArray: a JavaScript class extending Array whose
|
||||
// constructor fills the new array with zeros, constructed directly and
|
||||
// through map (species construction runs the constructor again).
|
||||
class ZeroArray extends Array {
|
||||
constructor(size) {
|
||||
super(size);
|
||||
this.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
const z = new ZeroArray(4);
|
||||
z[1] = 7;
|
||||
console.log(z, z.length, z instanceof ZeroArray, Array.isArray(z));
|
||||
const m = z.map((v) => v + 1);
|
||||
console.log(m, m instanceof ZeroArray);
|
||||
console.log(z.slice(1) instanceof ZeroArray, String(z));
|
||||
@@ -0,0 +1,21 @@
|
||||
// A subclass instance passed where a plain array is expected is the same
|
||||
// object: writes through either view are seen through the other, and
|
||||
// species construction still applies in code typed with the plain array.
|
||||
class Path extends Array<string> {
|
||||
label = "path";
|
||||
}
|
||||
|
||||
function addSegment(parts: string[], segment: string): string[] {
|
||||
parts.push(segment);
|
||||
return parts.filter((part) => part.length > 0);
|
||||
}
|
||||
|
||||
const p = new Path();
|
||||
p.push("a", "", "b");
|
||||
const filtered = addSegment(p, "c");
|
||||
console.log(p.length, filtered.length, filtered instanceof Path, (filtered as Path).label);
|
||||
const view: string[] = p;
|
||||
view[0] = "z";
|
||||
console.log(p[0], view === p, p.label);
|
||||
console.log(p.map((s) => s.toUpperCase()));
|
||||
console.log(Object.keys(p));
|
||||
@@ -0,0 +1,36 @@
|
||||
// An Array subclass whose elements and fields refer back to the instance:
|
||||
// the cycles are collected once nothing outside them is reachable, and
|
||||
// the elements and fields read back as Node reads them.
|
||||
class Member {
|
||||
name: string;
|
||||
group: Group | undefined;
|
||||
constructor(name: string, group: Group | undefined) {
|
||||
this.name = name;
|
||||
this.group = group;
|
||||
}
|
||||
}
|
||||
|
||||
class Group extends Array<Member> {
|
||||
owner: Member | undefined = undefined;
|
||||
|
||||
add(name: string): Member {
|
||||
const member = new Member(name, this);
|
||||
this.push(member);
|
||||
return member;
|
||||
}
|
||||
}
|
||||
|
||||
function build(round: number): number {
|
||||
const g = new Group();
|
||||
const lead = g.add(`lead${round}`);
|
||||
g.add(`member${round}`);
|
||||
g.owner = lead;
|
||||
return g.length + (g[0]!.group === g ? 1 : 0) + (g.owner === lead ? 1 : 0);
|
||||
}
|
||||
|
||||
let total = 0;
|
||||
for (let i = 0; i < 200; i++) total += build(i);
|
||||
console.log(total);
|
||||
const kept = new Group();
|
||||
kept.add("a").group?.add("b");
|
||||
console.log(kept.length, kept.map((m) => m.name).join(","), kept[1]!.group === kept);
|
||||
@@ -0,0 +1,46 @@
|
||||
// Array operations on an Array subclass instance: iteration, spread,
|
||||
// destructuring, the mutators that return the instance itself, searches,
|
||||
// length writes, Array.isArray through unknown, instanceof through unknown,
|
||||
// and JSON.
|
||||
class Row extends Array<string> {
|
||||
tag = "row";
|
||||
#reads = 0;
|
||||
|
||||
static of2(a: string, b: string): Row {
|
||||
const row = new Row();
|
||||
row.push(a, b);
|
||||
return row;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
this.#reads++;
|
||||
return this.length;
|
||||
}
|
||||
|
||||
get reads(): number {
|
||||
return this.#reads;
|
||||
}
|
||||
|
||||
label(): string {
|
||||
return `${this.tag}:${this.join("|")}`;
|
||||
}
|
||||
}
|
||||
|
||||
const r = new Row();
|
||||
console.log(r.push("b", "a", "c"), r.length, r.label());
|
||||
const [first, ...rest] = r;
|
||||
console.log(first, rest, [...r].length);
|
||||
for (const [i, v] of r.entries()) console.log(i, v);
|
||||
console.log(r.sort() === r, r.label(), r.reverse() === r, r.label());
|
||||
console.log(r.fill("x", 1) === r, r.label(), r.indexOf("x"), r.lastIndexOf("x"), r.at(-1));
|
||||
console.log(r.pop(), r.shift(), r.length, r.unshift("z"), r.label());
|
||||
r.length = 0;
|
||||
console.log(r.length, r.label(), JSON.stringify({ r }));
|
||||
const u: unknown = r;
|
||||
console.log(Array.isArray(u), u instanceof Row, u instanceof Array);
|
||||
const rows: Row[] = [new Row(), new Row()];
|
||||
rows[1]!.push("q");
|
||||
console.log(rows.map((row) => row.label()).join(" "));
|
||||
console.log({ nested: rows[1] });
|
||||
const pair = Row.of2("m", "n");
|
||||
console.log(pair.size, pair.size, pair.reads, pair.label(), pair instanceof Row);
|
||||
@@ -0,0 +1,21 @@
|
||||
// Classes extending Array reached through expressions rather than
|
||||
// declarations and locals: a construction used in place, a class
|
||||
// expression held by a binding, and an instanceof test whose class the
|
||||
// program never constructs.
|
||||
class Sized extends Array {
|
||||
describe() {
|
||||
return `sized ${this.length}`;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(new Sized(3).length, new Sized(2).describe(), new Sized(1, 2).join("-"));
|
||||
console.log(new Sized(4).fill(1), (new Sized(2)).length);
|
||||
|
||||
const Holder = class extends Array {};
|
||||
const held = new Holder();
|
||||
held.push("a", "b");
|
||||
console.log(held instanceof Holder, held instanceof Array, [] instanceof Holder, held.length);
|
||||
|
||||
class Unbuilt extends Array {}
|
||||
const values = [[1, 2], "text", new Sized(1)];
|
||||
for (const value of values) console.log(value instanceof Unbuilt, Array.isArray(value));
|
||||
@@ -0,0 +1,52 @@
|
||||
// map, filter and flatMap on a class that extends Array construct their
|
||||
// result through the class before the first callback runs, with the
|
||||
// receiver's length for map and 0 for filter and flatMap.
|
||||
class Logged extends Array<number> {
|
||||
constructor(...args: number[]) {
|
||||
super(...args);
|
||||
console.log("construct", args.length === 1 ? `length ${args[0]}` : `${args.length} items`);
|
||||
}
|
||||
}
|
||||
|
||||
const source = Logged.from([1, 2, 3]);
|
||||
console.log("map");
|
||||
const mapped = source.map((value) => {
|
||||
console.log(" callback", value);
|
||||
return value * 10;
|
||||
});
|
||||
console.log(mapped, mapped instanceof Logged);
|
||||
|
||||
console.log("filter");
|
||||
const kept = source.filter((value) => {
|
||||
console.log(" callback", value);
|
||||
return value !== 2;
|
||||
});
|
||||
console.log(kept, kept instanceof Logged);
|
||||
|
||||
console.log("flatMap");
|
||||
const spread = source.flatMap((value) => {
|
||||
console.log(" callback", value);
|
||||
return [value, -value];
|
||||
});
|
||||
console.log(spread.length, spread instanceof Logged);
|
||||
|
||||
console.log("map to strings");
|
||||
const labels = source.map((value) => {
|
||||
console.log(" callback", value);
|
||||
return `#${value}`;
|
||||
});
|
||||
console.log(labels, labels instanceof Logged);
|
||||
|
||||
console.log("throwing callback");
|
||||
try {
|
||||
source.map((value) => {
|
||||
if (value === 2) throw new Error(`stopped at ${value}`);
|
||||
return value;
|
||||
});
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
}
|
||||
|
||||
console.log("plain receiver");
|
||||
const plain: number[] = [4, 5];
|
||||
console.log(plain.map((value) => value + 1), plain.filter((value) => value > 4));
|
||||
@@ -0,0 +1,33 @@
|
||||
// A cycle-capable class that extends Array (its elements and its own field
|
||||
// can close a cycle) whose map results take other element types: the
|
||||
// collector traces and tears down each instance by the elements it holds.
|
||||
// Garbage cycles make the collector run while the results are alive.
|
||||
class Holder {
|
||||
group: Group | null = null;
|
||||
}
|
||||
|
||||
class Group extends Array<Holder | null> {
|
||||
owner: Holder | null = null;
|
||||
}
|
||||
|
||||
function churn(rounds: number): number {
|
||||
let made = 0;
|
||||
for (let i = 0; i < rounds; i++) {
|
||||
const holder = new Holder();
|
||||
const group = new Group();
|
||||
group.owner = holder;
|
||||
holder.group = group;
|
||||
group.push(holder);
|
||||
made++;
|
||||
}
|
||||
return made;
|
||||
}
|
||||
|
||||
const groups = new Group();
|
||||
groups.push(new Holder(), null, new Holder());
|
||||
const numbers = groups.map((_, i) => i * 1.5 + 1e6);
|
||||
const labels = groups.map((_, i) => `label-${i}`);
|
||||
console.log(churn(20000));
|
||||
console.log(numbers instanceof Group, numbers.length, numbers.join(","));
|
||||
console.log(labels instanceof Group, labels.join(","));
|
||||
console.log(churn(20000), numbers[2], labels[1]);
|
||||
@@ -0,0 +1,17 @@
|
||||
// Releasing a long chain of instances of a class that extends Array, each
|
||||
// holding the next one, tears them down without growing the native stack.
|
||||
class Link extends Array<number> {
|
||||
next: Link | null = null;
|
||||
}
|
||||
|
||||
let head: Link | null = new Link();
|
||||
let tail = head;
|
||||
for (let i = 0; i < 300000; i++) {
|
||||
const link = new Link();
|
||||
link.push(i);
|
||||
tail.next = link;
|
||||
tail = link;
|
||||
}
|
||||
console.log(tail.length, tail[0]);
|
||||
head = null;
|
||||
console.log("released", head);
|
||||
@@ -0,0 +1,32 @@
|
||||
// map, filter and flatMap read the receiver's length before the species
|
||||
// constructor runs: elements the constructor appends to the receiver are
|
||||
// not visited.
|
||||
let watched: Growing | null = null;
|
||||
|
||||
class Growing extends Array<number> {
|
||||
constructor(...args: number[]) {
|
||||
super(...args);
|
||||
if (watched !== null) watched.push(watched.length * 100);
|
||||
}
|
||||
}
|
||||
|
||||
const source = Growing.from([1, 2, 3]);
|
||||
watched = source;
|
||||
const visits: number[] = [];
|
||||
const doubled = source.map((value) => {
|
||||
visits.push(value);
|
||||
return value * 2;
|
||||
});
|
||||
console.log(doubled.join(","), visits.join(","), source.join(","));
|
||||
visits.length = 0;
|
||||
const kept = source.filter((value) => {
|
||||
visits.push(value);
|
||||
return value > 1;
|
||||
});
|
||||
console.log(kept.join(","), visits.join(","), source.length);
|
||||
visits.length = 0;
|
||||
const spread = source.flatMap((value) => {
|
||||
visits.push(value);
|
||||
return [value];
|
||||
});
|
||||
console.log(spread.length, visits.length, source.length);
|
||||
@@ -0,0 +1,26 @@
|
||||
// A class that extends Array whose constructor's length parameter is
|
||||
// optional or has a default takes the species length argument: map,
|
||||
// filter and slice construct their results through it.
|
||||
class Optional extends Array<number> {
|
||||
constructor(length?: number) {
|
||||
super(length ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
class Defaulted extends Array<string> {
|
||||
tag: string;
|
||||
constructor(length = 0, tag = "none") {
|
||||
super(length);
|
||||
this.tag = tag;
|
||||
}
|
||||
}
|
||||
|
||||
const optional = Optional.from([1, 2, 3]);
|
||||
const mapped = optional.map((value) => value * 10);
|
||||
console.log(mapped instanceof Optional, mapped.join(","), optional.slice(1) instanceof Optional);
|
||||
console.log(optional.filter((value) => value !== 2).join(","));
|
||||
|
||||
const defaulted = new Defaulted(2).fill("x");
|
||||
const sliced = defaulted.slice(0, 1);
|
||||
if (sliced instanceof Defaulted) console.log(sliced.length, sliced.tag);
|
||||
console.log(defaulted.map((s) => s + "!").join(","));
|
||||
@@ -0,0 +1,27 @@
|
||||
// An Array subclass written through an unknown[] view: a number pushed
|
||||
// through the view, and values its element type cannot hold, which the
|
||||
// array keeps as written. The instance keeps its class and its fields, and
|
||||
// the species results of slice and concat keep the written values.
|
||||
class Scores extends Array<number> {
|
||||
label = "scores";
|
||||
}
|
||||
|
||||
const plain = new Scores();
|
||||
plain.push(1, 2);
|
||||
const plainView: unknown[] = plain;
|
||||
plainView.push(3);
|
||||
console.log(plain instanceof Scores, plain.label, plain.length, JSON.stringify(plain));
|
||||
|
||||
const scores = new Scores();
|
||||
scores.push(1, 2);
|
||||
const view: unknown[] = scores;
|
||||
view.push("x", { a: 1 });
|
||||
console.log(scores instanceof Scores, scores.label, scores.length, JSON.stringify(scores), scores.join("|"));
|
||||
const tail = scores.slice(1);
|
||||
console.log(tail instanceof Scores, tail.length, JSON.stringify(tail));
|
||||
const joined = scores.concat([9]);
|
||||
console.log(joined instanceof Scores, joined.length, JSON.stringify(joined));
|
||||
scores[2] = 7;
|
||||
console.log(JSON.stringify(scores), view[3]);
|
||||
scores.length = 1;
|
||||
console.log(scores instanceof Scores, scores.label, JSON.stringify(scores), JSON.stringify(tail));
|
||||
@@ -0,0 +1,13 @@
|
||||
// util.inspect of Array subclass instances: the `Name(length)` prefix, and
|
||||
// the `[Name]` placeholder past the depth limit, plain and with colors.
|
||||
import { inspect } from "node:util";
|
||||
|
||||
class Scores extends Array<number> {}
|
||||
|
||||
const s = new Scores();
|
||||
s.push(1, 2);
|
||||
console.log(inspect(new Scores()), inspect(s));
|
||||
console.log(inspect([s], { depth: 0 }), inspect([[1]], { depth: 0 }));
|
||||
console.log(JSON.stringify(inspect([s], { depth: 0, colors: true })));
|
||||
console.log(JSON.stringify(inspect([[1]], { depth: 0, colors: true })));
|
||||
console.log(JSON.stringify(inspect(s, { colors: true })));
|
||||
@@ -0,0 +1,25 @@
|
||||
// Array subclasses that do not compile yet, each refused where it is
|
||||
// declared: a generic subclass, a subclass of a class that extends Array,
|
||||
// a class that declares its own Symbol.species, and a class whose elements
|
||||
// are its own instances.
|
||||
class Bag<T> extends Array<T> {
|
||||
firstOr(fallback: T): T {
|
||||
return this.length > 0 ? this[0]! : fallback;
|
||||
}
|
||||
}
|
||||
|
||||
class Base extends Array<number> {}
|
||||
class Derived extends Base {}
|
||||
|
||||
class Speciated extends Array<number> {
|
||||
static get [Symbol.species](): ArrayConstructor {
|
||||
return Array;
|
||||
}
|
||||
}
|
||||
|
||||
class Nested extends Array<Nested | null> {}
|
||||
|
||||
console.log(new Bag<number>().firstOr(1));
|
||||
console.log(new Derived().length);
|
||||
console.log(new Speciated().length);
|
||||
console.log(new Nested().length);
|
||||
@@ -0,0 +1,24 @@
|
||||
// A species result of another element type is still an instance of the
|
||||
// class, so `instanceof` narrows it back. Its elements are not the class's,
|
||||
// so the class's fields and methods on it are refused where they are read.
|
||||
class Holder {
|
||||
tag = "h";
|
||||
}
|
||||
|
||||
class Group extends Array<Holder | null> {
|
||||
owner: Holder | null = null;
|
||||
|
||||
firstTag(): string {
|
||||
const first = this[0];
|
||||
return first === null || first === undefined ? "none" : first.tag;
|
||||
}
|
||||
}
|
||||
|
||||
const groups = new Group();
|
||||
groups.push(new Holder(), null);
|
||||
const numbers = groups.map((_, i) => i + 1);
|
||||
if (numbers instanceof Group) {
|
||||
numbers.owner = new Holder();
|
||||
console.log(numbers.owner.tag);
|
||||
console.log(numbers.firstTag());
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
array-subclass-fences.ts:5:22 - error SC1090: a generic class that extends Array is not supported yet
|
||||
|
||||
4 | // are its own instances.
|
||||
5 | class Bag<T> extends Array<T> {
|
||||
| ^~~~~~~~
|
||||
6 | firstOr(fallback: T): T {
|
||||
|
||||
array-subclass-fences.ts:12:23 - error SC1090: extending 'Base', a class that extends Array (one level of Array subclass compiles) is not supported yet
|
||||
|
||||
11 | class Base extends Array<number> {}
|
||||
12 | class Derived extends Base {}
|
||||
| ^~~~
|
||||
13 |
|
||||
|
||||
array-subclass-fences.ts:15:3 - error SC1090: Symbol.species on a class that extends Array (its methods' results construct the class itself) is not supported yet
|
||||
|
||||
14 | class Speciated extends Array<number> {
|
||||
15 | static get [Symbol.species](): ArrayConstructor {
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
16 | return Array;
|
||||
|
||||
array-subclass-fences.ts:20:22 - error SC1090: classes that extend an Array of their own instances are not supported yet
|
||||
|
||||
19 |
|
||||
20 | class Nested extends Array<Nested | null> {}
|
||||
| ^~~~~~~~~~~~~~~~~~~~
|
||||
21 |
|
||||
|
||||
array-subclass-fences.ts:22:13 - error SC1090: constructing values other than classes declared in the program is not supported yet
|
||||
|
||||
21 |
|
||||
22 | console.log(new Bag<number>().firstOr(1));
|
||||
| ^~~~~~~~~~~~~~~~~
|
||||
23 | console.log(new Derived().length);
|
||||
|
||||
array-subclass-fences.ts:23:13 - error SC1090: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet
|
||||
|
||||
22 | console.log(new Bag<number>().firstOr(1));
|
||||
23 | console.log(new Derived().length);
|
||||
| ^~~~~~~~~~~~~
|
||||
24 | console.log(new Speciated().length);
|
||||
|
||||
array-subclass-fences.ts:24:13 - error SC1090: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet
|
||||
|
||||
23 | console.log(new Derived().length);
|
||||
24 | console.log(new Speciated().length);
|
||||
| ^~~~~~~~~~~~~~~
|
||||
25 | console.log(new Nested().length);
|
||||
|
||||
array-subclass-fences.ts:25:13 - error SC1090: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet
|
||||
|
||||
24 | console.log(new Speciated().length);
|
||||
25 | console.log(new Nested().length);
|
||||
| ^~~~~~~~~~~~
|
||||
26 |
|
||||
@@ -0,0 +1,20 @@
|
||||
array-subclass-retyped-members.ts:21:3 - error SC1090: members of an Array subclass on an array whose elements are another type (a species result narrowed back to the class) is not supported yet
|
||||
|
||||
20 | if (numbers instanceof Group) {
|
||||
21 | numbers.owner = new Holder();
|
||||
| ^~~~~~~
|
||||
22 | console.log(numbers.owner.tag);
|
||||
|
||||
array-subclass-retyped-members.ts:22:15 - error SC1090: members of an Array subclass on an array whose elements are another type (a species result narrowed back to the class) is not supported yet
|
||||
|
||||
21 | numbers.owner = new Holder();
|
||||
22 | console.log(numbers.owner.tag);
|
||||
| ^~~~~~~
|
||||
23 | console.log(numbers.firstTag());
|
||||
|
||||
array-subclass-retyped-members.ts:23:15 - error SC1090: members of an Array subclass on an array whose elements are another type (a species result narrowed back to the class) is not supported yet
|
||||
|
||||
22 | console.log(numbers.owner.tag);
|
||||
23 | console.log(numbers.firstTag());
|
||||
| ^~~~~~~
|
||||
24 | }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 11000, "san": 9000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 11000, "san": 9000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 11000, "san": 9000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 11000, "san": 9000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 11000, "san": 9000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 11000, "san": 9000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 11000, "san": 9000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 11000, "san": 9000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 11000, "san": 9000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 11000, "san": 9000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 11000, "san": 9000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 11000, "san": 9000 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2527, "san": 3409 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2468, "san": 3577 }
|
||||
Reference in New Issue
Block a user