mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-05 08:13:06 +08:00
Fall back to the whole-slot dynamic when a unit fails with a pushed-down type
Push-down is a refinement and must never add a failure: a type's lenient shape can meet a conversion the whole-slot checked dynamic would have taken (a width copy, a library member on a partial promise). Each retryable unit (a statement, a flushed declaration, a generic instance) records the pushed-down types it maps; when it still fails, those types widen to the whole-slot checked dynamic and the unit lowers again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
95f9d259b7
commit
2d63ad13a5
@@ -757,8 +757,11 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir
|
||||
const pendingSlots: DynamicSlot[] = [];
|
||||
try {
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
const hits = new Set<number>();
|
||||
try {
|
||||
lowerOneStatement(lowerer, stmt, out, usingMarkers, diagsBefore, deferFences);
|
||||
lowerer.withPartialHits(hits, () =>
|
||||
lowerOneStatement(lowerer, stmt, out, usingMarkers, diagsBefore, deferFences),
|
||||
);
|
||||
break;
|
||||
} catch (e) {
|
||||
// Localized failure's statement commit point (design §3.3): a
|
||||
@@ -768,7 +771,14 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir
|
||||
// no retry runs) can retry here too.
|
||||
if (attempt >= MAX_DEGRADE_RETRIES || deferFences) throw e;
|
||||
const slot = lowerer.degradeAtCommit(e, STATEMENT_SLOTS);
|
||||
if (slot === null && !lowerer.reopenFlushedAtCommit(diagsBefore)) throw e;
|
||||
// Pushed-down types the failed attempt mapped widen to the
|
||||
// whole slot before anything else is given up.
|
||||
if (
|
||||
slot === null &&
|
||||
!lowerer.reopenFlushedAtCommit(diagsBefore) &&
|
||||
!lowerer.widenPartials(hits)
|
||||
)
|
||||
throw e;
|
||||
// A failed attempt adds nothing to `out` but the TDZ boxes
|
||||
// forward captures declared (they stay declared), so the
|
||||
// retry appends after them.
|
||||
|
||||
@@ -3788,6 +3788,9 @@ export class Lowerer {
|
||||
number,
|
||||
{ lenient: IrType | null; code: string; reason: string }
|
||||
>();
|
||||
/** One frame per unit a commit point may retry: the pushed-down
|
||||
* (partial) degraded types its lowering mapped (withPartialHits). */
|
||||
readonly partialHits: Set<number>[] = [];
|
||||
/** The dynamic slots the commit points created: one SC6104 each. */
|
||||
readonly dynamicSlots: DynamicSlot[] = [];
|
||||
/** Declarations retryDeferred registered. */
|
||||
@@ -3950,6 +3953,7 @@ export class Lowerer {
|
||||
degraded.lenient = DYN;
|
||||
degraded.lenient = this.lenientType(type);
|
||||
}
|
||||
if (degraded.lenient.kind !== "dyn") this.partialHits[this.partialHits.length - 1]?.add(id);
|
||||
return degraded.lenient;
|
||||
},
|
||||
isErasing: () => this.erasingDepth > 0,
|
||||
@@ -9572,16 +9576,17 @@ export class Lowerer {
|
||||
const slots: DynamicSlot[] = [];
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
const mark = this.diags.length;
|
||||
const hits = new Set<number>();
|
||||
try {
|
||||
const result = lower();
|
||||
const result = this.withPartialHits(hits, lower);
|
||||
for (const slot of slots) this.noteDynamicSlot(slot);
|
||||
return result;
|
||||
} catch (e) {
|
||||
if (!(e instanceof PoisonError)) throw e;
|
||||
if (attempt >= MAX_DECLARATION_RETRIES) return null;
|
||||
const slot = this.degradeAtCommit(e, DECLARATION_SLOTS);
|
||||
if (slot === null) return null;
|
||||
slots.push(slot);
|
||||
if (slot === null && !this.widenPartials(hits)) return null;
|
||||
if (slot !== null) slots.push(slot);
|
||||
this.dropAttemptDiags(mark);
|
||||
}
|
||||
}
|
||||
@@ -9614,6 +9619,34 @@ export class Lowerer {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Runs a unit a commit point may retry, recording the pushed-down
|
||||
* types it maps; the frame's hits also count for the enclosing unit. */
|
||||
withPartialHits<T>(hits: Set<number>, run: () => T): T {
|
||||
this.partialHits.push(hits);
|
||||
try {
|
||||
return run();
|
||||
} finally {
|
||||
this.partialHits.pop();
|
||||
const outer = this.partialHits[this.partialHits.length - 1];
|
||||
if (outer !== undefined) for (const id of hits) outer.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
/** Push-down is a refinement, never a new failure: a unit that still
|
||||
* fails after mapping pushed-down types falls back to the whole-slot
|
||||
* checked dynamic for them. True when some type widened (retry). */
|
||||
widenPartials(hits: ReadonlySet<number>): boolean {
|
||||
if (this.tierMode !== "auto" || this.diagSink !== null) return false;
|
||||
let widened = false;
|
||||
for (const id of hits) {
|
||||
const degraded = this.degradedTypes.get(id);
|
||||
if (degraded === undefined || degraded.lenient?.kind === "dyn") continue;
|
||||
degraded.lenient = DYN;
|
||||
widened = true;
|
||||
}
|
||||
return widened;
|
||||
}
|
||||
|
||||
/** Records a dynamic slot whose unit lowered with it (one per site). */
|
||||
noteDynamicSlot(slot: DynamicSlot): void {
|
||||
const seen = this.dynamicSlots.some(
|
||||
@@ -12654,9 +12687,19 @@ export class Lowerer {
|
||||
this.deferredDiags.delete(symbol);
|
||||
const lifted = this.liftDynFieldPolicy;
|
||||
this.liftDynFieldPolicy = true;
|
||||
let registered: boolean;
|
||||
// Pushed-down types that did not help widen to the whole slot, and the
|
||||
// collection runs again (a few times at most).
|
||||
let registered = false;
|
||||
try {
|
||||
registered = this.collectDeferring(collector.symbolOf, collector.collect) === null;
|
||||
for (let widen = 0; widen < 4; widen++) {
|
||||
const hits = new Set<number>();
|
||||
registered =
|
||||
this.withPartialHits(hits, () =>
|
||||
this.collectDeferring(collector.symbolOf, collector.collect),
|
||||
) === null;
|
||||
if (registered || !this.widenPartials(hits)) break;
|
||||
this.deferredDiags.delete(symbol);
|
||||
}
|
||||
} finally {
|
||||
this.liftDynFieldPolicy = lifted;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user