From dee240efc709629c1b15863444879dd6777cead3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 20 Aug 2026 11:04:31 -0500 Subject: [PATCH] Preserve generic lowering record order --- .../src/frontend/lowering/lower-calls.ts | 1 + .../src/frontend/lowering/lower-classes.ts | 2 + .../src/frontend/lowering/lower-stmts.ts | 45 +++- .../compiler/src/frontend/lowering/lowerer.ts | 240 ++++++++++++++++-- packages/compiler/src/frontend/types.ts | 62 ++++- .../2691-retained-lowering-record-order.ts | 81 ++++++ 6 files changed, 394 insertions(+), 37 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index e20941124..353f6d49e 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -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; } diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 52fa5f054..cad7f1437 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -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 }; } diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 28e146070..a42cbf1a6 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -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; diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index cf8f40e4b..ebb8cd269 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -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(); + private readonly genericClassDemandOwner = new Map(); + private readonly genericDemandPriority = new Map(); + private readonly genericClassDemandPriority = new Map(); + + private withGenericDemandOwner( + 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(); + const seenClasses = new Set(); + 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 | 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(); const queue: string[] = []; const loweredUnits = new Map(); @@ -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 = [ diff --git a/packages/compiler/src/frontend/types.ts b/packages/compiler/src/frontend/types.ts index d13efb286..6865c2ebd 100644 --- a/packages/compiler/src/frontend/types.ts +++ b/packages/compiler/src/frontend/types.ts @@ -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(); - private currentDeclaredOrderPriority: readonly [phase: number, order: number] = [0, 0]; + private readonly declaredOrderPriority = new Map(); + 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(priority: readonly [phase: number, order: number], fn: () => T): T { + withDeclaredOrderPriority(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 { diff --git a/tests/corpus/2691-retained-lowering-record-order.ts b/tests/corpus/2691-retained-lowering-record-order.ts index 9a3229e5f..ffb6e0930 100644 --- a/tests/corpus/2691-retained-lowering-record-order.ts +++ b/tests/corpus/2691-retained-lowering-record-order.ts @@ -54,6 +54,78 @@ function printRecursiveRecord(): void { console.log(inspect(value)); } +class GenericOrderedPair { + genericFirst = 1; + genericSecond = 2; +} + +function genericSourceFirst(_: 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): 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 { + constructor(_: T) { + const { ...value } = new GenericOrderedPair(); + console.log(Object.keys(value).join(",")); + } +} + +class GenericClassSourceSecond { + 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): void { + const value = { nestedFirst: 1, nestedSecond: 2 }; + console.log(Object.keys(value).length); +} + +function nestedGenericSourceFirst(): void { + nestedGenericInstanceSourceFirst(1); +} + +function genericReachesEarlierSource(_: 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();