Skip to content

Commit 4624917

Browse files
authored
Tree shake parameter defaults (#4498)
* Implement basic support for parameter default tree-shaking * Refine parameter default tree-shaking * Use an inclusion parameter instead of separate methods to handle this * Make call argument inclusion path aware so that in the future, we can use this mechanism to determine possible values for parameters for deoptimization * Support parameter default treeshaking for functions, object and class methods, functions in array tuples and template tags * Use full CallExpression logic for TaggedTemplateExpression * Use optional chaining instead of null checks * Always call applyDeoptimizations both in hasEffects and include * Add more tests * Replace includeAsStatement with parameter * Revert previous because it is handled via NodeBase * Ensure properties are deoptimized by default * Track calls through possible TDZ violations * Improve coverage * Remove defaults without side effects for unused parameters * Check for unknown values via typeof to prepare for different types of unknown * Remove defaults when passing non-literal values * Deoptimize this for tagged template expression and improve coverage
1 parent 05d294f commit 4624917

104 files changed

Lines changed: 1112 additions & 611 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

rollup.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import commonjs from '@rollup/plugin-commonjs';
66
import json from '@rollup/plugin-json';
77
import { nodeResolve } from '@rollup/plugin-node-resolve';
88
import typescript from '@rollup/plugin-typescript';
9-
import type { RollupOptions, WarningHandlerWithDefault } from 'rollup';
9+
import type { Plugin, RollupOptions, WarningHandlerWithDefault } from 'rollup';
1010
import { string } from 'rollup-plugin-string';
1111
import { terser } from 'rollup-plugin-terser';
1212
import addCliEntry from './build-plugins/add-cli-entry';
@@ -65,7 +65,7 @@ const treeshake = {
6565
tryCatchDeoptimization: false
6666
};
6767

68-
const nodePlugins = [
68+
const nodePlugins: Plugin[] = [
6969
alias(moduleAliases),
7070
nodeResolve(),
7171
json(),

src/ast/nodes/ArrayExpression.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import type { CallOptions } from '../CallOptions';
22
import type { DeoptimizableEntity } from '../DeoptimizableEntity';
33
import type { HasEffectsContext } from '../ExecutionContext';
4+
import { InclusionContext } from '../ExecutionContext';
45
import type { NodeEvent } from '../NodeEvents';
5-
import { type ObjectPath, type PathTracker, UnknownInteger } from '../utils/PathTracker';
6+
import {
7+
type ObjectPath,
8+
type PathTracker,
9+
UNKNOWN_PATH,
10+
UnknownInteger
11+
} from '../utils/PathTracker';
612
import { UNDEFINED_EXPRESSION, UNKNOWN_LITERAL_NUMBER } from '../values';
713
import type * as NodeType from './NodeType';
814
import SpreadElement from './SpreadElement';
@@ -14,6 +20,7 @@ import { ObjectEntity, type ObjectProperty } from './shared/ObjectEntity';
1420
export default class ArrayExpression extends NodeBase {
1521
declare elements: readonly (ExpressionNode | SpreadElement | null)[];
1622
declare type: NodeType.tArrayExpression;
23+
protected deoptimized = false;
1724
private objectEntity: ObjectEntity | null = null;
1825

1926
deoptimizePath(path: ObjectPath): void {
@@ -56,7 +63,7 @@ export default class ArrayExpression extends NodeBase {
5663
);
5764
}
5865

59-
hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean {
66+
hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined {
6067
return this.getObjectEntity().hasEffectsWhenAccessedAtPath(path, context);
6168
}
6269

@@ -72,6 +79,29 @@ export default class ArrayExpression extends NodeBase {
7279
return this.getObjectEntity().hasEffectsWhenCalledAtPath(path, callOptions, context);
7380
}
7481

82+
includeArgumentsWhenCalledAtPath(
83+
path: ObjectPath,
84+
context: InclusionContext,
85+
args: readonly (ExpressionEntity | SpreadElement)[]
86+
) {
87+
this.getObjectEntity().includeArgumentsWhenCalledAtPath(path, context, args);
88+
}
89+
90+
protected applyDeoptimizations(): void {
91+
this.deoptimized = true;
92+
let hasSpread = false;
93+
for (let index = 0; index < this.elements.length; index++) {
94+
const element = this.elements[index];
95+
if (hasSpread || element instanceof SpreadElement) {
96+
if (element) {
97+
hasSpread = true;
98+
element.deoptimizePath(UNKNOWN_PATH);
99+
}
100+
}
101+
}
102+
this.context.requestTreeshakingPass();
103+
}
104+
75105
private getObjectEntity(): ObjectEntity {
76106
if (this.objectEntity !== null) {
77107
return this.objectEntity;
@@ -82,7 +112,7 @@ export default class ArrayExpression extends NodeBase {
82112
let hasSpread = false;
83113
for (let index = 0; index < this.elements.length; index++) {
84114
const element = this.elements[index];
85-
if (element instanceof SpreadElement || hasSpread) {
115+
if (hasSpread || element instanceof SpreadElement) {
86116
if (element) {
87117
hasSpread = true;
88118
properties.unshift({ key: UnknownInteger, kind: 'init', property: element });

src/ast/nodes/ArrayPattern.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ export default class ArrayPattern extends NodeBase implements PatternNode {
1616
exportNamesByVariable: ReadonlyMap<Variable, readonly string[]>
1717
): void {
1818
for (const element of this.elements) {
19-
if (element !== null) {
20-
element.addExportedVariables(variables, exportNamesByVariable);
21-
}
19+
element?.addExportedVariables(variables, exportNamesByVariable);
2220
}
2321
}
2422

@@ -32,30 +30,24 @@ export default class ArrayPattern extends NodeBase implements PatternNode {
3230
return variables;
3331
}
3432

35-
deoptimizePath(path: ObjectPath): void {
36-
if (path.length === 0) {
37-
for (const element of this.elements) {
38-
if (element !== null) {
39-
element.deoptimizePath(path);
40-
}
41-
}
33+
// Patterns can only be deoptimized at the empty path at the moment
34+
deoptimizePath(): void {
35+
for (const element of this.elements) {
36+
element?.deoptimizePath(EMPTY_PATH);
4237
}
4338
}
4439

45-
hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean {
46-
if (path.length > 0) return true;
40+
// Patterns are only checked at the emtpy path at the moment
41+
hasEffectsWhenAssignedAtPath(_path: ObjectPath, context: HasEffectsContext): boolean {
4742
for (const element of this.elements) {
48-
if (element !== null && element.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context))
49-
return true;
43+
if (element?.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) return true;
5044
}
5145
return false;
5246
}
5347

5448
markDeclarationReached(): void {
5549
for (const element of this.elements) {
56-
if (element !== null) {
57-
element.markDeclarationReached();
58-
}
50+
element?.markDeclarationReached();
5951
}
6052
}
6153
}

src/ast/nodes/ArrowFunctionExpression.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { type CallOptions } from '../CallOptions';
2-
import { type HasEffectsContext, InclusionContext } from '../ExecutionContext';
2+
import { type HasEffectsContext } from '../ExecutionContext';
33
import ReturnValueScope from '../scopes/ReturnValueScope';
44
import type Scope from '../scopes/Scope';
55
import { type ObjectPath } from '../utils/PathTracker';
66
import BlockStatement from './BlockStatement';
7-
import Identifier from './Identifier';
87
import * as NodeType from './NodeType';
98
import FunctionBase from './shared/FunctionBase';
10-
import { type ExpressionNode, IncludeChildren } from './shared/Node';
9+
import { type ExpressionNode } from './shared/Node';
1110
import { ObjectEntity } from './shared/ObjectEntity';
1211
import { OBJECT_PROTOTYPE } from './shared/ObjectPrototype';
1312
import type { PatternNode } from './shared/Pattern';
@@ -48,15 +47,6 @@ export default class ArrowFunctionExpression extends FunctionBase {
4847
return false;
4948
}
5049

51-
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
52-
super.include(context, includeChildrenRecursively);
53-
for (const param of this.params) {
54-
if (!(param instanceof Identifier)) {
55-
param.include(context, includeChildrenRecursively);
56-
}
57-
}
58-
}
59-
6050
protected getObjectEntity(): ObjectEntity {
6151
if (this.objectEntity !== null) {
6252
return this.objectEntity;

src/ast/nodes/AssignmentExpression.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export default class AssignmentExpression extends NodeBase {
5454
);
5555
}
5656

57-
hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean {
57+
hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean | undefined {
5858
return path.length > 0 && this.right.hasEffectsWhenAccessedAtPath(path, context);
5959
}
6060

src/ast/nodes/AssignmentPattern.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import type MagicString from 'magic-string';
22
import { BLANK } from '../../utils/blank';
33
import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers';
44
import type { HasEffectsContext } from '../ExecutionContext';
5+
import { InclusionContext } from '../ExecutionContext';
56
import { EMPTY_PATH, type ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker';
67
import type LocalVariable from '../variables/LocalVariable';
78
import type Variable from '../variables/Variable';
89
import type * as NodeType from './NodeType';
910
import type { ExpressionEntity } from './shared/Expression';
10-
import { type ExpressionNode, NodeBase } from './shared/Node';
11+
import { type ExpressionNode, IncludeChildren, NodeBase } from './shared/Node';
1112
import type { PatternNode } from './shared/Pattern';
1213

1314
export default class AssignmentPattern extends NodeBase implements PatternNode {
@@ -35,6 +36,12 @@ export default class AssignmentPattern extends NodeBase implements PatternNode {
3536
return path.length > 0 || this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context);
3637
}
3738

39+
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
40+
this.included = true;
41+
this.left.include(context, includeChildrenRecursively);
42+
this.right.include(context, includeChildrenRecursively);
43+
}
44+
3845
markDeclarationReached(): void {
3946
this.left.markDeclarationReached();
4047
}
@@ -45,7 +52,11 @@ export default class AssignmentPattern extends NodeBase implements PatternNode {
4552
{ isShorthandProperty }: NodeRenderOptions = BLANK
4653
): void {
4754
this.left.render(code, options, { isShorthandProperty });
48-
this.right.render(code, options);
55+
if (this.right.included) {
56+
this.right.render(code, options);
57+
} else {
58+
code.remove(this.left.end, this.end);
59+
}
4960
}
5061

5162
protected applyDeoptimizations(): void {

src/ast/nodes/AwaitExpression.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { InclusionContext } from '../ExecutionContext';
2-
import { UNKNOWN_PATH } from '../utils/PathTracker';
32
import ArrowFunctionExpression from './ArrowFunctionExpression';
43
import type * as NodeType from './NodeType';
54
import FunctionNode from './shared/FunctionNode';
@@ -30,10 +29,4 @@ export default class AwaitExpression extends NodeBase {
3029
}
3130
this.argument.include(context, includeChildrenRecursively);
3231
}
33-
34-
protected applyDeoptimizations(): void {
35-
this.deoptimized = true;
36-
this.argument.deoptimizePath(UNKNOWN_PATH);
37-
this.context.requestTreeshakingPass();
38-
}
3932
}

src/ast/nodes/BinaryExpression.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,18 @@ export default class BinaryExpression extends NodeBase implements DeoptimizableE
6060
): LiteralValueOrUnknown {
6161
if (path.length > 0) return UnknownValue;
6262
const leftValue = this.left.getLiteralValueAtPath(EMPTY_PATH, recursionTracker, origin);
63-
if (leftValue === UnknownValue) return UnknownValue;
63+
if (typeof leftValue === 'symbol') return UnknownValue;
6464

6565
const rightValue = this.right.getLiteralValueAtPath(EMPTY_PATH, recursionTracker, origin);
66-
if (rightValue === UnknownValue) return UnknownValue;
66+
if (typeof rightValue === 'symbol') return UnknownValue;
6767

6868
const operatorFn = binaryOperators[this.operator];
6969
if (!operatorFn) return UnknownValue;
7070

7171
return operatorFn(leftValue, rightValue);
7272
}
7373

74-
hasEffects(context: HasEffectsContext): boolean {
74+
hasEffects(context: HasEffectsContext): boolean | undefined {
7575
// support some implicit type coercion runtime errors
7676
if (
7777
this.operator === '+' &&

0 commit comments

Comments
 (0)