Skip to content

Commit b9d9e6f

Browse files
authored
Deoptimize awaited expressions (#4162)
1 parent 5b55f99 commit b9d9e6f

File tree

10 files changed

+47
-53
lines changed

10 files changed

+47
-53
lines changed

src/ast/ExecutionContext.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ interface ExecutionContextIgnore {
77
breaks: boolean;
88
continues: boolean;
99
labels: Set<string>;
10-
returnAwaitYield: boolean;
10+
returnYield: boolean;
1111
}
1212

1313
export const BROKEN_FLOW_NONE = 0;
@@ -51,7 +51,7 @@ export function createHasEffectsContext(): HasEffectsContext {
5151
breaks: false,
5252
continues: false,
5353
labels: new Set(),
54-
returnAwaitYield: false
54+
returnYield: false
5555
},
5656
includedLabels: new Set(),
5757
instantiated: new DiscriminatedPathTracker(),

src/ast/nodes/ArrowFunctionExpression.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export default class ArrowFunctionExpression extends NodeBase {
8383
breaks: false,
8484
continues: false,
8585
labels: new Set(),
86-
returnAwaitYield: true
86+
returnYield: true
8787
};
8888
if (this.body.hasEffects(context)) return true;
8989
context.ignore = ignore;

src/ast/nodes/AwaitExpression.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { NormalizedTreeshakingOptions } from '../../rollup/types';
2-
import { NO_ARGS } from '../CallOptions';
3-
import { HasEffectsContext, InclusionContext } from '../ExecutionContext';
1+
import { InclusionContext } from '../ExecutionContext';
2+
import { UNKNOWN_PATH } from '../utils/PathTracker';
43
import ArrowFunctionExpression from './ArrowFunctionExpression';
54
import * as NodeType from './NodeType';
65
import FunctionNode from './shared/FunctionNode';
@@ -9,25 +8,15 @@ import { ExpressionNode, IncludeChildren, Node, NodeBase } from './shared/Node';
98
export default class AwaitExpression extends NodeBase {
109
argument!: ExpressionNode;
1110
type!: NodeType.tAwaitExpression;
11+
protected deoptimized = false;
1212

13-
hasEffects(context: HasEffectsContext): boolean {
14-
const { propertyReadSideEffects } = this.context.options
15-
.treeshake as NormalizedTreeshakingOptions;
16-
return (
17-
!context.ignore.returnAwaitYield ||
18-
this.argument.hasEffects(context) ||
19-
this.argument.hasEffectsWhenCalledAtPath(
20-
['then'],
21-
{ args: NO_ARGS, thisParam: null, withNew: false },
22-
context
23-
) ||
24-
(propertyReadSideEffects &&
25-
(propertyReadSideEffects === 'always' ||
26-
this.argument.hasEffectsWhenAccessedAtPath(['then'], context)))
27-
);
13+
hasEffects(): boolean {
14+
if (!this.deoptimized) this.applyDeoptimizations();
15+
return true;
2816
}
2917

3018
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
19+
if (!this.deoptimized) this.applyDeoptimizations();
3120
if (!this.included) {
3221
this.included = true;
3322
checkTopLevelAwait: if (!this.context.usesTopLevelAwait) {
@@ -41,4 +30,9 @@ export default class AwaitExpression extends NodeBase {
4130
}
4231
this.argument.include(context, includeChildrenRecursively);
4332
}
33+
34+
protected applyDeoptimizations(): void {
35+
this.deoptimized = true;
36+
this.argument.deoptimizePath(UNKNOWN_PATH);
37+
}
4438
}

src/ast/nodes/ReturnStatement.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default class ReturnStatement extends StatementBase {
1515

1616
hasEffects(context: HasEffectsContext): boolean {
1717
if (
18-
!context.ignore.returnAwaitYield ||
18+
!context.ignore.returnYield ||
1919
(this.argument !== null && this.argument.hasEffects(context))
2020
)
2121
return true;

src/ast/nodes/YieldExpression.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ export default class YieldExpression extends NodeBase {
1414
hasEffects(context: HasEffectsContext): boolean {
1515
if (!this.deoptimized) this.applyDeoptimizations();
1616
return (
17-
!context.ignore.returnAwaitYield ||
18-
(this.argument !== null && this.argument.hasEffects(context))
17+
!context.ignore.returnYield || (this.argument !== null && this.argument.hasEffects(context))
1918
);
2019
}
2120

src/ast/nodes/shared/FunctionNode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export default class FunctionNode extends NodeBase {
114114
breaks: false,
115115
continues: false,
116116
labels: new Set(),
117-
returnAwaitYield: true
117+
returnYield: true
118118
};
119119
if (this.body.hasEffects(context)) return true;
120120
context.brokenFlow = brokenFlow;

test/form/samples/async-function-effects/main.js

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,6 @@
7777
return { then() {} };
7878
})();
7979

80-
// removed
81-
(async function () {
82-
await {
83-
then: function () {}
84-
};
85-
return { then() {} };
86-
})();
87-
8880
(async function () {
8981
await {
9082
get then() {
@@ -104,16 +96,6 @@
10496
return { then() {} };
10597
})();
10698

107-
// removed
108-
(async function () {
109-
await {
110-
get then() {
111-
return () => {};
112-
}
113-
};
114-
return { then() {} };
115-
})();
116-
11799
(async function () {
118100
await await {
119101
then(resolve) {

test/form/samples/ignore-property-access-side-effects/main.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,3 @@ const foo7 = (async () => ({
3838
return () => {};
3939
}
4040
}))();
41-
42-
const foo8 = (async function () {
43-
await {
44-
get then() {
45-
console.log('effect');
46-
return () => {};
47-
}
48-
};
49-
return { then() {} };
50-
})();
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const assert = require('assert');
2+
3+
module.exports = {
4+
description: 'tracks object mutations through await',
5+
async exports(exports) {
6+
assert.strictEqual(exports.toggled, false);
7+
await exports.test();
8+
assert.strictEqual(exports.toggled, true);
9+
}
10+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
function fn() {
2+
const obj = {
3+
test() {
4+
if (typeof obj.testfn === 'function') {
5+
obj.testfn();
6+
}
7+
}
8+
};
9+
10+
return obj;
11+
}
12+
13+
export let toggled = false;
14+
15+
export const test = async function () {
16+
const obj = await fn();
17+
obj.testfn = () => (toggled = true);
18+
obj.test();
19+
};

0 commit comments

Comments
 (0)