From c9f63de3f61da680a40514a37bf416e50c0c3e7f Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Thu, 1 Jul 2021 07:05:09 +0200 Subject: [PATCH] Deoptimize awaited expressions --- src/ast/ExecutionContext.ts | 4 +-- src/ast/nodes/ArrowFunctionExpression.ts | 2 +- src/ast/nodes/AwaitExpression.ts | 30 ++++++++----------- src/ast/nodes/ReturnStatement.ts | 2 +- src/ast/nodes/YieldExpression.ts | 3 +- src/ast/nodes/shared/FunctionNode.ts | 2 +- .../samples/async-function-effects/main.js | 18 ----------- .../main.js | 10 ------- .../samples/track-awaited-object/_config.js | 10 +++++++ .../samples/track-awaited-object/main.js | 19 ++++++++++++ 10 files changed, 47 insertions(+), 53 deletions(-) create mode 100644 test/function/samples/track-awaited-object/_config.js create mode 100644 test/function/samples/track-awaited-object/main.js diff --git a/src/ast/ExecutionContext.ts b/src/ast/ExecutionContext.ts index 09d6d313fc0..bcc451d773b 100644 --- a/src/ast/ExecutionContext.ts +++ b/src/ast/ExecutionContext.ts @@ -7,7 +7,7 @@ interface ExecutionContextIgnore { breaks: boolean; continues: boolean; labels: Set; - returnAwaitYield: boolean; + returnYield: boolean; } export const BROKEN_FLOW_NONE = 0; @@ -51,7 +51,7 @@ export function createHasEffectsContext(): HasEffectsContext { breaks: false, continues: false, labels: new Set(), - returnAwaitYield: false + returnYield: false }, includedLabels: new Set(), instantiated: new DiscriminatedPathTracker(), diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index dd4de3a79f3..c9f9aae3ba6 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -83,7 +83,7 @@ export default class ArrowFunctionExpression extends NodeBase { breaks: false, continues: false, labels: new Set(), - returnAwaitYield: true + returnYield: true }; if (this.body.hasEffects(context)) return true; context.ignore = ignore; diff --git a/src/ast/nodes/AwaitExpression.ts b/src/ast/nodes/AwaitExpression.ts index cbe54cc392d..8a9f5c3569e 100644 --- a/src/ast/nodes/AwaitExpression.ts +++ b/src/ast/nodes/AwaitExpression.ts @@ -1,6 +1,5 @@ -import { NormalizedTreeshakingOptions } from '../../rollup/types'; -import { NO_ARGS } from '../CallOptions'; -import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; +import { InclusionContext } from '../ExecutionContext'; +import { UNKNOWN_PATH } from '../utils/PathTracker'; import ArrowFunctionExpression from './ArrowFunctionExpression'; import * as NodeType from './NodeType'; import FunctionNode from './shared/FunctionNode'; @@ -9,25 +8,15 @@ import { ExpressionNode, IncludeChildren, Node, NodeBase } from './shared/Node'; export default class AwaitExpression extends NodeBase { argument!: ExpressionNode; type!: NodeType.tAwaitExpression; + protected deoptimized = false; - hasEffects(context: HasEffectsContext): boolean { - const { propertyReadSideEffects } = this.context.options - .treeshake as NormalizedTreeshakingOptions; - return ( - !context.ignore.returnAwaitYield || - this.argument.hasEffects(context) || - this.argument.hasEffectsWhenCalledAtPath( - ['then'], - { args: NO_ARGS, thisParam: null, withNew: false }, - context - ) || - (propertyReadSideEffects && - (propertyReadSideEffects === 'always' || - this.argument.hasEffectsWhenAccessedAtPath(['then'], context))) - ); + hasEffects(): boolean { + if (!this.deoptimized) this.applyDeoptimizations(); + return true; } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { + if (!this.deoptimized) this.applyDeoptimizations(); if (!this.included) { this.included = true; checkTopLevelAwait: if (!this.context.usesTopLevelAwait) { @@ -41,4 +30,9 @@ export default class AwaitExpression extends NodeBase { } this.argument.include(context, includeChildrenRecursively); } + + protected applyDeoptimizations(): void { + this.deoptimized = true; + this.argument.deoptimizePath(UNKNOWN_PATH); + } } diff --git a/src/ast/nodes/ReturnStatement.ts b/src/ast/nodes/ReturnStatement.ts index 62ec2efcfaa..6240ac591ce 100644 --- a/src/ast/nodes/ReturnStatement.ts +++ b/src/ast/nodes/ReturnStatement.ts @@ -15,7 +15,7 @@ export default class ReturnStatement extends StatementBase { hasEffects(context: HasEffectsContext): boolean { if ( - !context.ignore.returnAwaitYield || + !context.ignore.returnYield || (this.argument !== null && this.argument.hasEffects(context)) ) return true; diff --git a/src/ast/nodes/YieldExpression.ts b/src/ast/nodes/YieldExpression.ts index bd8cec8f3b1..a313c736fd2 100644 --- a/src/ast/nodes/YieldExpression.ts +++ b/src/ast/nodes/YieldExpression.ts @@ -14,8 +14,7 @@ export default class YieldExpression extends NodeBase { hasEffects(context: HasEffectsContext): boolean { if (!this.deoptimized) this.applyDeoptimizations(); return ( - !context.ignore.returnAwaitYield || - (this.argument !== null && this.argument.hasEffects(context)) + !context.ignore.returnYield || (this.argument !== null && this.argument.hasEffects(context)) ); } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index f549d901395..8003a023e85 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -114,7 +114,7 @@ export default class FunctionNode extends NodeBase { breaks: false, continues: false, labels: new Set(), - returnAwaitYield: true + returnYield: true }; if (this.body.hasEffects(context)) return true; context.brokenFlow = brokenFlow; diff --git a/test/form/samples/async-function-effects/main.js b/test/form/samples/async-function-effects/main.js index 595ab6691b9..c870d8d364f 100644 --- a/test/form/samples/async-function-effects/main.js +++ b/test/form/samples/async-function-effects/main.js @@ -77,14 +77,6 @@ return { then() {} }; })(); -// removed -(async function () { - await { - then: function () {} - }; - return { then() {} }; -})(); - (async function () { await { get then() { @@ -104,16 +96,6 @@ return { then() {} }; })(); -// removed -(async function () { - await { - get then() { - return () => {}; - } - }; - return { then() {} }; -})(); - (async function () { await await { then(resolve) { diff --git a/test/form/samples/ignore-property-access-side-effects/main.js b/test/form/samples/ignore-property-access-side-effects/main.js index a5a0020200d..c0c2b1d6432 100644 --- a/test/form/samples/ignore-property-access-side-effects/main.js +++ b/test/form/samples/ignore-property-access-side-effects/main.js @@ -38,13 +38,3 @@ const foo7 = (async () => ({ return () => {}; } }))(); - -const foo8 = (async function () { - await { - get then() { - console.log('effect'); - return () => {}; - } - }; - return { then() {} }; -})(); diff --git a/test/function/samples/track-awaited-object/_config.js b/test/function/samples/track-awaited-object/_config.js new file mode 100644 index 00000000000..5d5bffcc40b --- /dev/null +++ b/test/function/samples/track-awaited-object/_config.js @@ -0,0 +1,10 @@ +const assert = require('assert'); + +module.exports = { + description: 'tracks object mutations through await', + async exports(exports) { + assert.strictEqual(exports.toggled, false); + await exports.test(); + assert.strictEqual(exports.toggled, true); + } +}; diff --git a/test/function/samples/track-awaited-object/main.js b/test/function/samples/track-awaited-object/main.js new file mode 100644 index 00000000000..abeaa8a8482 --- /dev/null +++ b/test/function/samples/track-awaited-object/main.js @@ -0,0 +1,19 @@ +function fn() { + const obj = { + test() { + if (typeof obj.testfn === 'function') { + obj.testfn(); + } + } + }; + + return obj; +} + +export let toggled = false; + +export const test = async function () { + const obj = await fn(); + obj.testfn = () => (toggled = true); + obj.test(); +};