mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-08 08:44:37 +08:00
Initialize a forward-captured binding's box when its statement lowers again
A binding that a function declared earlier captures is pre-declared as a TDZ box, and its declaration's assign initializes the box. The declaration dropped the pre-declaration before lowering its initializer. When the initializer failed with a degradable type (a cast whose type does not compile) and the statement commit point lowered the statement again, the retry no longer saw the box: it declared a new local and initialized that, and a later call of the function threw ReferenceError. The pre-declaration is now dropped after the initializer lowers, for variable, class and generic-function declarations.
This commit is contained in:
@@ -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) };
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/localized-failure-forward-capture.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -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, (x: number) => string> | AbortSignal | (() => number) | Set<string>;
|
||||
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();
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3500 }
|
||||
Reference in New Issue
Block a user