Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement try-statement-deoptimization for feature detection, tree-shake unused arguments #2892

Merged
merged 8 commits into from Jun 5, 2019
2 changes: 1 addition & 1 deletion src/Module.ts
Expand Up @@ -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);
Expand Down
9 changes: 0 additions & 9 deletions src/ast/ExecutionPathOptions.ts
Expand Up @@ -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,
Expand Down Expand Up @@ -78,10 +77,6 @@ export class ExecutionPathOptions {
);
}

getArgumentsVariables(): ExpressionEntity[] {
return (this.get(OptionTypes.ARGUMENTS_VARIABLES) || []) as ExpressionEntity[];
}

getHasEffectsWhenCalledOptions() {
return this.setIgnoreReturnAwaitYield()
.setIgnoreBreakStatements(false)
Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 3 additions & 1 deletion src/ast/nodes/ArrayPattern.ts
Expand Up @@ -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) {
Expand Down
22 changes: 20 additions & 2 deletions src/ast/nodes/ArrowFunctionExpression.ts
Expand Up @@ -4,9 +4,12 @@ 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';
import { PatternNode } from './shared/Pattern';
import SpreadElement from './SpreadElement';

export default class ArrowFunctionExpression extends NodeBase {
body!: BlockStatement | ExpressionNode;
Expand Down Expand Up @@ -57,10 +60,25 @@ export default class ArrowFunctionExpression extends NodeBase {
return this.body.hasEffects(options);
}

initialise() {
include(includeChildrenRecursively: boolean | 'variables') {
this.included = true;
this.body.include(includeChildrenRecursively);
for (const param of this.params) {
param.declare('parameter', UNKNOWN_EXPRESSION);
if (!(param instanceof Identifier)) {
param.include(includeChildrenRecursively);
}
}
}

includeCallArguments(args: (ExpressionNode | SpreadElement)[]): void {
this.scope.includeCallArguments(args);
}

initialise() {
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 {
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/AssignmentPattern.ts
Expand Up @@ -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) {
Expand Down
8 changes: 4 additions & 4 deletions src/ast/nodes/AwaitExpression.ts
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down
8 changes: 4 additions & 4 deletions src/ast/nodes/BlockStatement.ts
Expand Up @@ -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[];
Expand All @@ -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);
}
}

Expand Down
50 changes: 45 additions & 5 deletions 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';
Expand All @@ -19,7 +23,7 @@ import {
import Identifier from './Identifier';
import * as NodeType from './NodeType';
import { ExpressionEntity } from './shared/Expression';
import { ExpressionNode, 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 {
Expand Down Expand Up @@ -196,8 +200,21 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt
);
}

include(includeAllChildrenRecursively: boolean) {
super.include(includeAllChildrenRecursively);
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);
}
this.callee.includeCallArguments(this.arguments);
if (!(this.returnExpression as ExpressionEntity).included) {
(this.returnExpression as ExpressionEntity).include(false);
}
Expand All @@ -216,7 +233,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
Expand Down
14 changes: 7 additions & 7 deletions src/ast/nodes/ConditionalExpression.ts
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/ast/nodes/ExportDefaultDeclaration.ts
Expand Up @@ -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/;

Expand Down Expand Up @@ -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);
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/ast/nodes/ForInStatement.ts
Expand Up @@ -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';

Expand Down Expand Up @@ -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) {
Expand Down
10 changes: 5 additions & 5 deletions src/ast/nodes/ForOfStatement.ts
Expand Up @@ -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';

Expand Down Expand Up @@ -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) {
Expand Down
19 changes: 14 additions & 5 deletions src/ast/nodes/Identifier.ts
Expand Up @@ -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 { NodeBase } from './shared/Node';
import { ExpressionNode, NodeBase } from './shared/Node';
import { PatternNode } from './shared/Pattern';
import SpreadElement from './SpreadElement';

export type IdentifierWithVariable = Identifier & { variable: Variable };

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -119,7 +122,7 @@ export default class Identifier extends NodeBase implements PatternNode {
return !this.variable || this.variable.hasEffectsWhenCalledAtPath(path, callOptions, options);
}

include(_includeAllChildrenRecursively: boolean) {
include() {
if (!this.included) {
this.included = true;
if (this.variable !== null) {
Expand All @@ -128,6 +131,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,
Expand Down