diff --git a/packages/compiler/src/backend/llvm/classes.ts b/packages/compiler/src/backend/llvm/classes.ts index d848b86fc..e80370405 100644 --- a/packages/compiler/src/backend/llvm/classes.ts +++ b/packages/compiler/src/backend/llvm/classes.ts @@ -1481,13 +1481,11 @@ export function emitClassShapes( tracedFields.forEach((f, i) => { tr.push(...traceFieldVisitLines(struct, f.index, i, llvmCommentText(f.name))); }); - // An Array subclass instance's elements, when they are cycle-capable. - const tracedElements = - cls.arrayElem !== undefined && - traceAdapter(host, { kind: "array", elem: cls.arrayElem }) !== null; - if (tracedElements) { - host.declare(`declare void @scr_arr_trace_v(ptr, ptr, ptr)`); - tr.push(` call void @scr_arr_trace_v(ptr %o, ptr %visit, ptr %ctx) ; the elements`); + // 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); @@ -1508,9 +1506,9 @@ 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. - host.declare(`declare void @scr_arr_subclass_gc_drop(ptr, i1 zeroext)`); - gf.push(` call void @scr_arr_subclass_gc_drop(ptr %o, i1 ${tracedElements})`); + // 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); diff --git a/packages/compiler/src/frontend/lowering/builtins/array-classes.ts b/packages/compiler/src/frontend/lowering/builtins/array-classes.ts index c4ae4a60d..b416a8bec 100644 --- a/packages/compiler/src/frontend/lowering/builtins/array-classes.ts +++ b/packages/compiler/src/frontend/lowering/builtins/array-classes.ts @@ -12,6 +12,7 @@ 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, @@ -34,6 +35,18 @@ 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") @@ -47,6 +60,26 @@ export function arrayClassBase( 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`): 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, +): 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; @@ -349,6 +382,15 @@ export function arrayClassFieldsFn(lowerer: Lowerer, info: ClassInfo): IrFunctio * 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); @@ -381,31 +423,35 @@ export function arrayClassSpeciesFn(lowerer: Lowerer, info: ClassInfo): IrFuncti 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]!.type.kind === "f64" || params[0]!.type.kind === "dyn") && + (params[0]!.mode === "required" || params[0]!.mode === "omittable") && + takesLength(lowerer, params[0]!.type) && params.slice(1).every((p) => p.mode === "omittable") ) { - const rest = params - .slice(1) - .map((p) => - lowerer.coerceToExpected( - { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } as IrExpr, - p.type, - ), - ); - // A dyn first parameter (a JavaScript `constructor(size)`) takes the - // length as a number, and undefined in the no-argument form. - if (params[0]!.type.kind === "dyn") without = [dynUndefinedExpr(loc), ...rest.map((e) => e)]; - withLength = [ - params[0]!.type.kind === "dyn" ? { kind: "dynFrom", value: n(), type: DYN, loc } : n(), - ...params - .slice(1) - .map((p) => - lowerer.coerceToExpected( + // 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; @@ -517,14 +563,17 @@ export function lowerWithArraySpecies( 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, then the instance is created (with - // the receiver's length for map, 0 otherwise), then the method runs and - // its elements move into the instance. - if ( - result.kind !== "call" || - result.args[0]?.kind !== "varRef" || - result.args[0].localId !== local.id - ) + // 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, @@ -537,9 +586,25 @@ export function lowerWithArraySpecies( 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 - ? { kind: "arrIntrinsic", method: "length", receiver: receiverRef, args: [], type: F64, loc } + ? boundRef : { kind: "numLit", value: 0, type: F64, loc }; stmts.push({ kind: "varDecl", @@ -561,7 +626,7 @@ export function lowerWithArraySpecies( fn: "array.speciesFinish", args: [ { kind: "varRef", localId: instance.id, type: receiver.type, loc }, - { ...result, args }, + { ...result, callee: bounded, args }, { kind: "boolLit", value: mode.setLength, type: BOOL, loc }, ], type: result.type, diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index edf65ab2b..923224950 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -3171,3 +3171,33 @@ export function lowerTuplePredicateCall( loc, }; } + +/** The `%arr.` 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; +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7730-array-subclass-retyped-trace.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7730-array-subclass-retyped-trace.ts.json new file mode 100644 index 000000000..a02b66c5a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7730-array-subclass-retyped-trace.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7730-array-subclass-retyped-trace.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7731-array-subclass-deep-release.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7731-array-subclass-deep-release.ts.json new file mode 100644 index 000000000..76a9b3988 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7731-array-subclass-deep-release.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7731-array-subclass-deep-release.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7732-array-subclass-species-bound.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7732-array-subclass-species-bound.ts.json new file mode 100644 index 000000000..c36a8a1ba --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7732-array-subclass-species-bound.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7732-array-subclass-species-bound.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7733-array-subclass-optional-length.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7733-array-subclass-optional-length.ts.json new file mode 100644 index 000000000..96cb08414 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7733-array-subclass-optional-length.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7733-array-subclass-optional-length.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index 3b243dbb0..40e0580a4 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -1868,8 +1868,21 @@ const ScrArrClass *scr_arr_class_of(const ScrArr *a) { return scr_arr_ext_of((ScrArr *)a)->cls; } -void scr_arr_subclass_gc_drop(ScrArr *a, bool traced) { - if (!traced) scr_arr_release_contents(a); +/* 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; @@ -1906,7 +1919,7 @@ void scr_arr_release(ScrArr *a) { /* 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_arr_ext_of(a)->cls->destroy(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 diff --git a/packages/runtime/src/scr_array.h b/packages/runtime/src/scr_array.h index 56b7511e2..84057ffc9 100644 --- a/packages/runtime/src/scr_array.h +++ b/packages/runtime/src/scr_array.h @@ -185,7 +185,7 @@ typedef struct ScrArrClass { 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)(ScrArr *a); + 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. */ @@ -503,7 +503,10 @@ void scr_arr_subclass_init(ScrArr *a, uint8_t elem, const ScrArrOps *ops, const 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, bool 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); diff --git a/tests/corpus/7730-array-subclass-retyped-trace.ts b/tests/corpus/7730-array-subclass-retyped-trace.ts new file mode 100644 index 000000000..d1546c0af --- /dev/null +++ b/tests/corpus/7730-array-subclass-retyped-trace.ts @@ -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 { + 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]); diff --git a/tests/corpus/7731-array-subclass-deep-release.ts b/tests/corpus/7731-array-subclass-deep-release.ts new file mode 100644 index 000000000..6e4b4c352 --- /dev/null +++ b/tests/corpus/7731-array-subclass-deep-release.ts @@ -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 { + 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); diff --git a/tests/corpus/7732-array-subclass-species-bound.ts b/tests/corpus/7732-array-subclass-species-bound.ts new file mode 100644 index 000000000..715c51165 --- /dev/null +++ b/tests/corpus/7732-array-subclass-species-bound.ts @@ -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 { + 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); diff --git a/tests/corpus/7733-array-subclass-optional-length.ts b/tests/corpus/7733-array-subclass-optional-length.ts new file mode 100644 index 000000000..302fc22cb --- /dev/null +++ b/tests/corpus/7733-array-subclass-optional-length.ts @@ -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 { + constructor(length?: number) { + super(length ?? 0); + } +} + +class Defaulted extends Array { + 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(",")); diff --git a/tests/diagnostics/array-subclass-fences.ts b/tests/diagnostics/array-subclass-fences.ts index cc2966201..478e615f9 100644 --- a/tests/diagnostics/array-subclass-fences.ts +++ b/tests/diagnostics/array-subclass-fences.ts @@ -1,6 +1,7 @@ // Array subclasses that do not compile yet, each refused where it is // declared: a generic subclass, a subclass of a class that extends Array, -// and a class that declares its own Symbol.species. +// a class that declares its own Symbol.species, and a class whose elements +// are its own instances. class Bag extends Array { firstOr(fallback: T): T { return this.length > 0 ? this[0]! : fallback; @@ -16,6 +17,9 @@ class Speciated extends Array { } } +class Nested extends Array {} + console.log(new Bag().firstOr(1)); console.log(new Derived().length); console.log(new Speciated().length); +console.log(new Nested().length); diff --git a/tests/harness/__snapshots__/array-subclass-fences.ts.txt b/tests/harness/__snapshots__/array-subclass-fences.ts.txt index 3d81a58a9..97e7bdc8d 100644 --- a/tests/harness/__snapshots__/array-subclass-fences.ts.txt +++ b/tests/harness/__snapshots__/array-subclass-fences.ts.txt @@ -1,41 +1,55 @@ -array-subclass-fences.ts:4:22 - error SC1090: a generic class that extends Array is not supported yet +array-subclass-fences.ts:5:22 - error SC1090: a generic class that extends Array is not supported yet - 3 | // and a class that declares its own Symbol.species. - 4 | class Bag extends Array { + 4 | // are its own instances. + 5 | class Bag extends Array { | ^~~~~~~~ - 5 | firstOr(fallback: T): T { + 6 | firstOr(fallback: T): T { -array-subclass-fences.ts:11:23 - error SC1090: extending 'Base', a class that extends Array (one level of Array subclass compiles) is not supported yet +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 - 10 | class Base extends Array {} - 11 | class Derived extends Base {} + 11 | class Base extends Array {} + 12 | class Derived extends Base {} | ^~~~ - 12 | + 13 | -array-subclass-fences.ts:14:3 - error SC1090: Symbol.species on a class that extends Array (its methods' results construct the class itself) is not supported yet +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 - 13 | class Speciated extends Array { - 14 | static get [Symbol.species](): ArrayConstructor { + 14 | class Speciated extends Array { + 15 | static get [Symbol.species](): ArrayConstructor { | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 15 | return Array; + 16 | return Array; -array-subclass-fences.ts:19:13 - error SC1090: constructing values other than classes declared in the program is not supported yet +array-subclass-fences.ts:20:22 - error SC1090: classes that extend an Array of their own instances are not supported yet - 18 | - 19 | console.log(new Bag().firstOr(1)); + 19 | + 20 | class Nested extends Array {} + | ^~~~~~~~~~~~~~~~~~~~ + 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().firstOr(1)); | ^~~~~~~~~~~~~~~~~ - 20 | console.log(new Derived().length); + 23 | console.log(new Derived().length); -array-subclass-fences.ts:20: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 +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 - 19 | console.log(new Bag().firstOr(1)); - 20 | console.log(new Derived().length); + 22 | console.log(new Bag().firstOr(1)); + 23 | console.log(new Derived().length); | ^~~~~~~~~~~~~ - 21 | console.log(new Speciated().length); + 24 | console.log(new Speciated().length); -array-subclass-fences.ts:21: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 +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 - 20 | console.log(new Derived().length); - 21 | console.log(new Speciated().length); + 23 | console.log(new Derived().length); + 24 | console.log(new Speciated().length); | ^~~~~~~~~~~~~~~ - 22 | \ No newline at end of file + 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 | \ No newline at end of file diff --git a/tests/harness/corpus-costs/7730-array-subclass-retyped-trace.ts.json b/tests/harness/corpus-costs/7730-array-subclass-retyped-trace.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7730-array-subclass-retyped-trace.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7731-array-subclass-deep-release.ts.json b/tests/harness/corpus-costs/7731-array-subclass-deep-release.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7731-array-subclass-deep-release.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7732-array-subclass-species-bound.ts.json b/tests/harness/corpus-costs/7732-array-subclass-species-bound.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7732-array-subclass-species-bound.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7733-array-subclass-optional-length.ts.json b/tests/harness/corpus-costs/7733-array-subclass-optional-length.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7733-array-subclass-optional-length.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 }