mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-30 07:14:39 +08:00
Preserve generic lowering record order
This commit is contained in:
@@ -1121,6 +1121,7 @@ export function genericFnOf(L: Lowerer, ident: ts.Identifier): GenericFnInfo | n
|
||||
info.instances.set(key, inst);
|
||||
L.instantiationQueue.push({ info, inst });
|
||||
}
|
||||
L.noteGenericInstanceDemand(inst);
|
||||
return inst;
|
||||
}
|
||||
|
||||
|
||||
@@ -2406,6 +2406,7 @@ export function collectClassShapeInner(L: Lowerer, decl: ts.ClassLikeDeclaration
|
||||
const key = mapped.map(typeKey).join(",");
|
||||
const existing = gci.instances.get(key);
|
||||
if (existing) {
|
||||
if (existing.info) L.noteGenericClassInstanceDemand(existing.info);
|
||||
return existing.poisoned ? null : { kind: "object", className: existing.name };
|
||||
}
|
||||
// The generic-fn cap, same rationale (polymorphic recursion through
|
||||
@@ -2444,6 +2445,7 @@ export function collectClassShapeInner(L: Lowerer, decl: ts.ClassLikeDeclaration
|
||||
}
|
||||
entry.info = info;
|
||||
L.genericClassInstances.push(info);
|
||||
L.noteGenericClassInstanceDemand(info);
|
||||
L.onLateClassCollected?.(info);
|
||||
return { kind: "object", className: name };
|
||||
}
|
||||
|
||||
@@ -2505,12 +2505,45 @@ export function isParseArgsDynCheckerType(L: Lowerer, type: ts.Type): boolean {
|
||||
}
|
||||
}
|
||||
const emitOrder = restShape.declaredOrder ?? restShape.fields.map((f) => f.name);
|
||||
if (emitOrder.length !== remaining.length || emitOrder.some((n, i) => n !== remaining[i]!.name)) {
|
||||
L.unsupported(
|
||||
"SC1031",
|
||||
blame,
|
||||
"rest bindings over class instances whose packed key order cannot match Node's (Object.keys/JSON.stringify would enumerate the copied fields in a different order)",
|
||||
);
|
||||
const orderMatches = (order: readonly string[]): boolean =>
|
||||
order.length === remaining.length && order.every((n, i) => n === remaining[i]!.name);
|
||||
if (!orderMatches(emitOrder)) {
|
||||
if (L.instantiationContext === null || L.onEdge === null || isJsSourceFile(blame.getSourceFile())) {
|
||||
L.unsupported(
|
||||
"SC1031",
|
||||
blame,
|
||||
"rest bindings over class instances whose packed key order cannot match Node's (Object.keys/JSON.stringify would enumerate the copied fields in a different order)",
|
||||
);
|
||||
}
|
||||
// Retained reachability can learn about an earlier source-order body
|
||||
// only while a generic instance lowers. Defer this metadata-only fence
|
||||
// until that worklist closes; unlike emitted helper bodies, there is no
|
||||
// IR to rebuild here — only the settled support decision matters.
|
||||
const context = L.instantiationContext;
|
||||
const countFailure = !L.suppressStats;
|
||||
const sf = blame.getSourceFile();
|
||||
L.shapeOrderMetadataFinalizers.push(() => {
|
||||
const current = L.shapes.get(restT.shapeId) ?? restShape;
|
||||
const currentOrder = current.declaredOrder ?? current.fields.map((f) => f.name);
|
||||
if (orderMatches(currentOrder)) return;
|
||||
const previous = L.instantiationContext;
|
||||
L.instantiationContext = context;
|
||||
try {
|
||||
if (countFailure) {
|
||||
L.stats.statementsFailed++;
|
||||
L.bumpFileStat(sf.fileName, "failed");
|
||||
}
|
||||
L.pushDiag({
|
||||
code: "SC1031",
|
||||
message:
|
||||
"rest bindings over class instances whose packed key order cannot match Node's " +
|
||||
"(Object.keys/JSON.stringify would enumerate the copied fields in a different order) are not supported yet",
|
||||
loc: locOf(blame),
|
||||
});
|
||||
} finally {
|
||||
L.instantiationContext = previous;
|
||||
}
|
||||
});
|
||||
}
|
||||
const fields = remaining.map((f) => {
|
||||
const fieldType = info.fields.get(f.name) ?? f.type;
|
||||
|
||||
@@ -83,6 +83,7 @@ import {
|
||||
isUnitOnlyTsType,
|
||||
mapType,
|
||||
ShapeRegistry,
|
||||
type DeclaredOrderPriorityRef,
|
||||
typeKey,
|
||||
type TypeMapperCtx,
|
||||
UnionRegistry,
|
||||
@@ -114,6 +115,12 @@ import { fenceCrossBlockNsRef, nsPathPrefix } from "./lower-namespaces.js";
|
||||
* function can never collide with it (mangling is injective per prefix). */
|
||||
export const ENTRY_NAME = "%main";
|
||||
|
||||
interface GenericDemandOwner {
|
||||
priority?: readonly [phase: number, order: number];
|
||||
functionDemands: GenericInstance[];
|
||||
classDemands: ClassInfo[];
|
||||
}
|
||||
|
||||
/** One step of the copy-reshape width relation (widthLiftPlan): how a
|
||||
* source-typed value enters a destination slot. Pure data — the plan half;
|
||||
* applyWidthLift is the build half. */
|
||||
@@ -873,6 +880,152 @@ export class Lowerer {
|
||||
/** Monomorphization worklist: instances queued by call sites, drained in
|
||||
* run() (processing an instance body can queue more). */
|
||||
readonly instantiationQueue: { info: GenericFnInfo; inst: GenericInstance }[] = [];
|
||||
/** Historical emit rank of the retained declaration/init body currently
|
||||
* lowering, and the earliest such owner that demanded each generic
|
||||
* instance. Reachability can encounter a later caller first; the minimum
|
||||
* rank recovers the old emitter's source-order monomorphization queue. */
|
||||
private genericDemandOwner: GenericDemandOwner | null = null;
|
||||
private readonly genericDemandRoots: GenericDemandOwner[] = [];
|
||||
private readonly genericFunctionDemandOwner = new Map<GenericInstance, GenericDemandOwner>();
|
||||
private readonly genericClassDemandOwner = new Map<ClassInfo, GenericDemandOwner>();
|
||||
private readonly genericDemandPriority = new Map<GenericInstance, DeclaredOrderPriorityRef>();
|
||||
private readonly genericClassDemandPriority = new Map<ClassInfo, DeclaredOrderPriorityRef>();
|
||||
|
||||
private withGenericDemandOwner<T>(
|
||||
owner: GenericDemandOwner,
|
||||
fn: () => T,
|
||||
): T {
|
||||
const previous = this.genericDemandOwner;
|
||||
this.genericDemandOwner = owner;
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
this.genericDemandOwner = previous;
|
||||
}
|
||||
}
|
||||
|
||||
noteGenericInstanceDemand(inst: GenericInstance): void {
|
||||
const owner = this.genericDemandOwner;
|
||||
owner?.functionDemands.push(inst);
|
||||
const ref = this.genericDemandPriority.get(inst) ?? { rank: [4, Number.MAX_SAFE_INTEGER] };
|
||||
const currentRank = owner?.priority;
|
||||
if (currentRank && (
|
||||
currentRank[0] < ref.rank[0]! ||
|
||||
(currentRank[0] === ref.rank[0] && currentRank[1] < (ref.rank[1] ?? 0))
|
||||
)) {
|
||||
ref.rank = currentRank;
|
||||
}
|
||||
this.genericDemandPriority.set(inst, ref);
|
||||
}
|
||||
|
||||
noteGenericClassInstanceDemand(info: ClassInfo): void {
|
||||
const owner = this.genericDemandOwner;
|
||||
owner?.classDemands.push(info);
|
||||
const ref = this.genericClassDemandPriority.get(info) ?? { rank: [4, Number.MAX_SAFE_INTEGER] };
|
||||
const currentRank = owner?.priority;
|
||||
if (currentRank && (
|
||||
currentRank[0] < ref.rank[0]! ||
|
||||
(currentRank[0] === ref.rank[0] && currentRank[1] < (ref.rank[1] ?? 0))
|
||||
)) {
|
||||
ref.rank = currentRank;
|
||||
}
|
||||
this.genericClassDemandPriority.set(info, ref);
|
||||
}
|
||||
|
||||
/** The old emitter lowered all reachable declarations in source order,
|
||||
* then every init, before draining generic instances FIFO. Reorder the
|
||||
* retained queue from those recorded demands before any generic body
|
||||
* lowers, so immediate support decisions see the same shape metadata too. */
|
||||
private restoreGenericInstanceOrder(from = 0): void {
|
||||
const tail = this.instantiationQueue.slice(from);
|
||||
const discoveryOrder = new Map(tail.map((entry, index) => [entry.inst, index] as const));
|
||||
tail.sort((left, right) => {
|
||||
const a = this.genericDemandPriority.get(left.inst)?.rank;
|
||||
const b = this.genericDemandPriority.get(right.inst)?.rank;
|
||||
if (a !== undefined && b !== undefined) {
|
||||
const phase = a[0]! - b[0]!;
|
||||
if (phase !== 0) return phase;
|
||||
const order = a[1]! - b[1]!;
|
||||
if (order !== 0) return order;
|
||||
} else if (a !== undefined) {
|
||||
return -1;
|
||||
} else if (b !== undefined) {
|
||||
return 1;
|
||||
}
|
||||
return discoveryOrder.get(left.inst)! - discoveryOrder.get(right.inst)!;
|
||||
});
|
||||
this.instantiationQueue.splice(from, tail.length, ...tail);
|
||||
}
|
||||
|
||||
private restoreGenericClassInstanceOrder(from = 0): void {
|
||||
const tail = this.genericClassInstances.slice(from);
|
||||
const discoveryOrder = new Map(tail.map((info, index) => [info, index] as const));
|
||||
tail.sort((left, right) => {
|
||||
const a = this.genericClassDemandPriority.get(left)?.rank;
|
||||
const b = this.genericClassDemandPriority.get(right)?.rank;
|
||||
if (a !== undefined && b !== undefined) {
|
||||
const phase = a[0]! - b[0]!;
|
||||
if (phase !== 0) return phase;
|
||||
const order = a[1]! - b[1]!;
|
||||
if (order !== 0) return order;
|
||||
} else if (a !== undefined) {
|
||||
return -1;
|
||||
} else if (b !== undefined) {
|
||||
return 1;
|
||||
}
|
||||
return discoveryOrder.get(left)! - discoveryOrder.get(right)!;
|
||||
});
|
||||
this.genericClassInstances.splice(from, tail.length, ...tail);
|
||||
}
|
||||
|
||||
private settleGenericDemandPriorities(): void {
|
||||
const functionQueue: GenericInstance[] = [];
|
||||
const classQueue: ClassInfo[] = [];
|
||||
const seenFunctions = new Set<GenericInstance>();
|
||||
const seenClasses = new Set<ClassInfo>();
|
||||
const enqueue = (owner: GenericDemandOwner): void => {
|
||||
for (const info of owner.classDemands) {
|
||||
if (seenClasses.has(info)) continue;
|
||||
seenClasses.add(info);
|
||||
classQueue.push(info);
|
||||
}
|
||||
for (const inst of owner.functionDemands) {
|
||||
if (seenFunctions.has(inst)) continue;
|
||||
seenFunctions.add(inst);
|
||||
functionQueue.push(inst);
|
||||
}
|
||||
};
|
||||
for (const root of [...this.genericDemandRoots].sort((a, b) => {
|
||||
const left = a.priority!;
|
||||
const right = b.priority!;
|
||||
return left[0] - right[0] || left[1] - right[1];
|
||||
})) enqueue(root);
|
||||
let classIndex = 0;
|
||||
let functionIndex = 0;
|
||||
let order = 0;
|
||||
while (classIndex < classQueue.length || functionIndex < functionQueue.length) {
|
||||
while (classIndex < classQueue.length) {
|
||||
const info = classQueue[classIndex++]!;
|
||||
this.genericClassDemandPriority.get(info)!.rank = [4, order++];
|
||||
const owner = this.genericClassDemandOwner.get(info);
|
||||
if (owner) enqueue(owner);
|
||||
}
|
||||
while (functionIndex < functionQueue.length) {
|
||||
const inst = functionQueue[functionIndex++]!;
|
||||
this.genericDemandPriority.get(inst)!.rank = [4, order++];
|
||||
const owner = this.genericFunctionDemandOwner.get(inst);
|
||||
if (owner) enqueue(owner);
|
||||
}
|
||||
}
|
||||
for (const info of this.genericClassInstances) {
|
||||
const ref = this.genericClassDemandPriority.get(info);
|
||||
if (ref && ref.rank[1] === Number.MAX_SAFE_INTEGER) ref.rank = [4, order++];
|
||||
}
|
||||
for (const { inst } of this.instantiationQueue) {
|
||||
const ref = this.genericDemandPriority.get(inst);
|
||||
if (ref && ref.rank[1] === Number.MAX_SAFE_INTEGER) ref.rank = [4, order++];
|
||||
}
|
||||
}
|
||||
/** Non-null while an instance body lowers: type-parameter symbol →
|
||||
* concrete IR type, consulted inside mapType's recursion. */
|
||||
typeParamBindings: Map<ts.Symbol, IrType> | null = null;
|
||||
@@ -2499,7 +2652,15 @@ export class Lowerer {
|
||||
for (const name of units.keys()) rank(name);
|
||||
let expressionMetadataOrder = 0;
|
||||
let instanceMetadataOrder = 0;
|
||||
|
||||
const demandOwner = (priority?: readonly [number, number]): GenericDemandOwner => {
|
||||
const owner: GenericDemandOwner = {
|
||||
...(priority ? { priority } : {}),
|
||||
functionDemands: [],
|
||||
classDemands: [],
|
||||
};
|
||||
if (priority) this.genericDemandRoots.push(owner);
|
||||
return owner;
|
||||
};
|
||||
const reachable = new Set<string>();
|
||||
const queue: string[] = [];
|
||||
const loweredUnits = new Map<string, IrFunction>();
|
||||
@@ -2543,29 +2704,30 @@ export class Lowerer {
|
||||
clsInstLowered < this.genericClassInstances.length ||
|
||||
specLowered < this.emitSpecQueue.length
|
||||
) {
|
||||
while (clsInstLowered < this.genericClassInstances.length) {
|
||||
const info = this.genericClassInstances[clsInstLowered++]!;
|
||||
const owner = demandOwner();
|
||||
this.genericClassDemandOwner.set(info, owner);
|
||||
const ref = this.genericClassDemandPriority.get(info) ?? { rank: [4, instanceMetadataOrder++] };
|
||||
this.genericClassDemandPriority.set(info, ref);
|
||||
instanceFunctions.push(...this.withGenericDemandOwner(owner, () =>
|
||||
this.shapes.withDeclaredOrderPriority(ref, () => this.lowerClassMembers(info))));
|
||||
}
|
||||
while (instLowered < this.instantiationQueue.length) {
|
||||
const { info, inst } = this.instantiationQueue[instLowered++]!;
|
||||
const owner = demandOwner();
|
||||
this.genericFunctionDemandOwner.set(inst, owner);
|
||||
const ref = this.genericDemandPriority.get(inst) ?? { rank: [4, instanceMetadataOrder++] };
|
||||
this.genericDemandPriority.set(inst, ref);
|
||||
// Body-level poisons skip the instance after retaining the
|
||||
// diagnostic and every edge fired before poisoning.
|
||||
try {
|
||||
instanceFunctions.push(
|
||||
this.shapes.withDeclaredOrderPriority(
|
||||
[4, instanceMetadataOrder++],
|
||||
() => this.lowerGenericInstance(info, inst),
|
||||
),
|
||||
);
|
||||
instanceFunctions.push(this.withGenericDemandOwner(owner, () =>
|
||||
this.shapes.withDeclaredOrderPriority(ref, () => this.lowerGenericInstance(info, inst))));
|
||||
} catch (e) {
|
||||
if (!(e instanceof PoisonError)) throw e;
|
||||
}
|
||||
}
|
||||
while (clsInstLowered < this.genericClassInstances.length) {
|
||||
instanceFunctions.push(
|
||||
...this.shapes.withDeclaredOrderPriority(
|
||||
[4, instanceMetadataOrder++],
|
||||
() => this.lowerClassMembers(this.genericClassInstances[clsInstLowered++]!),
|
||||
),
|
||||
);
|
||||
}
|
||||
// Emit-override specialization bodies fire edges of their own
|
||||
// (the super-forward chain, closures, generic calls) — lower them
|
||||
// exactly like generic instances.
|
||||
@@ -2584,13 +2746,17 @@ export class Lowerer {
|
||||
};
|
||||
|
||||
parts.forEach((fp, index) => {
|
||||
const priority = [2, index] as const;
|
||||
const owner = demandOwner(priority);
|
||||
initFunctions.push(
|
||||
this.shapes.withDeclaredOrderPriority(
|
||||
[2, index],
|
||||
() => this.lowerFileInit(fp.sf, fp.topStmts, this.initNameOf.get(fp.sf)!),
|
||||
this.withGenericDemandOwner(
|
||||
owner,
|
||||
() => this.shapes.withDeclaredOrderPriority(
|
||||
priority,
|
||||
() => this.lowerFileInit(fp.sf, fp.topStmts, this.initNameOf.get(fp.sf)!),
|
||||
),
|
||||
),
|
||||
);
|
||||
drainInstances();
|
||||
});
|
||||
// LIBRARY mode's extra reachability roots (LowerOptions.libRoots): the
|
||||
// profile-mapped exports are called from outside the graph, so they
|
||||
@@ -2605,19 +2771,49 @@ export class Lowerer {
|
||||
try {
|
||||
const name = queue.shift()!;
|
||||
const unit = units.get(name)!;
|
||||
const fn = this.shapes.withDeclaredOrderPriority(
|
||||
metadataPriority.get(name) ?? [3, expressionMetadataOrder++],
|
||||
unit.lower,
|
||||
const priority = metadataPriority.get(name) ?? [3, expressionMetadataOrder++];
|
||||
const owner = demandOwner(priority);
|
||||
const fn = this.withGenericDemandOwner(
|
||||
owner,
|
||||
() => this.shapes.withDeclaredOrderPriority(priority, unit.lower),
|
||||
);
|
||||
if (fn) loweredUnits.set(name, fn);
|
||||
} catch (e) {
|
||||
if (!(e instanceof PoisonError)) throw e;
|
||||
}
|
||||
}
|
||||
this.restoreGenericInstanceOrder();
|
||||
this.restoreGenericClassInstanceOrder();
|
||||
// Generic bodies can reach ordinary declarations, whose bodies can in
|
||||
// turn queue more instances. Continue to the joint fixpoint; the initial
|
||||
// queue above is the only portion whose discovery order differed from
|
||||
// historical emit order.
|
||||
for (;;) {
|
||||
drainInstances();
|
||||
if (queue.length === 0) break;
|
||||
while (queue.length > 0) {
|
||||
try {
|
||||
const name = queue.shift()!;
|
||||
const unit = units.get(name)!;
|
||||
const priority = metadataPriority.get(name) ?? [3, expressionMetadataOrder++];
|
||||
const owner = demandOwner(priority);
|
||||
const fn = this.withGenericDemandOwner(
|
||||
owner,
|
||||
() => this.shapes.withDeclaredOrderPriority(priority, unit.lower),
|
||||
);
|
||||
if (fn) loweredUnits.set(name, fn);
|
||||
} catch (e) {
|
||||
if (!(e instanceof PoisonError)) throw e;
|
||||
}
|
||||
}
|
||||
this.restoreGenericInstanceOrder(instLowered);
|
||||
this.restoreGenericClassInstanceOrder(clsInstLowered);
|
||||
}
|
||||
const orderedUnits = [...loweredUnits]
|
||||
.sort(([left], [right]) => units.get(left)!.order - units.get(right)!.order)
|
||||
.map(([, fn]) => fn);
|
||||
this.settleGenericDemandPriorities();
|
||||
this.shapes.settleDeclaredOrderPriorities();
|
||||
for (const finalize of this.shapeOrderMetadataFinalizers) finalize();
|
||||
for (const finalize of this.shapeOrderHelperFinalizers) finalize();
|
||||
const functions = [
|
||||
|
||||
@@ -61,6 +61,27 @@ export function isParseArgsDynTypeName(name: string): boolean {
|
||||
return PARSE_ARGS_DYN_TYPES.has(name);
|
||||
}
|
||||
|
||||
export interface DeclaredOrderPriorityRef {
|
||||
rank: readonly number[];
|
||||
}
|
||||
|
||||
export type DeclaredOrderPriority = readonly number[] | DeclaredOrderPriorityRef;
|
||||
|
||||
function priorityRank(priority: DeclaredOrderPriority): readonly number[] {
|
||||
return "rank" in priority ? priority.rank : priority;
|
||||
}
|
||||
|
||||
function comparePriority(left: DeclaredOrderPriority, right: DeclaredOrderPriority): number {
|
||||
const a = priorityRank(left);
|
||||
const b = priorityRank(right);
|
||||
const length = Math.max(a.length, b.length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
const d = (a[i] ?? 0) - (b[i] ?? 0);
|
||||
if (d !== 0) return d;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** The frontend's record-shape interner. Records are monomorphic structural
|
||||
* shapes: fields sorted by name form the canonical identity, and two types
|
||||
* with the same canonical field list share one shapeId (and later one C
|
||||
@@ -74,8 +95,12 @@ export class ShapeRegistry {
|
||||
* install their old emit positions while they lower. This lets a body
|
||||
* reached after an init replace metadata the worklist encountered first
|
||||
* when the old emitter would have lowered that body first. */
|
||||
private readonly declaredOrderPriority = new Map<string, readonly [phase: number, order: number]>();
|
||||
private currentDeclaredOrderPriority: readonly [phase: number, order: number] = [0, 0];
|
||||
private readonly declaredOrderPriority = new Map<string, DeclaredOrderPriority>();
|
||||
private readonly declaredOrderCandidates = new Map<
|
||||
string,
|
||||
{ order: string[]; priority: DeclaredOrderPriority }[]
|
||||
>();
|
||||
private currentDeclaredOrderPriority: DeclaredOrderPriority = [0, 0];
|
||||
/** All interned shapes in first-seen (`r0`, `r1`, ...) order. */
|
||||
readonly shapes: IrRecordShape[] = [];
|
||||
/** ts.Types currently being mapped — a BACK-REFERENCE to one of these is
|
||||
@@ -114,7 +139,7 @@ export class ShapeRegistry {
|
||||
/** Runs one lowering unit under its historical emit-order rank. Shape
|
||||
* ids remain demand-assigned; only first-seen declaration-order metadata
|
||||
* uses this rank, because Object.keys/JSON/inspect observe it. */
|
||||
withDeclaredOrderPriority<T>(priority: readonly [phase: number, order: number], fn: () => T): T {
|
||||
withDeclaredOrderPriority<T>(priority: DeclaredOrderPriority, fn: () => T): T {
|
||||
const previous = this.currentDeclaredOrderPriority;
|
||||
this.currentDeclaredOrderPriority = priority;
|
||||
try {
|
||||
@@ -126,19 +151,35 @@ export class ShapeRegistry {
|
||||
|
||||
private adoptDeclaredOrder(shape: IrRecordShape, declaredOrder: string[] | undefined): void {
|
||||
if (declaredOrder === undefined) return;
|
||||
const candidates = this.declaredOrderCandidates.get(shape.id);
|
||||
const candidate = { order: declaredOrder, priority: this.currentDeclaredOrderPriority };
|
||||
if (candidates) candidates.push(candidate);
|
||||
else this.declaredOrderCandidates.set(shape.id, [candidate]);
|
||||
const previous = this.declaredOrderPriority.get(shape.id);
|
||||
if (
|
||||
previous !== undefined &&
|
||||
(previous[0] < this.currentDeclaredOrderPriority[0] ||
|
||||
(previous[0] === this.currentDeclaredOrderPriority[0] &&
|
||||
previous[1] <= this.currentDeclaredOrderPriority[1]))
|
||||
) {
|
||||
if (previous !== undefined && comparePriority(previous, this.currentDeclaredOrderPriority) <= 0) {
|
||||
return;
|
||||
}
|
||||
shape.declaredOrder = declaredOrder;
|
||||
this.declaredOrderPriority.set(shape.id, this.currentDeclaredOrderPriority);
|
||||
}
|
||||
|
||||
/** Mutable generic-instance ranks settle only after reachability closes.
|
||||
* Re-choose each shape's first historical writer from the retained
|
||||
* candidates before derived metadata and helper bodies finalize. */
|
||||
settleDeclaredOrderPriorities(): void {
|
||||
for (const [shapeId, candidates] of this.declaredOrderCandidates) {
|
||||
let best = candidates[0];
|
||||
if (!best) continue;
|
||||
for (const candidate of candidates.slice(1)) {
|
||||
if (comparePriority(candidate.priority, best.priority) < 0) best = candidate;
|
||||
}
|
||||
const shape = this.byId.get(shapeId);
|
||||
if (!shape) continue;
|
||||
shape.declaredOrder = best.order;
|
||||
this.declaredOrderPriority.set(shapeId, best.priority);
|
||||
}
|
||||
}
|
||||
|
||||
/** Captures the current historical rank for a derived shape whose order
|
||||
* must be recomputed after retained reachability settles its source. */
|
||||
declaredOrderFinalizer(
|
||||
@@ -233,6 +274,9 @@ export class ShapeRegistry {
|
||||
this.byId.set(id, shape);
|
||||
this.shapes.push(shape);
|
||||
if (declaredOrder !== undefined) {
|
||||
this.declaredOrderCandidates.set(id, [
|
||||
{ order: declaredOrder, priority: this.currentDeclaredOrderPriority },
|
||||
]);
|
||||
this.declaredOrderPriority.set(id, this.currentDeclaredOrderPriority);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -54,6 +54,78 @@ function printRecursiveRecord(): void {
|
||||
console.log(inspect(value));
|
||||
}
|
||||
|
||||
class GenericOrderedPair {
|
||||
genericFirst = 1;
|
||||
genericSecond = 2;
|
||||
}
|
||||
|
||||
function genericSourceFirst<T>(_: T): void {
|
||||
// This rest lowering checks order immediately; a helper finalizer cannot
|
||||
// repair it after the wrong generic instance has already fenced.
|
||||
const { ...value } = new GenericOrderedPair();
|
||||
console.log(Object.keys(value).join(","));
|
||||
}
|
||||
|
||||
function genericSourceSecond<T>(_: T): void {
|
||||
const value = { genericSecond: 2, genericFirst: 1 };
|
||||
console.log(Object.keys(value).length);
|
||||
}
|
||||
|
||||
function callGenericSourceFirst(): void {
|
||||
genericSourceFirst(1);
|
||||
}
|
||||
|
||||
function callGenericSourceSecond(): void {
|
||||
genericSourceSecond(1);
|
||||
}
|
||||
|
||||
class GenericClassSourceFirst<T> {
|
||||
constructor(_: T) {
|
||||
const { ...value } = new GenericOrderedPair();
|
||||
console.log(Object.keys(value).join(","));
|
||||
}
|
||||
}
|
||||
|
||||
class GenericClassSourceSecond<T> {
|
||||
constructor(_: T) {
|
||||
const value = { genericSecond: 2, genericFirst: 1 };
|
||||
console.log(Object.keys(value).length);
|
||||
}
|
||||
}
|
||||
|
||||
function constructGenericClassSourceFirst(): void {
|
||||
new GenericClassSourceFirst(1);
|
||||
}
|
||||
|
||||
function constructGenericClassSourceSecond(): void {
|
||||
new GenericClassSourceSecond(1);
|
||||
}
|
||||
|
||||
class NestedGenericOrderedPair {
|
||||
nestedFirst = 1;
|
||||
nestedSecond = 2;
|
||||
}
|
||||
|
||||
function nestedGenericInstanceSourceFirst<T>(_: T): void {
|
||||
const value = { nestedFirst: 1, nestedSecond: 2 };
|
||||
console.log(Object.keys(value).length);
|
||||
}
|
||||
|
||||
function nestedGenericSourceFirst(): void {
|
||||
nestedGenericInstanceSourceFirst(1);
|
||||
}
|
||||
|
||||
function genericReachesEarlierSource<T>(_: T): void {
|
||||
const value = { nestedSecond: 2, nestedFirst: 1 };
|
||||
console.log(Object.keys(value).length);
|
||||
// Reachability learns about this earlier declaration only while this
|
||||
// instance lowers. Its metadata must still settle before the rest-order
|
||||
// support decision becomes final.
|
||||
nestedGenericSourceFirst();
|
||||
const { ...rest } = new NestedGenericOrderedPair();
|
||||
console.log(Object.keys(rest).join(","));
|
||||
}
|
||||
|
||||
console.log(Object.keys({ b: 2, a: 1 }).length);
|
||||
console.log(Object.values({ b: 2, a: 1 }).length);
|
||||
console.log(Object.entries({ b: 2, a: 1 }).length);
|
||||
@@ -64,6 +136,15 @@ console.log(Object.entries({ b: 2, a: 1 } as { b: number; a: number; [key: strin
|
||||
console.log(capturedCount({ b: 2, a: 1 }));
|
||||
console.log(assignedCount());
|
||||
console.log(inspect({ b: 2, a: 1, next: null } as { b: number; a: number; next: OrderedNode | null }).length);
|
||||
// Runtime reachability encounters the second caller first. The historical
|
||||
// emitter visited these caller bodies in source order before it drained the
|
||||
// generic-instance queue, so the first instance owns the shared shape's key
|
||||
// order.
|
||||
callGenericSourceSecond();
|
||||
callGenericSourceFirst();
|
||||
constructGenericClassSourceSecond();
|
||||
constructGenericClassSourceFirst();
|
||||
genericReachesEarlierSource(1);
|
||||
|
||||
printFunctionRecord();
|
||||
printIndexRecord();
|
||||
|
||||
Reference in New Issue
Block a user