From 895763f3b651caf9b27c7cadff9545bc4f31035f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 26 Oct 2026 07:38:24 -0500 Subject: [PATCH] Keep a give-up statement's first-attempt diagnostics --- .../compiler/src/frontend/lowering/lower-stmts.ts | 14 ++++++++++++-- packages/compiler/src/frontend/lowering/lowerer.ts | 13 +++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 7c0f05bfc4..12b7aabcc2 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -759,6 +759,11 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir // out of tdzPredeclared; a retry after a later declarator degrades // must initialize the same box again. const tdzBefore = new Map(lowerer.ctx.tdzPredeclared); + // The first attempt's diagnostics: a statement that still fails + // after degrading reports them, since a retry does not repeat what + // lowered once (a poisoned generic instance) and its own diagnostics + // can miss the failure's cause. + let firstAttempt: ScrDiagnostic[] | null = null; try { for (let attempt = 0; ; attempt++) { const hits = new Set(); @@ -773,7 +778,12 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir // global degrades that type and the statement lowers again. // A declaration this statement flushed inside a probe (where // no retry runs) can retry here too. - if (attempt >= MAX_DEGRADE_RETRIES || deferFences) throw e; + firstAttempt ??= lowerer.diags.slice(diagsBefore); + const giveUp = (): unknown => { + if (attempt > 0) lowerer.restoreFirstAttemptDiags(diagsBefore, firstAttempt!); + return e; + }; + if (attempt >= MAX_DEGRADE_RETRIES || deferFences) throw giveUp(); const slot = lowerer.degradeAtCommit(e, STATEMENT_SLOTS); // Pushed-down types the failed attempt mapped widen to the // whole slot before anything else is given up. @@ -782,7 +792,7 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir !lowerer.reopenFlushedAtCommit(diagsBefore) && !lowerer.widenPartials(hits) ) - throw e; + throw giveUp(); // A failed attempt adds nothing to `out` but the TDZ boxes // forward captures declared (they stay declared), so the // retry appends after them. diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index cfa7987a63..9184a4e36b 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -9853,6 +9853,19 @@ export class Lowerer { this.diags.push(...kept); } + /** A unit that still fails after its retries commits its first + * attempt's own diagnostics (`first`, taken before the first + * degradation) in place of the last attempt's. The diagnostics the + * retries carry (dropAttemptDiags) stay as they are now: a declaration + * reopened since may have registered. */ + restoreFirstAttemptDiags(diagsBefore: number, first: readonly ScrDiagnostic[]): void { + const carried = (d: ScrDiagnostic): boolean => + d.rejects !== undefined || this.loweredOnceDiags.has(d); + const kept = this.diags.slice(diagsBefore).filter(carried); + this.diags.length = diagsBefore; + this.diags.push(...first.filter((d) => !carried(d)), ...kept); + } + /** Lowers one unit whose body-level poison skips it (a generic * instance), with localized failure's commit point: a degradable type * failure at one of its declaration slots degrades the type and the unit