From 2ff03662d21936586bd75f3ce764fb779b803f53 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sat, 1 Jun 2019 14:43:22 +0200 Subject: [PATCH 1/8] Use a Set for pure functions --- src/ast/nodes/shared/pureFunctions.ts | 54 ++++++++++++--------------- src/ast/variables/GlobalVariable.ts | 8 ++-- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/ast/nodes/shared/pureFunctions.ts b/src/ast/nodes/shared/pureFunctions.ts index 95e27ab24ef..6fd4746e7e6 100644 --- a/src/ast/nodes/shared/pureFunctions.ts +++ b/src/ast/nodes/shared/pureFunctions.ts @@ -1,22 +1,4 @@ -import { NameCollection } from '../../../utils/reservedNames'; - -const pureFunctions: NameCollection = Object.create(null); - -const arrayTypes = 'Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split( - ' ' -); -const simdTypes = 'Int8x16 Int16x8 Int32x4 Float32x4 Float64x2'.split(' '); -const simdMethods = 'abs add and bool check div equal extractLane fromFloat32x4 fromFloat32x4Bits fromFloat64x2 fromFloat64x2Bits fromInt16x8Bits fromInt32x4 fromInt32x4Bits fromInt8x16Bits greaterThan greaterThanOrEqual lessThan lessThanOrEqual load max maxNum min minNum mul neg not notEqual or reciprocalApproximation reciprocalSqrtApproximation replaceLane select selectBits shiftLeftByScalar shiftRightArithmeticByScalar shiftRightLogicalByScalar shuffle splat sqrt store sub swizzle xor'.split( - ' ' -); -const allSimdMethods: string[] = []; -simdTypes.forEach(t => { - simdMethods.forEach(m => { - allSimdMethods.push(`SIMD.${t}.${m}`); - }); -}); - -[ +const pureFunctions = [ 'Array.isArray', 'Error', 'EvalError', @@ -120,17 +102,29 @@ simdTypes.forEach(t => { 'Intl.DateTimeFormat.supportedLocalesOf', 'Intl.NumberFormat', 'Intl.NumberFormat.supportedLocalesOf' +]; + +const arrayTypes = 'Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array'.split( + ' ' +); + +for (const type of arrayTypes) { + pureFunctions.push(type, `${type}.from`, `${type}.of`); +} + +const simdTypes = 'Int8x16 Int16x8 Int32x4 Float32x4 Float64x2'.split(' '); +const simdMethods = 'abs add and bool check div equal extractLane fromFloat32x4 fromFloat32x4Bits fromFloat64x2 fromFloat64x2Bits fromInt16x8Bits fromInt32x4 fromInt32x4Bits fromInt8x16Bits greaterThan greaterThanOrEqual lessThan lessThanOrEqual load max maxNum min minNum mul neg not notEqual or reciprocalApproximation reciprocalSqrtApproximation replaceLane select selectBits shiftLeftByScalar shiftRightArithmeticByScalar shiftRightLogicalByScalar shuffle splat sqrt store sub swizzle xor'.split( + ' ' +); + +for (const type of simdTypes) { + const typeString = `SIMD.${type}`; + pureFunctions.push(typeString); + for (const method of simdMethods) { + pureFunctions.push(`${typeString}.${method}`); + } +} - // TODO properties of e.g. window... -] - .concat( - arrayTypes, - arrayTypes.map(t => `${t}.from`), - arrayTypes.map(t => `${t}.of`), - simdTypes.map(t => `SIMD.${t}`), - allSimdMethods - ) - .forEach(name => (pureFunctions[name] = true)); // TODO add others to this list from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects -export default pureFunctions; +export default new Set(pureFunctions); diff --git a/src/ast/variables/GlobalVariable.ts b/src/ast/variables/GlobalVariable.ts index 2ad76c6a6b9..07358c7d3ea 100644 --- a/src/ast/variables/GlobalVariable.ts +++ b/src/ast/variables/GlobalVariable.ts @@ -13,15 +13,15 @@ export default class GlobalVariable extends Variable { } hasEffectsWhenCalledAtPath(path: ObjectPath) { - return !pureFunctions[[this.name, ...path].join('.')]; + return !pureFunctions.has([this.name, ...path].join('.')); } private isPureFunctionMember(path: ObjectPath) { return ( - pureFunctions[[this.name, ...path].join('.')] || - (path.length >= 1 && pureFunctions[[this.name, ...path.slice(0, -1)].join('.')]) || + pureFunctions.has([this.name, ...path].join('.')) || + (path.length >= 1 && pureFunctions.has([this.name, ...path.slice(0, -1)].join('.'))) || (path.length >= 2 && - pureFunctions[[this.name, ...path.slice(0, -2)].join('.')] && + pureFunctions.has([this.name, ...path.slice(0, -2)].join('.')) && path[path.length - 2] === 'prototype') ); } From 9f0f918db2882565aa583af39383615435febde0 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sat, 1 Jun 2019 15:19:37 +0200 Subject: [PATCH 2/8] Include all statements directly in a try-statement-block --- src/ast/nodes/TryStatement.ts | 33 +++++++++++++++++++ src/ast/nodes/index.ts | 5 +-- .../{tla => top-level-await}/_config.js | 0 .../{tla => top-level-await}/_expected/es.js | 0 .../_expected/system.js | 0 .../samples/{tla => top-level-await}/main.js | 0 .../direct-inclusion/_config.js | 4 +++ .../direct-inclusion/_expected.js | 11 +++++++ .../direct-inclusion/main.js | 17 ++++++++++ 9 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/ast/nodes/TryStatement.ts rename test/form/samples/{tla => top-level-await}/_config.js (100%) rename test/form/samples/{tla => top-level-await}/_expected/es.js (100%) rename test/form/samples/{tla => top-level-await}/_expected/system.js (100%) rename test/form/samples/{tla => top-level-await}/main.js (100%) create mode 100644 test/form/samples/try-statement-deoptimization/direct-inclusion/_config.js create mode 100644 test/form/samples/try-statement-deoptimization/direct-inclusion/_expected.js create mode 100644 test/form/samples/try-statement-deoptimization/direct-inclusion/main.js diff --git a/src/ast/nodes/TryStatement.ts b/src/ast/nodes/TryStatement.ts new file mode 100644 index 00000000000..27a0a8e404d --- /dev/null +++ b/src/ast/nodes/TryStatement.ts @@ -0,0 +1,33 @@ +import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import BlockStatement from './BlockStatement'; +import CatchClause from './CatchClause'; +import * as NodeType from './NodeType'; +import { StatementBase } from './shared/Node'; + +export default class TryStatement extends StatementBase { + block!: BlockStatement; + finalizer!: BlockStatement | null; + handler!: CatchClause | null; + type!: NodeType.tTryStatement; + + hasEffects(options: ExecutionPathOptions): boolean { + return ( + this.block.body.length > 0 || + (this.handler !== null && this.handler.hasEffects(options)) || + (this.finalizer !== null && this.finalizer.hasEffects(options)) + ); + } + + include(includeAllChildrenRecursively: boolean) { + if (!this.included) { + this.included = true; + this.block.include(true); + } + if (this.handler !== null) { + this.handler.include(includeAllChildrenRecursively); + } + if (this.finalizer !== null) { + this.finalizer.include(includeAllChildrenRecursively); + } + } +} diff --git a/src/ast/nodes/index.ts b/src/ast/nodes/index.ts index 0002a4fbc7e..e2509ba0da2 100644 --- a/src/ast/nodes/index.ts +++ b/src/ast/nodes/index.ts @@ -42,7 +42,7 @@ import Property from './Property'; import RestElement from './RestElement'; import ReturnStatement from './ReturnStatement'; import SequenceExpression from './SequenceExpression'; -import { NodeBase, StatementBase } from './shared/Node'; +import { NodeBase } from './shared/Node'; import SpreadElement from './SpreadElement'; import SwitchCase from './SwitchCase'; import SwitchStatement from './SwitchStatement'; @@ -51,6 +51,7 @@ import TemplateElement from './TemplateElement'; import TemplateLiteral from './TemplateLiteral'; import ThisExpression from './ThisExpression'; import ThrowStatement from './ThrowStatement'; +import TryStatement from './TryStatement'; import UnaryExpression from './UnaryExpression'; import UnknownNode from './UnknownNode'; import UpdateExpression from './UpdateExpression'; @@ -114,7 +115,7 @@ export const nodeConstructors: { TemplateLiteral, ThisExpression, ThrowStatement, - TryStatement: StatementBase, + TryStatement, UnaryExpression, UnknownNode, UpdateExpression, diff --git a/test/form/samples/tla/_config.js b/test/form/samples/top-level-await/_config.js similarity index 100% rename from test/form/samples/tla/_config.js rename to test/form/samples/top-level-await/_config.js diff --git a/test/form/samples/tla/_expected/es.js b/test/form/samples/top-level-await/_expected/es.js similarity index 100% rename from test/form/samples/tla/_expected/es.js rename to test/form/samples/top-level-await/_expected/es.js diff --git a/test/form/samples/tla/_expected/system.js b/test/form/samples/top-level-await/_expected/system.js similarity index 100% rename from test/form/samples/tla/_expected/system.js rename to test/form/samples/top-level-await/_expected/system.js diff --git a/test/form/samples/tla/main.js b/test/form/samples/top-level-await/main.js similarity index 100% rename from test/form/samples/tla/main.js rename to test/form/samples/top-level-await/main.js diff --git a/test/form/samples/try-statement-deoptimization/direct-inclusion/_config.js b/test/form/samples/try-statement-deoptimization/direct-inclusion/_config.js new file mode 100644 index 00000000000..54014524ca1 --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/direct-inclusion/_config.js @@ -0,0 +1,4 @@ +module.exports = { + solo: true, + description: 'retains side-effect-free code in try-statement-blocks' +}; diff --git a/test/form/samples/try-statement-deoptimization/direct-inclusion/_expected.js b/test/form/samples/try-statement-deoptimization/direct-inclusion/_expected.js new file mode 100644 index 00000000000..33a1e846962 --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/direct-inclusion/_expected.js @@ -0,0 +1,11 @@ +try {} catch { + console.log('retained'); +} + +try {} catch {} finally { + console.log('retained'); +} + +try { + Object.create(null); +} catch {} diff --git a/test/form/samples/try-statement-deoptimization/direct-inclusion/main.js b/test/form/samples/try-statement-deoptimization/direct-inclusion/main.js new file mode 100644 index 00000000000..e44eaedaf8a --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/direct-inclusion/main.js @@ -0,0 +1,17 @@ +Object.create(null); // this will be removed + +try {} catch {} // this will be removed + +try {} catch { + Object.create(null); // this will be removed + console.log('retained'); +} + +try {} catch {} finally { + Object.create(null); // this will be removed + console.log('retained'); +} + +try { + Object.create(null); +} catch {} From d9b9afce8daf3d25b1ee253ab8a8a2fc493c3640 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sun, 2 Jun 2019 07:45:06 +0200 Subject: [PATCH 3/8] Retain side-effect-free code in functions called from try-statement-blocks --- src/Module.ts | 2 +- src/ast/nodes/AwaitExpression.ts | 8 ++++---- src/ast/nodes/BlockStatement.ts | 8 ++++---- src/ast/nodes/CallExpression.ts | 6 +++--- src/ast/nodes/ConditionalExpression.ts | 14 +++++++------- src/ast/nodes/ExportDefaultDeclaration.ts | 8 ++++---- src/ast/nodes/ForInStatement.ts | 10 +++++----- src/ast/nodes/ForOfStatement.ts | 10 +++++----- src/ast/nodes/Identifier.ts | 7 +++++-- src/ast/nodes/IfStatement.ts | 12 ++++++------ src/ast/nodes/LogicalExpression.ts | 12 ++++++------ src/ast/nodes/MemberExpression.ts | 8 ++++---- src/ast/nodes/Program.ts | 8 ++++---- src/ast/nodes/SequenceExpression.ts | 10 +++++----- src/ast/nodes/SwitchCase.ts | 10 +++++----- src/ast/nodes/TryStatement.ts | 10 +++++----- src/ast/nodes/VariableDeclaration.ts | 12 ++++++------ src/ast/nodes/shared/Expression.ts | 3 ++- src/ast/nodes/shared/FunctionNode.ts | 6 +++--- src/ast/nodes/shared/Node.ts | 17 ++++++++++------- src/ast/variables/LocalVariable.ts | 12 +++++++++++- src/ast/variables/Variable.ts | 2 ++ .../direct-inclusion/_config.js | 1 - .../follow-variables/_config.js | 3 +++ .../follow-variables/_expected.js | 7 +++++++ .../follow-variables/main.js | 7 +++++++ 26 files changed, 124 insertions(+), 89 deletions(-) create mode 100644 test/form/samples/try-statement-deoptimization/follow-variables/_config.js create mode 100644 test/form/samples/try-statement-deoptimization/follow-variables/_expected.js create mode 100644 test/form/samples/try-statement-deoptimization/follow-variables/main.js diff --git a/src/Module.ts b/src/Module.ts index 8c24293c905..73225650bd6 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -631,7 +631,7 @@ export default class Module { const otherModule = importDeclaration.module as Module | ExternalModule; if (otherModule instanceof Module && importDeclaration.name === '*') { - return (otherModule).getOrCreateNamespace(); + return otherModule.getOrCreateNamespace(); } const declaration = otherModule.getVariableForExportName(importDeclaration.name); diff --git a/src/ast/nodes/AwaitExpression.ts b/src/ast/nodes/AwaitExpression.ts index 936ea75bac1..2182b597e10 100644 --- a/src/ast/nodes/AwaitExpression.ts +++ b/src/ast/nodes/AwaitExpression.ts @@ -4,7 +4,7 @@ import { ExecutionPathOptions } from '../ExecutionPathOptions'; import ArrowFunctionExpression from './ArrowFunctionExpression'; import * as NodeType from './NodeType'; import FunctionNode from './shared/FunctionNode'; -import { ExpressionNode, Node, NodeBase } from './shared/Node'; +import { ExpressionNode, IncludeChildren, Node, NodeBase } from './shared/Node'; export default class AwaitExpression extends NodeBase { argument!: ExpressionNode; @@ -14,15 +14,15 @@ export default class AwaitExpression extends NodeBase { return super.hasEffects(options) || !options.ignoreReturnAwaitYield(); } - include(includeAllChildrenRecursively: boolean) { - super.include(includeAllChildrenRecursively); - if (!this.context.usesTopLevelAwait) { + include(includeChildrenRecursively: IncludeChildren) { + if (!this.included && !this.context.usesTopLevelAwait) { let parent = this.parent; do { if (parent instanceof FunctionNode || parent instanceof ArrowFunctionExpression) return; } while ((parent = (parent as Node).parent as Node)); this.context.usesTopLevelAwait = true; } + super.include(includeChildrenRecursively); } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/BlockStatement.ts b/src/ast/nodes/BlockStatement.ts index 1951954fdc5..83f3bd01cfb 100644 --- a/src/ast/nodes/BlockStatement.ts +++ b/src/ast/nodes/BlockStatement.ts @@ -6,7 +6,7 @@ import ChildScope from '../scopes/ChildScope'; import Scope from '../scopes/Scope'; import { UNKNOWN_EXPRESSION } from '../values'; import * as NodeType from './NodeType'; -import { Node, StatementBase, StatementNode } from './shared/Node'; +import { IncludeChildren, Node, StatementBase, StatementNode } from './shared/Node'; export default class BlockStatement extends StatementBase { body!: StatementNode[]; @@ -32,11 +32,11 @@ export default class BlockStatement extends StatementBase { return false; } - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { this.included = true; for (const node of this.body) { - if (includeAllChildrenRecursively || node.shouldBeIncluded()) - node.include(includeAllChildrenRecursively); + if (includeChildrenRecursively || node.shouldBeIncluded()) + node.include(includeChildrenRecursively); } } diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 29ef44ff584..0debd189842 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -19,7 +19,7 @@ import { import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; -import { ExpressionNode, NodeBase } from './shared/Node'; +import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; import SpreadElement from './SpreadElement'; export default class CallExpression extends NodeBase implements DeoptimizableEntity { @@ -196,8 +196,8 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt ); } - include(includeAllChildrenRecursively: boolean) { - super.include(includeAllChildrenRecursively); + include(includeChildrenRecursively: IncludeChildren) { + super.include(includeChildrenRecursively); if (!(this.returnExpression as ExpressionEntity).included) { (this.returnExpression as ExpressionEntity).include(false); } diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index 6d31e4251f3..0c344db9c06 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -20,7 +20,7 @@ import CallExpression from './CallExpression'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; import { MultiExpression } from './shared/MultiExpression'; -import { ExpressionNode, NodeBase } from './shared/Node'; +import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; export default class ConditionalExpression extends NodeBase implements DeoptimizableEntity { alternate!: ExpressionNode; @@ -133,14 +133,14 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, options); } - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { this.included = true; - if (includeAllChildrenRecursively || this.usedBranch === null || this.test.shouldBeIncluded()) { - this.test.include(includeAllChildrenRecursively); - this.consequent.include(includeAllChildrenRecursively); - this.alternate.include(includeAllChildrenRecursively); + if (includeChildrenRecursively || this.usedBranch === null || this.test.shouldBeIncluded()) { + this.test.include(includeChildrenRecursively); + this.consequent.include(includeChildrenRecursively); + this.alternate.include(includeChildrenRecursively); } else { - this.usedBranch.include(includeAllChildrenRecursively); + this.usedBranch.include(includeChildrenRecursively); } } diff --git a/src/ast/nodes/ExportDefaultDeclaration.ts b/src/ast/nodes/ExportDefaultDeclaration.ts index 6493009027d..32dcac1a977 100644 --- a/src/ast/nodes/ExportDefaultDeclaration.ts +++ b/src/ast/nodes/ExportDefaultDeclaration.ts @@ -12,7 +12,7 @@ import ClassDeclaration from './ClassDeclaration'; import FunctionDeclaration from './FunctionDeclaration'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; -import { ExpressionNode, NodeBase } from './shared/Node'; +import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; const WHITESPACE = /\s/; @@ -43,9 +43,9 @@ export default class ExportDefaultDeclaration extends NodeBase { private declarationName: string | undefined; - include(includeAllChildrenRecursively: boolean) { - super.include(includeAllChildrenRecursively); - if (includeAllChildrenRecursively) { + include(includeChildrenRecursively: IncludeChildren) { + super.include(includeChildrenRecursively); + if (includeChildrenRecursively) { this.context.includeVariable(this.variable); } } diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index 192531f7a77..515e9ad4413 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -5,7 +5,7 @@ import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import { EMPTY_PATH } from '../values'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; import { PatternNode } from './shared/Pattern'; import VariableDeclaration from './VariableDeclaration'; @@ -36,12 +36,12 @@ export default class ForInStatement extends StatementBase { ); } - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { this.included = true; - this.left.includeWithAllDeclaredVariables(includeAllChildrenRecursively); + this.left.includeWithAllDeclaredVariables(includeChildrenRecursively); this.left.deoptimizePath(EMPTY_PATH); - this.right.include(includeAllChildrenRecursively); - this.body.include(includeAllChildrenRecursively); + this.right.include(includeChildrenRecursively); + this.body.include(includeChildrenRecursively); } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index 3bbbe76bd26..587e3c7c537 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -5,7 +5,7 @@ import BlockScope from '../scopes/BlockScope'; import Scope from '../scopes/Scope'; import { EMPTY_PATH } from '../values'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; import { PatternNode } from './shared/Pattern'; import VariableDeclaration from './VariableDeclaration'; @@ -37,12 +37,12 @@ export default class ForOfStatement extends StatementBase { ); } - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { this.included = true; - this.left.includeWithAllDeclaredVariables(includeAllChildrenRecursively); + this.left.includeWithAllDeclaredVariables(includeChildrenRecursively); this.left.deoptimizePath(EMPTY_PATH); - this.right.include(includeAllChildrenRecursively); - this.body.include(includeAllChildrenRecursively); + this.right.include(includeChildrenRecursively); + this.body.include(includeChildrenRecursively); } render(code: MagicString, options: RenderOptions) { diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index b5d23860f27..068bc13f9fd 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -12,7 +12,7 @@ import LocalVariable from '../variables/LocalVariable'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; -import { NodeBase } from './shared/Node'; +import { INCLUDE_VARIABLES, IncludeChildren, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; export type IdentifierWithVariable = Identifier & { variable: Variable }; @@ -119,13 +119,16 @@ export default class Identifier extends NodeBase implements PatternNode { return !this.variable || this.variable.hasEffectsWhenCalledAtPath(path, callOptions, options); } - include(_includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { if (!this.included) { this.included = true; if (this.variable !== null) { this.context.includeVariable(this.variable); } } + if (includeChildrenRecursively === INCLUDE_VARIABLES && this.variable) { + this.variable.includeInitRecursively(); + } } render( diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 9a1b1301bda..5d2a9847dbc 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -6,7 +6,7 @@ import { ExecutionPathOptions } from '../ExecutionPathOptions'; import { EMPTY_IMMUTABLE_TRACKER } from '../utils/ImmutableEntityPathTracker'; import { EMPTY_PATH, LiteralValueOrUnknown, UNKNOWN_VALUE } from '../values'; import * as NodeType from './NodeType'; -import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; +import { ExpressionNode, IncludeChildren, StatementBase, StatementNode } from './shared/Node'; export default class IfStatement extends StatementBase implements DeoptimizableEntity { alternate!: StatementNode | null; @@ -43,13 +43,13 @@ export default class IfStatement extends StatementBase implements DeoptimizableE : this.alternate !== null && this.alternate.hasEffects(options); } - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { this.included = true; - if (includeAllChildrenRecursively) { - this.test.include(true); - this.consequent.include(true); + if (includeChildrenRecursively) { + this.test.include(includeChildrenRecursively); + this.consequent.include(includeChildrenRecursively); if (this.alternate !== null) { - this.alternate.include(true); + this.alternate.include(includeChildrenRecursively); } return; } diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index d3de85a8381..3b7e55e1c38 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -20,7 +20,7 @@ import CallExpression from './CallExpression'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; import { MultiExpression } from './shared/MultiExpression'; -import { ExpressionNode, NodeBase } from './shared/Node'; +import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; export type LogicalOperator = '||' | '&&'; @@ -134,17 +134,17 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, options); } - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { this.included = true; if ( - includeAllChildrenRecursively || + includeChildrenRecursively || this.usedBranch === null || (this.unusedBranch as ExpressionNode).shouldBeIncluded() ) { - this.left.include(includeAllChildrenRecursively); - this.right.include(includeAllChildrenRecursively); + this.left.include(includeChildrenRecursively); + this.right.include(includeChildrenRecursively); } else { - this.usedBranch.include(includeAllChildrenRecursively); + this.usedBranch.include(includeChildrenRecursively); } } diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 0cae867c9ae..ffc6d9a033d 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -23,7 +23,7 @@ import Variable from '../variables/Variable'; import Identifier from './Identifier'; import Literal from './Literal'; import * as NodeType from './NodeType'; -import { ExpressionNode, NodeBase } from './shared/Node'; +import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; function getResolvablePropertyKey(memberExpression: MemberExpression): string | null { @@ -211,15 +211,15 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { if (!this.included) { this.included = true; if (this.variable !== null) { this.context.includeVariable(this.variable); } } - this.object.include(includeAllChildrenRecursively); - this.property.include(includeAllChildrenRecursively); + this.object.include(includeChildrenRecursively); + this.property.include(includeChildrenRecursively); } initialise() { diff --git a/src/ast/nodes/Program.ts b/src/ast/nodes/Program.ts index 078d265ab67..d11ca72523d 100644 --- a/src/ast/nodes/Program.ts +++ b/src/ast/nodes/Program.ts @@ -2,7 +2,7 @@ import MagicString from 'magic-string'; import { RenderOptions, renderStatementList } from '../../utils/renderHelpers'; import { ExecutionPathOptions } from '../ExecutionPathOptions'; import * as NodeType from './NodeType'; -import { NodeBase, StatementNode } from './shared/Node'; +import { IncludeChildren, NodeBase, StatementNode } from './shared/Node'; export default class Program extends NodeBase { body!: StatementNode[]; @@ -16,11 +16,11 @@ export default class Program extends NodeBase { return false; } - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { this.included = true; for (const node of this.body) { - if (includeAllChildrenRecursively || node.shouldBeIncluded()) { - node.include(includeAllChildrenRecursively); + if (includeChildrenRecursively || node.shouldBeIncluded()) { + node.include(includeChildrenRecursively); } } } diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index 9f3c267ce84..a871817d590 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -13,7 +13,7 @@ import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker' import { LiteralValueOrUnknown, ObjectPath } from '../values'; import CallExpression from './CallExpression'; import * as NodeType from './NodeType'; -import { ExpressionNode, NodeBase } from './shared/Node'; +import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; export default class SequenceExpression extends NodeBase { expressions!: ExpressionNode[]; @@ -68,14 +68,14 @@ export default class SequenceExpression extends NodeBase { ); } - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { this.included = true; for (let i = 0; i < this.expressions.length - 1; i++) { const node = this.expressions[i]; - if (includeAllChildrenRecursively || node.shouldBeIncluded()) - node.include(includeAllChildrenRecursively); + if (includeChildrenRecursively || node.shouldBeIncluded()) + node.include(includeChildrenRecursively); } - this.expressions[this.expressions.length - 1].include(includeAllChildrenRecursively); + this.expressions[this.expressions.length - 1].include(includeChildrenRecursively); } render( diff --git a/src/ast/nodes/SwitchCase.ts b/src/ast/nodes/SwitchCase.ts index 5fafd439936..fae1a539978 100644 --- a/src/ast/nodes/SwitchCase.ts +++ b/src/ast/nodes/SwitchCase.ts @@ -5,19 +5,19 @@ import { renderStatementList } from '../../utils/renderHelpers'; import * as NodeType from './NodeType'; -import { ExpressionNode, NodeBase, StatementNode } from './shared/Node'; +import { ExpressionNode, IncludeChildren, NodeBase, StatementNode } from './shared/Node'; export default class SwitchCase extends NodeBase { consequent!: StatementNode[]; test!: ExpressionNode | null; type!: NodeType.tSwitchCase; - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { this.included = true; - if (this.test) this.test.include(includeAllChildrenRecursively); + if (this.test) this.test.include(includeChildrenRecursively); for (const node of this.consequent) { - if (includeAllChildrenRecursively || node.shouldBeIncluded()) - node.include(includeAllChildrenRecursively); + if (includeChildrenRecursively || node.shouldBeIncluded()) + node.include(includeChildrenRecursively); } } diff --git a/src/ast/nodes/TryStatement.ts b/src/ast/nodes/TryStatement.ts index 27a0a8e404d..4cf74c1aced 100644 --- a/src/ast/nodes/TryStatement.ts +++ b/src/ast/nodes/TryStatement.ts @@ -2,7 +2,7 @@ import { ExecutionPathOptions } from '../ExecutionPathOptions'; import BlockStatement from './BlockStatement'; import CatchClause from './CatchClause'; import * as NodeType from './NodeType'; -import { StatementBase } from './shared/Node'; +import { INCLUDE_VARIABLES, IncludeChildren, StatementBase } from './shared/Node'; export default class TryStatement extends StatementBase { block!: BlockStatement; @@ -18,16 +18,16 @@ export default class TryStatement extends StatementBase { ); } - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { if (!this.included) { this.included = true; - this.block.include(true); + this.block.include(INCLUDE_VARIABLES); } if (this.handler !== null) { - this.handler.include(includeAllChildrenRecursively); + this.handler.include(includeChildrenRecursively); } if (this.finalizer !== null) { - this.finalizer.include(includeAllChildrenRecursively); + this.finalizer.include(includeChildrenRecursively); } } } diff --git a/src/ast/nodes/VariableDeclaration.ts b/src/ast/nodes/VariableDeclaration.ts index 763c3ba340a..dc85f0f0d58 100644 --- a/src/ast/nodes/VariableDeclaration.ts +++ b/src/ast/nodes/VariableDeclaration.ts @@ -11,7 +11,7 @@ import { EMPTY_PATH, ObjectPath } from '../values'; import Variable from '../variables/Variable'; import Identifier, { IdentifierWithVariable } from './Identifier'; import * as NodeType from './NodeType'; -import { NodeBase } from './shared/Node'; +import { IncludeChildren, NodeBase } from './shared/Node'; import VariableDeclarator from './VariableDeclarator'; function isReassignedExportsMember(variable: Variable): boolean { @@ -49,18 +49,18 @@ export default class VariableDeclaration extends NodeBase { return false; } - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { this.included = true; for (const declarator of this.declarations) { - if (includeAllChildrenRecursively || declarator.shouldBeIncluded()) - declarator.include(includeAllChildrenRecursively); + if (includeChildrenRecursively || declarator.shouldBeIncluded()) + declarator.include(includeChildrenRecursively); } } - includeWithAllDeclaredVariables(includeAllChildrenRecursively: boolean) { + includeWithAllDeclaredVariables(includeChildrenRecursively: IncludeChildren) { this.included = true; for (const declarator of this.declarations) { - declarator.include(includeAllChildrenRecursively); + declarator.include(includeChildrenRecursively); } } diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index 7c4eb07559e..a78476b74f3 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -4,6 +4,7 @@ import { WritableEntity } from '../../Entity'; import { ExecutionPathOptions } from '../../ExecutionPathOptions'; import { ImmutableEntityPathTracker } from '../../utils/ImmutableEntityPathTracker'; import { LiteralValueOrUnknown, ObjectPath } from '../../values'; +import { IncludeChildren } from './Node'; export interface ExpressionEntity extends WritableEntity { included: boolean; @@ -29,5 +30,5 @@ export interface ExpressionEntity extends WritableEntity { callOptions: CallOptions, options: ExecutionPathOptions ): boolean; - include(includeAllChildrenRecursively: boolean): void; + include(includeChildrenRecursively: IncludeChildren): void; } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 6e5dc20336a..08b2d35c0b9 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -5,7 +5,7 @@ import BlockScope from '../../scopes/FunctionScope'; import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY, UNKNOWN_PATH } from '../../values'; import BlockStatement from '../BlockStatement'; import { IdentifierWithVariable } from '../Identifier'; -import { GenericEsTreeNode, NodeBase } from './Node'; +import { GenericEsTreeNode, IncludeChildren, NodeBase } from './Node'; import { PatternNode } from './Pattern'; export default class FunctionNode extends NodeBase { @@ -73,9 +73,9 @@ export default class FunctionNode extends NodeBase { return this.body.hasEffects(innerOptions); } - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { this.scope.argumentsVariable.include(); - super.include(includeAllChildrenRecursively); + super.include(includeChildrenRecursively); } initialise() { diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index 153390e8185..a7f1366d3f4 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -18,6 +18,9 @@ export interface GenericEsTreeNode { [key: string]: any; } +export const INCLUDE_VARIABLES: 'variables' = 'variables'; +export type IncludeChildren = boolean | typeof INCLUDE_VARIABLES; + export interface Node extends Entity { annotations?: CommentDescription[]; context: AstContext; @@ -54,14 +57,14 @@ export interface Node extends Entity { * if they are necessary for this node (e.g. a function body) or if they have effects. * Necessary variables need to be included as well. */ - include(includeAllChildrenRecursively: boolean): void; + include(includeChildrenRecursively: IncludeChildren): void; /** * Alternative version of include to override the default behaviour of * declarations to only include nodes for declarators that have an effect. Necessary * for for-loops that do not use a declared loop variable. */ - includeWithAllDeclaredVariables(includeAllChildrenRecursively: boolean): void; + includeWithAllDeclaredVariables(includeChildrenRecursively: IncludeChildren): void; render(code: MagicString, options: RenderOptions, nodeRenderOptions?: NodeRenderOptions): void; /** @@ -178,23 +181,23 @@ export class NodeBase implements ExpressionNode { return true; } - include(includeAllChildrenRecursively: boolean) { + include(includeChildrenRecursively: IncludeChildren) { this.included = true; for (const key of this.keys) { const value = (this as GenericEsTreeNode)[key]; if (value === null || key === 'annotations') continue; if (Array.isArray(value)) { for (const child of value) { - if (child !== null) child.include(includeAllChildrenRecursively); + if (child !== null) child.include(includeChildrenRecursively); } } else { - value.include(includeAllChildrenRecursively); + value.include(includeChildrenRecursively); } } } - includeWithAllDeclaredVariables(includeAllChildrenRecursively: boolean) { - this.include(includeAllChildrenRecursively); + includeWithAllDeclaredVariables(includeChildrenRecursively: IncludeChildren) { + this.include(includeChildrenRecursively); } /** diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 6eec92f9c8c..2b1a1b71f6f 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -7,7 +7,7 @@ import ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; import { ExpressionEntity } from '../nodes/shared/Expression'; -import { Node } from '../nodes/shared/Node'; +import { INCLUDE_VARIABLES, Node } from '../nodes/shared/Node'; import { EntityPathTracker } from '../utils/EntityPathTracker'; import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; import { @@ -32,6 +32,7 @@ export default class LocalVariable extends Variable { // We track deoptimization when we do not return something unknown private deoptimizationTracker: EntityPathTracker; private expressionsToBeDeoptimized: DeoptimizableEntity[] = []; + private hasInitBeenIncluded = false; constructor( name: string, @@ -190,4 +191,13 @@ export default class LocalVariable extends Variable { } } } + + includeInitRecursively() { + if (!this.hasInitBeenIncluded) { + this.hasInitBeenIncluded = true; + if (this.init && this.init !== UNKNOWN_EXPRESSION) { + this.init.include(INCLUDE_VARIABLES); + } + } + } } diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index 2f8b40d0560..1549828eb39 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -84,6 +84,8 @@ export default class Variable implements ExpressionEntity { this.included = true; } + includeInitRecursively() {} + setRenderNames(baseName: string | null, name: string | null) { this.renderBaseName = baseName; this.renderName = name; diff --git a/test/form/samples/try-statement-deoptimization/direct-inclusion/_config.js b/test/form/samples/try-statement-deoptimization/direct-inclusion/_config.js index 54014524ca1..2b9014d625b 100644 --- a/test/form/samples/try-statement-deoptimization/direct-inclusion/_config.js +++ b/test/form/samples/try-statement-deoptimization/direct-inclusion/_config.js @@ -1,4 +1,3 @@ module.exports = { - solo: true, description: 'retains side-effect-free code in try-statement-blocks' }; diff --git a/test/form/samples/try-statement-deoptimization/follow-variables/_config.js b/test/form/samples/try-statement-deoptimization/follow-variables/_config.js new file mode 100644 index 00000000000..f9aca82d54c --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/follow-variables/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'retains side-effect-free code in functions called from try-statement-blocks' +}; diff --git a/test/form/samples/try-statement-deoptimization/follow-variables/_expected.js b/test/form/samples/try-statement-deoptimization/follow-variables/_expected.js new file mode 100644 index 00000000000..95e7b463ae1 --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/follow-variables/_expected.js @@ -0,0 +1,7 @@ +function callGlobal() { + Object.create(null); +} + +try { + callGlobal(); +} catch {} diff --git a/test/form/samples/try-statement-deoptimization/follow-variables/main.js b/test/form/samples/try-statement-deoptimization/follow-variables/main.js new file mode 100644 index 00000000000..95e7b463ae1 --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/follow-variables/main.js @@ -0,0 +1,7 @@ +function callGlobal() { + Object.create(null); +} + +try { + callGlobal(); +} catch {} From 4936f5501d6dc5aafc226979aa1d026d2451c557 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 3 Jun 2019 12:31:47 +0200 Subject: [PATCH 4/8] Deoptimize try blocks, tree-shake excessive parameters --- src/ast/ExecutionPathOptions.ts | 9 --- src/ast/nodes/ArrayPattern.ts | 4 +- src/ast/nodes/ArrowFunctionExpression.ts | 13 ++++- src/ast/nodes/AssignmentPattern.ts | 2 +- src/ast/nodes/CallExpression.ts | 39 ++++++++++++- src/ast/nodes/Identifier.ts | 17 ++++-- src/ast/nodes/ObjectPattern.ts | 4 +- src/ast/nodes/Property.ts | 2 +- src/ast/nodes/RestElement.ts | 2 +- src/ast/nodes/shared/Expression.ts | 4 +- src/ast/nodes/shared/FunctionNode.ts | 19 ++++--- src/ast/nodes/shared/MultiExpression.ts | 8 +++ src/ast/nodes/shared/Node.ts | 14 ++++- src/ast/scopes/FunctionScope.ts | 32 ++++++----- src/ast/scopes/ParameterScope.ts | 31 ++++++++-- src/ast/values.ts | 35 ++++++++++++ src/ast/variables/ArgumentsVariable.ts | 56 +++---------------- src/ast/variables/LocalVariable.ts | 19 +++++-- src/ast/variables/Variable.ts | 8 +++ .../_expected/amd/dep.js | 4 +- .../_expected/cjs/dep.js | 4 +- .../_expected/es/dep.js | 4 +- .../_expected/system/dep.js | 4 +- .../samples/missing-export-compact/dep.js | 4 +- .../missing-export/_expected/amd/dep.js | 4 +- .../missing-export/_expected/amd/main.js | 2 +- .../missing-export/_expected/cjs/dep.js | 4 +- .../missing-export/_expected/cjs/main.js | 2 +- .../missing-export/_expected/es/dep.js | 4 +- .../missing-export/_expected/es/main.js | 2 +- .../missing-export/_expected/system/dep.js | 4 +- .../missing-export/_expected/system/main.js | 2 +- .../samples/missing-export/dep.js | 4 +- .../_expected/amd.js | 2 +- .../_expected/cjs.js | 2 +- .../_expected/es.js | 2 +- .../_expected/iife.js | 2 +- .../_expected/system.js | 2 +- .../_expected/umd.js | 2 +- .../samples/no-treeshake/_expected/amd.js | 14 +++++ .../samples/no-treeshake/_expected/cjs.js | 14 +++++ .../form/samples/no-treeshake/_expected/es.js | 14 +++++ .../samples/no-treeshake/_expected/iife.js | 14 +++++ .../samples/no-treeshake/_expected/system.js | 14 +++++ .../samples/no-treeshake/_expected/umd.js | 14 +++++ test/form/samples/no-treeshake/main.js | 14 +++++ .../_expected/amd.js | 4 +- .../_expected/cjs.js | 4 +- .../_expected/es.js | 4 +- .../_expected/iife.js | 4 +- .../_expected/system.js | 4 +- .../_expected/umd.js | 4 +- .../foo.js | 4 +- .../samples/side-effect-j/_expected/amd.js | 2 +- .../samples/side-effect-j/_expected/cjs.js | 2 +- .../samples/side-effect-j/_expected/es.js | 2 +- .../samples/side-effect-j/_expected/iife.js | 2 +- .../samples/side-effect-j/_expected/system.js | 2 +- .../samples/side-effect-j/_expected/umd.js | 2 +- test/form/samples/side-effect-j/foo.js | 2 +- .../arguments-variable/_config.js | 3 + .../arguments-variable/_expected.js | 15 +++++ .../arguments-variable/main.js | 15 +++++ .../arrow-function-arguments/_config.js | 3 + .../arrow-function-arguments/_expected.js | 10 ++++ .../arrow-function-arguments/main.js | 13 +++++ .../function-arguments/_config.js | 3 + .../function-arguments/_expected.js | 41 ++++++++++++++ .../function-arguments/main.js | 51 +++++++++++++++++ .../patterns/_config.js | 3 + .../patterns/_expected.js | 7 +++ .../patterns/main.js | 8 +++ .../rest-element/_config.js | 3 + .../rest-element/_expected.js | 16 ++++++ .../rest-element/main.js | 16 ++++++ .../follow-parameters/_config.js | 8 +++ .../follow-parameters/_expected.js | 14 +++++ .../follow-parameters/main.js | 15 +++++ .../follow-variables/_config.js | 1 + 79 files changed, 606 insertions(+), 157 deletions(-) create mode 100644 test/form/samples/treeshake-excessive-arguments/arguments-variable/_config.js create mode 100644 test/form/samples/treeshake-excessive-arguments/arguments-variable/_expected.js create mode 100644 test/form/samples/treeshake-excessive-arguments/arguments-variable/main.js create mode 100644 test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/_config.js create mode 100644 test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/_expected.js create mode 100644 test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/main.js create mode 100644 test/form/samples/treeshake-excessive-arguments/function-arguments/_config.js create mode 100644 test/form/samples/treeshake-excessive-arguments/function-arguments/_expected.js create mode 100644 test/form/samples/treeshake-excessive-arguments/function-arguments/main.js create mode 100644 test/form/samples/treeshake-excessive-arguments/patterns/_config.js create mode 100644 test/form/samples/treeshake-excessive-arguments/patterns/_expected.js create mode 100644 test/form/samples/treeshake-excessive-arguments/patterns/main.js create mode 100644 test/form/samples/treeshake-excessive-arguments/rest-element/_config.js create mode 100644 test/form/samples/treeshake-excessive-arguments/rest-element/_expected.js create mode 100644 test/form/samples/treeshake-excessive-arguments/rest-element/main.js create mode 100644 test/form/samples/try-statement-deoptimization/follow-parameters/_config.js create mode 100644 test/form/samples/try-statement-deoptimization/follow-parameters/_expected.js create mode 100644 test/form/samples/try-statement-deoptimization/follow-parameters/main.js diff --git a/src/ast/ExecutionPathOptions.ts b/src/ast/ExecutionPathOptions.ts index 6aad03831d7..5402bd6f882 100644 --- a/src/ast/ExecutionPathOptions.ts +++ b/src/ast/ExecutionPathOptions.ts @@ -10,7 +10,6 @@ import ThisVariable from './variables/ThisVariable'; export enum OptionTypes { IGNORED_LABELS, ACCESSED_NODES, - ARGUMENTS_VARIABLES, ASSIGNED_NODES, IGNORE_BREAK_STATEMENTS, IGNORE_RETURN_AWAIT_YIELD, @@ -78,10 +77,6 @@ export class ExecutionPathOptions { ); } - getArgumentsVariables(): ExpressionEntity[] { - return (this.get(OptionTypes.ARGUMENTS_VARIABLES) || []) as ExpressionEntity[]; - } - getHasEffectsWhenCalledOptions() { return this.setIgnoreReturnAwaitYield() .setIgnoreBreakStatements(false) @@ -171,10 +166,6 @@ export class ExecutionPathOptions { return this.setIn([OptionTypes.REPLACED_VARIABLE_INITS, variable], init); } - setArgumentsVariables(variables: ExpressionEntity[]) { - return this.set(OptionTypes.ARGUMENTS_VARIABLES, variables); - } - setIgnoreBreakStatements(value = true) { return this.set(OptionTypes.IGNORE_BREAK_STATEMENTS, value); } diff --git a/src/ast/nodes/ArrayPattern.ts b/src/ast/nodes/ArrayPattern.ts index a4be8b707b9..dc59174acf0 100644 --- a/src/ast/nodes/ArrayPattern.ts +++ b/src/ast/nodes/ArrayPattern.ts @@ -19,11 +19,13 @@ export default class ArrayPattern extends NodeBase implements PatternNode { } declare(kind: string, _init: ExpressionEntity) { + const variables = []; for (const element of this.elements) { if (element !== null) { - element.declare(kind, UNKNOWN_EXPRESSION); + variables.push(...element.declare(kind, UNKNOWN_EXPRESSION)); } } + return variables; } deoptimizePath(path: ObjectPath) { diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 1ad780d7b30..3ca05e96b63 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -5,8 +5,10 @@ import Scope from '../scopes/Scope'; import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY, UNKNOWN_PATH } from '../values'; import BlockStatement from './BlockStatement'; import * as NodeType from './NodeType'; +import RestElement from './RestElement'; import { ExpressionNode, GenericEsTreeNode, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; +import SpreadElement from './SpreadElement'; export default class ArrowFunctionExpression extends NodeBase { body!: BlockStatement | ExpressionNode; @@ -57,10 +59,15 @@ export default class ArrowFunctionExpression extends NodeBase { return this.body.hasEffects(options); } + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + this.scope.includeCallArguments(args); + } + initialise() { - for (const param of this.params) { - param.declare('parameter', UNKNOWN_EXPRESSION); - } + this.scope.addParameterVariables( + this.params.map(param => param.declare('parameter', UNKNOWN_EXPRESSION)), + this.params[this.params.length - 1] instanceof RestElement + ); if (this.body instanceof BlockStatement) { this.body.addImplicitReturnExpressionToScope(); } else { diff --git a/src/ast/nodes/AssignmentPattern.ts b/src/ast/nodes/AssignmentPattern.ts index 4428b9d2b42..f172dd1bf5b 100644 --- a/src/ast/nodes/AssignmentPattern.ts +++ b/src/ast/nodes/AssignmentPattern.ts @@ -25,7 +25,7 @@ export default class AssignmentPattern extends NodeBase implements PatternNode { } declare(kind: string, init: ExpressionEntity) { - this.left.declare(kind, init); + return this.left.declare(kind, init); } deoptimizePath(path: ObjectPath) { diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 0debd189842..818f417517a 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -1,6 +1,10 @@ import MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; -import { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; +import { + findFirstOccurrenceOutsideComment, + NodeRenderOptions, + RenderOptions +} from '../../utils/renderHelpers'; import CallOptions from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionPathOptions } from '../ExecutionPathOptions'; @@ -197,7 +201,13 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt } include(includeChildrenRecursively: IncludeChildren) { - super.include(includeChildrenRecursively); + if (includeChildrenRecursively) { + super.include(includeChildrenRecursively); + } else { + this.included = true; + this.callee.include(false); + } + this.callee.includeCallArguments(this.arguments); if (!(this.returnExpression as ExpressionEntity).included) { (this.returnExpression as ExpressionEntity).include(false); } @@ -216,7 +226,30 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt options: RenderOptions, { renderedParentType }: NodeRenderOptions = BLANK ) { - super.render(code, options); + this.callee.render(code, options); + if (this.arguments.length > 0) { + if (this.arguments[this.arguments.length - 1].included) { + for (const arg of this.arguments) { + arg.render(code, options); + } + } else { + let lastIncludedIndex = this.arguments.length - 2; + while (lastIncludedIndex >= 0 && !this.arguments[lastIncludedIndex].included) { + lastIncludedIndex--; + } + if (lastIncludedIndex >= 0) { + for (let index = 0; index <= lastIncludedIndex; index++) { + this.arguments[index].render(code, options); + } + code.remove(this.arguments[lastIncludedIndex].end, this.end - 1); + } else { + code.remove( + findFirstOccurrenceOutsideComment(code.original, '(', this.callee.end) + 1, + this.end - 1 + ); + } + } + } if ( renderedParentType === NodeType.ExpressionStatement && this.callee.type === NodeType.FunctionExpression diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 068bc13f9fd..913885449b4 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -12,8 +12,9 @@ import LocalVariable from '../variables/LocalVariable'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; -import { INCLUDE_VARIABLES, IncludeChildren, NodeBase } from './shared/Node'; +import { ExpressionNode, INCLUDE_VARIABLES, IncludeChildren, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; +import SpreadElement from './SpreadElement'; export type IdentifierWithVariable = Identifier & { variable: Variable }; @@ -47,22 +48,24 @@ export default class Identifier extends NodeBase implements PatternNode { } declare(kind: string, init: ExpressionEntity) { + let variable: LocalVariable; switch (kind) { case 'var': case 'function': - this.variable = this.scope.addDeclaration(this, this.context, init, true); + variable = this.scope.addDeclaration(this, this.context, init, true); break; case 'let': case 'const': case 'class': - this.variable = this.scope.addDeclaration(this, this.context, init, false); + variable = this.scope.addDeclaration(this, this.context, init, false); break; case 'parameter': - this.variable = (this.scope as FunctionScope).addParameterDeclaration(this); + variable = (this.scope as FunctionScope).addParameterDeclaration(this); break; default: throw new Error(`Unexpected identifier kind ${kind}.`); } + return [(this.variable = variable)]; } deoptimizePath(path: ObjectPath) { @@ -131,6 +134,12 @@ export default class Identifier extends NodeBase implements PatternNode { } } + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + if (this.variable) { + this.variable.includeCallArguments(args); + } + } + render( code: MagicString, _options: RenderOptions, diff --git a/src/ast/nodes/ObjectPattern.ts b/src/ast/nodes/ObjectPattern.ts index d72ba408714..c1ed96bfe57 100644 --- a/src/ast/nodes/ObjectPattern.ts +++ b/src/ast/nodes/ObjectPattern.ts @@ -23,9 +23,11 @@ export default class ObjectPattern extends NodeBase implements PatternNode { } declare(kind: string, init: ExpressionEntity) { + const variables = []; for (const property of this.properties) { - property.declare(kind, init); + variables.push(...property.declare(kind, init)); } + return variables; } deoptimizePath(path: ObjectPath) { diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index 1fa9d60912a..f06dbbcb616 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -42,7 +42,7 @@ export default class Property extends NodeBase implements DeoptimizableEntity { declare(kind: string, init: ExpressionEntity) { this.declarationInit = init; - this.value.declare(kind, UNKNOWN_EXPRESSION); + return this.value.declare(kind, UNKNOWN_EXPRESSION); } deoptimizeCache() { diff --git a/src/ast/nodes/RestElement.ts b/src/ast/nodes/RestElement.ts index 6927f9865c3..7c5e3e84bbb 100644 --- a/src/ast/nodes/RestElement.ts +++ b/src/ast/nodes/RestElement.ts @@ -24,8 +24,8 @@ export default class RestElement extends NodeBase implements PatternNode { } declare(kind: string, init: ExpressionEntity) { - this.argument.declare(kind, UNKNOWN_EXPRESSION); this.declarationInit = init; + return this.argument.declare(kind, UNKNOWN_EXPRESSION); } deoptimizePath(path: ObjectPath) { diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index a78476b74f3..292aa1fba43 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -4,7 +4,8 @@ import { WritableEntity } from '../../Entity'; import { ExecutionPathOptions } from '../../ExecutionPathOptions'; import { ImmutableEntityPathTracker } from '../../utils/ImmutableEntityPathTracker'; import { LiteralValueOrUnknown, ObjectPath } from '../../values'; -import { IncludeChildren } from './Node'; +import SpreadElement from '../SpreadElement'; +import { ExpressionNode, IncludeChildren } from './Node'; export interface ExpressionEntity extends WritableEntity { included: boolean; @@ -31,4 +32,5 @@ export interface ExpressionEntity extends WritableEntity { options: ExecutionPathOptions ): boolean; include(includeChildrenRecursively: IncludeChildren): void; + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void; } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 08b2d35c0b9..a5bde934d3c 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -1,11 +1,12 @@ import CallOptions from '../../CallOptions'; import { ExecutionPathOptions } from '../../ExecutionPathOptions'; import FunctionScope from '../../scopes/FunctionScope'; -import BlockScope from '../../scopes/FunctionScope'; import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY, UNKNOWN_PATH } from '../../values'; import BlockStatement from '../BlockStatement'; import { IdentifierWithVariable } from '../Identifier'; -import { GenericEsTreeNode, IncludeChildren, NodeBase } from './Node'; +import RestElement from '../RestElement'; +import SpreadElement from '../SpreadElement'; +import { ExpressionNode, GenericEsTreeNode, NodeBase } from './Node'; import { PatternNode } from './Pattern'; export default class FunctionNode extends NodeBase { @@ -14,7 +15,7 @@ export default class FunctionNode extends NodeBase { id!: IdentifierWithVariable | null; params!: PatternNode[]; preventChildBlockScope!: true; - scope!: BlockScope; + scope!: FunctionScope; private isPrototypeDeoptimized = false; @@ -73,18 +74,18 @@ export default class FunctionNode extends NodeBase { return this.body.hasEffects(innerOptions); } - include(includeChildrenRecursively: IncludeChildren) { - this.scope.argumentsVariable.include(); - super.include(includeChildrenRecursively); + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + this.scope.includeCallArguments(args); } initialise() { if (this.id !== null) { this.id.declare('function', this); } - for (const param of this.params) { - param.declare('parameter', UNKNOWN_EXPRESSION); - } + this.scope.addParameterVariables( + this.params.map(param => param.declare('parameter', UNKNOWN_EXPRESSION)), + this.params[this.params.length - 1] instanceof RestElement + ); this.body.addImplicitReturnExpressionToScope(); } diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index 0bdef758f1c..4dbbaf7b345 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -3,7 +3,9 @@ import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { ExecutionPathOptions } from '../../ExecutionPathOptions'; import { ImmutableEntityPathTracker } from '../../utils/ImmutableEntityPathTracker'; import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../../values'; +import SpreadElement from '../SpreadElement'; import { ExpressionEntity } from './Expression'; +import { ExpressionNode } from './Node'; export class MultiExpression implements ExpressionEntity { included = false; @@ -62,4 +64,10 @@ export class MultiExpression implements ExpressionEntity { } include(): void {} + + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + for (const expression of this.expressions) { + expression.includeCallArguments(args); + } + } } diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index a7f1366d3f4..323b3fa62c6 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -10,7 +10,9 @@ import { getAndCreateKeys, keys } from '../../keys'; import ChildScope from '../../scopes/ChildScope'; import { ImmutableEntityPathTracker } from '../../utils/ImmutableEntityPathTracker'; import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_VALUE } from '../../values'; +import LocalVariable from '../../variables/LocalVariable'; import Variable from '../../variables/Variable'; +import SpreadElement from '../SpreadElement'; import { ExpressionEntity } from './Expression'; export interface GenericEsTreeNode { @@ -42,7 +44,7 @@ export interface Node extends Entity { /** * Declare a new variable with the optional initialisation. */ - declare(kind: string, init: ExpressionEntity | null): void; + declare(kind: string, init: ExpressionEntity | null): LocalVariable[]; /** * Determine if this Node would have an effect on the bundle. @@ -132,7 +134,9 @@ export class NodeBase implements ExpressionNode { this.scope = parentScope; } - declare(_kind: string, _init: ExpressionEntity | null) {} + declare(_kind: string, _init: ExpressionEntity | null): LocalVariable[] { + return []; + } deoptimizePath(_path: ObjectPath) {} @@ -196,6 +200,12 @@ export class NodeBase implements ExpressionNode { } } + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + for (const arg of args) { + arg.include(false); + } + } + includeWithAllDeclaredVariables(includeChildrenRecursively: IncludeChildren) { this.include(includeChildrenRecursively); } diff --git a/src/ast/scopes/FunctionScope.ts b/src/ast/scopes/FunctionScope.ts index 5c524bee952..fa050708d34 100644 --- a/src/ast/scopes/FunctionScope.ts +++ b/src/ast/scopes/FunctionScope.ts @@ -1,6 +1,8 @@ import { AstContext } from '../../Module'; import CallOptions from '../CallOptions'; import { ExecutionPathOptions } from '../ExecutionPathOptions'; +import { ExpressionNode } from '../nodes/shared/Node'; +import SpreadElement from '../nodes/SpreadElement'; import { UNKNOWN_EXPRESSION, UnknownObjectExpression } from '../values'; import ArgumentsVariable from '../variables/ArgumentsVariable'; import ThisVariable from '../variables/ThisVariable'; @@ -13,10 +15,7 @@ export default class FunctionScope extends ReturnValueScope { constructor(parent: ChildScope, context: AstContext) { super(parent, context); - this.variables.set( - 'arguments', - (this.argumentsVariable = new ArgumentsVariable(super.getParameterVariables(), context)) - ); + this.variables.set('arguments', (this.argumentsVariable = new ArgumentsVariable(context))); this.variables.set('this', (this.thisVariable = new ThisVariable(context))); } @@ -25,16 +24,23 @@ export default class FunctionScope extends ReturnValueScope { } getOptionsWhenCalledWith( - { args, withNew }: CallOptions, + { withNew }: CallOptions, options: ExecutionPathOptions ): ExecutionPathOptions { - return options - .replaceVariableInit( - this.thisVariable, - withNew ? new UnknownObjectExpression() : UNKNOWN_EXPRESSION - ) - .setArgumentsVariables( - args.map((parameter, index) => super.getParameterVariables()[index] || parameter) - ); + return options.replaceVariableInit( + this.thisVariable, + withNew ? new UnknownObjectExpression() : UNKNOWN_EXPRESSION + ); + } + + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + super.includeCallArguments(args); + if (this.argumentsVariable.included) { + for (const arg of args) { + if (!arg.included) { + arg.include(false); + } + } + } } } diff --git a/src/ast/scopes/ParameterScope.ts b/src/ast/scopes/ParameterScope.ts index 8e013382c70..07ed8a2b97d 100644 --- a/src/ast/scopes/ParameterScope.ts +++ b/src/ast/scopes/ParameterScope.ts @@ -1,5 +1,7 @@ import { AstContext } from '../../Module'; import Identifier from '../nodes/Identifier'; +import { ExpressionNode, INCLUDE_VARIABLES } from '../nodes/shared/Node'; +import SpreadElement from '../nodes/SpreadElement'; import { UNKNOWN_EXPRESSION } from '../values'; import LocalVariable from '../variables/LocalVariable'; import ChildScope from './ChildScope'; @@ -8,8 +10,9 @@ import Scope from './Scope'; export default class ParameterScope extends ChildScope { hoistedBodyVarScope: ChildScope; + protected parameters: LocalVariable[][] = []; private context: AstContext; - private parameters: LocalVariable[] = []; + private hasRest = false; constructor(parent: Scope, context: AstContext) { super(parent); @@ -30,11 +33,31 @@ export default class ParameterScope extends ChildScope { variable = new LocalVariable(name, identifier, UNKNOWN_EXPRESSION, this.context); } this.variables.set(name, variable); - this.parameters.push(variable); return variable; } - getParameterVariables() { - return this.parameters; + addParameterVariables(parameters: LocalVariable[][], hasRest: boolean) { + this.parameters = parameters; + this.hasRest = hasRest; + } + + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + let hasInitBeenForceIncluded = false; + for (let index = 0; index < args.length; index++) { + const paramVars = this.parameters[index]; + const arg = args[index]; + if (paramVars) { + hasInitBeenForceIncluded = false; + for (const variable of paramVars) { + if (variable.hasInitBeenForceIncluded) { + hasInitBeenForceIncluded = true; + break; + } + } + arg.include(hasInitBeenForceIncluded ? INCLUDE_VARIABLES : false); + } else if (this.hasRest || arg.shouldBeIncluded()) { + arg.include(hasInitBeenForceIncluded ? INCLUDE_VARIABLES : false); + } + } } } diff --git a/src/ast/values.ts b/src/ast/values.ts index 9d33fd6a319..a218a5ba4bb 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -2,6 +2,8 @@ import CallOptions from './CallOptions'; import { ExecutionPathOptions } from './ExecutionPathOptions'; import { LiteralValue } from './nodes/Literal'; import { ExpressionEntity } from './nodes/shared/Expression'; +import { ExpressionNode } from './nodes/shared/Node'; +import SpreadElement from './nodes/SpreadElement'; export interface UnknownKey { UNKNOWN_KEY: true; @@ -49,6 +51,11 @@ export const UNKNOWN_EXPRESSION: ExpressionEntity = { hasEffectsWhenAssignedAtPath: path => path.length > 0, hasEffectsWhenCalledAtPath: () => true, include: () => {}, + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + for (const arg of args) { + arg.include(false); + } + }, included: true, toString: () => '[[UNKNOWN]]' }; @@ -60,6 +67,7 @@ export const UNDEFINED_EXPRESSION: ExpressionEntity = { hasEffectsWhenAssignedAtPath: path => path.length > 0, hasEffectsWhenCalledAtPath: () => true, include: () => {}, + includeCallArguments(): void {}, included: true, toString: () => 'undefined' }; @@ -117,6 +125,12 @@ export class UnknownArrayExpression implements ExpressionEntity { this.included = true; } + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + for (const arg of args) { + arg.include(false); + } + } + toString() { return '[[UNKNOWN ARRAY]]'; } @@ -174,6 +188,11 @@ const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = { return true; }, include: () => {}, + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + for (const arg of args) { + arg.include(false); + } + }, included: true, toString: () => '[[UNKNOWN BOOLEAN]]' }; @@ -214,6 +233,11 @@ const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = { return true; }, include: () => {}, + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + for (const arg of args) { + arg.include(false); + } + }, included: true, toString: () => '[[UNKNOWN NUMBER]]' }; @@ -261,6 +285,11 @@ const UNKNOWN_LITERAL_STRING: ExpressionEntity = { return true; }, include: () => {}, + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + for (const arg of args) { + arg.include(false); + } + }, included: true, toString: () => '[[UNKNOWN STRING]]' }; @@ -313,6 +342,12 @@ export class UnknownObjectExpression implements ExpressionEntity { this.included = true; } + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + for (const arg of args) { + arg.include(false); + } + } + toString() { return '[[UNKNOWN OBJECT]]'; } diff --git a/src/ast/variables/ArgumentsVariable.ts b/src/ast/variables/ArgumentsVariable.ts index af693040fbb..05ac3f84a37 100644 --- a/src/ast/variables/ArgumentsVariable.ts +++ b/src/ast/variables/ArgumentsVariable.ts @@ -1,63 +1,21 @@ import { AstContext } from '../../Module'; -import CallOptions from '../CallOptions'; -import { ExecutionPathOptions } from '../ExecutionPathOptions'; import { ObjectPath, UNKNOWN_EXPRESSION } from '../values'; import LocalVariable from './LocalVariable'; -const getParameterVariable = (path: ObjectPath, options: ExecutionPathOptions) => { - const firstArgNum = parseInt(path[0] as string, 10); - - return ( - (firstArgNum < options.getArgumentsVariables().length && - options.getArgumentsVariables()[firstArgNum]) || - UNKNOWN_EXPRESSION - ); -}; - export default class ArgumentsVariable extends LocalVariable { - private parameters: LocalVariable[]; - - constructor(parameters: LocalVariable[], context: AstContext) { + constructor(context: AstContext) { super('arguments', null, UNKNOWN_EXPRESSION, context); - this.parameters = parameters; - } - - deoptimizePath(path: ObjectPath) { - const firstArgNum = parseInt(path[0] as string, 10); - if (path.length > 0) { - if (firstArgNum >= 0 && this.parameters[firstArgNum]) { - this.parameters[firstArgNum].deoptimizePath(path.slice(1)); - } - } } - hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions) { - return ( - path.length > 1 && - getParameterVariable(path, options).hasEffectsWhenAccessedAtPath(path.slice(1), options) - ); + hasEffectsWhenAccessedAtPath(path: ObjectPath) { + return path.length > 1; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { - return ( - path.length === 0 || - this.included || - getParameterVariable(path, options).hasEffectsWhenAssignedAtPath(path.slice(1), options) - ); + hasEffectsWhenAssignedAtPath() { + return true; } - hasEffectsWhenCalledAtPath( - path: ObjectPath, - callOptions: CallOptions, - options: ExecutionPathOptions - ): boolean { - if (path.length === 0) { - return true; - } - return getParameterVariable(path, options).hasEffectsWhenCalledAtPath( - path.slice(1), - callOptions, - options - ); + hasEffectsWhenCalledAtPath(): boolean { + return true; } } diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 2b1a1b71f6f..5ac6089acb7 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -7,7 +7,8 @@ import ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; import { ExpressionEntity } from '../nodes/shared/Expression'; -import { INCLUDE_VARIABLES, Node } from '../nodes/shared/Node'; +import { ExpressionNode, INCLUDE_VARIABLES, Node } from '../nodes/shared/Node'; +import SpreadElement from '../nodes/SpreadElement'; import { EntityPathTracker } from '../utils/EntityPathTracker'; import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; import { @@ -25,6 +26,7 @@ const MAX_PATH_DEPTH = 7; export default class LocalVariable extends Variable { additionalInitializers: ExpressionEntity[] | null = null; declarations: (Identifier | ExportDefaultDeclaration)[]; + hasInitBeenForceIncluded = false; init: ExpressionEntity | null; module: Module; @@ -32,7 +34,6 @@ export default class LocalVariable extends Variable { // We track deoptimization when we do not return something unknown private deoptimizationTracker: EntityPathTracker; private expressionsToBeDeoptimized: DeoptimizableEntity[] = []; - private hasInitBeenIncluded = false; constructor( name: string, @@ -192,9 +193,19 @@ export default class LocalVariable extends Variable { } } + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + if (this.isReassigned) { + for (const arg of args) { + arg.include(false); + } + } else if (this.init) { + this.init.includeCallArguments(args); + } + } + includeInitRecursively() { - if (!this.hasInitBeenIncluded) { - this.hasInitBeenIncluded = true; + if (!this.hasInitBeenForceIncluded) { + this.hasInitBeenForceIncluded = true; if (this.init && this.init !== UNKNOWN_EXPRESSION) { this.init.include(INCLUDE_VARIABLES); } diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index 1549828eb39..e8a4eca34e6 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -5,6 +5,8 @@ import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { ExecutionPathOptions } from '../ExecutionPathOptions'; import Identifier from '../nodes/Identifier'; import { ExpressionEntity } from '../nodes/shared/Expression'; +import { ExpressionNode } from '../nodes/shared/Node'; +import SpreadElement from '../nodes/SpreadElement'; import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_VALUE } from '../values'; @@ -84,6 +86,12 @@ export default class Variable implements ExpressionEntity { this.included = true; } + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + for (const arg of args) { + arg.include(false); + } + } + includeInitRecursively() {} setRenderNames(baseName: string | null, name: string | null) { diff --git a/test/chunking-form/samples/missing-export-compact/_expected/amd/dep.js b/test/chunking-form/samples/missing-export-compact/_expected/amd/dep.js index 27486e82854..086ad0730e7 100644 --- a/test/chunking-form/samples/missing-export-compact/_expected/amd/dep.js +++ b/test/chunking-form/samples/missing-export-compact/_expected/amd/dep.js @@ -1,3 +1,3 @@ -define(['exports'],function(exports){'use strict';var _missingExportShim=void 0;function x () { - sideEffect(); +define(['exports'],function(exports){'use strict';var _missingExportShim=void 0;function x (arg) { + sideEffect(arg); }exports.missingExport=_missingExportShim;exports.missingFn=_missingExportShim;exports.x=x;Object.defineProperty(exports,'__esModule',{value:true});}); \ No newline at end of file diff --git a/test/chunking-form/samples/missing-export-compact/_expected/cjs/dep.js b/test/chunking-form/samples/missing-export-compact/_expected/cjs/dep.js index 6112fb22cfc..7dba4c72167 100644 --- a/test/chunking-form/samples/missing-export-compact/_expected/cjs/dep.js +++ b/test/chunking-form/samples/missing-export-compact/_expected/cjs/dep.js @@ -1,3 +1,3 @@ -'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _missingExportShim=void 0;function x () { - sideEffect(); +'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _missingExportShim=void 0;function x (arg) { + sideEffect(arg); }exports.missingExport=_missingExportShim;exports.missingFn=_missingExportShim;exports.x=x; \ No newline at end of file diff --git a/test/chunking-form/samples/missing-export-compact/_expected/es/dep.js b/test/chunking-form/samples/missing-export-compact/_expected/es/dep.js index 299eb65ec5a..927ac0c4d3b 100644 --- a/test/chunking-form/samples/missing-export-compact/_expected/es/dep.js +++ b/test/chunking-form/samples/missing-export-compact/_expected/es/dep.js @@ -1,3 +1,3 @@ -var _missingExportShim=void 0;function x () { - sideEffect(); +var _missingExportShim=void 0;function x (arg) { + sideEffect(arg); }export{_missingExportShim as missingExport,_missingExportShim as missingFn,x}; \ No newline at end of file diff --git a/test/chunking-form/samples/missing-export-compact/_expected/system/dep.js b/test/chunking-form/samples/missing-export-compact/_expected/system/dep.js index fb1c2386f1c..0513609b4f3 100644 --- a/test/chunking-form/samples/missing-export-compact/_expected/system/dep.js +++ b/test/chunking-form/samples/missing-export-compact/_expected/system/dep.js @@ -1,3 +1,3 @@ -System.register([],function(exports){'use strict';return{execute:function(){exports('x',x);var _missingExportShim=void 0;function x () { - sideEffect(); +System.register([],function(exports){'use strict';return{execute:function(){exports('x',x);var _missingExportShim=void 0;function x (arg) { + sideEffect(arg); }exports({missingExport:_missingExportShim,missingFn:_missingExportShim});}}}); \ No newline at end of file diff --git a/test/chunking-form/samples/missing-export-compact/dep.js b/test/chunking-form/samples/missing-export-compact/dep.js index 3cedc5e4cfe..95031f2ca31 100644 --- a/test/chunking-form/samples/missing-export-compact/dep.js +++ b/test/chunking-form/samples/missing-export-compact/dep.js @@ -1,3 +1,3 @@ -export function x () { - sideEffect(); +export function x (arg) { + sideEffect(arg); } diff --git a/test/chunking-form/samples/missing-export/_expected/amd/dep.js b/test/chunking-form/samples/missing-export/_expected/amd/dep.js index b4213dd6603..b0a4d553740 100644 --- a/test/chunking-form/samples/missing-export/_expected/amd/dep.js +++ b/test/chunking-form/samples/missing-export/_expected/amd/dep.js @@ -2,8 +2,8 @@ define(['exports'], function (exports) { 'use strict'; var _missingExportShim = void 0; - function x () { - sideEffect(); + function x (arg) { + sideEffect(arg); } exports.default = _missingExportShim; diff --git a/test/chunking-form/samples/missing-export/_expected/amd/main.js b/test/chunking-form/samples/missing-export/_expected/amd/main.js index 45cf3ece8f6..b26a873671d 100644 --- a/test/chunking-form/samples/missing-export/_expected/amd/main.js +++ b/test/chunking-form/samples/missing-export/_expected/amd/main.js @@ -1,6 +1,6 @@ define(['./dep'], function (dep) { 'use strict'; dep.missingFn(); - dep.x(dep.missingFn, dep.missingFn); + dep.x(dep.missingFn); }); diff --git a/test/chunking-form/samples/missing-export/_expected/cjs/dep.js b/test/chunking-form/samples/missing-export/_expected/cjs/dep.js index 2d1e33f1e8c..fcee436f762 100644 --- a/test/chunking-form/samples/missing-export/_expected/cjs/dep.js +++ b/test/chunking-form/samples/missing-export/_expected/cjs/dep.js @@ -4,8 +4,8 @@ Object.defineProperty(exports, '__esModule', { value: true }); var _missingExportShim = void 0; -function x () { - sideEffect(); +function x (arg) { + sideEffect(arg); } exports.default = _missingExportShim; diff --git a/test/chunking-form/samples/missing-export/_expected/cjs/main.js b/test/chunking-form/samples/missing-export/_expected/cjs/main.js index 25cbad61dc1..60f71a76480 100644 --- a/test/chunking-form/samples/missing-export/_expected/cjs/main.js +++ b/test/chunking-form/samples/missing-export/_expected/cjs/main.js @@ -3,4 +3,4 @@ var dep = require('./dep.js'); dep.missingFn(); -dep.x(dep.missingFn, dep.missingFn); +dep.x(dep.missingFn); diff --git a/test/chunking-form/samples/missing-export/_expected/es/dep.js b/test/chunking-form/samples/missing-export/_expected/es/dep.js index 26e5b1abbf6..5fe7b66c9fe 100644 --- a/test/chunking-form/samples/missing-export/_expected/es/dep.js +++ b/test/chunking-form/samples/missing-export/_expected/es/dep.js @@ -1,7 +1,7 @@ var _missingExportShim = void 0; -function x () { - sideEffect(); +function x (arg) { + sideEffect(arg); } export default _missingExportShim; diff --git a/test/chunking-form/samples/missing-export/_expected/es/main.js b/test/chunking-form/samples/missing-export/_expected/es/main.js index 42ed3d2bae5..69c955048c8 100644 --- a/test/chunking-form/samples/missing-export/_expected/es/main.js +++ b/test/chunking-form/samples/missing-export/_expected/es/main.js @@ -1,4 +1,4 @@ import { missingFn as _missingExportShim, x } from './dep.js'; _missingExportShim(); -x(_missingExportShim, _missingExportShim); +x(_missingExportShim); diff --git a/test/chunking-form/samples/missing-export/_expected/system/dep.js b/test/chunking-form/samples/missing-export/_expected/system/dep.js index 7a4af01c110..550be0c3cf2 100644 --- a/test/chunking-form/samples/missing-export/_expected/system/dep.js +++ b/test/chunking-form/samples/missing-export/_expected/system/dep.js @@ -7,8 +7,8 @@ System.register([], function (exports) { var _missingExportShim = void 0; - function x () { - sideEffect(); + function x (arg) { + sideEffect(arg); } exports({ diff --git a/test/chunking-form/samples/missing-export/_expected/system/main.js b/test/chunking-form/samples/missing-export/_expected/system/main.js index 7d5c20be8ff..f7e096a7b2c 100644 --- a/test/chunking-form/samples/missing-export/_expected/system/main.js +++ b/test/chunking-form/samples/missing-export/_expected/system/main.js @@ -9,7 +9,7 @@ System.register(['./dep.js'], function () { execute: function () { _missingExportShim(); - x(_missingExportShim, _missingExportShim); + x(_missingExportShim); } }; diff --git a/test/chunking-form/samples/missing-export/dep.js b/test/chunking-form/samples/missing-export/dep.js index 3cedc5e4cfe..95031f2ca31 100644 --- a/test/chunking-form/samples/missing-export/dep.js +++ b/test/chunking-form/samples/missing-export/dep.js @@ -1,3 +1,3 @@ -export function x () { - sideEffect(); +export function x (arg) { + sideEffect(arg); } diff --git a/test/form/samples/exclude-unnecessary-modifications/_expected/amd.js b/test/form/samples/exclude-unnecessary-modifications/_expected/amd.js index 84befa4545d..36fce8b38ed 100644 --- a/test/form/samples/exclude-unnecessary-modifications/_expected/amd.js +++ b/test/form/samples/exclude-unnecessary-modifications/_expected/amd.js @@ -2,7 +2,7 @@ define(function () { 'use strict'; var foo = {}; - mutate1( foo ); + mutate1(); // should be included [ 'a', 'b', 'c' ].forEach( function ( letter, i ) { diff --git a/test/form/samples/exclude-unnecessary-modifications/_expected/cjs.js b/test/form/samples/exclude-unnecessary-modifications/_expected/cjs.js index 596c91015ba..6435ca1eaa0 100644 --- a/test/form/samples/exclude-unnecessary-modifications/_expected/cjs.js +++ b/test/form/samples/exclude-unnecessary-modifications/_expected/cjs.js @@ -2,7 +2,7 @@ var foo = {}; -mutate1( foo ); +mutate1(); // should be included [ 'a', 'b', 'c' ].forEach( function ( letter, i ) { diff --git a/test/form/samples/exclude-unnecessary-modifications/_expected/es.js b/test/form/samples/exclude-unnecessary-modifications/_expected/es.js index a88d21e07e7..3e70b09d43e 100644 --- a/test/form/samples/exclude-unnecessary-modifications/_expected/es.js +++ b/test/form/samples/exclude-unnecessary-modifications/_expected/es.js @@ -1,6 +1,6 @@ var foo = {}; -mutate1( foo ); +mutate1(); // should be included [ 'a', 'b', 'c' ].forEach( function ( letter, i ) { diff --git a/test/form/samples/exclude-unnecessary-modifications/_expected/iife.js b/test/form/samples/exclude-unnecessary-modifications/_expected/iife.js index 9a3501b2a65..84e2328cc7b 100644 --- a/test/form/samples/exclude-unnecessary-modifications/_expected/iife.js +++ b/test/form/samples/exclude-unnecessary-modifications/_expected/iife.js @@ -3,7 +3,7 @@ var foo = {}; - mutate1( foo ); + mutate1(); // should be included [ 'a', 'b', 'c' ].forEach( function ( letter, i ) { diff --git a/test/form/samples/exclude-unnecessary-modifications/_expected/system.js b/test/form/samples/exclude-unnecessary-modifications/_expected/system.js index 97debcdb2f3..e653701513c 100644 --- a/test/form/samples/exclude-unnecessary-modifications/_expected/system.js +++ b/test/form/samples/exclude-unnecessary-modifications/_expected/system.js @@ -5,7 +5,7 @@ System.register([], function () { var foo = {}; - mutate1( foo ); + mutate1(); // should be included [ 'a', 'b', 'c' ].forEach( function ( letter, i ) { diff --git a/test/form/samples/exclude-unnecessary-modifications/_expected/umd.js b/test/form/samples/exclude-unnecessary-modifications/_expected/umd.js index e981f0d05dd..4bd4a276a1c 100644 --- a/test/form/samples/exclude-unnecessary-modifications/_expected/umd.js +++ b/test/form/samples/exclude-unnecessary-modifications/_expected/umd.js @@ -5,7 +5,7 @@ var foo = {}; - mutate1( foo ); + mutate1(); // should be included [ 'a', 'b', 'c' ].forEach( function ( letter, i ) { diff --git a/test/form/samples/no-treeshake/_expected/amd.js b/test/form/samples/no-treeshake/_expected/amd.js index ecb358c7923..895b9292d6f 100644 --- a/test/form/samples/no-treeshake/_expected/amd.js +++ b/test/form/samples/no-treeshake/_expected/amd.js @@ -31,6 +31,20 @@ define(['exports', 'external'], function (exports, external) { 'use strict'; } } + function test( + unusedParam = { + prop: function test() { + var unused = 1; + } + } + ) {} + + test({ + prop: function test() { + var unused = 1; + } + }); + exports.create = create; exports.getPrototypeOf = getPrototypeOf; exports.strange = quux; diff --git a/test/form/samples/no-treeshake/_expected/cjs.js b/test/form/samples/no-treeshake/_expected/cjs.js index c8f47529115..b7ee6f6291a 100644 --- a/test/form/samples/no-treeshake/_expected/cjs.js +++ b/test/form/samples/no-treeshake/_expected/cjs.js @@ -35,6 +35,20 @@ function unusedButIncluded() { } } +function test( + unusedParam = { + prop: function test() { + var unused = 1; + } + } +) {} + +test({ + prop: function test() { + var unused = 1; + } +}); + exports.create = create; exports.getPrototypeOf = getPrototypeOf; exports.strange = quux; diff --git a/test/form/samples/no-treeshake/_expected/es.js b/test/form/samples/no-treeshake/_expected/es.js index 28b4f47617f..9303e39feb6 100644 --- a/test/form/samples/no-treeshake/_expected/es.js +++ b/test/form/samples/no-treeshake/_expected/es.js @@ -31,4 +31,18 @@ function unusedButIncluded() { } } +function test( + unusedParam = { + prop: function test() { + var unused = 1; + } + } +) {} + +test({ + prop: function test() { + var unused = 1; + } +}); + export { create, getPrototypeOf, quux as strange }; diff --git a/test/form/samples/no-treeshake/_expected/iife.js b/test/form/samples/no-treeshake/_expected/iife.js index c1fe5df589b..041845fac3a 100644 --- a/test/form/samples/no-treeshake/_expected/iife.js +++ b/test/form/samples/no-treeshake/_expected/iife.js @@ -32,6 +32,20 @@ var stirred = (function (exports, external) { } } + function test( + unusedParam = { + prop: function test() { + var unused = 1; + } + } + ) {} + + test({ + prop: function test() { + var unused = 1; + } + }); + exports.create = create; exports.getPrototypeOf = getPrototypeOf; exports.strange = quux; diff --git a/test/form/samples/no-treeshake/_expected/system.js b/test/form/samples/no-treeshake/_expected/system.js index ba8701af20f..7efce8dae51 100644 --- a/test/form/samples/no-treeshake/_expected/system.js +++ b/test/form/samples/no-treeshake/_expected/system.js @@ -38,6 +38,20 @@ System.register('stirred', ['external'], function (exports) { } } + function test( + unusedParam = { + prop: function test() { + var unused = 1; + } + } + ) {} + + test({ + prop: function test() { + var unused = 1; + } + }); + } }; }); diff --git a/test/form/samples/no-treeshake/_expected/umd.js b/test/form/samples/no-treeshake/_expected/umd.js index 79952d3b35c..4b3992141d2 100644 --- a/test/form/samples/no-treeshake/_expected/umd.js +++ b/test/form/samples/no-treeshake/_expected/umd.js @@ -35,6 +35,20 @@ } } + function test( + unusedParam = { + prop: function test() { + var unused = 1; + } + } + ) {} + + test({ + prop: function test() { + var unused = 1; + } + }); + exports.create = create; exports.getPrototypeOf = getPrototypeOf; exports.strange = quux; diff --git a/test/form/samples/no-treeshake/main.js b/test/form/samples/no-treeshake/main.js index 2a6361ba2b7..25adc231533 100644 --- a/test/form/samples/no-treeshake/main.js +++ b/test/form/samples/no-treeshake/main.js @@ -26,3 +26,17 @@ function unusedButIncluded() { const ignored = 2; } } + +function test( + unusedParam = { + prop: function test() { + var unused = 1; + } + } +) {} + +test({ + prop: function test() { + var unused = 1; + } +}); diff --git a/test/form/samples/removes-existing-sourcemap-comments/_expected/amd.js b/test/form/samples/removes-existing-sourcemap-comments/_expected/amd.js index ca0b4cc3269..10203946401 100644 --- a/test/form/samples/removes-existing-sourcemap-comments/_expected/amd.js +++ b/test/form/samples/removes-existing-sourcemap-comments/_expected/amd.js @@ -1,7 +1,7 @@ define(function () { 'use strict'; - function foo () { - return 42; + function foo (x) { + return x; } var str = ` diff --git a/test/form/samples/removes-existing-sourcemap-comments/_expected/cjs.js b/test/form/samples/removes-existing-sourcemap-comments/_expected/cjs.js index e40d32de697..cd62e2329cc 100644 --- a/test/form/samples/removes-existing-sourcemap-comments/_expected/cjs.js +++ b/test/form/samples/removes-existing-sourcemap-comments/_expected/cjs.js @@ -1,7 +1,7 @@ 'use strict'; -function foo () { - return 42; +function foo (x) { + return x; } var str = ` diff --git a/test/form/samples/removes-existing-sourcemap-comments/_expected/es.js b/test/form/samples/removes-existing-sourcemap-comments/_expected/es.js index 83580889ea2..4b4822d2c2b 100644 --- a/test/form/samples/removes-existing-sourcemap-comments/_expected/es.js +++ b/test/form/samples/removes-existing-sourcemap-comments/_expected/es.js @@ -1,5 +1,5 @@ -function foo () { - return 42; +function foo (x) { + return x; } var str = ` diff --git a/test/form/samples/removes-existing-sourcemap-comments/_expected/iife.js b/test/form/samples/removes-existing-sourcemap-comments/_expected/iife.js index ea07088b1d5..4ce1d3c6533 100644 --- a/test/form/samples/removes-existing-sourcemap-comments/_expected/iife.js +++ b/test/form/samples/removes-existing-sourcemap-comments/_expected/iife.js @@ -1,8 +1,8 @@ (function () { 'use strict'; - function foo () { - return 42; + function foo (x) { + return x; } var str = ` diff --git a/test/form/samples/removes-existing-sourcemap-comments/_expected/system.js b/test/form/samples/removes-existing-sourcemap-comments/_expected/system.js index 05a84a854a8..6725a5b140a 100644 --- a/test/form/samples/removes-existing-sourcemap-comments/_expected/system.js +++ b/test/form/samples/removes-existing-sourcemap-comments/_expected/system.js @@ -3,8 +3,8 @@ System.register([], function () { return { execute: function () { - function foo () { - return 42; + function foo (x) { + return x; } var str = ` diff --git a/test/form/samples/removes-existing-sourcemap-comments/_expected/umd.js b/test/form/samples/removes-existing-sourcemap-comments/_expected/umd.js index 83fff4c78f5..f9e13b988c7 100644 --- a/test/form/samples/removes-existing-sourcemap-comments/_expected/umd.js +++ b/test/form/samples/removes-existing-sourcemap-comments/_expected/umd.js @@ -3,8 +3,8 @@ factory(); }(function () { 'use strict'; - function foo () { - return 42; + function foo (x) { + return x; } var str = ` diff --git a/test/form/samples/removes-existing-sourcemap-comments/foo.js b/test/form/samples/removes-existing-sourcemap-comments/foo.js index 74fda0830e5..4c1bbda5342 100644 --- a/test/form/samples/removes-existing-sourcemap-comments/foo.js +++ b/test/form/samples/removes-existing-sourcemap-comments/foo.js @@ -1,5 +1,5 @@ -export default function () { - return 42; +export default function (x) { + return x; } //# sourceMappingURL=foo.js.map diff --git a/test/form/samples/side-effect-j/_expected/amd.js b/test/form/samples/side-effect-j/_expected/amd.js index 0640671db1e..f65939a2d2a 100644 --- a/test/form/samples/side-effect-j/_expected/amd.js +++ b/test/form/samples/side-effect-j/_expected/amd.js @@ -1,7 +1,7 @@ define(function () { 'use strict'; var augment; - augment = x => x.augmented = true; + augment = y => y.augmented = true; function x () {} augment( x ); diff --git a/test/form/samples/side-effect-j/_expected/cjs.js b/test/form/samples/side-effect-j/_expected/cjs.js index ba3008134a0..8de74922cde 100644 --- a/test/form/samples/side-effect-j/_expected/cjs.js +++ b/test/form/samples/side-effect-j/_expected/cjs.js @@ -1,7 +1,7 @@ 'use strict'; var augment; -augment = x => x.augmented = true; +augment = y => y.augmented = true; function x () {} augment( x ); diff --git a/test/form/samples/side-effect-j/_expected/es.js b/test/form/samples/side-effect-j/_expected/es.js index 80f969bea62..54e415f780b 100644 --- a/test/form/samples/side-effect-j/_expected/es.js +++ b/test/form/samples/side-effect-j/_expected/es.js @@ -1,5 +1,5 @@ var augment; -augment = x => x.augmented = true; +augment = y => y.augmented = true; function x () {} augment( x ); diff --git a/test/form/samples/side-effect-j/_expected/iife.js b/test/form/samples/side-effect-j/_expected/iife.js index 7f340737096..d507fc7d950 100644 --- a/test/form/samples/side-effect-j/_expected/iife.js +++ b/test/form/samples/side-effect-j/_expected/iife.js @@ -2,7 +2,7 @@ var myBundle = (function () { 'use strict'; var augment; - augment = x => x.augmented = true; + augment = y => y.augmented = true; function x () {} augment( x ); diff --git a/test/form/samples/side-effect-j/_expected/system.js b/test/form/samples/side-effect-j/_expected/system.js index 9a3b6c947d9..c346c1112a4 100644 --- a/test/form/samples/side-effect-j/_expected/system.js +++ b/test/form/samples/side-effect-j/_expected/system.js @@ -6,7 +6,7 @@ System.register('myBundle', [], function (exports) { exports('default', x); var augment; - augment = x => x.augmented = true; + augment = y => y.augmented = true; function x () {} augment( x ); diff --git a/test/form/samples/side-effect-j/_expected/umd.js b/test/form/samples/side-effect-j/_expected/umd.js index b8615138438..d41a6ac547f 100644 --- a/test/form/samples/side-effect-j/_expected/umd.js +++ b/test/form/samples/side-effect-j/_expected/umd.js @@ -5,7 +5,7 @@ }(this, function () { 'use strict'; var augment; - augment = x => x.augmented = true; + augment = y => y.augmented = true; function x () {} augment( x ); diff --git a/test/form/samples/side-effect-j/foo.js b/test/form/samples/side-effect-j/foo.js index 2f4073f4861..3af32948fca 100644 --- a/test/form/samples/side-effect-j/foo.js +++ b/test/form/samples/side-effect-j/foo.js @@ -1,4 +1,4 @@ var augment; -augment = x => x.augmented = true; +augment = y => y.augmented = true; export { augment }; diff --git a/test/form/samples/treeshake-excessive-arguments/arguments-variable/_config.js b/test/form/samples/treeshake-excessive-arguments/arguments-variable/_config.js new file mode 100644 index 00000000000..86854f25350 --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/arguments-variable/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'does not remove arguments from call when arguments variables is accessed' +}; diff --git a/test/form/samples/treeshake-excessive-arguments/arguments-variable/_expected.js b/test/form/samples/treeshake-excessive-arguments/arguments-variable/_expected.js new file mode 100644 index 00000000000..82354dff062 --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/arguments-variable/_expected.js @@ -0,0 +1,15 @@ +function useArguments1() { + console.log(arguments.length); +} + +function useArguments2(existing) { + console.log(existing, arguments[1]); +} + +const needed11 = 1; +const needed12 = 2; +useArguments1(needed11, needed12); + +const needed21 = 1; +const needed22 = 2; +useArguments2(needed21, needed22); diff --git a/test/form/samples/treeshake-excessive-arguments/arguments-variable/main.js b/test/form/samples/treeshake-excessive-arguments/arguments-variable/main.js new file mode 100644 index 00000000000..82354dff062 --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/arguments-variable/main.js @@ -0,0 +1,15 @@ +function useArguments1() { + console.log(arguments.length); +} + +function useArguments2(existing) { + console.log(existing, arguments[1]); +} + +const needed11 = 1; +const needed12 = 2; +useArguments1(needed11, needed12); + +const needed21 = 1; +const needed22 = 2; +useArguments2(needed21, needed22); diff --git a/test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/_config.js b/test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/_config.js new file mode 100644 index 00000000000..8c4887c4e0a --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes unneeded arguments from calls to arrow functions' +}; diff --git a/test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/_expected.js b/test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/_expected.js new file mode 100644 index 00000000000..5533e3d376c --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/_expected.js @@ -0,0 +1,10 @@ +const noParams = () => console.log(); +const someUsedParams = (p1, p2, p3) => console.log(p1, p3); + +noParams(); + +const needed21 = 1; +const needed22 = 2; +const needed23 = 3; + +someUsedParams(needed21, needed22, needed23); diff --git a/test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/main.js b/test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/main.js new file mode 100644 index 00000000000..53a5527c4b2 --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/main.js @@ -0,0 +1,13 @@ +const noParams = () => console.log(); +const someUsedParams = (p1, p2, p3) => console.log(p1, p3); +const unneeded1 = 1; + +noParams(unneeded1); + +const unneeded2 = 1; + +const needed21 = 1; +const needed22 = 2; +const needed23 = 3; + +someUsedParams(needed21, needed22, needed23, unneeded2); diff --git a/test/form/samples/treeshake-excessive-arguments/function-arguments/_config.js b/test/form/samples/treeshake-excessive-arguments/function-arguments/_config.js new file mode 100644 index 00000000000..2a51e6c38af --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/function-arguments/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes unneeded arguments from calls to functions' +}; diff --git a/test/form/samples/treeshake-excessive-arguments/function-arguments/_expected.js b/test/form/samples/treeshake-excessive-arguments/function-arguments/_expected.js new file mode 100644 index 00000000000..6c216679275 --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/function-arguments/_expected.js @@ -0,0 +1,41 @@ +function noParams() { + console.log(); +} +function someUsedParams(p1, p2, p3) { + console.log(p1, p3); +} + +noParams(); + +const needed21 = 1; +const needed22 = 2; +const needed23 = 3; + +someUsedParams(needed21, needed22, needed23); + +const noParamsExp = function() { + console.log(); +}; +const someUsedParamsExp = function(p1, p2, p3) { + console.log(p1, p3); +}; + +noParamsExp(); + +const needed41 = 1; +const needed42 = 2; +const needed43 = 3; + +someUsedParamsExp(needed41, needed42, needed43); + +(function() { + console.log(); +}()); + +const needed61 = 1; +const needed62 = 2; +const needed63 = 3; + +(function(p1, p2, p3) { + console.log(p1, p3); +} (needed61, needed62, needed63)); diff --git a/test/form/samples/treeshake-excessive-arguments/function-arguments/main.js b/test/form/samples/treeshake-excessive-arguments/function-arguments/main.js new file mode 100644 index 00000000000..a7d8a634f68 --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/function-arguments/main.js @@ -0,0 +1,51 @@ +function noParams() { + console.log(); +} +function someUsedParams(p1, p2, p3) { + console.log(p1, p3); +} +const unneeded1 = 1; + +noParams(unneeded1); + +const unneeded2 = 1; + +const needed21 = 1; +const needed22 = 2; +const needed23 = 3; + +someUsedParams(needed21, needed22, needed23, unneeded2); + +const noParamsExp = function() { + console.log(); +}; +const someUsedParamsExp = function(p1, p2, p3) { + console.log(p1, p3); +}; +const unneeded3 = 1; + +noParamsExp(unneeded3); + +const unneeded4 = 1; + +const needed41 = 1; +const needed42 = 2; +const needed43 = 3; + +someUsedParamsExp(needed41, needed42, needed43, unneeded4); + +const unneeded5 = 1; + +(function() { + console.log(); +}(unneeded5)); + +const unneeded6 = 1; + +const needed61 = 1; +const needed62 = 2; +const needed63 = 3; + +(function(p1, p2, p3) { + console.log(p1, p3); +} (needed61, needed62, needed63, unneeded6)); diff --git a/test/form/samples/treeshake-excessive-arguments/patterns/_config.js b/test/form/samples/treeshake-excessive-arguments/patterns/_config.js new file mode 100644 index 00000000000..6a644e8b64e --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/patterns/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes unneeded arguments from calls when patterns are used' +}; diff --git a/test/form/samples/treeshake-excessive-arguments/patterns/_expected.js b/test/form/samples/treeshake-excessive-arguments/patterns/_expected.js new file mode 100644 index 00000000000..c6cba038206 --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/patterns/_expected.js @@ -0,0 +1,7 @@ +function usePatterns([a, b, ...c], d) { + console.log(a, b, c, d); +} + +const needed1 = 1; +const needed2 = 2; +usePatterns(needed1, needed2); diff --git a/test/form/samples/treeshake-excessive-arguments/patterns/main.js b/test/form/samples/treeshake-excessive-arguments/patterns/main.js new file mode 100644 index 00000000000..41e53c542a0 --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/patterns/main.js @@ -0,0 +1,8 @@ +function usePatterns([a, b, ...c], d) { + console.log(a, b, c, d); +} + +const needed1 = 1; +const needed2 = 2; +const unneeded = 1; +usePatterns(needed1, needed2, unneeded); diff --git a/test/form/samples/treeshake-excessive-arguments/rest-element/_config.js b/test/form/samples/treeshake-excessive-arguments/rest-element/_config.js new file mode 100644 index 00000000000..2daa706360d --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/rest-element/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'does not remove arguments from calls when rest parameters are used' +}; diff --git a/test/form/samples/treeshake-excessive-arguments/rest-element/_expected.js b/test/form/samples/treeshake-excessive-arguments/rest-element/_expected.js new file mode 100644 index 00000000000..563156f5651 --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/rest-element/_expected.js @@ -0,0 +1,16 @@ +function useRest1(...rest) { + console.log(rest.length); +} + +function useRest2(existing, ...rest) { + console.log(existing, rest[1]); +} + +const needed11 = 1; +const needed12 = 2; +useRest1(needed11, needed12); + +const needed21 = 1; +const needed22 = 2; +const needed23 = 4; +useRest2(needed21, needed22, needed23); diff --git a/test/form/samples/treeshake-excessive-arguments/rest-element/main.js b/test/form/samples/treeshake-excessive-arguments/rest-element/main.js new file mode 100644 index 00000000000..563156f5651 --- /dev/null +++ b/test/form/samples/treeshake-excessive-arguments/rest-element/main.js @@ -0,0 +1,16 @@ +function useRest1(...rest) { + console.log(rest.length); +} + +function useRest2(existing, ...rest) { + console.log(existing, rest[1]); +} + +const needed11 = 1; +const needed12 = 2; +useRest1(needed11, needed12); + +const needed21 = 1; +const needed22 = 2; +const needed23 = 4; +useRest2(needed21, needed22, needed23); diff --git a/test/form/samples/try-statement-deoptimization/follow-parameters/_config.js b/test/form/samples/try-statement-deoptimization/follow-parameters/_config.js new file mode 100644 index 00000000000..ccffd84ff52 --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/follow-parameters/_config.js @@ -0,0 +1,8 @@ +module.exports = { + description: 'retains side-effect-free code in parameters called from try-statement-blocks' +}; + +// TODO Lukas +// * limit following depth to one level +// * only follow direct identifier call expressions +// * add core-js test diff --git a/test/form/samples/try-statement-deoptimization/follow-parameters/_expected.js b/test/form/samples/try-statement-deoptimization/follow-parameters/_expected.js new file mode 100644 index 00000000000..a7882831013 --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/follow-parameters/_expected.js @@ -0,0 +1,14 @@ +function callGlobal1() { +} + +function callGlobal2() { + Object.create(null); +} + +function tryIt(other, callback) { + try { + callback(); + } catch {} +} + +tryIt(callGlobal1, callGlobal2); diff --git a/test/form/samples/try-statement-deoptimization/follow-parameters/main.js b/test/form/samples/try-statement-deoptimization/follow-parameters/main.js new file mode 100644 index 00000000000..daeec4ef1bf --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/follow-parameters/main.js @@ -0,0 +1,15 @@ +function callGlobal1() { + Object.create(null); +} + +function callGlobal2() { + Object.create(null); +} + +function tryIt(other, callback) { + try { + callback(); + } catch {} +} + +tryIt(callGlobal1, callGlobal2); diff --git a/test/form/samples/try-statement-deoptimization/follow-variables/_config.js b/test/form/samples/try-statement-deoptimization/follow-variables/_config.js index f9aca82d54c..c73f23dda97 100644 --- a/test/form/samples/try-statement-deoptimization/follow-variables/_config.js +++ b/test/form/samples/try-statement-deoptimization/follow-variables/_config.js @@ -1,3 +1,4 @@ module.exports = { description: 'retains side-effect-free code in functions called from try-statement-blocks' }; +// TODO Lukas also spread operator, namespace member, arguments variable From 28b3d09cb1dc32ba2a9c0efd71985c62a4d9de72 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 3 Jun 2019 17:23:15 +0200 Subject: [PATCH 5/8] Limit deoptimization to direct call expressions and one level --- src/ast/nodes/CallExpression.ts | 9 ++++++- src/ast/nodes/Identifier.ts | 7 ++--- src/ast/scopes/ParameterScope.ts | 11 +++++--- src/ast/variables/LocalVariable.ts | 4 +-- .../follow-parameters/_config.js | 5 ---- .../follow-parameters/_expected.js | 18 ++++++++++--- .../follow-parameters/main.js | 26 ++++++++++++++++--- .../follow-variables/_config.js | 1 + .../follow-variables/_expected.js | 8 ++++-- .../follow-variables/main.js | 14 ++++++++-- .../supports-core-js/_config.js | 3 +++ .../supports-core-js/_expected.js | 18 +++++++++++++ .../supports-core-js/main.js | 18 +++++++++++++ 13 files changed, 115 insertions(+), 27 deletions(-) create mode 100644 test/form/samples/try-statement-deoptimization/supports-core-js/_config.js create mode 100644 test/form/samples/try-statement-deoptimization/supports-core-js/_expected.js create mode 100644 test/form/samples/try-statement-deoptimization/supports-core-js/main.js diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 818f417517a..ee64e7284c7 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -23,7 +23,7 @@ import { import Identifier from './Identifier'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; -import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; +import { ExpressionNode, INCLUDE_VARIABLES, IncludeChildren, NodeBase } from './shared/Node'; import SpreadElement from './SpreadElement'; export default class CallExpression extends NodeBase implements DeoptimizableEntity { @@ -203,6 +203,13 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt include(includeChildrenRecursively: IncludeChildren) { if (includeChildrenRecursively) { super.include(includeChildrenRecursively); + if ( + includeChildrenRecursively === INCLUDE_VARIABLES && + this.callee instanceof Identifier && + this.callee.variable + ) { + this.callee.variable.includeInitRecursively(); + } } else { this.included = true; this.callee.include(false); diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 913885449b4..33ea3147fff 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -12,7 +12,7 @@ import LocalVariable from '../variables/LocalVariable'; import Variable from '../variables/Variable'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; -import { ExpressionNode, INCLUDE_VARIABLES, IncludeChildren, NodeBase } from './shared/Node'; +import { ExpressionNode, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; import SpreadElement from './SpreadElement'; @@ -122,16 +122,13 @@ export default class Identifier extends NodeBase implements PatternNode { return !this.variable || this.variable.hasEffectsWhenCalledAtPath(path, callOptions, options); } - include(includeChildrenRecursively: IncludeChildren) { + include() { if (!this.included) { this.included = true; if (this.variable !== null) { this.context.includeVariable(this.variable); } } - if (includeChildrenRecursively === INCLUDE_VARIABLES && this.variable) { - this.variable.includeInitRecursively(); - } } includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { diff --git a/src/ast/scopes/ParameterScope.ts b/src/ast/scopes/ParameterScope.ts index 07ed8a2b97d..e6df51c1260 100644 --- a/src/ast/scopes/ParameterScope.ts +++ b/src/ast/scopes/ParameterScope.ts @@ -1,6 +1,6 @@ import { AstContext } from '../../Module'; import Identifier from '../nodes/Identifier'; -import { ExpressionNode, INCLUDE_VARIABLES } from '../nodes/shared/Node'; +import { ExpressionNode } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; import { UNKNOWN_EXPRESSION } from '../values'; import LocalVariable from '../variables/LocalVariable'; @@ -54,9 +54,12 @@ export default class ParameterScope extends ChildScope { break; } } - arg.include(hasInitBeenForceIncluded ? INCLUDE_VARIABLES : false); - } else if (this.hasRest || arg.shouldBeIncluded()) { - arg.include(hasInitBeenForceIncluded ? INCLUDE_VARIABLES : false); + } + if (paramVars || this.hasRest || arg.shouldBeIncluded()) { + arg.include(hasInitBeenForceIncluded); + if (hasInitBeenForceIncluded && arg instanceof Identifier && arg.variable) { + arg.variable.includeInitRecursively(); + } } } } diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 5ac6089acb7..b9453112fc8 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -7,7 +7,7 @@ import ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; import { ExpressionEntity } from '../nodes/shared/Expression'; -import { ExpressionNode, INCLUDE_VARIABLES, Node } from '../nodes/shared/Node'; +import { ExpressionNode, Node } from '../nodes/shared/Node'; import SpreadElement from '../nodes/SpreadElement'; import { EntityPathTracker } from '../utils/EntityPathTracker'; import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker'; @@ -207,7 +207,7 @@ export default class LocalVariable extends Variable { if (!this.hasInitBeenForceIncluded) { this.hasInitBeenForceIncluded = true; if (this.init && this.init !== UNKNOWN_EXPRESSION) { - this.init.include(INCLUDE_VARIABLES); + this.init.include(true); } } } diff --git a/test/form/samples/try-statement-deoptimization/follow-parameters/_config.js b/test/form/samples/try-statement-deoptimization/follow-parameters/_config.js index ccffd84ff52..4093ba3d54e 100644 --- a/test/form/samples/try-statement-deoptimization/follow-parameters/_config.js +++ b/test/form/samples/try-statement-deoptimization/follow-parameters/_config.js @@ -1,8 +1,3 @@ module.exports = { description: 'retains side-effect-free code in parameters called from try-statement-blocks' }; - -// TODO Lukas -// * limit following depth to one level -// * only follow direct identifier call expressions -// * add core-js test diff --git a/test/form/samples/try-statement-deoptimization/follow-parameters/_expected.js b/test/form/samples/try-statement-deoptimization/follow-parameters/_expected.js index a7882831013..6a420fb59e4 100644 --- a/test/form/samples/try-statement-deoptimization/follow-parameters/_expected.js +++ b/test/form/samples/try-statement-deoptimization/follow-parameters/_expected.js @@ -1,8 +1,12 @@ -function callGlobal1() { +function callGlobalRemoved1() { } -function callGlobal2() { +function callGlobalRemoved2() { +} + +function callGlobalRetained() { Object.create(null); + callGlobalRemoved1(); } function tryIt(other, callback) { @@ -11,4 +15,12 @@ function tryIt(other, callback) { } catch {} } -tryIt(callGlobal1, callGlobal2); +tryIt(callGlobalRemoved2, callGlobalRetained); + +tryIt( + () => { + }, + () => { + Object.create(null); + } +); diff --git a/test/form/samples/try-statement-deoptimization/follow-parameters/main.js b/test/form/samples/try-statement-deoptimization/follow-parameters/main.js index daeec4ef1bf..5d27061fb90 100644 --- a/test/form/samples/try-statement-deoptimization/follow-parameters/main.js +++ b/test/form/samples/try-statement-deoptimization/follow-parameters/main.js @@ -1,9 +1,20 @@ -function callGlobal1() { +function callGlobalTreeshaken() { Object.create(null); } -function callGlobal2() { +function callGlobalRemoved1() { Object.create(null); + callGlobalTreeshaken(); +} + +function callGlobalRemoved2() { + Object.create(null); + callGlobalTreeshaken(); +} + +function callGlobalRetained() { + Object.create(null); + callGlobalRemoved1(); } function tryIt(other, callback) { @@ -12,4 +23,13 @@ function tryIt(other, callback) { } catch {} } -tryIt(callGlobal1, callGlobal2); +tryIt(callGlobalRemoved2, callGlobalRetained); + +tryIt( + () => { + Object.create(null); + }, + () => { + Object.create(null); + } +); diff --git a/test/form/samples/try-statement-deoptimization/follow-variables/_config.js b/test/form/samples/try-statement-deoptimization/follow-variables/_config.js index c73f23dda97..cc0106abef2 100644 --- a/test/form/samples/try-statement-deoptimization/follow-variables/_config.js +++ b/test/form/samples/try-statement-deoptimization/follow-variables/_config.js @@ -2,3 +2,4 @@ module.exports = { description: 'retains side-effect-free code in functions called from try-statement-blocks' }; // TODO Lukas also spread operator, namespace member, arguments variable +// Also tree-shake unused parameters diff --git a/test/form/samples/try-statement-deoptimization/follow-variables/_expected.js b/test/form/samples/try-statement-deoptimization/follow-variables/_expected.js index 95e7b463ae1..1310d726a07 100644 --- a/test/form/samples/try-statement-deoptimization/follow-variables/_expected.js +++ b/test/form/samples/try-statement-deoptimization/follow-variables/_expected.js @@ -1,7 +1,11 @@ -function callGlobal() { +function callGlobalRemoved() { +} + +function callGlobalRetained() { Object.create(null); + callGlobalRemoved(); } try { - callGlobal(); + callGlobalRetained(); } catch {} diff --git a/test/form/samples/try-statement-deoptimization/follow-variables/main.js b/test/form/samples/try-statement-deoptimization/follow-variables/main.js index 95e7b463ae1..84773829f78 100644 --- a/test/form/samples/try-statement-deoptimization/follow-variables/main.js +++ b/test/form/samples/try-statement-deoptimization/follow-variables/main.js @@ -1,7 +1,17 @@ -function callGlobal() { +function callGlobalTreeshaken() { Object.create(null); } +function callGlobalRemoved() { + Object.create(null); + callGlobalTreeshaken(); +} + +function callGlobalRetained() { + Object.create(null); + callGlobalRemoved(); +} + try { - callGlobal(); + callGlobalRetained(); } catch {} diff --git a/test/form/samples/try-statement-deoptimization/supports-core-js/_config.js b/test/form/samples/try-statement-deoptimization/supports-core-js/_config.js new file mode 100644 index 00000000000..ee05dfb0bf9 --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/supports-core-js/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'supports core-js feature detection (#2869)' +}; diff --git a/test/form/samples/try-statement-deoptimization/supports-core-js/_expected.js b/test/form/samples/try-statement-deoptimization/supports-core-js/_expected.js new file mode 100644 index 00000000000..dcff6f32a7a --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/supports-core-js/_expected.js @@ -0,0 +1,18 @@ +function fails(exec) { + try { + return !!exec(); + } catch (e) { + return true; + } +} + +var MAX_UINT32 = 0xffffffff; +var SUPPORTS_Y = !fails(function() { + RegExp(MAX_UINT32, 'y'); +}); + +if (SUPPORTS_Y) { + console.log('yes'); +} else { + console.log('no'); +} diff --git a/test/form/samples/try-statement-deoptimization/supports-core-js/main.js b/test/form/samples/try-statement-deoptimization/supports-core-js/main.js new file mode 100644 index 00000000000..dcff6f32a7a --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/supports-core-js/main.js @@ -0,0 +1,18 @@ +function fails(exec) { + try { + return !!exec(); + } catch (e) { + return true; + } +} + +var MAX_UINT32 = 0xffffffff; +var SUPPORTS_Y = !fails(function() { + RegExp(MAX_UINT32, 'y'); +}); + +if (SUPPORTS_Y) { + console.log('yes'); +} else { + console.log('no'); +} From b288b6600f462306c7728a67a8ba8755db87f7c4 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Tue, 4 Jun 2019 08:32:38 +0200 Subject: [PATCH 6/8] Also tree-shake namespace members --- src/ast/nodes/MemberExpression.ts | 9 ++++++++ .../arguments-variable/_config.js | 0 .../arguments-variable/_expected.js | 0 .../arguments-variable/main.js | 0 .../arrow-function-arguments/_config.js | 0 .../arrow-function-arguments/_expected.js | 0 .../arrow-function-arguments/main.js | 0 .../function-arguments/_config.js | 0 .../function-arguments/_expected.js | 0 .../function-arguments/main.js | 0 .../namespace-members/_config.js | 3 +++ .../namespace-members/_expected.js | 10 +++++++++ .../namespace-members/main.js | 13 +++++++++++ .../namespace-members/namespace.js | 2 ++ .../patterns/_config.js | 0 .../patterns/_expected.js | 0 .../patterns/main.js | 0 .../rest-element/_config.js | 0 .../rest-element/_expected.js | 0 .../rest-element/main.js | 0 .../follow-pattern-parameters/_config.js | 4 ++++ .../follow-pattern-parameters/_expected.js | 16 ++++++++++++++ .../follow-pattern-parameters/main.js | 22 +++++++++++++++++++ .../follow-variables/_config.js | 3 +-- 24 files changed, 80 insertions(+), 2 deletions(-) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/arguments-variable/_config.js (100%) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/arguments-variable/_expected.js (100%) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/arguments-variable/main.js (100%) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/arrow-function-arguments/_config.js (100%) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/arrow-function-arguments/_expected.js (100%) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/arrow-function-arguments/main.js (100%) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/function-arguments/_config.js (100%) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/function-arguments/_expected.js (100%) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/function-arguments/main.js (100%) create mode 100644 test/form/samples/treeshake-excess-arguments/namespace-members/_config.js create mode 100644 test/form/samples/treeshake-excess-arguments/namespace-members/_expected.js create mode 100644 test/form/samples/treeshake-excess-arguments/namespace-members/main.js create mode 100644 test/form/samples/treeshake-excess-arguments/namespace-members/namespace.js rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/patterns/_config.js (100%) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/patterns/_expected.js (100%) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/patterns/main.js (100%) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/rest-element/_config.js (100%) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/rest-element/_expected.js (100%) rename test/form/samples/{treeshake-excessive-arguments => treeshake-excess-arguments}/rest-element/main.js (100%) create mode 100644 test/form/samples/try-statement-deoptimization/follow-pattern-parameters/_config.js create mode 100644 test/form/samples/try-statement-deoptimization/follow-pattern-parameters/_expected.js create mode 100644 test/form/samples/try-statement-deoptimization/follow-pattern-parameters/main.js diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index ffc6d9a033d..85e73f4b761 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -25,6 +25,7 @@ import Literal from './Literal'; import * as NodeType from './NodeType'; import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; +import SpreadElement from './SpreadElement'; function getResolvablePropertyKey(memberExpression: MemberExpression): string | null { return memberExpression.computed @@ -222,6 +223,14 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE this.property.include(includeChildrenRecursively); } + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { + if (this.variable) { + this.variable.includeCallArguments(args); + } else { + super.includeCallArguments(args); + } + } + initialise() { this.propertyKey = getResolvablePropertyKey(this); } diff --git a/test/form/samples/treeshake-excessive-arguments/arguments-variable/_config.js b/test/form/samples/treeshake-excess-arguments/arguments-variable/_config.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/arguments-variable/_config.js rename to test/form/samples/treeshake-excess-arguments/arguments-variable/_config.js diff --git a/test/form/samples/treeshake-excessive-arguments/arguments-variable/_expected.js b/test/form/samples/treeshake-excess-arguments/arguments-variable/_expected.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/arguments-variable/_expected.js rename to test/form/samples/treeshake-excess-arguments/arguments-variable/_expected.js diff --git a/test/form/samples/treeshake-excessive-arguments/arguments-variable/main.js b/test/form/samples/treeshake-excess-arguments/arguments-variable/main.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/arguments-variable/main.js rename to test/form/samples/treeshake-excess-arguments/arguments-variable/main.js diff --git a/test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/_config.js b/test/form/samples/treeshake-excess-arguments/arrow-function-arguments/_config.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/_config.js rename to test/form/samples/treeshake-excess-arguments/arrow-function-arguments/_config.js diff --git a/test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/_expected.js b/test/form/samples/treeshake-excess-arguments/arrow-function-arguments/_expected.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/_expected.js rename to test/form/samples/treeshake-excess-arguments/arrow-function-arguments/_expected.js diff --git a/test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/main.js b/test/form/samples/treeshake-excess-arguments/arrow-function-arguments/main.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/arrow-function-arguments/main.js rename to test/form/samples/treeshake-excess-arguments/arrow-function-arguments/main.js diff --git a/test/form/samples/treeshake-excessive-arguments/function-arguments/_config.js b/test/form/samples/treeshake-excess-arguments/function-arguments/_config.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/function-arguments/_config.js rename to test/form/samples/treeshake-excess-arguments/function-arguments/_config.js diff --git a/test/form/samples/treeshake-excessive-arguments/function-arguments/_expected.js b/test/form/samples/treeshake-excess-arguments/function-arguments/_expected.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/function-arguments/_expected.js rename to test/form/samples/treeshake-excess-arguments/function-arguments/_expected.js diff --git a/test/form/samples/treeshake-excessive-arguments/function-arguments/main.js b/test/form/samples/treeshake-excess-arguments/function-arguments/main.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/function-arguments/main.js rename to test/form/samples/treeshake-excess-arguments/function-arguments/main.js diff --git a/test/form/samples/treeshake-excess-arguments/namespace-members/_config.js b/test/form/samples/treeshake-excess-arguments/namespace-members/_config.js new file mode 100644 index 00000000000..1f3e70ad8c1 --- /dev/null +++ b/test/form/samples/treeshake-excess-arguments/namespace-members/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes unneeded arguments across namespace member calls' +}; diff --git a/test/form/samples/treeshake-excess-arguments/namespace-members/_expected.js b/test/form/samples/treeshake-excess-arguments/namespace-members/_expected.js new file mode 100644 index 00000000000..5533e3d376c --- /dev/null +++ b/test/form/samples/treeshake-excess-arguments/namespace-members/_expected.js @@ -0,0 +1,10 @@ +const noParams = () => console.log(); +const someUsedParams = (p1, p2, p3) => console.log(p1, p3); + +noParams(); + +const needed21 = 1; +const needed22 = 2; +const needed23 = 3; + +someUsedParams(needed21, needed22, needed23); diff --git a/test/form/samples/treeshake-excess-arguments/namespace-members/main.js b/test/form/samples/treeshake-excess-arguments/namespace-members/main.js new file mode 100644 index 00000000000..e6e97744ae7 --- /dev/null +++ b/test/form/samples/treeshake-excess-arguments/namespace-members/main.js @@ -0,0 +1,13 @@ +import * as ns from './namespace'; + +const unneeded1 = 1; + +ns.noParams(unneeded1); + +const unneeded2 = 1; + +const needed21 = 1; +const needed22 = 2; +const needed23 = 3; + +ns.someUsedParams(needed21, needed22, needed23, unneeded2); diff --git a/test/form/samples/treeshake-excess-arguments/namespace-members/namespace.js b/test/form/samples/treeshake-excess-arguments/namespace-members/namespace.js new file mode 100644 index 00000000000..4d1907a1dbe --- /dev/null +++ b/test/form/samples/treeshake-excess-arguments/namespace-members/namespace.js @@ -0,0 +1,2 @@ +export const noParams = () => console.log(); +export const someUsedParams = (p1, p2, p3) => console.log(p1, p3); diff --git a/test/form/samples/treeshake-excessive-arguments/patterns/_config.js b/test/form/samples/treeshake-excess-arguments/patterns/_config.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/patterns/_config.js rename to test/form/samples/treeshake-excess-arguments/patterns/_config.js diff --git a/test/form/samples/treeshake-excessive-arguments/patterns/_expected.js b/test/form/samples/treeshake-excess-arguments/patterns/_expected.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/patterns/_expected.js rename to test/form/samples/treeshake-excess-arguments/patterns/_expected.js diff --git a/test/form/samples/treeshake-excessive-arguments/patterns/main.js b/test/form/samples/treeshake-excess-arguments/patterns/main.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/patterns/main.js rename to test/form/samples/treeshake-excess-arguments/patterns/main.js diff --git a/test/form/samples/treeshake-excessive-arguments/rest-element/_config.js b/test/form/samples/treeshake-excess-arguments/rest-element/_config.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/rest-element/_config.js rename to test/form/samples/treeshake-excess-arguments/rest-element/_config.js diff --git a/test/form/samples/treeshake-excessive-arguments/rest-element/_expected.js b/test/form/samples/treeshake-excess-arguments/rest-element/_expected.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/rest-element/_expected.js rename to test/form/samples/treeshake-excess-arguments/rest-element/_expected.js diff --git a/test/form/samples/treeshake-excessive-arguments/rest-element/main.js b/test/form/samples/treeshake-excess-arguments/rest-element/main.js similarity index 100% rename from test/form/samples/treeshake-excessive-arguments/rest-element/main.js rename to test/form/samples/treeshake-excess-arguments/rest-element/main.js diff --git a/test/form/samples/try-statement-deoptimization/follow-pattern-parameters/_config.js b/test/form/samples/try-statement-deoptimization/follow-pattern-parameters/_config.js new file mode 100644 index 00000000000..6ce753ad006 --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/follow-pattern-parameters/_config.js @@ -0,0 +1,4 @@ +module.exports = { + description: + 'retains side-effect-free code in pattern parameters called from try-statement-blocks' +}; diff --git a/test/form/samples/try-statement-deoptimization/follow-pattern-parameters/_expected.js b/test/form/samples/try-statement-deoptimization/follow-pattern-parameters/_expected.js new file mode 100644 index 00000000000..b60287c802c --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/follow-pattern-parameters/_expected.js @@ -0,0 +1,16 @@ +function callGlobalRetained() { +} + +function tryIt({ callback }) { + try { + callback(); + } catch {} +} + +tryIt({ callback: callGlobalRetained }); + +tryIt({ + callback: () => { + Object.create(null); + } +}); diff --git a/test/form/samples/try-statement-deoptimization/follow-pattern-parameters/main.js b/test/form/samples/try-statement-deoptimization/follow-pattern-parameters/main.js new file mode 100644 index 00000000000..00360093661 --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/follow-pattern-parameters/main.js @@ -0,0 +1,22 @@ +function callGlobalRemoved() { + Object.create(null); +} + +function callGlobalRetained() { + Object.create(null); + callGlobalRemoved(); +} + +function tryIt({ callback }) { + try { + callback(); + } catch {} +} + +tryIt({ callback: callGlobalRetained }); + +tryIt({ + callback: () => { + Object.create(null); + } +}); diff --git a/test/form/samples/try-statement-deoptimization/follow-variables/_config.js b/test/form/samples/try-statement-deoptimization/follow-variables/_config.js index cc0106abef2..48f943d4bcb 100644 --- a/test/form/samples/try-statement-deoptimization/follow-variables/_config.js +++ b/test/form/samples/try-statement-deoptimization/follow-variables/_config.js @@ -1,5 +1,4 @@ module.exports = { description: 'retains side-effect-free code in functions called from try-statement-blocks' }; -// TODO Lukas also spread operator, namespace member, arguments variable -// Also tree-shake unused parameters +// TODO Lukas Also tree-shake unused parameters From 095a8796cdaa22426aae823a11157413568f3fdf Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Tue, 4 Jun 2019 09:15:44 +0200 Subject: [PATCH 7/8] Also tree-shake arguments corresponding to unused parameters --- src/ast/nodes/ArrowFunctionExpression.ts | 11 +++++++++++ src/ast/nodes/shared/FunctionNode.ts | 16 +++++++++++++++- src/ast/scopes/ParameterScope.ts | 14 ++++++++++---- .../unused-parameters/_config.js | 3 +++ .../unused-parameters/_expected.js | 11 +++++++++++ .../unused-parameters/main.js | 15 +++++++++++++++ .../follow-variables/_config.js | 1 - 7 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 test/form/samples/treeshake-excess-arguments/unused-parameters/_config.js create mode 100644 test/form/samples/treeshake-excess-arguments/unused-parameters/_expected.js create mode 100644 test/form/samples/treeshake-excess-arguments/unused-parameters/main.js diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index 3ca05e96b63..0aac311d6ec 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -4,6 +4,7 @@ import ReturnValueScope from '../scopes/ReturnValueScope'; import Scope from '../scopes/Scope'; import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY, UNKNOWN_PATH } from '../values'; import BlockStatement from './BlockStatement'; +import Identifier from './Identifier'; import * as NodeType from './NodeType'; import RestElement from './RestElement'; import { ExpressionNode, GenericEsTreeNode, NodeBase } from './shared/Node'; @@ -59,6 +60,16 @@ export default class ArrowFunctionExpression extends NodeBase { return this.body.hasEffects(options); } + include(includeChildrenRecursively: boolean | 'variables') { + this.included = true; + this.body.include(includeChildrenRecursively); + for (const param of this.params) { + if (!(param instanceof Identifier)) { + param.include(includeChildrenRecursively); + } + } + } + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { this.scope.includeCallArguments(args); } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index a5bde934d3c..523303450f9 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -3,7 +3,7 @@ import { ExecutionPathOptions } from '../../ExecutionPathOptions'; import FunctionScope from '../../scopes/FunctionScope'; import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY, UNKNOWN_PATH } from '../../values'; import BlockStatement from '../BlockStatement'; -import { IdentifierWithVariable } from '../Identifier'; +import Identifier, { IdentifierWithVariable } from '../Identifier'; import RestElement from '../RestElement'; import SpreadElement from '../SpreadElement'; import { ExpressionNode, GenericEsTreeNode, NodeBase } from './Node'; @@ -74,6 +74,20 @@ export default class FunctionNode extends NodeBase { return this.body.hasEffects(innerOptions); } + include(includeChildrenRecursively: boolean | 'variables') { + this.included = true; + this.body.include(includeChildrenRecursively); + if (this.id) { + this.id.include(); + } + const hasArguments = this.scope.argumentsVariable.included; + for (const param of this.params) { + if (!(param instanceof Identifier) || hasArguments) { + param.include(includeChildrenRecursively); + } + } + } + includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { this.scope.includeCallArguments(args); } diff --git a/src/ast/scopes/ParameterScope.ts b/src/ast/scopes/ParameterScope.ts index e6df51c1260..98f15d2a02a 100644 --- a/src/ast/scopes/ParameterScope.ts +++ b/src/ast/scopes/ParameterScope.ts @@ -43,19 +43,25 @@ export default class ParameterScope extends ChildScope { includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void { let hasInitBeenForceIncluded = false; - for (let index = 0; index < args.length; index++) { - const paramVars = this.parameters[index]; + let argIncluded = false; + const restParam = this.hasRest && this.parameters[this.parameters.length - 1]; + for (let index = args.length - 1; index >= 0; index--) { + const paramVars = this.parameters[index] || restParam; const arg = args[index]; if (paramVars) { hasInitBeenForceIncluded = false; for (const variable of paramVars) { + if (variable.included) { + argIncluded = true; + } if (variable.hasInitBeenForceIncluded) { hasInitBeenForceIncluded = true; - break; } } + } else if (!argIncluded && arg.shouldBeIncluded()) { + argIncluded = true; } - if (paramVars || this.hasRest || arg.shouldBeIncluded()) { + if (argIncluded) { arg.include(hasInitBeenForceIncluded); if (hasInitBeenForceIncluded && arg instanceof Identifier && arg.variable) { arg.variable.includeInitRecursively(); diff --git a/test/form/samples/treeshake-excess-arguments/unused-parameters/_config.js b/test/form/samples/treeshake-excess-arguments/unused-parameters/_config.js new file mode 100644 index 00000000000..ebe92e0065f --- /dev/null +++ b/test/form/samples/treeshake-excess-arguments/unused-parameters/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'removes arguments that correspond to unused parameters' +}; diff --git a/test/form/samples/treeshake-excess-arguments/unused-parameters/_expected.js b/test/form/samples/treeshake-excess-arguments/unused-parameters/_expected.js new file mode 100644 index 00000000000..74123e6973b --- /dev/null +++ b/test/form/samples/treeshake-excess-arguments/unused-parameters/_expected.js @@ -0,0 +1,11 @@ +function test(a, b = global(), c) {} +test(1, 2); + +function noEffect() {} +test(1, 2, noEffect(), global()); + +const testArr = (a, b = global(), c) => {}; +testArr(1, 2); + +function noEffectArr() {} +testArr(1, 2, noEffectArr(), global()); diff --git a/test/form/samples/treeshake-excess-arguments/unused-parameters/main.js b/test/form/samples/treeshake-excess-arguments/unused-parameters/main.js new file mode 100644 index 00000000000..43ad6a6100f --- /dev/null +++ b/test/form/samples/treeshake-excess-arguments/unused-parameters/main.js @@ -0,0 +1,15 @@ +function test(a, b = global(), c) {} + +const someStuff = {x: 1}; +test(1, 2, 3, someStuff); + +function noEffect() {} +test(1, 2, noEffect(), global()); + +const testArr = (a, b = global(), c) => {} + +const someStuffArr = {x: 1}; +testArr(1, 2, 3, someStuffArr); + +function noEffectArr() {} +testArr(1, 2, noEffectArr(), global()); diff --git a/test/form/samples/try-statement-deoptimization/follow-variables/_config.js b/test/form/samples/try-statement-deoptimization/follow-variables/_config.js index 48f943d4bcb..f9aca82d54c 100644 --- a/test/form/samples/try-statement-deoptimization/follow-variables/_config.js +++ b/test/form/samples/try-statement-deoptimization/follow-variables/_config.js @@ -1,4 +1,3 @@ module.exports = { description: 'retains side-effect-free code in functions called from try-statement-blocks' }; -// TODO Lukas Also tree-shake unused parameters From 4dde54d89fe84ed6486128757617900609aa26d3 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Wed, 5 Jun 2019 09:13:58 +0200 Subject: [PATCH 8/8] Add option to deactivate the try statement deoptimization --- docs/999-big-list-of-options.md | 50 +++++++++++++++++++ src/Graph.ts | 7 ++- src/Module.ts | 3 ++ src/ast/nodes/TryStatement.ts | 4 +- src/rollup/types.d.ts | 1 + .../deactivate-via-option/_config.js | 8 +++ .../deactivate-via-option/_expected.js | 8 +++ .../deactivate-via-option/main.js | 15 ++++++ 8 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 test/form/samples/try-statement-deoptimization/deactivate-via-option/_config.js create mode 100644 test/form/samples/try-statement-deoptimization/deactivate-via-option/_expected.js create mode 100644 test/form/samples/try-statement-deoptimization/deactivate-via-option/main.js diff --git a/docs/999-big-list-of-options.md b/docs/999-big-list-of-options.md index eddc971bb6f..ae072d45189 100755 --- a/docs/999-big-list-of-options.md +++ b/docs/999-big-list-of-options.md @@ -825,6 +825,56 @@ const result = foo.bar; const illegalAccess = foo.quux.tooDeep; ``` +**treeshake.tryCatchDeoptimization** +Type: `boolean`
+CLI: `--treeshake.tryCatchDeoptimization`/`--no-treeshake.tryCatchDeoptimization`
+Default: `true` + +By default, Rollup assumes that many builtin globals of the runtime behave according to the latest specs when tree-shaking and do not throw unexpected errors. In order to support e.g. feature detection workflows that rely on those errors being thrown, Rollup will by default deactivate tree-shaking inside try-statements. Furthermore, it will also deactivate tree-shaking inside functions that are called directly from a try-statement if Rollup can resolve the function. Set `treeshake.tryCatchDeoptimization` to `false` if you do not need this feature and want to have tree-shaking inside try-statements as well as inside functions called from those statements. + +```js +function directlyCalled1() { + // as this function is directly called from a try-statement, it will be + // retained unmodified for tryCatchDeoptimization: true including staements + // that would usually be removed + Object.create(null); + notDirectlyCalled(); +} + +function directlyCalled2() { + Object.create(null); + notDirectlyCalled(); +} + +function notDirectlyCalled() { + // even if this function is retained, this will be removed as the function is + // never directly called from a try-statement + Object.create(null); +} + +function test(callback) { + try { + // calls to otherwise side-effect-free global functions are retained + // inside try-statements for tryCatchDeoptimization: true + Object.create(null); + + // directly resolvable calls will also be deoptimized + directlyCalled1(); + + // if a parameter is called, then all arguments passed to that function + // parameter will be deoptimized + callback(); + + // all calls will be retained but only calls of the form + // "identifier(someArguments)" will also deoptimize the target + (notDirectlyCalled && notDirectlyCalled)(); + } catch {} +} + +test(directlyCalled2); + +``` + ### Experimental options These options reflect new features that have not yet been fully finalized. Availability, behaviour and usage may therefore be subject to change between minor versions. diff --git a/src/Graph.ts b/src/Graph.ts index 0ee15e0fb5c..1576175fefe 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -117,13 +117,16 @@ export default class Graph { moduleSideEffects: (options.treeshake as TreeshakingOptions).moduleSideEffects, propertyReadSideEffects: (options.treeshake as TreeshakingOptions).propertyReadSideEffects !== false, - pureExternalModules: (options.treeshake as TreeshakingOptions).pureExternalModules + pureExternalModules: (options.treeshake as TreeshakingOptions).pureExternalModules, + tryCatchDeoptimization: + (options.treeshake as TreeshakingOptions).tryCatchDeoptimization !== false } : { annotations: true, moduleSideEffects: true, propertyReadSideEffects: true, - pureExternalModules: false + pureExternalModules: false, + tryCatchDeoptimization: true }; } diff --git a/src/Module.ts b/src/Module.ts index 73225650bd6..4736e1879c8 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -109,6 +109,7 @@ export interface AstContext { traceExport: (name: string) => Variable; traceVariable: (name: string) => Variable | null; treeshake: boolean; + tryCatchDeoptimization: boolean; usesTopLevelAwait: boolean; warn: (warning: RollupWarning, pos: number) => void; } @@ -589,6 +590,8 @@ export default class Module { traceExport: this.getVariableForExportName.bind(this), traceVariable: this.traceVariable.bind(this), treeshake: !!this.graph.treeshakingOptions, + tryCatchDeoptimization: (!this.graph.treeshakingOptions || + this.graph.treeshakingOptions.tryCatchDeoptimization) as boolean, usesTopLevelAwait: false, warn: this.warn.bind(this) }; diff --git a/src/ast/nodes/TryStatement.ts b/src/ast/nodes/TryStatement.ts index 4cf74c1aced..ce9b156f6b0 100644 --- a/src/ast/nodes/TryStatement.ts +++ b/src/ast/nodes/TryStatement.ts @@ -21,7 +21,9 @@ export default class TryStatement extends StatementBase { include(includeChildrenRecursively: IncludeChildren) { if (!this.included) { this.included = true; - this.block.include(INCLUDE_VARIABLES); + this.block.include( + this.context.tryCatchDeoptimization ? INCLUDE_VARIABLES : includeChildrenRecursively + ); } if (this.handler !== null) { this.handler.include(includeChildrenRecursively); diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 4c5e9be8986..e352d7be88b 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -332,6 +332,7 @@ export interface TreeshakingOptions { propertyReadSideEffects?: boolean; /** @deprecated Use `moduleSideEffects` instead */ pureExternalModules?: PureModulesOption; + tryCatchDeoptimization?: boolean; } export type GetManualChunk = (id: string) => string | void; diff --git a/test/form/samples/try-statement-deoptimization/deactivate-via-option/_config.js b/test/form/samples/try-statement-deoptimization/deactivate-via-option/_config.js new file mode 100644 index 00000000000..51ef18c06d6 --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/deactivate-via-option/_config.js @@ -0,0 +1,8 @@ +module.exports = { + description: 'deactivates try-catch-deoptimization via option', + options: { + treeshake: { + tryCatchDeoptimization: false + } + } +}; diff --git a/test/form/samples/try-statement-deoptimization/deactivate-via-option/_expected.js b/test/form/samples/try-statement-deoptimization/deactivate-via-option/_expected.js new file mode 100644 index 00000000000..b6a9ec668aa --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/deactivate-via-option/_expected.js @@ -0,0 +1,8 @@ +function test(callback) { + try { + callback(); + } catch {} +} + +test(() => { +}); diff --git a/test/form/samples/try-statement-deoptimization/deactivate-via-option/main.js b/test/form/samples/try-statement-deoptimization/deactivate-via-option/main.js new file mode 100644 index 00000000000..e823e4a0503 --- /dev/null +++ b/test/form/samples/try-statement-deoptimization/deactivate-via-option/main.js @@ -0,0 +1,15 @@ +function callGlobal() { + Object.create(null); +} + +function test(callback) { + try { + Object.create(null); + callback(); + callGlobal(); + } catch {} +} + +test(() => { + Object.create(null); +});