From 1a6b0e57822cbfb5d84bb6512982bcf7eb35f5c8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Nov 2019 14:51:49 -0800 Subject: [PATCH 1/4] Use existing 'continue' target labels for labeled statements --- src/compiler/binder.ts | 44 ++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index e09fa73e3c234..59876000d0165 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -10,7 +10,7 @@ namespace ts { interface ActiveLabel { name: __String; breakTarget: FlowLabel; - continueTarget: FlowLabel; + continueTarget: FlowLabel | undefined; referenced: boolean; } @@ -1063,8 +1063,20 @@ namespace ts { currentContinueTarget = saveContinueTarget; } + function setContinueTarget(node: Node, label: FlowLabel) { + if (activeLabels) { + let index = activeLabels.length; + while (node.parent.kind === SyntaxKind.LabeledStatement) { + index--; + activeLabels[index].continueTarget = label; + node = node.parent; + } + } + return label; + } + function bindWhileStatement(node: WhileStatement): void { - const preWhileLabel = createLoopLabel(); + const preWhileLabel = setContinueTarget(node, createLoopLabel()); const preBodyLabel = createBranchLabel(); const postWhileLabel = createBranchLabel(); addAntecedent(preWhileLabel, currentFlow); @@ -1078,13 +1090,8 @@ namespace ts { function bindDoStatement(node: DoStatement): void { const preDoLabel = createLoopLabel(); - const enclosingLabeledStatement = node.parent.kind === SyntaxKind.LabeledStatement - ? lastOrUndefined(activeLabels!) - : undefined; - // if do statement is wrapped in labeled statement then target labels for break/continue with or without - // label should be the same - const preConditionLabel = enclosingLabeledStatement ? enclosingLabeledStatement.continueTarget : createBranchLabel(); - const postDoLabel = enclosingLabeledStatement ? enclosingLabeledStatement.breakTarget : createBranchLabel(); + const preConditionLabel = setContinueTarget(node, createBranchLabel()); + const postDoLabel = createBranchLabel(); addAntecedent(preDoLabel, currentFlow); currentFlow = preDoLabel; bindIterativeStatement(node.statement, postDoLabel, preConditionLabel); @@ -1095,7 +1102,7 @@ namespace ts { } function bindForStatement(node: ForStatement): void { - const preLoopLabel = createLoopLabel(); + const preLoopLabel = setContinueTarget(node, createLoopLabel()); const preBodyLabel = createBranchLabel(); const postLoopLabel = createBranchLabel(); bind(node.initializer); @@ -1110,7 +1117,7 @@ namespace ts { } function bindForInOrForOfStatement(node: ForInOrOfStatement): void { - const preLoopLabel = createLoopLabel(); + const preLoopLabel = setContinueTarget(node, createLoopLabel()); const postLoopLabel = createBranchLabel(); bind(node.expression); addAntecedent(preLoopLabel, currentFlow); @@ -1313,11 +1320,11 @@ namespace ts { bindEach(node.statements); } - function pushActiveLabel(name: __String, breakTarget: FlowLabel, continueTarget: FlowLabel): ActiveLabel { + function pushActiveLabel(name: __String, breakTarget: FlowLabel): ActiveLabel { const activeLabel: ActiveLabel = { name, breakTarget, - continueTarget, + continueTarget: undefined, referenced: false }; (activeLabels || (activeLabels = [])).push(activeLabel); @@ -1341,21 +1348,16 @@ namespace ts { } function bindLabeledStatement(node: LabeledStatement): void { - const preStatementLabel = createLoopLabel(); const postStatementLabel = createBranchLabel(); bind(node.label); - addAntecedent(preStatementLabel, currentFlow); - const activeLabel = pushActiveLabel(node.label.escapedText, postStatementLabel, preStatementLabel); + const activeLabel = pushActiveLabel(node.label.escapedText, postStatementLabel); bind(node.statement); popActiveLabel(); if (!activeLabel.referenced && !options.allowUnusedLabels) { errorOrSuggestionOnNode(unusedLabelIsError(options), node.label, Diagnostics.Unused_label); } - if (!node.statement || node.statement.kind !== SyntaxKind.DoStatement) { - // do statement sets current flow inside bindDoStatement - addAntecedent(postStatementLabel, currentFlow); - currentFlow = finishFlowLabel(postStatementLabel); - } + addAntecedent(postStatementLabel, currentFlow); + currentFlow = finishFlowLabel(postStatementLabel); } function bindDestructuringTargetFlow(node: Expression) { From fa8546cc0d2f987ed89e3fe8b9d7409eb6a71f16 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Nov 2019 15:35:28 -0800 Subject: [PATCH 2/4] Use linked list for active labels --- src/compiler/binder.ts | 62 +++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 59876000d0165..262d8c72268c5 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -8,6 +8,7 @@ namespace ts { } interface ActiveLabel { + next: ActiveLabel | undefined; name: __String; breakTarget: FlowLabel; continueTarget: FlowLabel | undefined; @@ -199,7 +200,7 @@ namespace ts { let currentFalseTarget: FlowLabel | undefined; let currentExceptionTarget: FlowLabel | undefined; let preSwitchCaseFlow: FlowNode | undefined; - let activeLabels: ActiveLabel[] | undefined; + let activeLabelList: ActiveLabel | undefined; let hasExplicitReturn: boolean; // state used for emit helpers @@ -271,7 +272,7 @@ namespace ts { currentTrueTarget = undefined; currentFalseTarget = undefined; currentExceptionTarget = undefined; - activeLabels = undefined!; + activeLabelList = undefined; hasExplicitReturn = false; emitFlags = NodeFlags.None; subtreeTransformFlags = TransformFlags.None; @@ -629,7 +630,7 @@ namespace ts { const saveContinueTarget = currentContinueTarget; const saveReturnTarget = currentReturnTarget; const saveExceptionTarget = currentExceptionTarget; - const saveActiveLabels = activeLabels; + const saveActiveLabelList = activeLabelList; const saveHasExplicitReturn = hasExplicitReturn; const isIIFE = containerFlags & ContainerFlags.IsFunctionExpression && !hasModifier(node, ModifierFlags.Async) && !(node).asteriskToken && !!getImmediatelyInvokedFunctionExpression(node); @@ -647,7 +648,7 @@ namespace ts { currentExceptionTarget = undefined; currentBreakTarget = undefined; currentContinueTarget = undefined; - activeLabels = undefined; + activeLabelList = undefined; hasExplicitReturn = false; bindChildren(node); // Reset all reachability check related flags on node (for incremental scenarios) @@ -675,7 +676,7 @@ namespace ts { currentContinueTarget = saveContinueTarget; currentReturnTarget = saveReturnTarget; currentExceptionTarget = saveExceptionTarget; - activeLabels = saveActiveLabels; + activeLabelList = saveActiveLabelList; hasExplicitReturn = saveHasExplicitReturn; } else if (containerFlags & ContainerFlags.IsInterface) { @@ -1063,16 +1064,14 @@ namespace ts { currentContinueTarget = saveContinueTarget; } - function setContinueTarget(node: Node, label: FlowLabel) { - if (activeLabels) { - let index = activeLabels.length; - while (node.parent.kind === SyntaxKind.LabeledStatement) { - index--; - activeLabels[index].continueTarget = label; - node = node.parent; - } + function setContinueTarget(node: Node, target: FlowLabel) { + let label = activeLabelList; + while (label && node.parent.kind === SyntaxKind.LabeledStatement) { + label.continueTarget = target; + label = label.next; + node = node.parent; } - return label; + return target; } function bindWhileStatement(node: WhileStatement): void { @@ -1161,11 +1160,9 @@ namespace ts { } function findActiveLabel(name: __String) { - if (activeLabels) { - for (const label of activeLabels) { - if (label.name === name) { - return label; - } + for (let label = activeLabelList; label; label = label.next) { + if (label.name === name) { + return label; } } return undefined; @@ -1320,21 +1317,6 @@ namespace ts { bindEach(node.statements); } - function pushActiveLabel(name: __String, breakTarget: FlowLabel): ActiveLabel { - const activeLabel: ActiveLabel = { - name, - breakTarget, - continueTarget: undefined, - referenced: false - }; - (activeLabels || (activeLabels = [])).push(activeLabel); - return activeLabel; - } - - function popActiveLabel() { - activeLabels!.pop(); - } - function bindExpressionStatement(node: ExpressionStatement): void { bind(node.expression); // A top level call expression with a dotted function name and at least one argument @@ -1349,13 +1331,19 @@ namespace ts { function bindLabeledStatement(node: LabeledStatement): void { const postStatementLabel = createBranchLabel(); + activeLabelList = { + next: activeLabelList, + name: node.label.escapedText, + breakTarget: postStatementLabel, + continueTarget: undefined, + referenced: false + }; bind(node.label); - const activeLabel = pushActiveLabel(node.label.escapedText, postStatementLabel); bind(node.statement); - popActiveLabel(); - if (!activeLabel.referenced && !options.allowUnusedLabels) { + if (!activeLabelList.referenced && !options.allowUnusedLabels) { errorOrSuggestionOnNode(unusedLabelIsError(options), node.label, Diagnostics.Unused_label); } + activeLabelList = activeLabelList.next; addAntecedent(postStatementLabel, currentFlow); currentFlow = finishFlowLabel(postStatementLabel); } From 43ad5820d352fd762a902bd766d033358b15a04a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Nov 2019 15:47:54 -0800 Subject: [PATCH 3/4] Add regression test --- .../controlFlowBreakContinueWithLabel.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/cases/compiler/controlFlowBreakContinueWithLabel.ts diff --git a/tests/cases/compiler/controlFlowBreakContinueWithLabel.ts b/tests/cases/compiler/controlFlowBreakContinueWithLabel.ts new file mode 100644 index 0000000000000..5d451625795cb --- /dev/null +++ b/tests/cases/compiler/controlFlowBreakContinueWithLabel.ts @@ -0,0 +1,16 @@ +// @strict: true + +enum User { A, B } + +let user: User = User.A + +label: while (true) { + switch (user) { + case User.A: + user = User.B; + continue label; + case User.B: + break label; + } +} +user; From 10b3eb4093271bc472fc237f2ef4a1829c26fccf Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Nov 2019 15:48:01 -0800 Subject: [PATCH 4/4] Accept new baselines --- .../controlFlowBreakContinueWithLabel.js | 35 ++++++++++++++ .../controlFlowBreakContinueWithLabel.symbols | 40 ++++++++++++++++ .../controlFlowBreakContinueWithLabel.types | 46 +++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 tests/baselines/reference/controlFlowBreakContinueWithLabel.js create mode 100644 tests/baselines/reference/controlFlowBreakContinueWithLabel.symbols create mode 100644 tests/baselines/reference/controlFlowBreakContinueWithLabel.types diff --git a/tests/baselines/reference/controlFlowBreakContinueWithLabel.js b/tests/baselines/reference/controlFlowBreakContinueWithLabel.js new file mode 100644 index 0000000000000..1e49640b274ae --- /dev/null +++ b/tests/baselines/reference/controlFlowBreakContinueWithLabel.js @@ -0,0 +1,35 @@ +//// [controlFlowBreakContinueWithLabel.ts] +enum User { A, B } + +let user: User = User.A + +label: while (true) { + switch (user) { + case User.A: + user = User.B; + continue label; + case User.B: + break label; + } +} +user; + + +//// [controlFlowBreakContinueWithLabel.js] +"use strict"; +var User; +(function (User) { + User[User["A"] = 0] = "A"; + User[User["B"] = 1] = "B"; +})(User || (User = {})); +var user = User.A; +label: while (true) { + switch (user) { + case User.A: + user = User.B; + continue label; + case User.B: + break label; + } +} +user; diff --git a/tests/baselines/reference/controlFlowBreakContinueWithLabel.symbols b/tests/baselines/reference/controlFlowBreakContinueWithLabel.symbols new file mode 100644 index 0000000000000..f095c0e71505b --- /dev/null +++ b/tests/baselines/reference/controlFlowBreakContinueWithLabel.symbols @@ -0,0 +1,40 @@ +=== tests/cases/compiler/controlFlowBreakContinueWithLabel.ts === +enum User { A, B } +>User : Symbol(User, Decl(controlFlowBreakContinueWithLabel.ts, 0, 0)) +>A : Symbol(User.A, Decl(controlFlowBreakContinueWithLabel.ts, 0, 11)) +>B : Symbol(User.B, Decl(controlFlowBreakContinueWithLabel.ts, 0, 14)) + +let user: User = User.A +>user : Symbol(user, Decl(controlFlowBreakContinueWithLabel.ts, 2, 3)) +>User : Symbol(User, Decl(controlFlowBreakContinueWithLabel.ts, 0, 0)) +>User.A : Symbol(User.A, Decl(controlFlowBreakContinueWithLabel.ts, 0, 11)) +>User : Symbol(User, Decl(controlFlowBreakContinueWithLabel.ts, 0, 0)) +>A : Symbol(User.A, Decl(controlFlowBreakContinueWithLabel.ts, 0, 11)) + +label: while (true) { + switch (user) { +>user : Symbol(user, Decl(controlFlowBreakContinueWithLabel.ts, 2, 3)) + + case User.A: +>User.A : Symbol(User.A, Decl(controlFlowBreakContinueWithLabel.ts, 0, 11)) +>User : Symbol(User, Decl(controlFlowBreakContinueWithLabel.ts, 0, 0)) +>A : Symbol(User.A, Decl(controlFlowBreakContinueWithLabel.ts, 0, 11)) + + user = User.B; +>user : Symbol(user, Decl(controlFlowBreakContinueWithLabel.ts, 2, 3)) +>User.B : Symbol(User.B, Decl(controlFlowBreakContinueWithLabel.ts, 0, 14)) +>User : Symbol(User, Decl(controlFlowBreakContinueWithLabel.ts, 0, 0)) +>B : Symbol(User.B, Decl(controlFlowBreakContinueWithLabel.ts, 0, 14)) + + continue label; + case User.B: +>User.B : Symbol(User.B, Decl(controlFlowBreakContinueWithLabel.ts, 0, 14)) +>User : Symbol(User, Decl(controlFlowBreakContinueWithLabel.ts, 0, 0)) +>B : Symbol(User.B, Decl(controlFlowBreakContinueWithLabel.ts, 0, 14)) + + break label; + } +} +user; +>user : Symbol(user, Decl(controlFlowBreakContinueWithLabel.ts, 2, 3)) + diff --git a/tests/baselines/reference/controlFlowBreakContinueWithLabel.types b/tests/baselines/reference/controlFlowBreakContinueWithLabel.types new file mode 100644 index 0000000000000..f17f2d8d41bf1 --- /dev/null +++ b/tests/baselines/reference/controlFlowBreakContinueWithLabel.types @@ -0,0 +1,46 @@ +=== tests/cases/compiler/controlFlowBreakContinueWithLabel.ts === +enum User { A, B } +>User : User +>A : User.A +>B : User.B + +let user: User = User.A +>user : User +>User.A : User.A +>User : typeof User +>A : User.A + +label: while (true) { +>label : any +>true : true + + switch (user) { +>user : User + + case User.A: +>User.A : User.A +>User : typeof User +>A : User.A + + user = User.B; +>user = User.B : User.B +>user : User +>User.B : User.B +>User : typeof User +>B : User.B + + continue label; +>label : any + + case User.B: +>User.B : User.B +>User : typeof User +>B : User.B + + break label; +>label : any + } +} +user; +>user : User.B +