From 7f01ea827878ed308f8aa2502ddbaef39d4ddd65 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Thu, 17 Oct 2019 06:57:15 +0200 Subject: [PATCH] Differentiate between break and continue inside switch statements --- src/ast/ExecutionContext.ts | 2 ++ src/ast/nodes/ArrowFunctionExpression.ts | 1 + src/ast/nodes/BreakStatement.ts | 6 ++++- src/ast/nodes/ContinueStatement.ts | 4 ++- src/ast/nodes/DoWhileStatement.ts | 6 ++--- src/ast/nodes/ForInStatement.ts | 6 ++--- src/ast/nodes/ForStatement.ts | 6 ++--- src/ast/nodes/WhileStatement.ts | 6 ++--- src/ast/nodes/shared/FunctionNode.ts | 1 + .../side-effects-switch-statements/_config.js | 3 +-- .../_expected.js | 9 +++++++ .../side-effects-switch-statements/main.js | 9 +++++++ .../_config.js | 3 +++ .../switch-statement-nested-continue/main.js | 25 +++++++++++++++++++ 14 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 test/function/samples/switch-statement-nested-continue/_config.js create mode 100644 test/function/samples/switch-statement-nested-continue/main.js diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index dffd20fcc42..041e8b66ed1 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -3,6 +3,7 @@ import { PathTracker } from './utils/PathTracker'; import ThisVariable from './variables/ThisVariable'; interface ExecutionContextIgnore { + breakAndContinue: boolean; breakStatements: boolean; labels: Set; returnAwaitYield: boolean; @@ -39,6 +40,7 @@ export function createHasEffectsContext(): HasEffectsContext { breakFlow: BREAKFLOW_NONE, called: new PathTracker(), ignore: { + breakAndContinue: false, breakStatements: false, labels: new Set(), returnAwaitYield: false diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 4d344c934c1..eac44039128 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -58,6 +58,7 @@ export default class ArrowFunctionExpression extends NodeBase { } const { ignore, breakFlow } = context; context.ignore = { + breakAndContinue: false, breakStatements: false, labels: new Set(), returnAwaitYield: true diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index d1c24b2a799..c95f9975db3 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -8,7 +8,11 @@ export default class BreakStatement extends StatementBase { type!: NodeType.tBreakStatement; hasEffects(context: HasEffectsContext) { - if (!(this.label ? context.ignore.labels.has(this.label.name) : context.ignore.breakStatements)) + if ( + !(this.label + ? context.ignore.labels.has(this.label.name) + : context.ignore.breakStatements || context.ignore.breakAndContinue) + ) return true; context.breakFlow = new Set([this.label && this.label.name]); return false; diff --git a/src/ast/nodes/ContinueStatement.ts b/src/ast/nodes/ContinueStatement.ts index 93cfec4d6f8..90e6d89f8e0 100644 --- a/src/ast/nodes/ContinueStatement.ts +++ b/src/ast/nodes/ContinueStatement.ts @@ -8,7 +8,9 @@ export default class ContinueStatement extends StatementBase { type!: NodeType.tContinueStatement; hasEffects(context: HasEffectsContext) { - if (!(this.label ? context.ignore.labels.has(this.label.name) : context.ignore.breakStatements)) + if ( + !(this.label ? context.ignore.labels.has(this.label.name) : context.ignore.breakAndContinue) + ) return true; context.breakFlow = new Set([this.label && this.label.name]); return false; diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index 6dbed258576..ec465737e87 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -11,11 +11,11 @@ export default class DoWhileStatement extends StatementBase { if (this.test.hasEffects(context)) return true; const { breakFlow, - ignore: { breakStatements } + ignore: { breakAndContinue } } = context; - context.ignore.breakStatements = true; + context.ignore.breakAndContinue = true; if (this.body.hasEffects(context)) return true; - context.ignore.breakStatements = breakStatements; + context.ignore.breakAndContinue = breakAndContinue; if (context.breakFlow instanceof Set && context.breakFlow.has(null)) { context.breakFlow = breakFlow; } diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index e078f79f6a8..4d46879a2ec 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -36,11 +36,11 @@ export default class ForInStatement extends StatementBase { return true; const { breakFlow, - ignore: { breakStatements } + ignore: { breakAndContinue } } = context; - context.ignore.breakStatements = true; + context.ignore.breakAndContinue = true; if (this.body.hasEffects(context)) return true; - context.ignore.breakStatements = breakStatements; + context.ignore.breakAndContinue = breakAndContinue; context.breakFlow = breakFlow; return false; } diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index f07001e3716..285d711d72a 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -27,11 +27,11 @@ export default class ForStatement extends StatementBase { return true; const { breakFlow, - ignore: { breakStatements } + ignore: { breakAndContinue } } = context; - context.ignore.breakStatements = true; + context.ignore.breakAndContinue = true; if (this.body.hasEffects(context)) return true; - context.ignore.breakStatements = breakStatements; + context.ignore.breakAndContinue = breakAndContinue; context.breakFlow = breakFlow; return false; } diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index 3779f10650e..7a044349be4 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -11,11 +11,11 @@ export default class WhileStatement extends StatementBase { if (this.test.hasEffects(context)) return true; const { breakFlow, - ignore: { breakStatements } + ignore: { breakAndContinue } } = context; - context.ignore.breakStatements = true; + context.ignore.breakAndContinue = true; if (this.body.hasEffects(context)) return true; - context.ignore.breakStatements = breakStatements; + context.ignore.breakAndContinue = breakAndContinue; context.breakFlow = breakFlow; return false; } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index e3d7ca49b8e..04e379caad0 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -74,6 +74,7 @@ export default class FunctionNode extends NodeBase { ); const { breakFlow, ignore } = context; context.ignore = { + breakAndContinue: false, breakStatements: false, labels: new Set(), returnAwaitYield: true diff --git a/test/form/samples/side-effects-switch-statements/_config.js b/test/form/samples/side-effects-switch-statements/_config.js index e1fe26d2000..b35f375c5b6 100644 --- a/test/form/samples/side-effects-switch-statements/_config.js +++ b/test/form/samples/side-effects-switch-statements/_config.js @@ -1,4 +1,3 @@ module.exports = { - description: 'switch statements should be correctly tree-shaken', - options: { output: { name: 'myBundle' } } + description: 'switch statements should be correctly tree-shaken' }; diff --git a/test/form/samples/side-effects-switch-statements/_expected.js b/test/form/samples/side-effects-switch-statements/_expected.js index a7173331756..1af63c0a9ef 100644 --- a/test/form/samples/side-effects-switch-statements/_expected.js +++ b/test/form/samples/side-effects-switch-statements/_expected.js @@ -44,3 +44,12 @@ switch (globalThis.unknown) { break; case 'bar': } + +for (var i = 0; i < 4; i++) { + switch (i) { + case 0: + case 1: + continue; + } + effect(); +} diff --git a/test/form/samples/side-effects-switch-statements/main.js b/test/form/samples/side-effects-switch-statements/main.js index a712e4020d8..efbcbd1a663 100644 --- a/test/form/samples/side-effects-switch-statements/main.js +++ b/test/form/samples/side-effects-switch-statements/main.js @@ -65,3 +65,12 @@ switch (globalThis.unknown) { case 'bar': noEffect(); } + +for (var i = 0; i < 4; i++) { + switch (i) { + case 0: + case 1: + continue; + } + effect(); +} diff --git a/test/function/samples/switch-statement-nested-continue/_config.js b/test/function/samples/switch-statement-nested-continue/_config.js new file mode 100644 index 00000000000..3c0f55f0cc5 --- /dev/null +++ b/test/function/samples/switch-statement-nested-continue/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'treats continue different from break in switch statements' +}; diff --git a/test/function/samples/switch-statement-nested-continue/main.js b/test/function/samples/switch-statement-nested-continue/main.js new file mode 100644 index 00000000000..c44313a0f7a --- /dev/null +++ b/test/function/samples/switch-statement-nested-continue/main.js @@ -0,0 +1,25 @@ +function getValueContinue() { + for (var i = 0; i < 4; i++) { + switch (i) { + case 0: + case 1: + continue; + } + return i; + } +} + +assert.strictEqual(getValueContinue(), 2); + +function getValueBreak() { + for (var i = 0; i < 4; i++) { + switch (i) { + case 0: + case 1: + break; + } + return i; + } +} + +assert.strictEqual(getValueBreak(), 0);