Skip to content

Commit

Permalink
Reset error record in downlevel for-of (#31519)
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed May 22, 2019
1 parent 7611c5b commit b3dc32f
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 18 deletions.
42 changes: 24 additions & 18 deletions src/compiler/transformers/es2015.ts
Expand Up @@ -145,7 +145,7 @@ namespace ts {
loopOutParameters: LoopOutParameter[];
}

type LoopConverter = (node: IterationStatement, outermostLabeledStatement: LabeledStatement | undefined, convertedLoopBodyStatements: Statement[] | undefined) => Statement;
type LoopConverter = (node: IterationStatement, outermostLabeledStatement: LabeledStatement | undefined, convertedLoopBodyStatements: Statement[] | undefined, ancestorFacts: HierarchyFacts) => Statement;

// Facts we track as we traverse the tree
const enum HierarchyFacts {
Expand All @@ -163,11 +163,12 @@ namespace ts {
ExportedVariableStatement = 1 << 5, // Enclosed in an exported variable statement in the current scope
TopLevel = 1 << 6, // Enclosing block-scoped container is a top-level container
Block = 1 << 7, // Enclosing block-scoped container is a Block
IterationStatement = 1 << 8, // Enclosed in an IterationStatement
IterationStatement = 1 << 8, // Immediately enclosed in an IterationStatement
IterationStatementBlock = 1 << 9, // Enclosing Block is enclosed in an IterationStatement
ForStatement = 1 << 10, // Enclosing block-scoped container is a ForStatement
ForInOrForOfStatement = 1 << 11, // Enclosing block-scoped container is a ForInStatement or ForOfStatement
ConstructorWithCapturedSuper = 1 << 12, // Enclosed in a constructor that captures 'this' for use with 'super'
IterationContainer = 1 << 10, // Enclosed in an outer IterationStatement
ForStatement = 1 << 11, // Enclosing block-scoped container is a ForStatement
ForInOrForOfStatement = 1 << 12, // Enclosing block-scoped container is a ForInStatement or ForOfStatement
ConstructorWithCapturedSuper = 1 << 13, // Enclosed in a constructor that captures 'this' for use with 'super'
// NOTE: do not add more ancestor flags without also updating AncestorFactsMask below.
// NOTE: when adding a new ancestor flag, be sure to update the subtree flags below.

Expand All @@ -184,11 +185,11 @@ namespace ts {

// A source file is a top-level block scope.
SourceFileIncludes = TopLevel,
SourceFileExcludes = BlockScopeExcludes & ~TopLevel,
SourceFileExcludes = BlockScopeExcludes & ~TopLevel | IterationContainer,

// Functions, methods, and accessors are both new lexical scopes and new block scopes.
FunctionIncludes = Function | TopLevel,
FunctionExcludes = BlockScopeExcludes & ~TopLevel | ArrowFunction | AsyncFunctionBody | CapturesThis | NonStaticClassElement | ConstructorWithCapturedSuper,
FunctionExcludes = BlockScopeExcludes & ~TopLevel | ArrowFunction | AsyncFunctionBody | CapturesThis | NonStaticClassElement | ConstructorWithCapturedSuper | IterationContainer,

AsyncFunctionBodyIncludes = FunctionIncludes | AsyncFunctionBody,
AsyncFunctionBodyExcludes = FunctionExcludes & ~NonStaticClassElement,
Expand All @@ -205,16 +206,16 @@ namespace ts {
// 'do' and 'while' statements are not block scopes. We track that the subtree is contained
// within an IterationStatement to indicate whether the embedded statement is an
// IterationStatementBlock.
DoOrWhileStatementIncludes = IterationStatement,
DoOrWhileStatementIncludes = IterationStatement | IterationContainer,
DoOrWhileStatementExcludes = None,

// 'for' statements are new block scopes and have special handling for 'let' declarations.
ForStatementIncludes = IterationStatement | ForStatement,
ForStatementIncludes = IterationStatement | ForStatement | IterationContainer,
ForStatementExcludes = BlockScopeExcludes & ~ForStatement,

// 'for-in' and 'for-of' statements are new block scopes and have special handling for
// 'let' declarations.
ForInOrForOfStatementIncludes = IterationStatement | ForInOrForOfStatement,
ForInOrForOfStatementIncludes = IterationStatement | ForInOrForOfStatement | IterationContainer,
ForInOrForOfStatementExcludes = BlockScopeExcludes & ~ForInOrForOfStatement,

// Blocks (other than function bodies) are new block scopes.
Expand All @@ -228,8 +229,8 @@ namespace ts {
// Subtree facts
//

NewTarget = 1 << 13, // Contains a 'new.target' meta-property
CapturedLexicalThis = 1 << 14, // Contains a lexical `this` reference captured by an arrow function.
NewTarget = 1 << 14, // Contains a 'new.target' meta-property
CapturedLexicalThis = 1 << 15, // Contains a lexical `this` reference captured by an arrow function.

//
// Subtree masks
Expand Down Expand Up @@ -2227,7 +2228,7 @@ namespace ts {

function visitIterationStatementWithFacts(excludeFacts: HierarchyFacts, includeFacts: HierarchyFacts, node: IterationStatement, outermostLabeledStatement: LabeledStatement | undefined, convert?: LoopConverter) {
const ancestorFacts = enterSubtree(excludeFacts, includeFacts);
const updated = convertIterationStatementBodyIfNecessary(node, outermostLabeledStatement, convert);
const updated = convertIterationStatementBodyIfNecessary(node, outermostLabeledStatement, ancestorFacts, convert);
exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None);
return updated;
}
Expand Down Expand Up @@ -2434,7 +2435,7 @@ namespace ts {
return restoreEnclosingLabel(forStatement, outermostLabeledStatement, convertedLoopState && resetLabel);
}

function convertForOfStatementForIterable(node: ForOfStatement, outermostLabeledStatement: LabeledStatement, convertedLoopBodyStatements: Statement[]): Statement {
function convertForOfStatementForIterable(node: ForOfStatement, outermostLabeledStatement: LabeledStatement, convertedLoopBodyStatements: Statement[], ancestorFacts: HierarchyFacts): Statement {
const expression = visitNode(node.expression, visitor, isExpression);
const iterator = isIdentifier(expression) ? getGeneratedNameForNode(expression) : createTempVariable(/*recordTempVariable*/ undefined);
const result = isIdentifier(expression) ? getGeneratedNameForNode(iterator) : createTempVariable(/*recordTempVariable*/ undefined);
Expand All @@ -2447,13 +2448,18 @@ namespace ts {
hoistVariableDeclaration(errorRecord);
hoistVariableDeclaration(returnMethod);

// if we are enclosed in an outer loop ensure we reset 'errorRecord' per each iteration
const initializer = ancestorFacts & HierarchyFacts.IterationContainer
? inlineExpressions([createAssignment(errorRecord, createVoidZero()), values])
: values;

const forStatement = setEmitFlags(
setTextRange(
createFor(
/*initializer*/ setEmitFlags(
setTextRange(
createVariableDeclarationList([
setTextRange(createVariableDeclaration(iterator, /*type*/ undefined, values), node.expression),
setTextRange(createVariableDeclaration(iterator, /*type*/ undefined, initializer), node.expression),
createVariableDeclaration(result, /*type*/ undefined, next)
]),
node.expression
Expand Down Expand Up @@ -2665,7 +2671,7 @@ namespace ts {
}
}

function convertIterationStatementBodyIfNecessary(node: IterationStatement, outermostLabeledStatement: LabeledStatement | undefined, convert?: LoopConverter): VisitResult<Statement> {
function convertIterationStatementBodyIfNecessary(node: IterationStatement, outermostLabeledStatement: LabeledStatement | undefined, ancestorFacts: HierarchyFacts, convert?: LoopConverter): VisitResult<Statement> {
if (!shouldConvertIterationStatement(node)) {
let saveAllowedNonLabeledJumps: Jump | undefined;
if (convertedLoopState) {
Expand All @@ -2676,7 +2682,7 @@ namespace ts {
}

const result = convert
? convert(node, outermostLabeledStatement, /*convertedLoopBodyStatements*/ undefined)
? convert(node, outermostLabeledStatement, /*convertedLoopBodyStatements*/ undefined, ancestorFacts)
: restoreEnclosingLabel(visitEachChild(node, visitor, context), outermostLabeledStatement, convertedLoopState && resetLabel);

if (convertedLoopState) {
Expand Down Expand Up @@ -2708,7 +2714,7 @@ namespace ts {
let loop: Statement;
if (bodyFunction) {
if (convert) {
loop = convert(node, outermostLabeledStatement, bodyFunction.part);
loop = convert(node, outermostLabeledStatement, bodyFunction.part, ancestorFacts);
}
else {
const clone = convertIterationStatementCore(node, initializerFunction, createBlock(bodyFunction.part, /*multiLine*/ true));
Expand Down
64 changes: 64 additions & 0 deletions tests/baselines/reference/ES5For-of37.js
@@ -0,0 +1,64 @@
//// [ES5For-of37.ts]
// https://github.com/microsoft/TypeScript/issues/30083

for (const i of [0, 1, 2, 3, 4]) {
try {
// Ensure catch binding for the following loop is reset per iteration:
for (const j of [1, 2, 3]) {
if (i === 2) {
throw new Error('ERR');
}
}
console.log(i);
} catch (err) {
console.log('E %s %s', i, err);
}
}

//// [ES5For-of37.js]
// https://github.com/microsoft/TypeScript/issues/30083
var __values = (this && this.__values) || function (o) {
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0;
if (m) return m.call(o);
return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
};
var e_1, _a, e_2, _b;
try {
for (var _c = __values([0, 1, 2, 3, 4]), _d = _c.next(); !_d.done; _d = _c.next()) {
var i = _d.value;
try {
try {
// Ensure catch binding for the following loop is reset per iteration:
for (var _e = (e_2 = void 0, __values([1, 2, 3])), _f = _e.next(); !_f.done; _f = _e.next()) {
var j = _f.value;
if (i === 2) {
throw new Error('ERR');
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_f && !_f.done && (_b = _e["return"])) _b.call(_e);
}
finally { if (e_2) throw e_2.error; }
}
console.log(i);
}
catch (err) {
console.log('E %s %s', i, err);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_d && !_d.done && (_a = _c["return"])) _a.call(_c);
}
finally { if (e_1) throw e_1.error; }
}
35 changes: 35 additions & 0 deletions tests/baselines/reference/ES5For-of37.symbols
@@ -0,0 +1,35 @@
=== tests/cases/conformance/statements/for-ofStatements/ES5For-of37.ts ===
// https://github.com/microsoft/TypeScript/issues/30083

for (const i of [0, 1, 2, 3, 4]) {
>i : Symbol(i, Decl(ES5For-of37.ts, 2, 10))

try {
// Ensure catch binding for the following loop is reset per iteration:
for (const j of [1, 2, 3]) {
>j : Symbol(j, Decl(ES5For-of37.ts, 5, 18))

if (i === 2) {
>i : Symbol(i, Decl(ES5For-of37.ts, 2, 10))

throw new Error('ERR');
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}
}
console.log(i);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>i : Symbol(i, Decl(ES5For-of37.ts, 2, 10))

} catch (err) {
>err : Symbol(err, Decl(ES5For-of37.ts, 11, 13))

console.log('E %s %s', i, err);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>i : Symbol(i, Decl(ES5For-of37.ts, 2, 10))
>err : Symbol(err, Decl(ES5For-of37.ts, 11, 13))
}
}
52 changes: 52 additions & 0 deletions tests/baselines/reference/ES5For-of37.types
@@ -0,0 +1,52 @@
=== tests/cases/conformance/statements/for-ofStatements/ES5For-of37.ts ===
// https://github.com/microsoft/TypeScript/issues/30083

for (const i of [0, 1, 2, 3, 4]) {
>i : number
>[0, 1, 2, 3, 4] : number[]
>0 : 0
>1 : 1
>2 : 2
>3 : 3
>4 : 4

try {
// Ensure catch binding for the following loop is reset per iteration:
for (const j of [1, 2, 3]) {
>j : number
>[1, 2, 3] : number[]
>1 : 1
>2 : 2
>3 : 3

if (i === 2) {
>i === 2 : boolean
>i : number
>2 : 2

throw new Error('ERR');
>new Error('ERR') : Error
>Error : ErrorConstructor
>'ERR' : "ERR"
}
}
console.log(i);
>console.log(i) : void
>console.log : (message?: any, ...optionalParams: any[]) => void
>console : Console
>log : (message?: any, ...optionalParams: any[]) => void
>i : number

} catch (err) {
>err : any

console.log('E %s %s', i, err);
>console.log('E %s %s', i, err) : void
>console.log : (message?: any, ...optionalParams: any[]) => void
>console : Console
>log : (message?: any, ...optionalParams: any[]) => void
>'E %s %s' : "E %s %s"
>i : number
>err : any
}
}
16 changes: 16 additions & 0 deletions tests/cases/conformance/statements/for-ofStatements/ES5For-of37.ts
@@ -0,0 +1,16 @@
// @downlevelIteration: true
// https://github.com/microsoft/TypeScript/issues/30083

for (const i of [0, 1, 2, 3, 4]) {
try {
// Ensure catch binding for the following loop is reset per iteration:
for (const j of [1, 2, 3]) {
if (i === 2) {
throw new Error('ERR');
}
}
console.log(i);
} catch (err) {
console.log('E %s %s', i, err);
}
}

0 comments on commit b3dc32f

Please sign in to comment.