diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 4ab2aae08c..d4c686a90c 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1772,8 +1772,8 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt const symbol = lowerer.checker.getSymbolAtLocation(stmt.name); const pre = symbol ? lowerer.ctx.tdzPredeclared.get(symbol) : undefined; if (pre) { - lowerer.ctx.tdzPredeclared.delete(symbol!); const value = lowerer.coerceToExpected(lowerer.lowerClassExpression(stmt), DYN); + lowerer.ctx.tdzPredeclared.delete(symbol!); return { kind: "assign", localId: pre.id, value, initializes: true, loc: locOf(stmt) }; } const type: IrType = { kind: "classval", className: info.def.name }; @@ -5789,8 +5789,8 @@ export function lowerVarDecl( const symbol = lowerer.checker.getSymbolAtLocation(decl.name); const pre = symbol ? lowerer.ctx.tdzPredeclared.get(symbol) : undefined; if (pre) { - lowerer.ctx.tdzPredeclared.delete(symbol!); const value = genericBindingStoredValue(lowerer, decl, info); + lowerer.ctx.tdzPredeclared.delete(symbol!); return { kind: "assign", localId: pre.id, value, initializes: true, loc: locOf(decl) }; } return genericBindingPresenceStore(info, decl); @@ -5953,11 +5953,13 @@ export function lowerVarDecl( // A forward-captured binding pre-declared as a TDZ box (an earlier // function in this scope captured it — predeclareForwardCapture): the // binding and its scope-entry varDecl already exist; the source - // declaration is the one initializing `assign` into the shared box. + // declaration is the one initializing `assign` into the shared box. The + // box stays pre-declared until the initializer lowers: a statement the + // commit point lowers again (localized failure) initializes the same box. const pre = declSymbol ? lowerer.ctx.tdzPredeclared.get(declSymbol) : undefined; if (pre && decl.initializer) { - lowerer.ctx.tdzPredeclared.delete(declSymbol!); const init = lowerer.lowerExprExpecting(decl.initializer, pre.type); + lowerer.ctx.tdzPredeclared.delete(declSymbol!); return { kind: "assign", localId: pre.id, value: init, initializes: true, loc: locOf(decl) }; } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-forward-capture.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-forward-capture.ts.json new file mode 100644 index 0000000000..f775701663 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/localized-failure-forward-capture.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/localized-failure-forward-capture.ts" + ], + "diags": [] +} diff --git a/tests/corpus/localized-failure-forward-capture.ts b/tests/corpus/localized-failure-forward-capture.ts new file mode 100644 index 0000000000..9265c113fe --- /dev/null +++ b/tests/corpus/localized-failure-forward-capture.ts @@ -0,0 +1,30 @@ +// Localized failure, stage L4, at a forward-captured binding: functions +// declared before a const capture it, and the const's initializer has an +// expression whose type does not compile. The statement lowers again with +// that expression degraded, and it initializes the binding the functions +// captured: before the declaration a call throws ReferenceError, after it +// the call reads the value. +type Hook = Map string> | AbortSignal | (() => number) | Set; +interface Plugin { + name: string; + hook: Hook; +} +function load(text: string): unknown { + return JSON.parse(text); +} +function main(): void { + function nameOf(): string { + return plugin.name; + } + function attempt(): string { + try { + return nameOf(); + } catch (e) { + return e instanceof ReferenceError ? "ReferenceError" : "other"; + } + } + console.log(attempt()); + const plugin = { name: (load('{"name":"tags","hook":null}') as Plugin).name }; + console.log(attempt(), nameOf().length); +} +main(); diff --git a/tests/harness/corpus-costs/localized-failure-forward-capture.ts.json b/tests/harness/corpus-costs/localized-failure-forward-capture.ts.json new file mode 100644 index 0000000000..3e8d4b6ac5 --- /dev/null +++ b/tests/harness/corpus-costs/localized-failure-forward-capture.ts.json @@ -0,0 +1 @@ +{ "plain": 3500 }