From a39dc4e1a80b226f9b3654753de4b6c2a4db4bbf Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Wed, 1 Jun 2022 14:56:26 +0200 Subject: [PATCH 01/13] Rename event -> interaction --- src/ast/NodeEvents.ts | 5 --- src/ast/NodeInteractions.ts | 8 ++++ src/ast/nodes/ArrayExpression.ts | 10 ++--- src/ast/nodes/CallExpression.ts | 6 +-- src/ast/nodes/ConditionalExpression.ts | 20 ++++++--- src/ast/nodes/Identifier.ts | 13 ++++-- src/ast/nodes/Literal.ts | 2 +- src/ast/nodes/LogicalExpression.ts | 15 ++++--- src/ast/nodes/MemberExpression.ts | 29 +++++++----- src/ast/nodes/ObjectExpression.ts | 10 ++--- src/ast/nodes/PropertyDefinition.ts | 13 ++++-- src/ast/nodes/SequenceExpression.ts | 10 ++--- src/ast/nodes/SpreadElement.ts | 10 ++--- src/ast/nodes/Super.ts | 13 ++++-- src/ast/nodes/TaggedTemplateExpression.ts | 6 +-- src/ast/nodes/TemplateLiteral.ts | 2 +- src/ast/nodes/ThisExpression.ts | 10 ++--- src/ast/nodes/shared/CallExpressionBase.ts | 10 ++--- src/ast/nodes/shared/ClassNode.ts | 10 ++--- src/ast/nodes/shared/Expression.ts | 6 +-- src/ast/nodes/shared/FunctionBase.ts | 10 ++--- src/ast/nodes/shared/FunctionNode.ts | 10 ++--- src/ast/nodes/shared/MethodBase.ts | 27 +++++++----- src/ast/nodes/shared/MethodTypes.ts | 12 +++-- src/ast/nodes/shared/ObjectEntity.ts | 51 ++++++++++++++++------ src/ast/nodes/shared/ObjectMember.ts | 10 ++--- src/ast/nodes/shared/ObjectPrototype.ts | 8 ++-- src/ast/variables/LocalVariable.ts | 14 ++++-- src/ast/variables/ThisVariable.ts | 32 +++++++------- 29 files changed, 233 insertions(+), 149 deletions(-) delete mode 100644 src/ast/NodeEvents.ts create mode 100644 src/ast/NodeInteractions.ts diff --git a/src/ast/NodeEvents.ts b/src/ast/NodeEvents.ts deleted file mode 100644 index d0ae880f6f3..00000000000 --- a/src/ast/NodeEvents.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const EVENT_ACCESSED = 0; -export const EVENT_ASSIGNED = 1; -export const EVENT_CALLED = 2; - -export type NodeEvent = typeof EVENT_ACCESSED | typeof EVENT_ASSIGNED | typeof EVENT_CALLED; diff --git a/src/ast/NodeInteractions.ts b/src/ast/NodeInteractions.ts new file mode 100644 index 00000000000..34e54cbaa61 --- /dev/null +++ b/src/ast/NodeInteractions.ts @@ -0,0 +1,8 @@ +export const INTERACTION_ACCESSED = 0; +export const INTERACTION_ASSIGNED = 1; +export const INTERACTION_CALLED = 2; + +export type NodeInteraction = + | typeof INTERACTION_ACCESSED + | typeof INTERACTION_ASSIGNED + | typeof INTERACTION_CALLED; diff --git a/src/ast/nodes/ArrayExpression.ts b/src/ast/nodes/ArrayExpression.ts index 67710bc8f13..c41321ed1f8 100644 --- a/src/ast/nodes/ArrayExpression.ts +++ b/src/ast/nodes/ArrayExpression.ts @@ -1,7 +1,7 @@ import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeEvent } from '../NodeEvents'; +import type { NodeInteraction } from '../NodeInteractions'; import { type ObjectPath, type PathTracker, @@ -25,14 +25,14 @@ export default class ArrayExpression extends NodeBase { this.getObjectEntity().deoptimizePath(path); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.getObjectEntity().deoptimizeThisOnEventAtPath( - event, + this.getObjectEntity().deoptimizeThisOnInteractionAtPath( + interaction, path, thisParameter, recursionTracker diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 2b6f84f234a..dfc144a48a9 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -5,7 +5,7 @@ import { renderCallArguments } from '../../utils/renderCallArguments'; import { type NodeRenderOptions, type RenderOptions } from '../../utils/renderHelpers'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import { EVENT_CALLED } from '../NodeEvents'; +import { INTERACTION_CALLED } from '../NodeInteractions'; import { EMPTY_PATH, type PathTracker, @@ -120,8 +120,8 @@ export default class CallExpression extends CallExpressionBase implements Deopti this.deoptimized = true; const { thisParam } = this.callOptions; if (thisParam) { - this.callee.deoptimizeThisOnEventAtPath( - EVENT_CALLED, + this.callee.deoptimizeThisOnInteractionAtPath( + INTERACTION_CALLED, EMPTY_PATH, thisParam, SHARED_RECURSION_TRACKER diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index 0ff6cc1fcbb..e97324efa2d 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -11,7 +11,7 @@ import { removeAnnotations } from '../../utils/treeshakeNode'; import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import { NodeEvent } from '../NodeEvents'; +import { NodeInteraction } from '../NodeInteractions'; import { EMPTY_PATH, ObjectPath, @@ -56,14 +56,24 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz } } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.consequent.deoptimizeThisOnEventAtPath(event, path, thisParameter, recursionTracker); - this.alternate.deoptimizeThisOnEventAtPath(event, path, thisParameter, recursionTracker); + this.consequent.deoptimizeThisOnInteractionAtPath( + interaction, + path, + thisParameter, + recursionTracker + ); + this.alternate.deoptimizeThisOnInteractionAtPath( + interaction, + path, + thisParameter, + recursionTracker + ); } getLiteralValueAtPath( diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 03125e097ce..b46bb3470dd 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -6,7 +6,7 @@ import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import type { NodeEvent } from '../NodeEvents'; +import type { NodeInteraction } from '../NodeInteractions'; import type FunctionScope from '../scopes/FunctionScope'; import { EMPTY_PATH, type ObjectPath, type PathTracker } from '../utils/PathTracker'; import GlobalVariable from '../variables/GlobalVariable'; @@ -95,13 +95,18 @@ export default class Identifier extends NodeBase implements PatternNode { this.variable?.deoptimizePath(path); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.variable!.deoptimizeThisOnEventAtPath(event, path, thisParameter, recursionTracker); + this.variable!.deoptimizeThisOnInteractionAtPath( + interaction, + path, + thisParameter, + recursionTracker + ); } getLiteralValueAtPath( diff --git a/src/ast/nodes/Literal.ts b/src/ast/nodes/Literal.ts index 192cffa8fe3..7e6c676f625 100644 --- a/src/ast/nodes/Literal.ts +++ b/src/ast/nodes/Literal.ts @@ -29,7 +29,7 @@ export default class Literal extends Node private declare members: { [key: string]: MemberDescription }; - deoptimizeThisOnEventAtPath(): void {} + deoptimizeThisOnInteractionAtPath(): void {} getLiteralValueAtPath(path: ObjectPath): LiteralValueOrUnknown { if ( diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index 763ff942b69..d00ea11d70b 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -11,7 +11,7 @@ import { removeAnnotations } from '../../utils/treeshakeNode'; import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import type { NodeEvent } from '../NodeEvents'; +import type { NodeInteraction } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, @@ -65,14 +65,19 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable } } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.left.deoptimizeThisOnEventAtPath(event, path, thisParameter, recursionTracker); - this.right.deoptimizeThisOnEventAtPath(event, path, thisParameter, recursionTracker); + this.left.deoptimizeThisOnInteractionAtPath(interaction, path, thisParameter, recursionTracker); + this.right.deoptimizeThisOnInteractionAtPath( + interaction, + path, + thisParameter, + recursionTracker + ); } getLiteralValueAtPath( diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index d98d548fa30..a149e98da6a 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -6,7 +6,11 @@ import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import { EVENT_ACCESSED, EVENT_ASSIGNED, type NodeEvent } from '../NodeEvents'; +import { + INTERACTION_ACCESSED, + INTERACTION_ASSIGNED, + type NodeInteraction +} from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, @@ -137,18 +141,23 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { if (this.variable) { - this.variable.deoptimizeThisOnEventAtPath(event, path, thisParameter, recursionTracker); + this.variable.deoptimizeThisOnInteractionAtPath( + interaction, + path, + thisParameter, + recursionTracker + ); } else if (!this.replacement) { if (path.length < MAX_PATH_DEPTH) { - this.object.deoptimizeThisOnEventAtPath( - event, + this.object.deoptimizeThisOnInteractionAtPath( + interaction, [this.getPropertyKey(), ...path], thisParameter, recursionTracker @@ -343,16 +352,16 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ) { // Regular Assignments do not access the property before assigning if (!(this.parent instanceof AssignmentExpression && this.parent.operator === '=')) { - this.object.deoptimizeThisOnEventAtPath( - EVENT_ACCESSED, + this.object.deoptimizeThisOnInteractionAtPath( + INTERACTION_ACCESSED, [this.propertyKey!], this.object, SHARED_RECURSION_TRACKER ); } if (this.parent instanceof AssignmentExpression) { - this.object.deoptimizeThisOnEventAtPath( - EVENT_ASSIGNED, + this.object.deoptimizeThisOnInteractionAtPath( + INTERACTION_ASSIGNED, [this.propertyKey!], this.object, SHARED_RECURSION_TRACKER diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index 8f956916205..07ef43d3b42 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -4,7 +4,7 @@ import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeEvent } from '../NodeEvents'; +import type { NodeInteraction } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, @@ -35,14 +35,14 @@ export default class ObjectExpression extends NodeBase implements DeoptimizableE this.getObjectEntity().deoptimizePath(path); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.getObjectEntity().deoptimizeThisOnEventAtPath( - event, + this.getObjectEntity().deoptimizeThisOnInteractionAtPath( + interaction, path, thisParameter, recursionTracker diff --git a/src/ast/nodes/PropertyDefinition.ts b/src/ast/nodes/PropertyDefinition.ts index 136d070ddaf..03575cec999 100644 --- a/src/ast/nodes/PropertyDefinition.ts +++ b/src/ast/nodes/PropertyDefinition.ts @@ -1,7 +1,7 @@ import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeEvent } from '../NodeEvents'; +import type { NodeInteraction } from '../NodeInteractions'; import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; import type PrivateIdentifier from './PrivateIdentifier'; @@ -24,13 +24,18 @@ export default class PropertyDefinition extends NodeBase { this.value?.deoptimizePath(path); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.value?.deoptimizeThisOnEventAtPath(event, path, thisParameter, recursionTracker); + this.value?.deoptimizeThisOnInteractionAtPath( + interaction, + path, + thisParameter, + recursionTracker + ); } getLiteralValueAtPath( diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index 1eaca85ad4b..095871863a4 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -10,7 +10,7 @@ import { treeshakeNode } from '../../utils/treeshakeNode'; import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import type { NodeEvent } from '../NodeEvents'; +import type { NodeInteraction } from '../NodeInteractions'; import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import ExpressionStatement from './ExpressionStatement'; import type * as NodeType from './NodeType'; @@ -25,14 +25,14 @@ export default class SequenceExpression extends NodeBase { this.expressions[this.expressions.length - 1].deoptimizePath(path); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.expressions[this.expressions.length - 1].deoptimizeThisOnEventAtPath( - event, + this.expressions[this.expressions.length - 1].deoptimizeThisOnInteractionAtPath( + interaction, path, thisParameter, recursionTracker diff --git a/src/ast/nodes/SpreadElement.ts b/src/ast/nodes/SpreadElement.ts index b6f5849fdbc..8b50543973a 100644 --- a/src/ast/nodes/SpreadElement.ts +++ b/src/ast/nodes/SpreadElement.ts @@ -1,6 +1,6 @@ import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeEvent } from '../NodeEvents'; +import type { NodeInteraction } from '../NodeInteractions'; import { type ObjectPath, type PathTracker, UNKNOWN_PATH, UnknownKey } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; import type { ExpressionEntity } from './shared/Expression'; @@ -10,15 +10,15 @@ export default class SpreadElement extends NodeBase { declare argument: ExpressionNode; declare type: NodeType.tSpreadElement; - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { if (path.length > 0) { - this.argument.deoptimizeThisOnEventAtPath( - event, + this.argument.deoptimizeThisOnInteractionAtPath( + interaction, [UnknownKey, ...path], thisParameter, recursionTracker diff --git a/src/ast/nodes/Super.ts b/src/ast/nodes/Super.ts index d04da03870f..3512a27be00 100644 --- a/src/ast/nodes/Super.ts +++ b/src/ast/nodes/Super.ts @@ -1,4 +1,4 @@ -import { NodeEvent } from '../NodeEvents'; +import { NodeInteraction } from '../NodeInteractions'; import type { ObjectPath } from '../utils/PathTracker'; import { PathTracker } from '../utils/PathTracker'; import Variable from '../variables/Variable'; @@ -18,13 +18,18 @@ export default class Super extends NodeBase { this.variable.deoptimizePath(path); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ) { - this.variable.deoptimizeThisOnEventAtPath(event, path, thisParameter, recursionTracker); + this.variable.deoptimizeThisOnInteractionAtPath( + interaction, + path, + thisParameter, + recursionTracker + ); } include(): void { diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index 3a661c29848..c9bf26e60b7 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -2,7 +2,7 @@ import type MagicString from 'magic-string'; import { type RenderOptions } from '../../utils/renderHelpers'; import type { HasEffectsContext } from '../ExecutionContext'; import { InclusionContext } from '../ExecutionContext'; -import { EVENT_CALLED } from '../NodeEvents'; +import { INTERACTION_CALLED } from '../NodeInteractions'; import { EMPTY_PATH, PathTracker, @@ -88,8 +88,8 @@ export default class TaggedTemplateExpression extends CallExpressionBase { this.deoptimized = true; const { thisParam } = this.callOptions; if (thisParam) { - this.tag.deoptimizeThisOnEventAtPath( - EVENT_CALLED, + this.tag.deoptimizeThisOnInteractionAtPath( + INTERACTION_CALLED, EMPTY_PATH, thisParam, SHARED_RECURSION_TRACKER diff --git a/src/ast/nodes/TemplateLiteral.ts b/src/ast/nodes/TemplateLiteral.ts index 85ac5537ae7..8ddc7dae135 100644 --- a/src/ast/nodes/TemplateLiteral.ts +++ b/src/ast/nodes/TemplateLiteral.ts @@ -23,7 +23,7 @@ export default class TemplateLiteral extends NodeBase { declare quasis: TemplateElement[]; declare type: NodeType.tTemplateLiteral; - deoptimizeThisOnEventAtPath(): void {} + deoptimizeThisOnInteractionAtPath(): void {} getLiteralValueAtPath(path: ObjectPath): LiteralValueOrUnknown { if (path.length > 0 || this.quasis.length !== 1) { diff --git a/src/ast/nodes/ThisExpression.ts b/src/ast/nodes/ThisExpression.ts index 801b11de75e..2fcd0ad6d1f 100644 --- a/src/ast/nodes/ThisExpression.ts +++ b/src/ast/nodes/ThisExpression.ts @@ -1,6 +1,6 @@ import type MagicString from 'magic-string'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeEvent } from '../NodeEvents'; +import type { NodeInteraction } from '../NodeInteractions'; import ModuleScope from '../scopes/ModuleScope'; import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import type Variable from '../variables/Variable'; @@ -21,14 +21,14 @@ export default class ThisExpression extends NodeBase { this.variable.deoptimizePath(path); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.variable.deoptimizeThisOnEventAtPath( - event, + this.variable.deoptimizeThisOnInteractionAtPath( + interaction, path, // We rewrite the parameter so that a ThisVariable can detect self-mutations thisParameter === this ? this.variable : thisParameter, diff --git a/src/ast/nodes/shared/CallExpressionBase.ts b/src/ast/nodes/shared/CallExpressionBase.ts index 72524d773cc..3300eaa102c 100644 --- a/src/ast/nodes/shared/CallExpressionBase.ts +++ b/src/ast/nodes/shared/CallExpressionBase.ts @@ -1,7 +1,7 @@ import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext } from '../../ExecutionContext'; -import { type NodeEvent } from '../../NodeEvents'; +import { type NodeInteraction } from '../../NodeInteractions'; import { type ObjectPath, type PathTracker, UNKNOWN_PATH } from '../../utils/PathTracker'; import { type ExpressionEntity, @@ -42,8 +42,8 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo } } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker @@ -57,8 +57,8 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo returnExpression, () => { this.expressionsToBeDeoptimized.add(thisParameter); - returnExpression.deoptimizeThisOnEventAtPath( - event, + returnExpression.deoptimizeThisOnInteractionAtPath( + interaction, path, thisParameter, recursionTracker diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index 5a6ee0e81dd..42c0a7a112c 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -1,7 +1,7 @@ import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; -import type { NodeEvent } from '../../NodeEvents'; +import type { NodeInteraction } from '../../NodeInteractions'; import ChildScope from '../../scopes/ChildScope'; import type Scope from '../../scopes/Scope'; import { @@ -41,14 +41,14 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { this.getObjectEntity().deoptimizePath(path); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.getObjectEntity().deoptimizeThisOnEventAtPath( - event, + this.getObjectEntity().deoptimizeThisOnInteractionAtPath( + interaction, path, thisParameter, recursionTracker diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index a28e47dd769..b366547329f 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -2,7 +2,7 @@ import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { WritableEntity } from '../../Entity'; import { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; -import { NodeEvent } from '../../NodeEvents'; +import { NodeInteraction } from '../../NodeInteractions'; import { ObjectPath, PathTracker, UNKNOWN_PATH } from '../../utils/PathTracker'; import { LiteralValue } from '../Literal'; import SpreadElement from '../SpreadElement'; @@ -25,8 +25,8 @@ export class ExpressionEntity implements WritableEntity { deoptimizePath(_path: ObjectPath): void {} - deoptimizeThisOnEventAtPath( - _event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + _interaction: NodeInteraction, _path: ObjectPath, thisParameter: ExpressionEntity, _recursionTracker: PathTracker diff --git a/src/ast/nodes/shared/FunctionBase.ts b/src/ast/nodes/shared/FunctionBase.ts index 05d09d3ac47..5abfa8aef04 100644 --- a/src/ast/nodes/shared/FunctionBase.ts +++ b/src/ast/nodes/shared/FunctionBase.ts @@ -6,7 +6,7 @@ import { type HasEffectsContext, type InclusionContext } from '../../ExecutionContext'; -import { NodeEvent } from '../../NodeEvents'; +import { NodeInteraction } from '../../NodeInteractions'; import ReturnValueScope from '../../scopes/ReturnValueScope'; import { type ObjectPath, PathTracker, UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; import BlockStatement from '../BlockStatement'; @@ -41,15 +41,15 @@ export default abstract class FunctionBase extends NodeBase { } } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { if (path.length > 0) { - this.getObjectEntity().deoptimizeThisOnEventAtPath( - event, + this.getObjectEntity().deoptimizeThisOnInteractionAtPath( + interaction, path, thisParameter, recursionTracker diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 95867d14844..5946136c7b2 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -1,6 +1,6 @@ import { type CallOptions } from '../../CallOptions'; import { type HasEffectsContext, type InclusionContext } from '../../ExecutionContext'; -import { EVENT_CALLED, type NodeEvent } from '../../NodeEvents'; +import { INTERACTION_CALLED, type NodeInteraction } from '../../NodeInteractions'; import FunctionScope from '../../scopes/FunctionScope'; import { type ObjectPath, PathTracker } from '../../utils/PathTracker'; import BlockStatement from '../BlockStatement'; @@ -25,14 +25,14 @@ export default class FunctionNode extends FunctionBase { this.scope = new FunctionScope(parentScope, this.context); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - super.deoptimizeThisOnEventAtPath(event, path, thisParameter, recursionTracker); - if (event === EVENT_CALLED && path.length === 0) { + super.deoptimizeThisOnInteractionAtPath(interaction, path, thisParameter, recursionTracker); + if (interaction === INTERACTION_CALLED && path.length === 0) { this.scope.thisVariable.addEntityToBeDeoptimized(thisParameter); } } diff --git a/src/ast/nodes/shared/MethodBase.ts b/src/ast/nodes/shared/MethodBase.ts index fb606ae81e3..757c8059d7a 100644 --- a/src/ast/nodes/shared/MethodBase.ts +++ b/src/ast/nodes/shared/MethodBase.ts @@ -1,7 +1,12 @@ import { type CallOptions, NO_ARGS } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext } from '../../ExecutionContext'; -import { EVENT_ACCESSED, EVENT_ASSIGNED, EVENT_CALLED, type NodeEvent } from '../../NodeEvents'; +import { + INTERACTION_ACCESSED, + INTERACTION_ASSIGNED, + INTERACTION_CALLED, + type NodeInteraction +} from '../../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, @@ -38,30 +43,30 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity this.getAccessedValue().deoptimizePath(path); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - if (event === EVENT_ACCESSED && this.kind === 'get' && path.length === 0) { - return this.value.deoptimizeThisOnEventAtPath( - EVENT_CALLED, + if (interaction === INTERACTION_ACCESSED && this.kind === 'get' && path.length === 0) { + return this.value.deoptimizeThisOnInteractionAtPath( + INTERACTION_CALLED, EMPTY_PATH, thisParameter, recursionTracker ); } - if (event === EVENT_ASSIGNED && this.kind === 'set' && path.length === 0) { - return this.value.deoptimizeThisOnEventAtPath( - EVENT_CALLED, + if (interaction === INTERACTION_ASSIGNED && this.kind === 'set' && path.length === 0) { + return this.value.deoptimizeThisOnInteractionAtPath( + INTERACTION_CALLED, EMPTY_PATH, thisParameter, recursionTracker ); } - this.getAccessedValue().deoptimizeThisOnEventAtPath( - event, + this.getAccessedValue().deoptimizeThisOnInteractionAtPath( + interaction, path, thisParameter, recursionTracker diff --git a/src/ast/nodes/shared/MethodTypes.ts b/src/ast/nodes/shared/MethodTypes.ts index f2cb1ea95a5..b116cbede68 100644 --- a/src/ast/nodes/shared/MethodTypes.ts +++ b/src/ast/nodes/shared/MethodTypes.ts @@ -1,6 +1,6 @@ import { type CallOptions, NO_ARGS } from '../../CallOptions'; import type { HasEffectsContext } from '../../ExecutionContext'; -import { EVENT_CALLED, type NodeEvent } from '../../NodeEvents'; +import { INTERACTION_CALLED, type NodeInteraction } from '../../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_INTEGER_PATH } from '../../utils/PathTracker'; import { UNKNOWN_LITERAL_BOOLEAN, @@ -28,12 +28,16 @@ export class Method extends ExpressionEntity { super(); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity ): void { - if (event === EVENT_CALLED && path.length === 0 && this.description.mutatesSelfAsArray) { + if ( + interaction === INTERACTION_CALLED && + path.length === 0 && + this.description.mutatesSelfAsArray + ) { thisParameter.deoptimizePath(UNKNOWN_INTEGER_PATH); } } diff --git a/src/ast/nodes/shared/ObjectEntity.ts b/src/ast/nodes/shared/ObjectEntity.ts index c1eb584b80a..16b2fdef102 100644 --- a/src/ast/nodes/shared/ObjectEntity.ts +++ b/src/ast/nodes/shared/ObjectEntity.ts @@ -1,7 +1,7 @@ import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { HasEffectsContext } from '../../ExecutionContext'; -import { EVENT_ACCESSED, EVENT_CALLED, NodeEvent } from '../../NodeEvents'; +import { INTERACTION_ACCESSED, INTERACTION_CALLED, NodeInteraction } from '../../NodeInteractions'; import { ObjectPath, ObjectPathKey, @@ -146,8 +146,8 @@ export class ObjectEntity extends ExpressionEntity { this.prototypeExpression?.deoptimizePath(path.length === 1 ? [...path, UnknownKey] : path); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker @@ -157,7 +157,7 @@ export class ObjectEntity extends ExpressionEntity { if ( this.hasLostTrack || // single paths that are deoptimized will not become getters or setters - ((event === EVENT_CALLED || path.length > 1) && + ((interaction === INTERACTION_CALLED || path.length > 1) && (this.hasUnknownDeoptimizedProperty || (typeof key === 'string' && this.deoptimizedPaths[key]))) ) { @@ -166,13 +166,13 @@ export class ObjectEntity extends ExpressionEntity { } const [propertiesForExactMatchByKey, relevantPropertiesByKey, relevantUnmatchableProperties] = - event === EVENT_CALLED || path.length > 1 + interaction === INTERACTION_CALLED || path.length > 1 ? [ this.propertiesAndGettersByKey, this.propertiesAndGettersByKey, this.unmatchablePropertiesAndGetters ] - : event === EVENT_ACCESSED + : interaction === INTERACTION_ACCESSED ? [this.propertiesAndGettersByKey, this.gettersByKey, this.unmatchableGetters] : [this.propertiesAndSettersByKey, this.settersByKey, this.unmatchableSetters]; @@ -181,7 +181,12 @@ export class ObjectEntity extends ExpressionEntity { const properties = relevantPropertiesByKey[key]; if (properties) { for (const property of properties) { - property.deoptimizeThisOnEventAtPath(event, subPath, thisParameter, recursionTracker); + property.deoptimizeThisOnInteractionAtPath( + interaction, + subPath, + thisParameter, + recursionTracker + ); } } if (!this.immutable) { @@ -190,11 +195,21 @@ export class ObjectEntity extends ExpressionEntity { return; } for (const property of relevantUnmatchableProperties) { - property.deoptimizeThisOnEventAtPath(event, subPath, thisParameter, recursionTracker); + property.deoptimizeThisOnInteractionAtPath( + interaction, + subPath, + thisParameter, + recursionTracker + ); } if (INTEGER_REG_EXP.test(key)) { for (const property of this.unknownIntegerProps) { - property.deoptimizeThisOnEventAtPath(event, subPath, thisParameter, recursionTracker); + property.deoptimizeThisOnInteractionAtPath( + interaction, + subPath, + thisParameter, + recursionTracker + ); } } } else { @@ -202,18 +217,28 @@ export class ObjectEntity extends ExpressionEntity { relevantUnmatchableProperties ])) { for (const property of properties) { - property.deoptimizeThisOnEventAtPath(event, subPath, thisParameter, recursionTracker); + property.deoptimizeThisOnInteractionAtPath( + interaction, + subPath, + thisParameter, + recursionTracker + ); } } for (const property of this.unknownIntegerProps) { - property.deoptimizeThisOnEventAtPath(event, subPath, thisParameter, recursionTracker); + property.deoptimizeThisOnInteractionAtPath( + interaction, + subPath, + thisParameter, + recursionTracker + ); } } if (!this.immutable) { this.thisParametersToBeDeoptimized.add(thisParameter); } - this.prototypeExpression?.deoptimizeThisOnEventAtPath( - event, + this.prototypeExpression?.deoptimizeThisOnInteractionAtPath( + interaction, path, thisParameter, recursionTracker diff --git a/src/ast/nodes/shared/ObjectMember.ts b/src/ast/nodes/shared/ObjectMember.ts index eded9c87631..4dcc8bf9c3b 100644 --- a/src/ast/nodes/shared/ObjectMember.ts +++ b/src/ast/nodes/shared/ObjectMember.ts @@ -1,7 +1,7 @@ import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext } from '../../ExecutionContext'; -import type { NodeEvent } from '../../NodeEvents'; +import type { NodeInteraction } from '../../NodeInteractions'; import type { ObjectPath, PathTracker } from '../../utils/PathTracker'; import { ExpressionEntity, type LiteralValueOrUnknown } from './Expression'; @@ -14,14 +14,14 @@ export class ObjectMember extends ExpressionEntity { this.object.deoptimizePath([this.key, ...path]); } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.object.deoptimizeThisOnEventAtPath( - event, + this.object.deoptimizeThisOnInteractionAtPath( + interaction, [this.key, ...path], thisParameter, recursionTracker diff --git a/src/ast/nodes/shared/ObjectPrototype.ts b/src/ast/nodes/shared/ObjectPrototype.ts index ca2b8112f61..f9ed4da0c42 100644 --- a/src/ast/nodes/shared/ObjectPrototype.ts +++ b/src/ast/nodes/shared/ObjectPrototype.ts @@ -1,4 +1,4 @@ -import { EVENT_CALLED, NodeEvent } from '../../NodeEvents'; +import { INTERACTION_CALLED, NodeInteraction } from '../../NodeInteractions'; import { ObjectPath, ObjectPathKey, UNKNOWN_PATH } from '../../utils/PathTracker'; import { ExpressionEntity, LiteralValueOrUnknown, UnknownValue } from './Expression'; import { @@ -16,12 +16,12 @@ const isInteger = (prop: ObjectPathKey): boolean => typeof prop === 'string' && // will improve tree-shaking for out-of-bounds array properties const OBJECT_PROTOTYPE_FALLBACK: ExpressionEntity = new (class ObjectPrototypeFallbackExpression extends ExpressionEntity { - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity ): void { - if (event === EVENT_CALLED && path.length === 1 && !isInteger(path[0])) { + if (interaction === INTERACTION_CALLED && path.length === 1 && !isInteger(path[0])) { thisParameter.deoptimizePath(UNKNOWN_PATH); } } diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index e585ea775a3..dc2e4301b92 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -2,7 +2,7 @@ import Module, { AstContext } from '../../Module'; import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import { createInclusionContext, HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import type { NodeEvent } from '../NodeEvents'; +import type { NodeInteraction } from '../NodeInteractions'; import type ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import type Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; @@ -81,8 +81,8 @@ export default class LocalVariable extends Variable { } } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity, recursionTracker: PathTracker @@ -93,7 +93,13 @@ export default class LocalVariable extends Variable { recursionTracker.withTrackedEntityAtPath( path, this.init, - () => this.init!.deoptimizeThisOnEventAtPath(event, path, thisParameter, recursionTracker), + () => + this.init!.deoptimizeThisOnInteractionAtPath( + interaction, + path, + thisParameter, + recursionTracker + ), undefined ); } diff --git a/src/ast/variables/ThisVariable.ts b/src/ast/variables/ThisVariable.ts index 7c4cba21045..08ee7501ee6 100644 --- a/src/ast/variables/ThisVariable.ts +++ b/src/ast/variables/ThisVariable.ts @@ -1,6 +1,6 @@ import type { AstContext } from '../../Module'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeEvent } from '../NodeEvents'; +import type { NodeInteraction } from '../NodeInteractions'; import { type ExpressionEntity, UNKNOWN_EXPRESSION } from '../nodes/shared/Expression'; import { DiscriminatedPathTracker, @@ -9,8 +9,8 @@ import { } from '../utils/PathTracker'; import LocalVariable from './LocalVariable'; -interface ThisDeoptimizationEvent { - event: NodeEvent; +interface ThisDeoptimizationInteraction { + interaction: NodeInteraction; path: ObjectPath; thisParameter: ExpressionEntity; } @@ -18,7 +18,7 @@ interface ThisDeoptimizationEvent { export default class ThisVariable extends LocalVariable { private readonly deoptimizedPaths: ObjectPath[] = []; private readonly entitiesToBeDeoptimized = new Set(); - private readonly thisDeoptimizationList: ThisDeoptimizationEvent[] = []; + private readonly thisDeoptimizationList: ThisDeoptimizationInteraction[] = []; private readonly thisDeoptimizations = new DiscriminatedPathTracker(); constructor(context: AstContext) { @@ -30,7 +30,7 @@ export default class ThisVariable extends LocalVariable { entity.deoptimizePath(path); } for (const thisDeoptimization of this.thisDeoptimizationList) { - this.applyThisDeoptimizationEvent(entity, thisDeoptimization); + this.applyThisDeoptimizationInteraction(entity, thisDeoptimization); } this.entitiesToBeDeoptimized.add(entity); } @@ -48,19 +48,21 @@ export default class ThisVariable extends LocalVariable { } } - deoptimizeThisOnEventAtPath( - event: NodeEvent, + deoptimizeThisOnInteractionAtPath( + interaction: NodeInteraction, path: ObjectPath, thisParameter: ExpressionEntity ): void { - const thisDeoptimization: ThisDeoptimizationEvent = { - event, + const thisDeoptimization: ThisDeoptimizationInteraction = { + interaction, path, thisParameter }; - if (!this.thisDeoptimizations.trackEntityAtPathAndGetIfTracked(path, event, thisParameter)) { + if ( + !this.thisDeoptimizations.trackEntityAtPathAndGetIfTracked(path, interaction, thisParameter) + ) { for (const entity of this.entitiesToBeDeoptimized) { - this.applyThisDeoptimizationEvent(entity, thisDeoptimization); + this.applyThisDeoptimizationInteraction(entity, thisDeoptimization); } this.thisDeoptimizationList.push(thisDeoptimization); } @@ -80,12 +82,12 @@ export default class ThisVariable extends LocalVariable { ); } - private applyThisDeoptimizationEvent( + private applyThisDeoptimizationInteraction( entity: ExpressionEntity, - { event, path, thisParameter }: ThisDeoptimizationEvent + { interaction, path, thisParameter }: ThisDeoptimizationInteraction ) { - entity.deoptimizeThisOnEventAtPath( - event, + entity.deoptimizeThisOnInteractionAtPath( + interaction, path, thisParameter === this ? entity : thisParameter, SHARED_RECURSION_TRACKER From ffc488831ce5be7247b3a5461e765b016edbc23b Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Thu, 2 Jun 2022 13:14:34 +0200 Subject: [PATCH 02/13] Refactor MemberExpression assignment logic to prepare for new interactions --- src/ast/nodes/AssignmentExpression.ts | 76 ++++++----- src/ast/nodes/MemberExpression.ts | 177 ++++++++++++++++---------- 2 files changed, 157 insertions(+), 96 deletions(-) diff --git a/src/ast/nodes/AssignmentExpression.ts b/src/ast/nodes/AssignmentExpression.ts index 8912b01cdb3..ee3cc1638b8 100644 --- a/src/ast/nodes/AssignmentExpression.ts +++ b/src/ast/nodes/AssignmentExpression.ts @@ -20,6 +20,7 @@ import { import { EMPTY_PATH, type ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import type Variable from '../variables/Variable'; import Identifier from './Identifier'; +import MemberExpression from './MemberExpression'; import * as NodeType from './NodeType'; import ObjectPattern from './ObjectPattern'; import { type ExpressionNode, type IncludeChildren, NodeBase } from './shared/Node'; @@ -46,10 +47,14 @@ export default class AssignmentExpression extends NodeBase { hasEffects(context: HasEffectsContext): boolean { if (!this.deoptimized) this.applyDeoptimizations(); + const { right, left } = this; + // MemberExpressions do not access the property before assignments if the + // operator is '='. Moreover, they imply a "this" value for setters. return ( - this.right.hasEffects(context) || - this.left.hasEffects(context) || - this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context) + right.hasEffects(context) || + (left instanceof MemberExpression + ? left.hasEffectsAsAssignmentTarget(context, this.operator !== '=') + : left.hasEffects(context) || left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) ); } @@ -61,17 +66,25 @@ export default class AssignmentExpression extends NodeBase { if (!this.deoptimized) this.applyDeoptimizations(); this.included = true; let hasEffectsContext; + const { left, right, operator } = this; + const isMemberExpression = left instanceof MemberExpression; if ( includeChildrenRecursively || - this.operator !== '=' || - this.left.included || + operator !== '=' || + left.included || ((hasEffectsContext = createHasEffectsContext()), - this.left.hasEffects(hasEffectsContext) || - this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, hasEffectsContext)) + isMemberExpression + ? left.hasEffectsAsAssignmentTarget(hasEffectsContext, false) + : left.hasEffects(hasEffectsContext) || + left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, hasEffectsContext)) ) { - this.left.include(context, includeChildrenRecursively); + if (isMemberExpression) { + left.includeAsAssignmentTarget(context, includeChildrenRecursively, operator !== '='); + } else { + left.include(context, includeChildrenRecursively); + } } - this.right.include(context, includeChildrenRecursively); + right.include(context, includeChildrenRecursively); } render( @@ -79,36 +92,37 @@ export default class AssignmentExpression extends NodeBase { options: RenderOptions, { preventASI, renderedParentType, renderedSurroundingElement }: NodeRenderOptions = BLANK ): void { - if (this.left.included) { - this.left.render(code, options); - this.right.render(code, options); + const { left, right, start, end, parent } = this; + if (left.included) { + left.render(code, options); + right.render(code, options); } else { const inclusionStart = findNonWhiteSpace( code.original, - findFirstOccurrenceOutsideComment(code.original, '=', this.left.end) + 1 + findFirstOccurrenceOutsideComment(code.original, '=', left.end) + 1 ); - code.remove(this.start, inclusionStart); + code.remove(start, inclusionStart); if (preventASI) { - removeLineBreaks(code, inclusionStart, this.right.start); + removeLineBreaks(code, inclusionStart, right.start); } - this.right.render(code, options, { - renderedParentType: renderedParentType || this.parent.type, - renderedSurroundingElement: renderedSurroundingElement || this.parent.type + right.render(code, options, { + renderedParentType: renderedParentType || parent.type, + renderedSurroundingElement: renderedSurroundingElement || parent.type }); } if (options.format === 'system') { - if (this.left instanceof Identifier) { - const variable = this.left.variable!; + if (left instanceof Identifier) { + const variable = left.variable!; const exportNames = options.exportNamesByVariable.get(variable); if (exportNames) { if (exportNames.length === 1) { - renderSystemExportExpression(variable, this.start, this.end, code, options); + renderSystemExportExpression(variable, start, end, code, options); } else { renderSystemExportSequenceAfterExpression( variable, - this.start, - this.end, - this.parent.type !== NodeType.ExpressionStatement, + start, + end, + parent.type !== NodeType.ExpressionStatement, code, options ); @@ -117,12 +131,12 @@ export default class AssignmentExpression extends NodeBase { } } else { const systemPatternExports: Variable[] = []; - this.left.addExportedVariables(systemPatternExports, options.exportNamesByVariable); + left.addExportedVariables(systemPatternExports, options.exportNamesByVariable); if (systemPatternExports.length > 0) { renderSystemExportFunction( systemPatternExports, - this.start, - this.end, + start, + end, renderedSurroundingElement === NodeType.ExpressionStatement, code, options @@ -132,13 +146,13 @@ export default class AssignmentExpression extends NodeBase { } } if ( - this.left.included && - this.left instanceof ObjectPattern && + left.included && + left instanceof ObjectPattern && (renderedSurroundingElement === NodeType.ExpressionStatement || renderedSurroundingElement === NodeType.ArrowFunctionExpression) ) { - code.appendRight(this.start, '('); - code.prependLeft(this.end, ')'); + code.appendRight(start, '('); + code.prependLeft(end, ')'); } } diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index a149e98da6a..869439f4531 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -1,4 +1,5 @@ import type MagicString from 'magic-string'; +import { AstContext } from '../../Module'; import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import { BLANK } from '../../utils/blank'; import relativeId from '../../utils/relativeId'; @@ -24,7 +25,6 @@ import { import ExternalVariable from '../variables/ExternalVariable'; import type NamespaceVariable from '../variables/NamespaceVariable'; import type Variable from '../variables/Variable'; -import AssignmentExpression from './AssignmentExpression'; import Identifier from './Identifier'; import Literal from './Literal'; import type * as NodeType from './NodeType'; @@ -93,6 +93,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE declare propertyKey: ObjectPathKey | null; declare type: NodeType.tMemberExpression; variable: Variable | null = null; + private assignmentDeoptimized = false; private bound = false; private expressionsToBeDeoptimized: DeoptimizableEntity[] = []; private replacement: string | null = null; @@ -102,7 +103,11 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE const path = getPathIfNotComputed(this); const baseVariable = path && this.scope.findVariable(path[0].key); if (baseVariable && baseVariable.isNamespace) { - const resolvedVariable = this.resolveNamespaceVariables(baseVariable, path!.slice(1)); + const resolvedVariable = resolveNamespaceVariables( + baseVariable, + path!.slice(1), + this.context + ); if (!resolvedVariable) { super.bind(); } else if (typeof resolvedVariable === 'string') { @@ -173,7 +178,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { - if (this.variable !== null) { + if (this.variable) { return this.variable.getLiteralValueAtPath(path, recursionTracker, origin); } if (this.replacement) { @@ -196,7 +201,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { - if (this.variable !== null) { + if (this.variable) { return this.variable.getReturnExpressionWhenCalledAtPath( path, callOptions, @@ -221,25 +226,25 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE hasEffects(context: HasEffectsContext): boolean { if (!this.deoptimized) this.applyDeoptimizations(); - const { propertyReadSideEffects } = this.context.options - .treeshake as NormalizedTreeshakingOptions; return ( this.property.hasEffects(context) || this.object.hasEffects(context) || - // Assignments do not access the property before assigning - (!( - this.variable || - this.replacement || - (this.parent instanceof AssignmentExpression && this.parent.operator === '=') - ) && - propertyReadSideEffects && - (propertyReadSideEffects === 'always' || - this.object.hasEffectsWhenAccessedAtPath([this.getPropertyKey()], context))) + this.hasAccessEffect(context) + ); + } + + hasEffectsAsAssignmentTarget(context: HasEffectsContext, checkAccess: boolean): boolean { + if (!this.assignmentDeoptimized) this.applyAssignmentDeoptimization(); + return ( + this.property.hasEffects(context) || + this.object.hasEffects(context) || + (checkAccess && this.hasAccessEffect(context)) || + this.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context) ); } hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - if (this.variable !== null) { + if (this.variable) { return this.variable.hasEffectsWhenAccessedAtPath(path, context); } if (this.replacement) { @@ -252,7 +257,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - if (this.variable !== null) { + if (this.variable) { return this.variable.hasEffectsWhenAssignedAtPath(path, context); } if (this.replacement) { @@ -269,7 +274,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE callOptions: CallOptions, context: HasEffectsContext ): boolean { - if (this.variable !== null) { + if (this.variable) { return this.variable.hasEffectsWhenCalledAtPath(path, callOptions, context); } if (this.replacement) { @@ -287,14 +292,20 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { if (!this.deoptimized) this.applyDeoptimizations(); - if (!this.included) { - this.included = true; - if (this.variable !== null) { - this.context.includeVariableInModule(this.variable); - } + this.includeProperties(context, includeChildrenRecursively); + } + + includeAsAssignmentTarget( + context: InclusionContext, + includeChildrenRecursively: IncludeChildren, + deoptimizeAccess: boolean + ): void { + if (!this.assignmentDeoptimized) this.applyAssignmentDeoptimization(); + if (deoptimizeAccess) { + this.include(context, includeChildrenRecursively); + } else { + this.includeProperties(context, includeChildrenRecursively); } - this.object.include(context, includeChildrenRecursively); - this.property.include(context, includeChildrenRecursively); } includeCallArguments( @@ -350,23 +361,33 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE propertyReadSideEffects && !(this.variable || this.replacement) ) { - // Regular Assignments do not access the property before assigning - if (!(this.parent instanceof AssignmentExpression && this.parent.operator === '=')) { - this.object.deoptimizeThisOnInteractionAtPath( - INTERACTION_ACCESSED, - [this.propertyKey!], - this.object, - SHARED_RECURSION_TRACKER - ); - } - if (this.parent instanceof AssignmentExpression) { - this.object.deoptimizeThisOnInteractionAtPath( - INTERACTION_ASSIGNED, - [this.propertyKey!], - this.object, - SHARED_RECURSION_TRACKER - ); - } + const propertyKey = this.getPropertyKey(); + this.object.deoptimizeThisOnInteractionAtPath( + INTERACTION_ACCESSED, + [propertyKey], + this.object, + SHARED_RECURSION_TRACKER + ); + this.context.requestTreeshakingPass(); + } + } + + private applyAssignmentDeoptimization(): void { + this.assignmentDeoptimized = true; + const { propertyReadSideEffects } = this.context.options + .treeshake as NormalizedTreeshakingOptions; + if ( + // Namespaces are not bound and should not be deoptimized + this.bound && + propertyReadSideEffects && + !(this.variable || this.replacement) + ) { + this.object.deoptimizeThisOnInteractionAtPath( + INTERACTION_ASSIGNED, + [this.getPropertyKey()], + this.object, + SHARED_RECURSION_TRACKER + ); this.context.requestTreeshakingPass(); } } @@ -398,29 +419,55 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE return this.propertyKey; } - private resolveNamespaceVariables( - baseVariable: Variable, - path: PathWithPositions - ): Variable | string | null { - if (path.length === 0) return baseVariable; - if (!baseVariable.isNamespace || baseVariable instanceof ExternalVariable) return null; - const exportName = path[0].key; - const variable = (baseVariable as NamespaceVariable).context.traceExport(exportName); - if (!variable) { - const fileName = (baseVariable as NamespaceVariable).context.fileName; - this.context.warn( - { - code: 'MISSING_EXPORT', - exporter: relativeId(fileName), - importer: relativeId(this.context.fileName), - message: `'${exportName}' is not exported by '${relativeId(fileName)}'`, - missing: exportName, - url: `https://rollupjs.org/guide/en/#error-name-is-not-exported-by-module` - }, - path[0].pos - ); - return 'undefined'; + private hasAccessEffect(context: HasEffectsContext) { + const { propertyReadSideEffects } = this.context.options + .treeshake as NormalizedTreeshakingOptions; + return ( + !(this.variable || this.replacement) && + propertyReadSideEffects && + (propertyReadSideEffects === 'always' || + this.object.hasEffectsWhenAccessedAtPath([this.getPropertyKey()], context)) + ); + } + + private includeProperties( + context: InclusionContext, + includeChildrenRecursively: IncludeChildren + ) { + if (!this.included) { + this.included = true; + if (this.variable) { + this.context.includeVariableInModule(this.variable); + } } - return this.resolveNamespaceVariables(variable, path.slice(1)); + this.object.include(context, includeChildrenRecursively); + this.property.include(context, includeChildrenRecursively); + } +} + +function resolveNamespaceVariables( + baseVariable: Variable, + path: PathWithPositions, + astContext: AstContext +): Variable | string | null { + if (path.length === 0) return baseVariable; + if (!baseVariable.isNamespace || baseVariable instanceof ExternalVariable) return null; + const exportName = path[0].key; + const variable = (baseVariable as NamespaceVariable).context.traceExport(exportName); + if (!variable) { + const fileName = (baseVariable as NamespaceVariable).context.fileName; + astContext.warn( + { + code: 'MISSING_EXPORT', + exporter: relativeId(fileName), + importer: relativeId(astContext.fileName), + message: `'${exportName}' is not exported by '${relativeId(fileName)}'`, + missing: exportName, + url: `https://rollupjs.org/guide/en/#error-name-is-not-exported-by-module` + }, + path[0].pos + ); + return 'undefined'; } + return resolveNamespaceVariables(variable, path.slice(1), astContext); } From b700a02240719e286da2abde45e1f10bcbfad9a3 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 3 Jun 2022 06:28:18 +0200 Subject: [PATCH 03/13] Use objects for interactions --- src/ast/NodeInteractions.ts | 22 +++++++++++++++++++--- src/ast/nodes/CallExpression.ts | 3 ++- src/ast/nodes/MemberExpression.ts | 6 ++++-- src/ast/nodes/TaggedTemplateExpression.ts | 3 ++- src/ast/nodes/shared/Expression.ts | 1 + src/ast/nodes/shared/FunctionNode.ts | 2 +- src/ast/nodes/shared/MethodBase.ts | 10 ++++++---- src/ast/nodes/shared/MethodTypes.ts | 2 +- src/ast/nodes/shared/ObjectEntity.ts | 6 +++--- src/ast/nodes/shared/ObjectPrototype.ts | 2 +- 10 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/ast/NodeInteractions.ts b/src/ast/NodeInteractions.ts index 34e54cbaa61..e1cd26e4cbc 100644 --- a/src/ast/NodeInteractions.ts +++ b/src/ast/NodeInteractions.ts @@ -1,8 +1,24 @@ +import { CallOptions } from './CallOptions'; + export const INTERACTION_ACCESSED = 0; export const INTERACTION_ASSIGNED = 1; export const INTERACTION_CALLED = 2; +export interface NodeInteractionAccessed { + type: typeof INTERACTION_ACCESSED; +} + +interface NodeInteractionAssigned { + type: typeof INTERACTION_ASSIGNED; +} + +// TODO Lukas call options should be flattened into this one +interface NodeInteractionCalled { + callOptions: CallOptions; + type: typeof INTERACTION_CALLED; +} + export type NodeInteraction = - | typeof INTERACTION_ACCESSED - | typeof INTERACTION_ASSIGNED - | typeof INTERACTION_CALLED; + | NodeInteractionAccessed + | NodeInteractionAssigned + | NodeInteractionCalled; diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index dfc144a48a9..9151d22b721 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -120,8 +120,9 @@ export default class CallExpression extends CallExpressionBase implements Deopti this.deoptimized = true; const { thisParam } = this.callOptions; if (thisParam) { + // TODO Lukas cache interaction this.callee.deoptimizeThisOnInteractionAtPath( - INTERACTION_CALLED, + { callOptions: this.callOptions, type: INTERACTION_CALLED }, EMPTY_PATH, thisParam, SHARED_RECURSION_TRACKER diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 869439f4531..13bf7dcd5ab 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -362,8 +362,9 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE !(this.variable || this.replacement) ) { const propertyKey = this.getPropertyKey(); + // TODO Lukas cache interaction this.object.deoptimizeThisOnInteractionAtPath( - INTERACTION_ACCESSED, + { type: INTERACTION_ACCESSED }, [propertyKey], this.object, SHARED_RECURSION_TRACKER @@ -382,8 +383,9 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE propertyReadSideEffects && !(this.variable || this.replacement) ) { + // TODO Lukas cache interaction this.object.deoptimizeThisOnInteractionAtPath( - INTERACTION_ASSIGNED, + { type: INTERACTION_ASSIGNED }, [this.getPropertyKey()], this.object, SHARED_RECURSION_TRACKER diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index c9bf26e60b7..80c4eb5018b 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -88,8 +88,9 @@ export default class TaggedTemplateExpression extends CallExpressionBase { this.deoptimized = true; const { thisParam } = this.callOptions; if (thisParam) { + // TODO Lukas cache interaction this.tag.deoptimizeThisOnInteractionAtPath( - INTERACTION_CALLED, + { callOptions: this.callOptions, type: INTERACTION_CALLED }, EMPTY_PATH, thisParam, SHARED_RECURSION_TRACKER diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index b366547329f..f1c9a508d57 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -28,6 +28,7 @@ export class ExpressionEntity implements WritableEntity { deoptimizeThisOnInteractionAtPath( _interaction: NodeInteraction, _path: ObjectPath, + // TODO Lukas remove thisParameter: ExpressionEntity, _recursionTracker: PathTracker ): void { diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 5946136c7b2..7ab0d58caea 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -32,7 +32,7 @@ export default class FunctionNode extends FunctionBase { recursionTracker: PathTracker ): void { super.deoptimizeThisOnInteractionAtPath(interaction, path, thisParameter, recursionTracker); - if (interaction === INTERACTION_CALLED && path.length === 0) { + if (interaction.type === INTERACTION_CALLED && path.length === 0) { this.scope.thisVariable.addEntityToBeDeoptimized(thisParameter); } } diff --git a/src/ast/nodes/shared/MethodBase.ts b/src/ast/nodes/shared/MethodBase.ts index 757c8059d7a..5a721a553fa 100644 --- a/src/ast/nodes/shared/MethodBase.ts +++ b/src/ast/nodes/shared/MethodBase.ts @@ -49,17 +49,19 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - if (interaction === INTERACTION_ACCESSED && this.kind === 'get' && path.length === 0) { + // TODO Lukas cache and share interaction with hasEffects + if (interaction.type === INTERACTION_ACCESSED && this.kind === 'get' && path.length === 0) { return this.value.deoptimizeThisOnInteractionAtPath( - INTERACTION_CALLED, + { callOptions: this.accessorCallOptions, type: INTERACTION_CALLED }, EMPTY_PATH, thisParameter, recursionTracker ); } - if (interaction === INTERACTION_ASSIGNED && this.kind === 'set' && path.length === 0) { + // TODO Lukas cache and share interaction with hasEffects + if (interaction.type === INTERACTION_ASSIGNED && this.kind === 'set' && path.length === 0) { return this.value.deoptimizeThisOnInteractionAtPath( - INTERACTION_CALLED, + { callOptions: this.accessorCallOptions, type: INTERACTION_CALLED }, EMPTY_PATH, thisParameter, recursionTracker diff --git a/src/ast/nodes/shared/MethodTypes.ts b/src/ast/nodes/shared/MethodTypes.ts index b116cbede68..260557ccee0 100644 --- a/src/ast/nodes/shared/MethodTypes.ts +++ b/src/ast/nodes/shared/MethodTypes.ts @@ -34,7 +34,7 @@ export class Method extends ExpressionEntity { thisParameter: ExpressionEntity ): void { if ( - interaction === INTERACTION_CALLED && + interaction.type === INTERACTION_CALLED && path.length === 0 && this.description.mutatesSelfAsArray ) { diff --git a/src/ast/nodes/shared/ObjectEntity.ts b/src/ast/nodes/shared/ObjectEntity.ts index 16b2fdef102..68fe3ddf24f 100644 --- a/src/ast/nodes/shared/ObjectEntity.ts +++ b/src/ast/nodes/shared/ObjectEntity.ts @@ -157,7 +157,7 @@ export class ObjectEntity extends ExpressionEntity { if ( this.hasLostTrack || // single paths that are deoptimized will not become getters or setters - ((interaction === INTERACTION_CALLED || path.length > 1) && + ((interaction.type === INTERACTION_CALLED || path.length > 1) && (this.hasUnknownDeoptimizedProperty || (typeof key === 'string' && this.deoptimizedPaths[key]))) ) { @@ -166,13 +166,13 @@ export class ObjectEntity extends ExpressionEntity { } const [propertiesForExactMatchByKey, relevantPropertiesByKey, relevantUnmatchableProperties] = - interaction === INTERACTION_CALLED || path.length > 1 + interaction.type === INTERACTION_CALLED || path.length > 1 ? [ this.propertiesAndGettersByKey, this.propertiesAndGettersByKey, this.unmatchablePropertiesAndGetters ] - : interaction === INTERACTION_ACCESSED + : interaction.type === INTERACTION_ACCESSED ? [this.propertiesAndGettersByKey, this.gettersByKey, this.unmatchableGetters] : [this.propertiesAndSettersByKey, this.settersByKey, this.unmatchableSetters]; diff --git a/src/ast/nodes/shared/ObjectPrototype.ts b/src/ast/nodes/shared/ObjectPrototype.ts index f9ed4da0c42..1b7ee38330d 100644 --- a/src/ast/nodes/shared/ObjectPrototype.ts +++ b/src/ast/nodes/shared/ObjectPrototype.ts @@ -21,7 +21,7 @@ const OBJECT_PROTOTYPE_FALLBACK: ExpressionEntity = path: ObjectPath, thisParameter: ExpressionEntity ): void { - if (interaction === INTERACTION_CALLED && path.length === 1 && !isInteger(path[0])) { + if (interaction.type === INTERACTION_CALLED && path.length === 1 && !isInteger(path[0])) { thisParameter.deoptimizePath(UNKNOWN_PATH); } } From b40eab31092e1f45f8bf0fb06a56d47c74b51f26 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 3 Jun 2022 07:24:36 +0200 Subject: [PATCH 04/13] Add additional interaction parameters --- src/ast/CallOptions.ts | 4 +--- src/ast/NodeInteractions.ts | 13 ++++++++++--- src/ast/nodes/AssignmentExpression.ts | 11 ++++++++--- src/ast/nodes/CallExpression.ts | 10 +++++----- src/ast/nodes/MemberExpression.ts | 19 ++++++++++++------- src/ast/nodes/NewExpression.ts | 2 +- src/ast/nodes/TaggedTemplateExpression.ts | 11 +++++------ src/ast/nodes/shared/FunctionBase.ts | 6 +++--- src/ast/nodes/shared/MethodBase.ts | 14 ++++++++++---- src/ast/nodes/shared/MethodTypes.ts | 10 +++++----- src/ast/values.ts | 5 +++-- 11 files changed, 63 insertions(+), 42 deletions(-) diff --git a/src/ast/CallOptions.ts b/src/ast/CallOptions.ts index ff5b2c0601a..9c400929a88 100644 --- a/src/ast/CallOptions.ts +++ b/src/ast/CallOptions.ts @@ -1,10 +1,8 @@ import type SpreadElement from './nodes/SpreadElement'; import type { ExpressionEntity } from './nodes/shared/Expression'; -export const NO_ARGS = []; - export interface CallOptions { args: (ExpressionEntity | SpreadElement)[]; - thisParam: ExpressionEntity | null; + thisArg: ExpressionEntity | null; withNew: boolean; } diff --git a/src/ast/NodeInteractions.ts b/src/ast/NodeInteractions.ts index e1cd26e4cbc..c0466bc9b7c 100644 --- a/src/ast/NodeInteractions.ts +++ b/src/ast/NodeInteractions.ts @@ -1,23 +1,30 @@ -import { CallOptions } from './CallOptions'; +import SpreadElement from './nodes/SpreadElement'; +import { ExpressionEntity } from './nodes/shared/Expression'; export const INTERACTION_ACCESSED = 0; export const INTERACTION_ASSIGNED = 1; export const INTERACTION_CALLED = 2; export interface NodeInteractionAccessed { + thisArg: ExpressionEntity | null; type: typeof INTERACTION_ACCESSED; } interface NodeInteractionAssigned { + thisArg: ExpressionEntity | null; type: typeof INTERACTION_ASSIGNED; + value: ExpressionEntity; } -// TODO Lukas call options should be flattened into this one interface NodeInteractionCalled { - callOptions: CallOptions; + args: (ExpressionEntity | SpreadElement)[]; + thisArg: ExpressionEntity | null; type: typeof INTERACTION_CALLED; + withNew: boolean; } +export const NO_ARGS = []; + export type NodeInteraction = | NodeInteractionAccessed | NodeInteractionAssigned diff --git a/src/ast/nodes/AssignmentExpression.ts b/src/ast/nodes/AssignmentExpression.ts index ee3cc1638b8..4f31517b793 100644 --- a/src/ast/nodes/AssignmentExpression.ts +++ b/src/ast/nodes/AssignmentExpression.ts @@ -53,7 +53,7 @@ export default class AssignmentExpression extends NodeBase { return ( right.hasEffects(context) || (left instanceof MemberExpression - ? left.hasEffectsAsAssignmentTarget(context, this.operator !== '=') + ? left.hasEffectsAsAssignmentTarget(context, this.operator !== '=', right) : left.hasEffects(context) || left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) ); } @@ -74,12 +74,17 @@ export default class AssignmentExpression extends NodeBase { left.included || ((hasEffectsContext = createHasEffectsContext()), isMemberExpression - ? left.hasEffectsAsAssignmentTarget(hasEffectsContext, false) + ? left.hasEffectsAsAssignmentTarget(hasEffectsContext, false, right) : left.hasEffects(hasEffectsContext) || left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, hasEffectsContext)) ) { if (isMemberExpression) { - left.includeAsAssignmentTarget(context, includeChildrenRecursively, operator !== '='); + left.includeAsAssignmentTarget( + context, + includeChildrenRecursively, + operator !== '=', + right + ); } else { left.include(context, includeChildrenRecursively); } diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 9151d22b721..1e43f208351 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -55,7 +55,7 @@ export default class CallExpression extends CallExpressionBase implements Deopti } this.callOptions = { args: this.arguments, - thisParam: + thisArg: this.callee instanceof MemberExpression && !this.callee.variable ? this.callee.object : null, @@ -118,13 +118,13 @@ export default class CallExpression extends CallExpressionBase implements Deopti protected applyDeoptimizations(): void { this.deoptimized = true; - const { thisParam } = this.callOptions; - if (thisParam) { + const { args, thisArg, withNew } = this.callOptions; + if (thisArg) { // TODO Lukas cache interaction this.callee.deoptimizeThisOnInteractionAtPath( - { callOptions: this.callOptions, type: INTERACTION_CALLED }, + { args, thisArg, type: INTERACTION_CALLED, withNew }, EMPTY_PATH, - thisParam, + thisArg, SHARED_RECURSION_TRACKER ); } diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 13bf7dcd5ab..acedfe3fc19 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -233,8 +233,12 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - hasEffectsAsAssignmentTarget(context: HasEffectsContext, checkAccess: boolean): boolean { - if (!this.assignmentDeoptimized) this.applyAssignmentDeoptimization(); + hasEffectsAsAssignmentTarget( + context: HasEffectsContext, + checkAccess: boolean, + value: ExpressionEntity + ): boolean { + if (!this.assignmentDeoptimized) this.applyAssignmentDeoptimization(value); return ( this.property.hasEffects(context) || this.object.hasEffects(context) || @@ -298,9 +302,10 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE includeAsAssignmentTarget( context: InclusionContext, includeChildrenRecursively: IncludeChildren, - deoptimizeAccess: boolean + deoptimizeAccess: boolean, + value: ExpressionEntity ): void { - if (!this.assignmentDeoptimized) this.applyAssignmentDeoptimization(); + if (!this.assignmentDeoptimized) this.applyAssignmentDeoptimization(value); if (deoptimizeAccess) { this.include(context, includeChildrenRecursively); } else { @@ -364,7 +369,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE const propertyKey = this.getPropertyKey(); // TODO Lukas cache interaction this.object.deoptimizeThisOnInteractionAtPath( - { type: INTERACTION_ACCESSED }, + { thisArg: this.object, type: INTERACTION_ACCESSED }, [propertyKey], this.object, SHARED_RECURSION_TRACKER @@ -373,7 +378,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } } - private applyAssignmentDeoptimization(): void { + private applyAssignmentDeoptimization(value: ExpressionEntity): void { this.assignmentDeoptimized = true; const { propertyReadSideEffects } = this.context.options .treeshake as NormalizedTreeshakingOptions; @@ -385,7 +390,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ) { // TODO Lukas cache interaction this.object.deoptimizeThisOnInteractionAtPath( - { type: INTERACTION_ASSIGNED }, + { thisArg: this.object, type: INTERACTION_ASSIGNED, value }, [this.getPropertyKey()], this.object, SHARED_RECURSION_TRACKER diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index 7f395041ff8..5582606f156 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -52,7 +52,7 @@ export default class NewExpression extends NodeBase { initialise(): void { this.callOptions = { args: this.arguments, - thisParam: null, + thisArg: null, withNew: true }; } diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index 80c4eb5018b..656c3c3e089 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -73,8 +73,7 @@ export default class TaggedTemplateExpression extends CallExpressionBase { initialise(): void { this.callOptions = { args: [UNKNOWN_EXPRESSION, ...this.quasi.expressions], - thisParam: - this.tag instanceof MemberExpression && !this.tag.variable ? this.tag.object : null, + thisArg: this.tag instanceof MemberExpression && !this.tag.variable ? this.tag.object : null, withNew: false }; } @@ -86,13 +85,13 @@ export default class TaggedTemplateExpression extends CallExpressionBase { protected applyDeoptimizations(): void { this.deoptimized = true; - const { thisParam } = this.callOptions; - if (thisParam) { + const { args, thisArg, withNew } = this.callOptions; + if (thisArg) { // TODO Lukas cache interaction this.tag.deoptimizeThisOnInteractionAtPath( - { callOptions: this.callOptions, type: INTERACTION_CALLED }, + { args, thisArg, type: INTERACTION_CALLED, withNew }, EMPTY_PATH, - thisParam, + thisArg, SHARED_RECURSION_TRACKER ); } diff --git a/src/ast/nodes/shared/FunctionBase.ts b/src/ast/nodes/shared/FunctionBase.ts index 5abfa8aef04..51993600bd0 100644 --- a/src/ast/nodes/shared/FunctionBase.ts +++ b/src/ast/nodes/shared/FunctionBase.ts @@ -1,12 +1,12 @@ import type { NormalizedTreeshakingOptions } from '../../../rollup/types'; -import { type CallOptions, NO_ARGS } from '../../CallOptions'; +import { type CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { BROKEN_FLOW_NONE, type HasEffectsContext, type InclusionContext } from '../../ExecutionContext'; -import { NodeInteraction } from '../../NodeInteractions'; +import { NO_ARGS, NodeInteraction } from '../../NodeInteractions'; import ReturnValueScope from '../../scopes/ReturnValueScope'; import { type ObjectPath, PathTracker, UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; import BlockStatement from '../BlockStatement'; @@ -113,7 +113,7 @@ export default abstract class FunctionBase extends NodeBase { if ( returnExpression.hasEffectsWhenCalledAtPath( ['then'], - { args: NO_ARGS, thisParam: null, withNew: false }, + { args: NO_ARGS, thisArg: null, withNew: false }, context ) || (propertyReadSideEffects && diff --git a/src/ast/nodes/shared/MethodBase.ts b/src/ast/nodes/shared/MethodBase.ts index 5a721a553fa..968243f0361 100644 --- a/src/ast/nodes/shared/MethodBase.ts +++ b/src/ast/nodes/shared/MethodBase.ts @@ -1,10 +1,11 @@ -import { type CallOptions, NO_ARGS } from '../../CallOptions'; +import { type CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext } from '../../ExecutionContext'; import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, INTERACTION_CALLED, + NO_ARGS, type NodeInteraction } from '../../NodeInteractions'; import { @@ -31,7 +32,7 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity private accessedValue: ExpressionEntity | null = null; private accessorCallOptions: CallOptions = { args: NO_ARGS, - thisParam: null, + thisArg: null, withNew: false }; @@ -52,7 +53,7 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity // TODO Lukas cache and share interaction with hasEffects if (interaction.type === INTERACTION_ACCESSED && this.kind === 'get' && path.length === 0) { return this.value.deoptimizeThisOnInteractionAtPath( - { callOptions: this.accessorCallOptions, type: INTERACTION_CALLED }, + { args: NO_ARGS, thisArg: interaction.thisArg, type: INTERACTION_CALLED, withNew: false }, EMPTY_PATH, thisParameter, recursionTracker @@ -61,7 +62,12 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity // TODO Lukas cache and share interaction with hasEffects if (interaction.type === INTERACTION_ASSIGNED && this.kind === 'set' && path.length === 0) { return this.value.deoptimizeThisOnInteractionAtPath( - { callOptions: this.accessorCallOptions, type: INTERACTION_CALLED }, + { + args: [interaction.value], + thisArg: interaction.thisArg, + type: INTERACTION_CALLED, + withNew: false + }, EMPTY_PATH, thisParameter, recursionTracker diff --git a/src/ast/nodes/shared/MethodTypes.ts b/src/ast/nodes/shared/MethodTypes.ts index 260557ccee0..9defaf8acb8 100644 --- a/src/ast/nodes/shared/MethodTypes.ts +++ b/src/ast/nodes/shared/MethodTypes.ts @@ -1,6 +1,6 @@ -import { type CallOptions, NO_ARGS } from '../../CallOptions'; +import { type CallOptions } from '../../CallOptions'; import type { HasEffectsContext } from '../../ExecutionContext'; -import { INTERACTION_CALLED, type NodeInteraction } from '../../NodeInteractions'; +import { INTERACTION_CALLED, NO_ARGS, type NodeInteraction } from '../../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_INTEGER_PATH } from '../../utils/PathTracker'; import { UNKNOWN_LITERAL_BOOLEAN, @@ -52,7 +52,7 @@ export class Method extends ExpressionEntity { return ( this.description.returnsPrimitive || (this.description.returns === 'self' - ? callOptions.thisParam || UNKNOWN_EXPRESSION + ? callOptions.thisArg || UNKNOWN_EXPRESSION : this.description.returns()) ); } @@ -73,7 +73,7 @@ export class Method extends ExpressionEntity { if ( path.length > 0 || (this.description.mutatesSelfAsArray === true && - callOptions.thisParam?.hasEffectsWhenAssignedAtPath(UNKNOWN_INTEGER_PATH, context)) + callOptions.thisArg?.hasEffectsWhenAssignedAtPath(UNKNOWN_INTEGER_PATH, context)) ) { return true; } @@ -86,7 +86,7 @@ export class Method extends ExpressionEntity { EMPTY_PATH, { args: NO_ARGS, - thisParam: null, + thisArg: null, withNew: false }, context diff --git a/src/ast/values.ts b/src/ast/values.ts index b6f0f90eb84..39f9f5c1991 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -1,5 +1,6 @@ -import { type CallOptions, NO_ARGS } from './CallOptions'; +import { type CallOptions } from './CallOptions'; import type { HasEffectsContext } from './ExecutionContext'; +import { NO_ARGS } from './NodeInteractions'; import type { LiteralValue } from './nodes/Literal'; import { ExpressionEntity, UNKNOWN_EXPRESSION } from './nodes/shared/Expression'; import { @@ -152,7 +153,7 @@ const stringReplace: RawMemberDescription = { EMPTY_PATH, { args: NO_ARGS, - thisParam: null, + thisArg: null, withNew: false }, context From 443a93795305600c1acf4a9c21e88060b1ab9f8c Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 3 Jun 2022 11:11:58 +0200 Subject: [PATCH 05/13] Pick "this"-argument from interaction --- src/ast/NodeInteractions.ts | 2 + src/ast/nodes/ArrayExpression.ts | 12 ++--- src/ast/nodes/CallExpression.ts | 1 - src/ast/nodes/ConditionalExpression.ts | 19 ++------ src/ast/nodes/Identifier.ts | 12 ++--- src/ast/nodes/LogicalExpression.ts | 14 ++---- src/ast/nodes/MemberExpression.ts | 17 ++------ src/ast/nodes/ObjectExpression.ts | 12 ++--- src/ast/nodes/PropertyDefinition.ts | 12 ++--- src/ast/nodes/SequenceExpression.ts | 8 ++-- src/ast/nodes/SpreadElement.ts | 7 +-- src/ast/nodes/Super.ts | 13 ++---- src/ast/nodes/TaggedTemplateExpression.ts | 1 - src/ast/nodes/ThisExpression.ts | 11 ++--- src/ast/nodes/shared/CallExpressionBase.ts | 16 +++---- src/ast/nodes/shared/ClassNode.ts | 16 +++---- src/ast/nodes/shared/Expression.ts | 8 ++-- src/ast/nodes/shared/FunctionBase.ts | 12 ++--- src/ast/nodes/shared/FunctionNode.ts | 11 +++-- src/ast/nodes/shared/MethodBase.ts | 14 ++---- src/ast/nodes/shared/MethodTypes.ts | 15 +++---- src/ast/nodes/shared/ObjectEntity.ts | 51 ++++++---------------- src/ast/nodes/shared/ObjectMember.ts | 6 +-- src/ast/nodes/shared/ObjectPrototype.ts | 11 +++-- src/ast/variables/LocalVariable.ts | 15 ++----- src/ast/variables/ThisVariable.ts | 37 ++++++---------- 26 files changed, 105 insertions(+), 248 deletions(-) diff --git a/src/ast/NodeInteractions.ts b/src/ast/NodeInteractions.ts index c0466bc9b7c..8464d4219fc 100644 --- a/src/ast/NodeInteractions.ts +++ b/src/ast/NodeInteractions.ts @@ -29,3 +29,5 @@ export type NodeInteraction = | NodeInteractionAccessed | NodeInteractionAssigned | NodeInteractionCalled; + +export type NodeInteractionWithThisArg = NodeInteraction & { thisArg: ExpressionEntity }; diff --git a/src/ast/nodes/ArrayExpression.ts b/src/ast/nodes/ArrayExpression.ts index c41321ed1f8..485351c845a 100644 --- a/src/ast/nodes/ArrayExpression.ts +++ b/src/ast/nodes/ArrayExpression.ts @@ -1,7 +1,7 @@ import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeInteraction } from '../NodeInteractions'; +import type { NodeInteractionWithThisArg } from '../NodeInteractions'; import { type ObjectPath, type PathTracker, @@ -26,17 +26,11 @@ export default class ArrayExpression extends NodeBase { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.getObjectEntity().deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ); + this.getObjectEntity().deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); } getLiteralValueAtPath( diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 1e43f208351..b56fc913cd2 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -124,7 +124,6 @@ export default class CallExpression extends CallExpressionBase implements Deopti this.callee.deoptimizeThisOnInteractionAtPath( { args, thisArg, type: INTERACTION_CALLED, withNew }, EMPTY_PATH, - thisArg, SHARED_RECURSION_TRACKER ); } diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index e97324efa2d..4c8aa2fcbd3 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -11,7 +11,7 @@ import { removeAnnotations } from '../../utils/treeshakeNode'; import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import { NodeInteraction } from '../NodeInteractions'; +import { NodeInteractionWithThisArg } from '../NodeInteractions'; import { EMPTY_PATH, ObjectPath, @@ -57,23 +57,12 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.consequent.deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ); - this.alternate.deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ); + this.consequent.deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); + this.alternate.deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); } getLiteralValueAtPath( diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index b46bb3470dd..d0033598015 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -6,7 +6,7 @@ import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import type { NodeInteraction } from '../NodeInteractions'; +import type { NodeInteractionWithThisArg } from '../NodeInteractions'; import type FunctionScope from '../scopes/FunctionScope'; import { EMPTY_PATH, type ObjectPath, type PathTracker } from '../utils/PathTracker'; import GlobalVariable from '../variables/GlobalVariable'; @@ -96,17 +96,11 @@ export default class Identifier extends NodeBase implements PatternNode { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.variable!.deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ); + this.variable!.deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); } getLiteralValueAtPath( diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index d00ea11d70b..cf9a6d6ccaa 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -11,7 +11,7 @@ import { removeAnnotations } from '../../utils/treeshakeNode'; import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import type { NodeInteraction } from '../NodeInteractions'; +import type { NodeInteractionWithThisArg } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, @@ -66,18 +66,12 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.left.deoptimizeThisOnInteractionAtPath(interaction, path, thisParameter, recursionTracker); - this.right.deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ); + this.left.deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); + this.right.deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); } getLiteralValueAtPath( diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index acedfe3fc19..c3e0d56bd89 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -10,7 +10,7 @@ import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, - type NodeInteraction + NodeInteractionWithThisArg } from '../NodeInteractions'; import { EMPTY_PATH, @@ -147,28 +147,21 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { if (this.variable) { - this.variable.deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ); + this.variable.deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); } else if (!this.replacement) { if (path.length < MAX_PATH_DEPTH) { this.object.deoptimizeThisOnInteractionAtPath( interaction, [this.getPropertyKey(), ...path], - thisParameter, recursionTracker ); } else { - thisParameter.deoptimizePath(UNKNOWN_PATH); + interaction.thisArg.deoptimizePath(UNKNOWN_PATH); } } } @@ -371,7 +364,6 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE this.object.deoptimizeThisOnInteractionAtPath( { thisArg: this.object, type: INTERACTION_ACCESSED }, [propertyKey], - this.object, SHARED_RECURSION_TRACKER ); this.context.requestTreeshakingPass(); @@ -392,7 +384,6 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE this.object.deoptimizeThisOnInteractionAtPath( { thisArg: this.object, type: INTERACTION_ASSIGNED, value }, [this.getPropertyKey()], - this.object, SHARED_RECURSION_TRACKER ); this.context.requestTreeshakingPass(); diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index 07ef43d3b42..7bd10073080 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -4,7 +4,7 @@ import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeInteraction } from '../NodeInteractions'; +import type { NodeInteractionWithThisArg } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, @@ -36,17 +36,11 @@ export default class ObjectExpression extends NodeBase implements DeoptimizableE } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.getObjectEntity().deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ); + this.getObjectEntity().deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); } getLiteralValueAtPath( diff --git a/src/ast/nodes/PropertyDefinition.ts b/src/ast/nodes/PropertyDefinition.ts index 03575cec999..04277ed495d 100644 --- a/src/ast/nodes/PropertyDefinition.ts +++ b/src/ast/nodes/PropertyDefinition.ts @@ -1,7 +1,7 @@ import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeInteraction } from '../NodeInteractions'; +import type { NodeInteractionWithThisArg } from '../NodeInteractions'; import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; import type PrivateIdentifier from './PrivateIdentifier'; @@ -25,17 +25,11 @@ export default class PropertyDefinition extends NodeBase { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.value?.deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ); + this.value?.deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); } getLiteralValueAtPath( diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index 095871863a4..7d3279f4eb6 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -10,11 +10,11 @@ import { treeshakeNode } from '../../utils/treeshakeNode'; import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import type { NodeInteraction } from '../NodeInteractions'; +import type { NodeInteractionWithThisArg } from '../NodeInteractions'; import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import ExpressionStatement from './ExpressionStatement'; import type * as NodeType from './NodeType'; -import type { ExpressionEntity, LiteralValueOrUnknown } from './shared/Expression'; +import type { LiteralValueOrUnknown } from './shared/Expression'; import { type ExpressionNode, type IncludeChildren, NodeBase } from './shared/Node'; export default class SequenceExpression extends NodeBase { @@ -26,15 +26,13 @@ export default class SequenceExpression extends NodeBase { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { this.expressions[this.expressions.length - 1].deoptimizeThisOnInteractionAtPath( interaction, path, - thisParameter, recursionTracker ); } diff --git a/src/ast/nodes/SpreadElement.ts b/src/ast/nodes/SpreadElement.ts index 8b50543973a..d2422e8c765 100644 --- a/src/ast/nodes/SpreadElement.ts +++ b/src/ast/nodes/SpreadElement.ts @@ -1,9 +1,8 @@ import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeInteraction } from '../NodeInteractions'; +import type { NodeInteractionWithThisArg } from '../NodeInteractions'; import { type ObjectPath, type PathTracker, UNKNOWN_PATH, UnknownKey } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; -import type { ExpressionEntity } from './shared/Expression'; import { type ExpressionNode, NodeBase } from './shared/Node'; export default class SpreadElement extends NodeBase { @@ -11,16 +10,14 @@ export default class SpreadElement extends NodeBase { declare type: NodeType.tSpreadElement; deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { if (path.length > 0) { this.argument.deoptimizeThisOnInteractionAtPath( interaction, [UnknownKey, ...path], - thisParameter, recursionTracker ); } diff --git a/src/ast/nodes/Super.ts b/src/ast/nodes/Super.ts index 3512a27be00..dcaee1c292c 100644 --- a/src/ast/nodes/Super.ts +++ b/src/ast/nodes/Super.ts @@ -1,9 +1,8 @@ -import { NodeInteraction } from '../NodeInteractions'; +import { NodeInteractionWithThisArg } from '../NodeInteractions'; import type { ObjectPath } from '../utils/PathTracker'; import { PathTracker } from '../utils/PathTracker'; import Variable from '../variables/Variable'; import type * as NodeType from './NodeType'; -import { ExpressionEntity } from './shared/Expression'; import { NodeBase } from './shared/Node'; export default class Super extends NodeBase { @@ -19,17 +18,11 @@ export default class Super extends NodeBase { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ) { - this.variable.deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ); + this.variable.deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); } include(): void { diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index 656c3c3e089..91c54ad1ab3 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -91,7 +91,6 @@ export default class TaggedTemplateExpression extends CallExpressionBase { this.tag.deoptimizeThisOnInteractionAtPath( { args, thisArg, type: INTERACTION_CALLED, withNew }, EMPTY_PATH, - thisArg, SHARED_RECURSION_TRACKER ); } diff --git a/src/ast/nodes/ThisExpression.ts b/src/ast/nodes/ThisExpression.ts index 2fcd0ad6d1f..2cf1b8cbd06 100644 --- a/src/ast/nodes/ThisExpression.ts +++ b/src/ast/nodes/ThisExpression.ts @@ -1,11 +1,10 @@ import type MagicString from 'magic-string'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeInteraction } from '../NodeInteractions'; +import type { NodeInteractionWithThisArg } from '../NodeInteractions'; import ModuleScope from '../scopes/ModuleScope'; import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import type Variable from '../variables/Variable'; import type * as NodeType from './NodeType'; -import type { ExpressionEntity } from './shared/Expression'; import { NodeBase } from './shared/Node'; export default class ThisExpression extends NodeBase { @@ -22,16 +21,14 @@ export default class ThisExpression extends NodeBase { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { + // We rewrite the parameter so that a ThisVariable can detect self-mutations this.variable.deoptimizeThisOnInteractionAtPath( - interaction, + interaction.thisArg === this ? { ...interaction, thisArg: this.variable } : interaction, path, - // We rewrite the parameter so that a ThisVariable can detect self-mutations - thisParameter === this ? this.variable : thisParameter, recursionTracker ); } diff --git a/src/ast/nodes/shared/CallExpressionBase.ts b/src/ast/nodes/shared/CallExpressionBase.ts index 3300eaa102c..fa53ba1f53a 100644 --- a/src/ast/nodes/shared/CallExpressionBase.ts +++ b/src/ast/nodes/shared/CallExpressionBase.ts @@ -1,7 +1,7 @@ import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext } from '../../ExecutionContext'; -import { type NodeInteraction } from '../../NodeInteractions'; +import { NodeInteractionWithThisArg } from '../../NodeInteractions'; import { type ObjectPath, type PathTracker, UNKNOWN_PATH } from '../../utils/PathTracker'; import { type ExpressionEntity, @@ -43,26 +43,20 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { const returnExpression = this.getReturnExpression(recursionTracker); if (returnExpression === UNKNOWN_EXPRESSION) { - thisParameter.deoptimizePath(UNKNOWN_PATH); + interaction.thisArg.deoptimizePath(UNKNOWN_PATH); } else { recursionTracker.withTrackedEntityAtPath( path, returnExpression, () => { - this.expressionsToBeDeoptimized.add(thisParameter); - returnExpression.deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ); + this.expressionsToBeDeoptimized.add(interaction.thisArg); + returnExpression.deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); }, undefined ); diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index 42c0a7a112c..6df586e32e5 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -1,7 +1,7 @@ import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; -import type { NodeInteraction } from '../../NodeInteractions'; +import type { NodeInteractionWithThisArg } from '../../NodeInteractions'; import ChildScope from '../../scopes/ChildScope'; import type Scope from '../../scopes/Scope'; import { @@ -42,17 +42,11 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, - path: ObjectPath, - thisParameter: ExpressionEntity, - recursionTracker: PathTracker + interaction: NodeInteractionWithThisArg, + _path: ObjectPath, + _recursionTracker: PathTracker ): void { - this.getObjectEntity().deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ); + this.getObjectEntity().deoptimizeThisOnInteractionAtPath(interaction, _path, _recursionTracker); } getLiteralValueAtPath( diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index f1c9a508d57..f783520ca12 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -2,7 +2,7 @@ import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { WritableEntity } from '../../Entity'; import { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; -import { NodeInteraction } from '../../NodeInteractions'; +import { NodeInteractionWithThisArg } from '../../NodeInteractions'; import { ObjectPath, PathTracker, UNKNOWN_PATH } from '../../utils/PathTracker'; import { LiteralValue } from '../Literal'; import SpreadElement from '../SpreadElement'; @@ -26,13 +26,11 @@ export class ExpressionEntity implements WritableEntity { deoptimizePath(_path: ObjectPath): void {} deoptimizeThisOnInteractionAtPath( - _interaction: NodeInteraction, + { thisArg }: NodeInteractionWithThisArg, _path: ObjectPath, - // TODO Lukas remove - thisParameter: ExpressionEntity, _recursionTracker: PathTracker ): void { - thisParameter.deoptimizePath(UNKNOWN_PATH); + thisArg!.deoptimizePath(UNKNOWN_PATH); } /** diff --git a/src/ast/nodes/shared/FunctionBase.ts b/src/ast/nodes/shared/FunctionBase.ts index 51993600bd0..6b9f0d8e435 100644 --- a/src/ast/nodes/shared/FunctionBase.ts +++ b/src/ast/nodes/shared/FunctionBase.ts @@ -6,7 +6,7 @@ import { type HasEffectsContext, type InclusionContext } from '../../ExecutionContext'; -import { NO_ARGS, NodeInteraction } from '../../NodeInteractions'; +import { NO_ARGS, NodeInteractionWithThisArg } from '../../NodeInteractions'; import ReturnValueScope from '../../scopes/ReturnValueScope'; import { type ObjectPath, PathTracker, UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; import BlockStatement from '../BlockStatement'; @@ -42,18 +42,12 @@ export default abstract class FunctionBase extends NodeBase { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { if (path.length > 0) { - this.getObjectEntity().deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ); + this.getObjectEntity().deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); } } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 7ab0d58caea..d4d8d3f5f6d 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -1,11 +1,11 @@ import { type CallOptions } from '../../CallOptions'; import { type HasEffectsContext, type InclusionContext } from '../../ExecutionContext'; -import { INTERACTION_CALLED, type NodeInteraction } from '../../NodeInteractions'; +import { INTERACTION_CALLED, NodeInteractionWithThisArg } from '../../NodeInteractions'; import FunctionScope from '../../scopes/FunctionScope'; import { type ObjectPath, PathTracker } from '../../utils/PathTracker'; import BlockStatement from '../BlockStatement'; import Identifier, { type IdentifierWithVariable } from '../Identifier'; -import { type ExpressionEntity, UNKNOWN_EXPRESSION } from './Expression'; +import { UNKNOWN_EXPRESSION } from './Expression'; import FunctionBase from './FunctionBase'; import { type IncludeChildren } from './Node'; import { ObjectEntity } from './ObjectEntity'; @@ -26,14 +26,13 @@ export default class FunctionNode extends FunctionBase { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - super.deoptimizeThisOnInteractionAtPath(interaction, path, thisParameter, recursionTracker); + super.deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); if (interaction.type === INTERACTION_CALLED && path.length === 0) { - this.scope.thisVariable.addEntityToBeDeoptimized(thisParameter); + this.scope.thisVariable.addEntityToBeDeoptimized(interaction.thisArg); } } diff --git a/src/ast/nodes/shared/MethodBase.ts b/src/ast/nodes/shared/MethodBase.ts index 968243f0361..e417d2e83a6 100644 --- a/src/ast/nodes/shared/MethodBase.ts +++ b/src/ast/nodes/shared/MethodBase.ts @@ -6,7 +6,7 @@ import { INTERACTION_ASSIGNED, INTERACTION_CALLED, NO_ARGS, - type NodeInteraction + NodeInteractionWithThisArg } from '../../NodeInteractions'; import { EMPTY_PATH, @@ -45,9 +45,8 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { // TODO Lukas cache and share interaction with hasEffects @@ -55,7 +54,6 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity return this.value.deoptimizeThisOnInteractionAtPath( { args: NO_ARGS, thisArg: interaction.thisArg, type: INTERACTION_CALLED, withNew: false }, EMPTY_PATH, - thisParameter, recursionTracker ); } @@ -69,16 +67,10 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity withNew: false }, EMPTY_PATH, - thisParameter, recursionTracker ); } - this.getAccessedValue().deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ); + this.getAccessedValue().deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); } getLiteralValueAtPath( diff --git a/src/ast/nodes/shared/MethodTypes.ts b/src/ast/nodes/shared/MethodTypes.ts index 9defaf8acb8..e7cc66ee98a 100644 --- a/src/ast/nodes/shared/MethodTypes.ts +++ b/src/ast/nodes/shared/MethodTypes.ts @@ -1,6 +1,6 @@ import { type CallOptions } from '../../CallOptions'; import type { HasEffectsContext } from '../../ExecutionContext'; -import { INTERACTION_CALLED, NO_ARGS, type NodeInteraction } from '../../NodeInteractions'; +import { INTERACTION_CALLED, NO_ARGS, NodeInteractionWithThisArg } from '../../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_INTEGER_PATH } from '../../utils/PathTracker'; import { UNKNOWN_LITERAL_BOOLEAN, @@ -29,16 +29,11 @@ export class Method extends ExpressionEntity { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, - path: ObjectPath, - thisParameter: ExpressionEntity + { type, thisArg }: NodeInteractionWithThisArg, + path: ObjectPath ): void { - if ( - interaction.type === INTERACTION_CALLED && - path.length === 0 && - this.description.mutatesSelfAsArray - ) { - thisParameter.deoptimizePath(UNKNOWN_INTEGER_PATH); + if (type === INTERACTION_CALLED && path.length === 0 && this.description.mutatesSelfAsArray) { + thisArg.deoptimizePath(UNKNOWN_INTEGER_PATH); } } diff --git a/src/ast/nodes/shared/ObjectEntity.ts b/src/ast/nodes/shared/ObjectEntity.ts index 68fe3ddf24f..10c11f4fa5c 100644 --- a/src/ast/nodes/shared/ObjectEntity.ts +++ b/src/ast/nodes/shared/ObjectEntity.ts @@ -1,7 +1,11 @@ import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { HasEffectsContext } from '../../ExecutionContext'; -import { INTERACTION_ACCESSED, INTERACTION_CALLED, NodeInteraction } from '../../NodeInteractions'; +import { + INTERACTION_ACCESSED, + INTERACTION_CALLED, + NodeInteractionWithThisArg +} from '../../NodeInteractions'; import { ObjectPath, ObjectPathKey, @@ -147,9 +151,8 @@ export class ObjectEntity extends ExpressionEntity { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { const [key, ...subPath] = path; @@ -161,7 +164,7 @@ export class ObjectEntity extends ExpressionEntity { (this.hasUnknownDeoptimizedProperty || (typeof key === 'string' && this.deoptimizedPaths[key]))) ) { - thisParameter.deoptimizePath(UNKNOWN_PATH); + interaction.thisArg.deoptimizePath(UNKNOWN_PATH); return; } @@ -181,35 +184,20 @@ export class ObjectEntity extends ExpressionEntity { const properties = relevantPropertiesByKey[key]; if (properties) { for (const property of properties) { - property.deoptimizeThisOnInteractionAtPath( - interaction, - subPath, - thisParameter, - recursionTracker - ); + property.deoptimizeThisOnInteractionAtPath(interaction, subPath, recursionTracker); } } if (!this.immutable) { - this.thisParametersToBeDeoptimized.add(thisParameter); + this.thisParametersToBeDeoptimized.add(interaction.thisArg); } return; } for (const property of relevantUnmatchableProperties) { - property.deoptimizeThisOnInteractionAtPath( - interaction, - subPath, - thisParameter, - recursionTracker - ); + property.deoptimizeThisOnInteractionAtPath(interaction, subPath, recursionTracker); } if (INTEGER_REG_EXP.test(key)) { for (const property of this.unknownIntegerProps) { - property.deoptimizeThisOnInteractionAtPath( - interaction, - subPath, - thisParameter, - recursionTracker - ); + property.deoptimizeThisOnInteractionAtPath(interaction, subPath, recursionTracker); } } } else { @@ -217,30 +205,19 @@ export class ObjectEntity extends ExpressionEntity { relevantUnmatchableProperties ])) { for (const property of properties) { - property.deoptimizeThisOnInteractionAtPath( - interaction, - subPath, - thisParameter, - recursionTracker - ); + property.deoptimizeThisOnInteractionAtPath(interaction, subPath, recursionTracker); } } for (const property of this.unknownIntegerProps) { - property.deoptimizeThisOnInteractionAtPath( - interaction, - subPath, - thisParameter, - recursionTracker - ); + property.deoptimizeThisOnInteractionAtPath(interaction, subPath, recursionTracker); } } if (!this.immutable) { - this.thisParametersToBeDeoptimized.add(thisParameter); + this.thisParametersToBeDeoptimized.add(interaction.thisArg); } this.prototypeExpression?.deoptimizeThisOnInteractionAtPath( interaction, path, - thisParameter, recursionTracker ); } diff --git a/src/ast/nodes/shared/ObjectMember.ts b/src/ast/nodes/shared/ObjectMember.ts index 4dcc8bf9c3b..809a3d7a9b2 100644 --- a/src/ast/nodes/shared/ObjectMember.ts +++ b/src/ast/nodes/shared/ObjectMember.ts @@ -1,7 +1,7 @@ import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext } from '../../ExecutionContext'; -import type { NodeInteraction } from '../../NodeInteractions'; +import type { NodeInteractionWithThisArg } from '../../NodeInteractions'; import type { ObjectPath, PathTracker } from '../../utils/PathTracker'; import { ExpressionEntity, type LiteralValueOrUnknown } from './Expression'; @@ -15,15 +15,13 @@ export class ObjectMember extends ExpressionEntity { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { this.object.deoptimizeThisOnInteractionAtPath( interaction, [this.key, ...path], - thisParameter, recursionTracker ); } diff --git a/src/ast/nodes/shared/ObjectPrototype.ts b/src/ast/nodes/shared/ObjectPrototype.ts index 1b7ee38330d..94e800ebe7c 100644 --- a/src/ast/nodes/shared/ObjectPrototype.ts +++ b/src/ast/nodes/shared/ObjectPrototype.ts @@ -1,4 +1,4 @@ -import { INTERACTION_CALLED, NodeInteraction } from '../../NodeInteractions'; +import { INTERACTION_CALLED, NodeInteractionWithThisArg } from '../../NodeInteractions'; import { ObjectPath, ObjectPathKey, UNKNOWN_PATH } from '../../utils/PathTracker'; import { ExpressionEntity, LiteralValueOrUnknown, UnknownValue } from './Expression'; import { @@ -17,12 +17,11 @@ const isInteger = (prop: ObjectPathKey): boolean => typeof prop === 'string' && const OBJECT_PROTOTYPE_FALLBACK: ExpressionEntity = new (class ObjectPrototypeFallbackExpression extends ExpressionEntity { deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, - path: ObjectPath, - thisParameter: ExpressionEntity + { type, thisArg }: NodeInteractionWithThisArg, + path: ObjectPath ): void { - if (interaction.type === INTERACTION_CALLED && path.length === 1 && !isInteger(path[0])) { - thisParameter.deoptimizePath(UNKNOWN_PATH); + if (type === INTERACTION_CALLED && path.length === 1 && !isInteger(path[0])) { + thisArg.deoptimizePath(UNKNOWN_PATH); } } diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index dc2e4301b92..1c596979b2f 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -2,7 +2,7 @@ import Module, { AstContext } from '../../Module'; import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import { createInclusionContext, HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import type { NodeInteraction } from '../NodeInteractions'; +import type { NodeInteractionWithThisArg } from '../NodeInteractions'; import type ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import type Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; @@ -82,24 +82,17 @@ export default class LocalVariable extends Variable { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, + interaction: NodeInteractionWithThisArg, path: ObjectPath, - thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { if (this.isReassigned || !this.init) { - return thisParameter.deoptimizePath(UNKNOWN_PATH); + return interaction.thisArg.deoptimizePath(UNKNOWN_PATH); } recursionTracker.withTrackedEntityAtPath( path, this.init, - () => - this.init!.deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter, - recursionTracker - ), + () => this.init!.deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker), undefined ); } diff --git a/src/ast/variables/ThisVariable.ts b/src/ast/variables/ThisVariable.ts index 08ee7501ee6..530c362955a 100644 --- a/src/ast/variables/ThisVariable.ts +++ b/src/ast/variables/ThisVariable.ts @@ -1,6 +1,6 @@ import type { AstContext } from '../../Module'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeInteraction } from '../NodeInteractions'; +import type { NodeInteractionWithThisArg } from '../NodeInteractions'; import { type ExpressionEntity, UNKNOWN_EXPRESSION } from '../nodes/shared/Expression'; import { DiscriminatedPathTracker, @@ -10,9 +10,8 @@ import { import LocalVariable from './LocalVariable'; interface ThisDeoptimizationInteraction { - interaction: NodeInteraction; + interaction: NodeInteractionWithThisArg; path: ObjectPath; - thisParameter: ExpressionEntity; } export default class ThisVariable extends LocalVariable { @@ -29,8 +28,8 @@ export default class ThisVariable extends LocalVariable { for (const path of this.deoptimizedPaths) { entity.deoptimizePath(path); } - for (const thisDeoptimization of this.thisDeoptimizationList) { - this.applyThisDeoptimizationInteraction(entity, thisDeoptimization); + for (const { interaction, path } of this.thisDeoptimizationList) { + entity.deoptimizeThisOnInteractionAtPath(interaction, path, SHARED_RECURSION_TRACKER); } this.entitiesToBeDeoptimized.add(entity); } @@ -49,20 +48,22 @@ export default class ThisVariable extends LocalVariable { } deoptimizeThisOnInteractionAtPath( - interaction: NodeInteraction, - path: ObjectPath, - thisParameter: ExpressionEntity + interaction: NodeInteractionWithThisArg, + path: ObjectPath ): void { const thisDeoptimization: ThisDeoptimizationInteraction = { interaction, - path, - thisParameter + path }; if ( - !this.thisDeoptimizations.trackEntityAtPathAndGetIfTracked(path, interaction, thisParameter) + !this.thisDeoptimizations.trackEntityAtPathAndGetIfTracked( + path, + interaction, + interaction.thisArg + ) ) { for (const entity of this.entitiesToBeDeoptimized) { - this.applyThisDeoptimizationInteraction(entity, thisDeoptimization); + entity.deoptimizeThisOnInteractionAtPath(interaction, path, SHARED_RECURSION_TRACKER); } this.thisDeoptimizationList.push(thisDeoptimization); } @@ -82,18 +83,6 @@ export default class ThisVariable extends LocalVariable { ); } - private applyThisDeoptimizationInteraction( - entity: ExpressionEntity, - { interaction, path, thisParameter }: ThisDeoptimizationInteraction - ) { - entity.deoptimizeThisOnInteractionAtPath( - interaction, - path, - thisParameter === this ? entity : thisParameter, - SHARED_RECURSION_TRACKER - ); - } - private getInit(context: HasEffectsContext): ExpressionEntity { return context.replacedVariableInits.get(this) || UNKNOWN_EXPRESSION; } From 34636f51b381bd6bba9adafaa9f0373320d07d48 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 3 Jun 2022 15:04:18 +0200 Subject: [PATCH 06/13] Use interaction for getReturnExpression --- src/ast/NodeInteractions.ts | 2 +- src/ast/nodes/ArrayExpression.ts | 6 ++--- src/ast/nodes/CallExpression.ts | 15 ++++++------ src/ast/nodes/ConditionalExpression.ts | 10 ++++---- src/ast/nodes/Identifier.ts | 5 ++-- src/ast/nodes/LogicalExpression.ts | 9 ++++---- src/ast/nodes/MemberExpression.ts | 7 +++--- src/ast/nodes/ObjectExpression.ts | 5 ++-- src/ast/nodes/PropertyDefinition.ts | 5 ++-- src/ast/nodes/TaggedTemplateExpression.ts | 17 +++++++------- src/ast/nodes/shared/CallExpressionBase.ts | 8 +++---- src/ast/nodes/shared/ClassNode.ts | 6 ++--- src/ast/nodes/shared/Expression.ts | 4 ++-- src/ast/nodes/shared/FunctionBase.ts | 6 ++--- src/ast/nodes/shared/MethodBase.ts | 27 ++++++++++++++-------- src/ast/nodes/shared/MethodTypes.ts | 11 ++++++--- src/ast/nodes/shared/MultiExpression.ts | 5 ++-- src/ast/nodes/shared/ObjectEntity.ts | 7 +++--- src/ast/nodes/shared/ObjectMember.ts | 5 ++-- src/ast/variables/LocalVariable.ts | 6 ++--- 20 files changed, 92 insertions(+), 74 deletions(-) diff --git a/src/ast/NodeInteractions.ts b/src/ast/NodeInteractions.ts index 8464d4219fc..fe596ab72d4 100644 --- a/src/ast/NodeInteractions.ts +++ b/src/ast/NodeInteractions.ts @@ -16,7 +16,7 @@ interface NodeInteractionAssigned { value: ExpressionEntity; } -interface NodeInteractionCalled { +export interface NodeInteractionCalled { args: (ExpressionEntity | SpreadElement)[]; thisArg: ExpressionEntity | null; type: typeof INTERACTION_CALLED; diff --git a/src/ast/nodes/ArrayExpression.ts b/src/ast/nodes/ArrayExpression.ts index 485351c845a..f5a8cbb3bdf 100644 --- a/src/ast/nodes/ArrayExpression.ts +++ b/src/ast/nodes/ArrayExpression.ts @@ -1,7 +1,7 @@ import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeInteractionWithThisArg } from '../NodeInteractions'; +import type { NodeInteractionCalled, NodeInteractionWithThisArg } from '../NodeInteractions'; import { type ObjectPath, type PathTracker, @@ -43,13 +43,13 @@ export default class ArrayExpression extends NodeBase { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { return this.getObjectEntity().getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ); diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index b56fc913cd2..97a227c956a 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -5,7 +5,7 @@ import { renderCallArguments } from '../../utils/renderCallArguments'; import { type NodeRenderOptions, type RenderOptions } from '../../utils/renderHelpers'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import { INTERACTION_CALLED } from '../NodeInteractions'; +import { INTERACTION_CALLED, NodeInteractionWithThisArg } from '../NodeInteractions'; import { EMPTY_PATH, type PathTracker, @@ -53,12 +53,13 @@ export default class CallExpression extends CallExpressionBase implements Deopti ); } } - this.callOptions = { + this.interaction = { args: this.arguments, thisArg: this.callee instanceof MemberExpression && !this.callee.variable ? this.callee.object : null, + type: INTERACTION_CALLED, withNew: false }; } @@ -75,7 +76,7 @@ export default class CallExpression extends CallExpressionBase implements Deopti return false; return ( this.callee.hasEffects(context) || - this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) + this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.interaction, context) ); } finally { if (!this.deoptimized) this.applyDeoptimizations(); @@ -118,11 +119,9 @@ export default class CallExpression extends CallExpressionBase implements Deopti protected applyDeoptimizations(): void { this.deoptimized = true; - const { args, thisArg, withNew } = this.callOptions; - if (thisArg) { - // TODO Lukas cache interaction + if (this.interaction.thisArg) { this.callee.deoptimizeThisOnInteractionAtPath( - { args, thisArg, type: INTERACTION_CALLED, withNew }, + this.interaction as NodeInteractionWithThisArg, EMPTY_PATH, SHARED_RECURSION_TRACKER ); @@ -141,7 +140,7 @@ export default class CallExpression extends CallExpressionBase implements Deopti this.returnExpression = UNKNOWN_EXPRESSION; return (this.returnExpression = this.callee.getReturnExpressionWhenCalledAtPath( EMPTY_PATH, - this.callOptions, + this.interaction, recursionTracker, this )); diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index 4c8aa2fcbd3..780ad523920 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -11,7 +11,7 @@ import { removeAnnotations } from '../../utils/treeshakeNode'; import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import { NodeInteractionWithThisArg } from '../NodeInteractions'; +import { NodeInteractionCalled, NodeInteractionWithThisArg } from '../NodeInteractions'; import { EMPTY_PATH, ObjectPath, @@ -78,7 +78,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { @@ -87,13 +87,13 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return new MultiExpression([ this.consequent.getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ), this.alternate.getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ) @@ -101,7 +101,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz this.expressionsToBeDeoptimized.push(origin); return usedBranch.getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ); diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index d0033598015..19ca36ea7c2 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -7,6 +7,7 @@ import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../NodeInteractions'; +import { NodeInteractionCalled } from '../NodeInteractions'; import type FunctionScope from '../scopes/FunctionScope'; import { EMPTY_PATH, type ObjectPath, type PathTracker } from '../utils/PathTracker'; import GlobalVariable from '../variables/GlobalVariable'; @@ -113,13 +114,13 @@ export default class Identifier extends NodeBase implements PatternNode { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { return this.getVariableRespectingTDZ()!.getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ); diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index cf9a6d6ccaa..09550e595b1 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -12,6 +12,7 @@ import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../NodeInteractions'; +import { NodeInteractionCalled } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, @@ -87,20 +88,20 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { const usedBranch = this.getUsedBranch(); if (!usedBranch) return new MultiExpression([ - this.left.getReturnExpressionWhenCalledAtPath(path, callOptions, recursionTracker, origin), - this.right.getReturnExpressionWhenCalledAtPath(path, callOptions, recursionTracker, origin) + this.left.getReturnExpressionWhenCalledAtPath(path, interaction, recursionTracker, origin), + this.right.getReturnExpressionWhenCalledAtPath(path, interaction, recursionTracker, origin) ]); this.expressionsToBeDeoptimized.push(origin); return usedBranch.getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ); diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index c3e0d56bd89..8e0a1a06c91 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -10,6 +10,7 @@ import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, + NodeInteractionCalled, NodeInteractionWithThisArg } from '../NodeInteractions'; import { @@ -190,14 +191,14 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { if (this.variable) { return this.variable.getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ); @@ -209,7 +210,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE if (path.length < MAX_PATH_DEPTH) { return this.object.getReturnExpressionWhenCalledAtPath( [this.getPropertyKey(), ...path], - callOptions, + interaction, recursionTracker, origin ); diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index 7bd10073080..c8d6ddb3eb1 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -5,6 +5,7 @@ import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../NodeInteractions'; +import { NodeInteractionCalled } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, @@ -53,13 +54,13 @@ export default class ObjectExpression extends NodeBase implements DeoptimizableE getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { return this.getObjectEntity().getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ); diff --git a/src/ast/nodes/PropertyDefinition.ts b/src/ast/nodes/PropertyDefinition.ts index 04277ed495d..7a30781bf12 100644 --- a/src/ast/nodes/PropertyDefinition.ts +++ b/src/ast/nodes/PropertyDefinition.ts @@ -2,6 +2,7 @@ import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../NodeInteractions'; +import { NodeInteractionCalled } from '../NodeInteractions'; import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; import type PrivateIdentifier from './PrivateIdentifier'; @@ -44,12 +45,12 @@ export default class PropertyDefinition extends NodeBase { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { return this.value - ? this.value.getReturnExpressionWhenCalledAtPath(path, callOptions, recursionTracker, origin) + ? this.value.getReturnExpressionWhenCalledAtPath(path, interaction, recursionTracker, origin) : UNKNOWN_EXPRESSION; } diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index 91c54ad1ab3..197661c8216 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -2,7 +2,7 @@ import type MagicString from 'magic-string'; import { type RenderOptions } from '../../utils/renderHelpers'; import type { HasEffectsContext } from '../ExecutionContext'; import { InclusionContext } from '../ExecutionContext'; -import { INTERACTION_CALLED } from '../NodeInteractions'; +import { INTERACTION_CALLED, NodeInteractionWithThisArg } from '../NodeInteractions'; import { EMPTY_PATH, PathTracker, @@ -47,7 +47,7 @@ export default class TaggedTemplateExpression extends CallExpressionBase { } return ( this.tag.hasEffects(context) || - this.tag.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) + this.tag.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.interaction, context) ); } finally { if (!this.deoptimized) this.applyDeoptimizations(); @@ -63,7 +63,7 @@ export default class TaggedTemplateExpression extends CallExpressionBase { this.tag.include(context, includeChildrenRecursively); this.quasi.include(context, includeChildrenRecursively); } - this.tag.includeCallArguments(context, this.callOptions.args); + this.tag.includeCallArguments(context, this.interaction.args); const returnExpression = this.getReturnExpression(); if (!returnExpression.included) { returnExpression.include(context, false); @@ -71,9 +71,10 @@ export default class TaggedTemplateExpression extends CallExpressionBase { } initialise(): void { - this.callOptions = { + this.interaction = { args: [UNKNOWN_EXPRESSION, ...this.quasi.expressions], thisArg: this.tag instanceof MemberExpression && !this.tag.variable ? this.tag.object : null, + type: INTERACTION_CALLED, withNew: false }; } @@ -85,11 +86,9 @@ export default class TaggedTemplateExpression extends CallExpressionBase { protected applyDeoptimizations(): void { this.deoptimized = true; - const { args, thisArg, withNew } = this.callOptions; - if (thisArg) { - // TODO Lukas cache interaction + if (this.interaction.thisArg) { this.tag.deoptimizeThisOnInteractionAtPath( - { args, thisArg, type: INTERACTION_CALLED, withNew }, + this.interaction as NodeInteractionWithThisArg, EMPTY_PATH, SHARED_RECURSION_TRACKER ); @@ -108,7 +107,7 @@ export default class TaggedTemplateExpression extends CallExpressionBase { this.returnExpression = UNKNOWN_EXPRESSION; return (this.returnExpression = this.tag.getReturnExpressionWhenCalledAtPath( EMPTY_PATH, - this.callOptions, + this.interaction, recursionTracker, this )); diff --git a/src/ast/nodes/shared/CallExpressionBase.ts b/src/ast/nodes/shared/CallExpressionBase.ts index fa53ba1f53a..b5c42417d39 100644 --- a/src/ast/nodes/shared/CallExpressionBase.ts +++ b/src/ast/nodes/shared/CallExpressionBase.ts @@ -1,7 +1,7 @@ import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext } from '../../ExecutionContext'; -import { NodeInteractionWithThisArg } from '../../NodeInteractions'; +import { NodeInteractionCalled, NodeInteractionWithThisArg } from '../../NodeInteractions'; import { type ObjectPath, type PathTracker, UNKNOWN_PATH } from '../../utils/PathTracker'; import { type ExpressionEntity, @@ -12,7 +12,7 @@ import { import { NodeBase } from './Node'; export default abstract class CallExpressionBase extends NodeBase implements DeoptimizableEntity { - protected declare callOptions: CallOptions; + protected declare interaction: NodeInteractionCalled; protected returnExpression: ExpressionEntity | null = null; private readonly deoptimizableDependentExpressions: DeoptimizableEntity[] = []; private readonly expressionsToBeDeoptimized = new Set(); @@ -85,7 +85,7 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { @@ -100,7 +100,7 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo this.deoptimizableDependentExpressions.push(origin); return returnExpression.getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ); diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index 6df586e32e5..e0fd19efc90 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -1,7 +1,7 @@ import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; -import type { NodeInteractionWithThisArg } from '../../NodeInteractions'; +import type { NodeInteractionCalled, NodeInteractionWithThisArg } from '../../NodeInteractions'; import ChildScope from '../../scopes/ChildScope'; import type Scope from '../../scopes/Scope'; import { @@ -59,13 +59,13 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { return this.getObjectEntity().getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ); diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index f783520ca12..8b7d01edc2e 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -2,7 +2,7 @@ import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { WritableEntity } from '../../Entity'; import { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; -import { NodeInteractionWithThisArg } from '../../NodeInteractions'; +import { NodeInteractionCalled, NodeInteractionWithThisArg } from '../../NodeInteractions'; import { ObjectPath, PathTracker, UNKNOWN_PATH } from '../../utils/PathTracker'; import { LiteralValue } from '../Literal'; import SpreadElement from '../SpreadElement'; @@ -48,7 +48,7 @@ export class ExpressionEntity implements WritableEntity { getReturnExpressionWhenCalledAtPath( _path: ObjectPath, - _callOptions: CallOptions, + _interaction: NodeInteractionCalled, _recursionTracker: PathTracker, _origin: DeoptimizableEntity ): ExpressionEntity { diff --git a/src/ast/nodes/shared/FunctionBase.ts b/src/ast/nodes/shared/FunctionBase.ts index 6b9f0d8e435..d59189bd546 100644 --- a/src/ast/nodes/shared/FunctionBase.ts +++ b/src/ast/nodes/shared/FunctionBase.ts @@ -6,7 +6,7 @@ import { type HasEffectsContext, type InclusionContext } from '../../ExecutionContext'; -import { NO_ARGS, NodeInteractionWithThisArg } from '../../NodeInteractions'; +import { NO_ARGS, NodeInteractionCalled, NodeInteractionWithThisArg } from '../../NodeInteractions'; import ReturnValueScope from '../../scopes/ReturnValueScope'; import { type ObjectPath, PathTracker, UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; import BlockStatement from '../BlockStatement'; @@ -61,14 +61,14 @@ export default abstract class FunctionBase extends NodeBase { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { if (path.length > 0) { return this.getObjectEntity().getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ); diff --git a/src/ast/nodes/shared/MethodBase.ts b/src/ast/nodes/shared/MethodBase.ts index e417d2e83a6..10fc7112c06 100644 --- a/src/ast/nodes/shared/MethodBase.ts +++ b/src/ast/nodes/shared/MethodBase.ts @@ -6,6 +6,7 @@ import { INTERACTION_ASSIGNED, INTERACTION_CALLED, NO_ARGS, + NodeInteractionCalled, NodeInteractionWithThisArg } from '../../NodeInteractions'; import { @@ -30,11 +31,6 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity declare value: ExpressionNode | (ExpressionNode & PatternNode); private accessedValue: ExpressionEntity | null = null; - private accessorCallOptions: CallOptions = { - args: NO_ARGS, - thisArg: null, - withNew: false - }; // As getter properties directly receive their values from fixed function // expressions, there is no known situation where a getter is deoptimized. @@ -83,13 +79,13 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { return this.getAccessedValue().getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ); @@ -99,16 +95,26 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity return this.key.hasEffects(context); } + // TODO Lukas this should use the assigned thisArg hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (this.kind === 'get' && path.length === 0) { - return this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context); + return this.value.hasEffectsWhenCalledAtPath( + EMPTY_PATH, + { args: NO_ARGS, thisArg: null, withNew: false }, + context + ); } return this.getAccessedValue().hasEffectsWhenAccessedAtPath(path, context); } + // TODO Lukas this should use the assigned value and thisArg hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { if (this.kind === 'set') { - return this.value.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.accessorCallOptions, context); + return this.value.hasEffectsWhenCalledAtPath( + EMPTY_PATH, + { args: NO_ARGS, thisArg: null, withNew: false }, + context + ); } return this.getAccessedValue().hasEffectsWhenAssignedAtPath(path, context); } @@ -123,13 +129,14 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity protected applyDeoptimizations() {} + // TODO Lukas this should get the thisArg as parameter from the interaction if accessed, null otherwise protected getAccessedValue(): ExpressionEntity { if (this.accessedValue === null) { if (this.kind === 'get') { this.accessedValue = UNKNOWN_EXPRESSION; return (this.accessedValue = this.value.getReturnExpressionWhenCalledAtPath( EMPTY_PATH, - this.accessorCallOptions, + { args: NO_ARGS, thisArg: null, type: INTERACTION_CALLED, withNew: false }, SHARED_RECURSION_TRACKER, this )); diff --git a/src/ast/nodes/shared/MethodTypes.ts b/src/ast/nodes/shared/MethodTypes.ts index e7cc66ee98a..cd1298b60bd 100644 --- a/src/ast/nodes/shared/MethodTypes.ts +++ b/src/ast/nodes/shared/MethodTypes.ts @@ -1,6 +1,11 @@ import { type CallOptions } from '../../CallOptions'; import type { HasEffectsContext } from '../../ExecutionContext'; -import { INTERACTION_CALLED, NO_ARGS, NodeInteractionWithThisArg } from '../../NodeInteractions'; +import { + INTERACTION_CALLED, + NO_ARGS, + NodeInteraction, + NodeInteractionWithThisArg +} from '../../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_INTEGER_PATH } from '../../utils/PathTracker'; import { UNKNOWN_LITERAL_BOOLEAN, @@ -39,7 +44,7 @@ export class Method extends ExpressionEntity { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions + interaction: NodeInteraction ): ExpressionEntity { if (path.length > 0) { return UNKNOWN_EXPRESSION; @@ -47,7 +52,7 @@ export class Method extends ExpressionEntity { return ( this.description.returnsPrimitive || (this.description.returns === 'self' - ? callOptions.thisArg || UNKNOWN_EXPRESSION + ? interaction.thisArg || UNKNOWN_EXPRESSION : this.description.returns()) ); } diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index 40dd5df76d2..14213e5e491 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -1,6 +1,7 @@ import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; +import { NodeInteractionCalled } from '../../NodeInteractions'; import type { ObjectPath, PathTracker } from '../../utils/PathTracker'; import { ExpressionEntity } from './Expression'; import type { IncludeChildren } from './Node'; @@ -20,13 +21,13 @@ export class MultiExpression extends ExpressionEntity { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { return new MultiExpression( this.expressions.map(expression => - expression.getReturnExpressionWhenCalledAtPath(path, callOptions, recursionTracker, origin) + expression.getReturnExpressionWhenCalledAtPath(path, interaction, recursionTracker, origin) ) ); } diff --git a/src/ast/nodes/shared/ObjectEntity.ts b/src/ast/nodes/shared/ObjectEntity.ts index 10c11f4fa5c..0b070feaa05 100644 --- a/src/ast/nodes/shared/ObjectEntity.ts +++ b/src/ast/nodes/shared/ObjectEntity.ts @@ -4,6 +4,7 @@ import { HasEffectsContext } from '../../ExecutionContext'; import { INTERACTION_ACCESSED, INTERACTION_CALLED, + NodeInteractionCalled, NodeInteractionWithThisArg } from '../../NodeInteractions'; import { @@ -246,7 +247,7 @@ export class ObjectEntity extends ExpressionEntity { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { @@ -258,7 +259,7 @@ export class ObjectEntity extends ExpressionEntity { if (expressionAtPath) { return expressionAtPath.getReturnExpressionWhenCalledAtPath( path.slice(1), - callOptions, + interaction, recursionTracker, origin ); @@ -266,7 +267,7 @@ export class ObjectEntity extends ExpressionEntity { if (this.prototypeExpression) { return this.prototypeExpression.getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ); diff --git a/src/ast/nodes/shared/ObjectMember.ts b/src/ast/nodes/shared/ObjectMember.ts index 809a3d7a9b2..479960eec2d 100644 --- a/src/ast/nodes/shared/ObjectMember.ts +++ b/src/ast/nodes/shared/ObjectMember.ts @@ -2,6 +2,7 @@ import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext } from '../../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../../NodeInteractions'; +import { NodeInteractionCalled } from '../../NodeInteractions'; import type { ObjectPath, PathTracker } from '../../utils/PathTracker'; import { ExpressionEntity, type LiteralValueOrUnknown } from './Expression'; @@ -36,13 +37,13 @@ export class ObjectMember extends ExpressionEntity { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { return this.object.getReturnExpressionWhenCalledAtPath( [this.key, ...path], - callOptions, + interaction, recursionTracker, origin ); diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 1c596979b2f..a4700a1cd33 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -2,7 +2,7 @@ import Module, { AstContext } from '../../Module'; import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import { createInclusionContext, HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import type { NodeInteractionWithThisArg } from '../NodeInteractions'; +import type { NodeInteractionCalled, NodeInteractionWithThisArg } from '../NodeInteractions'; import type ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import type Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; @@ -118,7 +118,7 @@ export default class LocalVariable extends Variable { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { @@ -132,7 +132,7 @@ export default class LocalVariable extends Variable { this.expressionsToBeDeoptimized.push(origin); return this.init!.getReturnExpressionWhenCalledAtPath( path, - callOptions, + interaction, recursionTracker, origin ); From 16964623310236e7813bfac2aec614607c5ca549 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sat, 4 Jun 2022 14:59:14 +0200 Subject: [PATCH 07/13] Replace dedicated methods with hasEffectsOnInteractionAtPath --- src/ast/CallOptions.ts | 8 - src/ast/Entity.ts | 7 +- src/ast/NodeInteractions.ts | 7 +- src/ast/nodes/ArrayExpression.ts | 16 +- src/ast/nodes/ArrayPattern.ts | 9 +- src/ast/nodes/ArrowFunctionExpression.ts | 30 ++-- src/ast/nodes/AssignmentExpression.ts | 30 +++- src/ast/nodes/AssignmentPattern.ts | 11 +- src/ast/nodes/BinaryExpression.ts | 5 +- src/ast/nodes/CallExpression.ts | 2 +- src/ast/nodes/ConditionalExpression.ts | 39 ++-- src/ast/nodes/ForInStatement.ts | 8 +- src/ast/nodes/Identifier.ts | 52 +++--- src/ast/nodes/Literal.ts | 31 ++-- src/ast/nodes/LogicalExpression.ts | 35 +--- src/ast/nodes/MemberExpression.ts | 49 ++--- src/ast/nodes/MetaProperty.ts | 7 +- src/ast/nodes/NewExpression.ts | 18 +- src/ast/nodes/ObjectExpression.ts | 17 +- src/ast/nodes/ObjectPattern.ts | 9 +- src/ast/nodes/PropertyDefinition.ts | 17 +- src/ast/nodes/RestElement.ts | 12 +- src/ast/nodes/SequenceExpression.ts | 27 +-- src/ast/nodes/SpreadElement.ts | 7 +- src/ast/nodes/TaggedTemplateExpression.ts | 2 +- src/ast/nodes/TemplateLiteral.ts | 17 +- src/ast/nodes/ThisExpression.ts | 18 +- src/ast/nodes/UnaryExpression.ts | 18 +- src/ast/nodes/UpdateExpression.ts | 12 +- src/ast/nodes/VariableDeclaration.ts | 2 +- src/ast/nodes/shared/CallExpressionBase.ts | 52 +++--- src/ast/nodes/shared/ClassNode.ts | 30 ++-- src/ast/nodes/shared/Expression.ts | 19 +- src/ast/nodes/shared/FunctionBase.ts | 36 ++-- src/ast/nodes/shared/FunctionNode.ts | 61 ++++--- src/ast/nodes/shared/MethodBase.ts | 47 +++-- src/ast/nodes/shared/MethodTypes.ts | 59 +++--- src/ast/nodes/shared/MultiExpression.ts | 23 +-- src/ast/nodes/shared/ObjectEntity.ts | 200 +++++++++++---------- src/ast/nodes/shared/ObjectMember.ts | 17 +- src/ast/nodes/shared/ObjectPrototype.ts | 14 +- src/ast/nodes/shared/knownGlobals.ts | 17 +- src/ast/values.ts | 71 ++++---- src/ast/variables/ArgumentsVariable.ts | 15 +- src/ast/variables/ExternalVariable.ts | 5 +- src/ast/variables/GlobalVariable.ts | 35 ++-- src/ast/variables/LocalVariable.ts | 54 +++--- src/ast/variables/ThisVariable.ts | 19 +- src/ast/variables/Variable.ts | 12 +- 49 files changed, 660 insertions(+), 648 deletions(-) delete mode 100644 src/ast/CallOptions.ts diff --git a/src/ast/CallOptions.ts b/src/ast/CallOptions.ts deleted file mode 100644 index 9c400929a88..00000000000 --- a/src/ast/CallOptions.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type SpreadElement from './nodes/SpreadElement'; -import type { ExpressionEntity } from './nodes/shared/Expression'; - -export interface CallOptions { - args: (ExpressionEntity | SpreadElement)[]; - thisArg: ExpressionEntity | null; - withNew: boolean; -} diff --git a/src/ast/Entity.ts b/src/ast/Entity.ts index 7ab643b7008..13f958ba245 100644 --- a/src/ast/Entity.ts +++ b/src/ast/Entity.ts @@ -1,4 +1,5 @@ import type { HasEffectsContext } from './ExecutionContext'; +import { NodeInteractionAssigned } from './NodeInteractions'; import type { ObjectPath } from './utils/PathTracker'; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -13,5 +14,9 @@ export interface WritableEntity extends Entity { */ deoptimizePath(path: ObjectPath): void; - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean; + hasEffectsOnInteractionAtPath( + path: ObjectPath, + interaction: NodeInteractionAssigned, + context: HasEffectsContext + ): boolean; } diff --git a/src/ast/NodeInteractions.ts b/src/ast/NodeInteractions.ts index fe596ab72d4..062e87e6996 100644 --- a/src/ast/NodeInteractions.ts +++ b/src/ast/NodeInteractions.ts @@ -1,17 +1,16 @@ import SpreadElement from './nodes/SpreadElement'; import { ExpressionEntity } from './nodes/shared/Expression'; +// TODO Lukas for usages and see if caching makes sense export const INTERACTION_ACCESSED = 0; export const INTERACTION_ASSIGNED = 1; export const INTERACTION_CALLED = 2; export interface NodeInteractionAccessed { - thisArg: ExpressionEntity | null; type: typeof INTERACTION_ACCESSED; } -interface NodeInteractionAssigned { - thisArg: ExpressionEntity | null; +export interface NodeInteractionAssigned { type: typeof INTERACTION_ASSIGNED; value: ExpressionEntity; } @@ -25,9 +24,11 @@ export interface NodeInteractionCalled { export const NO_ARGS = []; +// TODO Lukas destructure interactions where they are not forwarded export type NodeInteraction = | NodeInteractionAccessed | NodeInteractionAssigned | NodeInteractionCalled; +// TODO Lukas getters and setters can only every have their actual parent as thisArg -> do not add them to the interaction but get them statically export type NodeInteractionWithThisArg = NodeInteraction & { thisArg: ExpressionEntity }; diff --git a/src/ast/nodes/ArrayExpression.ts b/src/ast/nodes/ArrayExpression.ts index f5a8cbb3bdf..b2111b8f92c 100644 --- a/src/ast/nodes/ArrayExpression.ts +++ b/src/ast/nodes/ArrayExpression.ts @@ -1,7 +1,7 @@ -import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeInteractionCalled, NodeInteractionWithThisArg } from '../NodeInteractions'; +import { NodeInteraction } from '../NodeInteractions'; import { type ObjectPath, type PathTracker, @@ -55,20 +55,12 @@ export default class ArrayExpression extends NodeBase { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return this.getObjectEntity().hasEffectsWhenAccessedAtPath(path, context); - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return this.getObjectEntity().hasEffectsWhenAssignedAtPath(path, context); - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - return this.getObjectEntity().hasEffectsWhenCalledAtPath(path, callOptions, context); + return this.getObjectEntity().hasEffectsOnInteractionAtPath(path, interaction, context); } protected applyDeoptimizations(): void { diff --git a/src/ast/nodes/ArrayPattern.ts b/src/ast/nodes/ArrayPattern.ts index aab64901f49..e8e2ccdc229 100644 --- a/src/ast/nodes/ArrayPattern.ts +++ b/src/ast/nodes/ArrayPattern.ts @@ -1,4 +1,5 @@ import type { HasEffectsContext } from '../ExecutionContext'; +import { NodeInteractionAssigned } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath } from '../utils/PathTracker'; import type LocalVariable from '../variables/LocalVariable'; import type Variable from '../variables/Variable'; @@ -38,9 +39,13 @@ export default class ArrayPattern extends NodeBase implements PatternNode { } // Patterns are only checked at the emtpy path at the moment - hasEffectsWhenAssignedAtPath(_path: ObjectPath, context: HasEffectsContext): boolean { + hasEffectsOnInteractionAtPath( + _path: ObjectPath, + interaction: NodeInteractionAssigned, + context: HasEffectsContext + ): boolean { for (const element of this.elements) { - if (element?.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) return true; + if (element?.hasEffectsOnInteractionAtPath(EMPTY_PATH, interaction, context)) return true; } return false; } diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index cb294d859e1..75ec03b1ac6 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -1,5 +1,5 @@ -import { type CallOptions } from '../CallOptions'; import { type HasEffectsContext, InclusionContext } from '../ExecutionContext'; +import { INTERACTION_CALLED, NodeInteraction } from '../NodeInteractions'; import ReturnValueScope from '../scopes/ReturnValueScope'; import type Scope from '../scopes/Scope'; import { type ObjectPath } from '../utils/PathTracker'; @@ -30,22 +30,24 @@ export default class ArrowFunctionExpression extends FunctionBase { return false; } - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - if (super.hasEffectsWhenCalledAtPath(path, callOptions, context)) return true; - const { ignore, brokenFlow } = context; - context.ignore = { - breaks: false, - continues: false, - labels: new Set(), - returnYield: true - }; - if (this.body.hasEffects(context)) return true; - context.ignore = ignore; - context.brokenFlow = brokenFlow; + if (super.hasEffectsOnInteractionAtPath(path, interaction, context)) return true; + if (interaction.type === INTERACTION_CALLED) { + const { ignore, brokenFlow } = context; + context.ignore = { + breaks: false, + continues: false, + labels: new Set(), + returnYield: true + }; + if (this.body.hasEffects(context)) return true; + context.ignore = ignore; + context.brokenFlow = brokenFlow; + } return false; } diff --git a/src/ast/nodes/AssignmentExpression.ts b/src/ast/nodes/AssignmentExpression.ts index 4f31517b793..efa29316ffc 100644 --- a/src/ast/nodes/AssignmentExpression.ts +++ b/src/ast/nodes/AssignmentExpression.ts @@ -17,6 +17,7 @@ import { type HasEffectsContext, type InclusionContext } from '../ExecutionContext'; +import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, NodeInteraction } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import type Variable from '../variables/Variable'; import Identifier from './Identifier'; @@ -54,12 +55,29 @@ export default class AssignmentExpression extends NodeBase { right.hasEffects(context) || (left instanceof MemberExpression ? left.hasEffectsAsAssignmentTarget(context, this.operator !== '=', right) - : left.hasEffects(context) || left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) + : left.hasEffects(context) || + left.hasEffectsOnInteractionAtPath( + EMPTY_PATH, + { type: INTERACTION_ASSIGNED, value: right }, + context + )) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return path.length > 0 && this.right.hasEffectsWhenAccessedAtPath(path, context); + hasEffectsOnInteractionAtPath( + path: ObjectPath, + interaction: NodeInteraction, + context: HasEffectsContext + ): boolean { + if (path.length === 0) { + if (interaction.type === INTERACTION_ACCESSED) { + return false; + } + if (interaction.type === INTERACTION_ASSIGNED) { + return true; + } + } + return this.right.hasEffectsOnInteractionAtPath(path, interaction, context); } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { @@ -76,7 +94,11 @@ export default class AssignmentExpression extends NodeBase { isMemberExpression ? left.hasEffectsAsAssignmentTarget(hasEffectsContext, false, right) : left.hasEffects(hasEffectsContext) || - left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, hasEffectsContext)) + left.hasEffectsOnInteractionAtPath( + EMPTY_PATH, + { type: INTERACTION_ASSIGNED, value: right }, + hasEffectsContext + )) ) { if (isMemberExpression) { left.includeAsAssignmentTarget( diff --git a/src/ast/nodes/AssignmentPattern.ts b/src/ast/nodes/AssignmentPattern.ts index f0238ae0fb7..fdfc06a4d3c 100644 --- a/src/ast/nodes/AssignmentPattern.ts +++ b/src/ast/nodes/AssignmentPattern.ts @@ -3,6 +3,7 @@ import { BLANK } from '../../utils/blank'; import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import type { HasEffectsContext } from '../ExecutionContext'; import { InclusionContext } from '../ExecutionContext'; +import { NodeInteractionAssigned } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import type LocalVariable from '../variables/LocalVariable'; import type Variable from '../variables/Variable'; @@ -31,8 +32,14 @@ export default class AssignmentPattern extends NodeBase implements PatternNode { path.length === 0 && this.left.deoptimizePath(path); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return path.length > 0 || this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context); + hasEffectsOnInteractionAtPath( + path: ObjectPath, + interaction: NodeInteractionAssigned, + context: HasEffectsContext + ): boolean { + return ( + path.length > 0 || this.left.hasEffectsOnInteractionAtPath(EMPTY_PATH, interaction, context) + ); } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { diff --git a/src/ast/nodes/BinaryExpression.ts b/src/ast/nodes/BinaryExpression.ts index 4f38da0106b..8cd19d7b39c 100644 --- a/src/ast/nodes/BinaryExpression.ts +++ b/src/ast/nodes/BinaryExpression.ts @@ -3,6 +3,7 @@ import { BLANK } from '../../utils/blank'; import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; +import { INTERACTION_ACCESSED, NodeInteraction } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, @@ -83,8 +84,8 @@ export default class BinaryExpression extends NodeBase implements DeoptimizableE return super.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - return path.length > 1; + hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { + return interaction.type !== INTERACTION_ACCESSED || path.length > 1; } render( diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 97a227c956a..414904a8608 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -76,7 +76,7 @@ export default class CallExpression extends CallExpressionBase implements Deopti return false; return ( this.callee.hasEffects(context) || - this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.interaction, context) + this.callee.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context) ); } finally { if (!this.deoptimized) this.applyDeoptimizations(); diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index 780ad523920..5673ad60a73 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -8,10 +8,13 @@ import { RenderOptions } from '../../utils/renderHelpers'; import { removeAnnotations } from '../../utils/treeshakeNode'; -import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import { NodeInteractionCalled, NodeInteractionWithThisArg } from '../NodeInteractions'; +import { + NodeInteraction, + NodeInteractionCalled, + NodeInteractionWithThisArg +} from '../NodeInteractions'; import { EMPTY_PATH, ObjectPath, @@ -116,41 +119,19 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return usedBranch.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - const usedBranch = this.getUsedBranch(); - if (!usedBranch) { - return ( - this.consequent.hasEffectsWhenAccessedAtPath(path, context) || - this.alternate.hasEffectsWhenAccessedAtPath(path, context) - ); - } - return usedBranch.hasEffectsWhenAccessedAtPath(path, context); - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - const usedBranch = this.getUsedBranch(); - if (!usedBranch) { - return ( - this.consequent.hasEffectsWhenAssignedAtPath(path, context) || - this.alternate.hasEffectsWhenAssignedAtPath(path, context) - ); - } - return usedBranch.hasEffectsWhenAssignedAtPath(path, context); - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { const usedBranch = this.getUsedBranch(); if (!usedBranch) { return ( - this.consequent.hasEffectsWhenCalledAtPath(path, callOptions, context) || - this.alternate.hasEffectsWhenCalledAtPath(path, callOptions, context) + this.consequent.hasEffectsOnInteractionAtPath(path, interaction, context) || + this.alternate.hasEffectsOnInteractionAtPath(path, interaction, context) ); } - return usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, context); + return usedBranch.hasEffectsOnInteractionAtPath(path, interaction, context); } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index 46a91d4595f..9c3d9196458 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -1,11 +1,13 @@ import type MagicString from 'magic-string'; import { NO_SEMICOLON, type RenderOptions } from '../../utils/renderHelpers'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; +import { INTERACTION_ASSIGNED } from '../NodeInteractions'; import BlockScope from '../scopes/BlockScope'; import type Scope from '../scopes/Scope'; import { EMPTY_PATH } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; import type VariableDeclaration from './VariableDeclaration'; +import { UNKNOWN_EXPRESSION } from './shared/Expression'; import { type ExpressionNode, type IncludeChildren, @@ -29,7 +31,11 @@ export default class ForInStatement extends StatementBase { if ( (this.left && (this.left.hasEffects(context) || - this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context))) || + this.left.hasEffectsOnInteractionAtPath( + EMPTY_PATH, + { type: INTERACTION_ASSIGNED, value: UNKNOWN_EXPRESSION }, + context + ))) || (this.right && this.right.hasEffects(context)) ) return true; diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 19ca36ea7c2..50d1abd9af7 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -3,11 +3,16 @@ import type MagicString from 'magic-string'; import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import { BLANK } from '../../utils/blank'; import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../NodeInteractions'; -import { NodeInteractionCalled } from '../NodeInteractions'; +import { + INTERACTION_ACCESSED, + INTERACTION_ASSIGNED, + INTERACTION_CALLED, + NodeInteraction, + NodeInteractionCalled +} from '../NodeInteractions'; import type FunctionScope from '../scopes/FunctionScope'; import { EMPTY_PATH, type ObjectPath, type PathTracker } from '../utils/PathTracker'; import GlobalVariable from '../variables/GlobalVariable'; @@ -126,7 +131,7 @@ export default class Identifier extends NodeBase implements PatternNode { ); } - hasEffects(): boolean { + hasEffects(context: HasEffectsContext): boolean { if (!this.deoptimized) this.applyDeoptimizations(); if (this.isPossibleTDZ() && this.variable!.kind !== 'var') { return true; @@ -134,29 +139,36 @@ export default class Identifier extends NodeBase implements PatternNode { return ( (this.context.options.treeshake as NormalizedTreeshakingOptions).unknownGlobalSideEffects && this.variable instanceof GlobalVariable && - this.variable.hasEffectsWhenAccessedAtPath(EMPTY_PATH) - ); - } - - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return ( - this.variable !== null && - this.getVariableRespectingTDZ()!.hasEffectsWhenAccessedAtPath(path, context) + this.variable.hasEffectsOnInteractionAtPath( + EMPTY_PATH, + { type: INTERACTION_ACCESSED }, + context + ) ); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return ( - path.length > 0 ? this.getVariableRespectingTDZ() : this.variable - )!.hasEffectsWhenAssignedAtPath(path, context); - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - return this.getVariableRespectingTDZ()!.hasEffectsWhenCalledAtPath(path, callOptions, context); + switch (interaction.type) { + case INTERACTION_ACCESSED: + return ( + this.variable !== null && + this.getVariableRespectingTDZ()!.hasEffectsOnInteractionAtPath(path, interaction, context) + ); + case INTERACTION_ASSIGNED: + return ( + path.length > 0 ? this.getVariableRespectingTDZ() : this.variable + )!.hasEffectsOnInteractionAtPath(path, interaction, context); + case INTERACTION_CALLED: + return this.getVariableRespectingTDZ()!.hasEffectsOnInteractionAtPath( + path, + interaction, + context + ); + } } include(): void { diff --git a/src/ast/nodes/Literal.ts b/src/ast/nodes/Literal.ts index 7e6c676f625..5ff42781d69 100644 --- a/src/ast/nodes/Literal.ts +++ b/src/ast/nodes/Literal.ts @@ -1,6 +1,11 @@ import type MagicString from 'magic-string'; -import type { CallOptions } from '../CallOptions'; import type { HasEffectsContext } from '../ExecutionContext'; +import { + INTERACTION_ACCESSED, + INTERACTION_ASSIGNED, + INTERACTION_CALLED, + NodeInteraction +} from '../NodeInteractions'; import type { ObjectPath } from '../utils/PathTracker'; import { getLiteralMembersForValue, @@ -50,22 +55,22 @@ export default class Literal extends Node return getMemberReturnExpressionWhenCalled(this.members, path[0]); } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - if (this.value === null) { - return path.length > 0; - } - return path.length > 1; - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - if (path.length === 1) { - return hasMemberEffectWhenCalled(this.members, path[0], callOptions, context); + switch (interaction.type) { + case INTERACTION_ACCESSED: + return path.length > (this.value === null ? 0 : 1); + case INTERACTION_ASSIGNED: + return true; + case INTERACTION_CALLED: + return ( + path.length !== 1 || + hasMemberEffectWhenCalled(this.members, path[0], interaction, context) + ); } - return true; } initialise(): void { diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index 09550e595b1..d7d496d6dd2 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -8,11 +8,10 @@ import { type RenderOptions } from '../../utils/renderHelpers'; import { removeAnnotations } from '../../utils/treeshakeNode'; -import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../NodeInteractions'; -import { NodeInteractionCalled } from '../NodeInteractions'; +import { NodeInteraction, NodeInteractionCalled } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, @@ -117,41 +116,19 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - const usedBranch = this.getUsedBranch(); - if (!usedBranch) { - return ( - this.left.hasEffectsWhenAccessedAtPath(path, context) || - this.right.hasEffectsWhenAccessedAtPath(path, context) - ); - } - return usedBranch.hasEffectsWhenAccessedAtPath(path, context); - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - const usedBranch = this.getUsedBranch(); - if (!usedBranch) { - return ( - this.left.hasEffectsWhenAssignedAtPath(path, context) || - this.right.hasEffectsWhenAssignedAtPath(path, context) - ); - } - return usedBranch.hasEffectsWhenAssignedAtPath(path, context); - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { const usedBranch = this.getUsedBranch(); if (!usedBranch) { return ( - this.left.hasEffectsWhenCalledAtPath(path, callOptions, context) || - this.right.hasEffectsWhenCalledAtPath(path, callOptions, context) + this.left.hasEffectsOnInteractionAtPath(path, interaction, context) || + this.right.hasEffectsOnInteractionAtPath(path, interaction, context) ); } - return usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, context); + return usedBranch.hasEffectsOnInteractionAtPath(path, interaction, context); } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 8e0a1a06c91..87f48c74b31 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -4,12 +4,12 @@ import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import { BLANK } from '../../utils/blank'; import relativeId from '../../utils/relativeId'; import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, + NodeInteraction, NodeInteractionCalled, NodeInteractionWithThisArg } from '../NodeInteractions'; @@ -86,6 +86,7 @@ function getStringFromPath(path: PathWithPositions): string { return pathString; } +// TODO Lukas if we check each access separately, we do not need to check getters when accessing a nested property in MethodBase, verify export default class MemberExpression extends NodeBase implements DeoptimizableEntity { declare computed: boolean; declare object: ExpressionNode | Super; @@ -237,51 +238,25 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE this.property.hasEffects(context) || this.object.hasEffects(context) || (checkAccess && this.hasAccessEffect(context)) || - this.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context) + this.hasEffectsOnInteractionAtPath(EMPTY_PATH, { type: INTERACTION_ASSIGNED, value }, context) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - if (this.variable) { - return this.variable.hasEffectsWhenAccessedAtPath(path, context); - } - if (this.replacement) { - return true; - } - if (path.length < MAX_PATH_DEPTH) { - return this.object.hasEffectsWhenAccessedAtPath([this.getPropertyKey(), ...path], context); - } - return true; - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - if (this.variable) { - return this.variable.hasEffectsWhenAssignedAtPath(path, context); - } - if (this.replacement) { - return true; - } - if (path.length < MAX_PATH_DEPTH) { - return this.object.hasEffectsWhenAssignedAtPath([this.getPropertyKey(), ...path], context); - } - return true; - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { if (this.variable) { - return this.variable.hasEffectsWhenCalledAtPath(path, callOptions, context); + return this.variable.hasEffectsOnInteractionAtPath(path, interaction, context); } if (this.replacement) { return true; } if (path.length < MAX_PATH_DEPTH) { - return this.object.hasEffectsWhenCalledAtPath( + return this.object.hasEffectsOnInteractionAtPath( [this.getPropertyKey(), ...path], - callOptions, + interaction, context ); } @@ -361,7 +336,6 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE !(this.variable || this.replacement) ) { const propertyKey = this.getPropertyKey(); - // TODO Lukas cache interaction this.object.deoptimizeThisOnInteractionAtPath( { thisArg: this.object, type: INTERACTION_ACCESSED }, [propertyKey], @@ -381,7 +355,6 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE propertyReadSideEffects && !(this.variable || this.replacement) ) { - // TODO Lukas cache interaction this.object.deoptimizeThisOnInteractionAtPath( { thisArg: this.object, type: INTERACTION_ASSIGNED, value }, [this.getPropertyKey()], @@ -425,7 +398,11 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE !(this.variable || this.replacement) && propertyReadSideEffects && (propertyReadSideEffects === 'always' || - this.object.hasEffectsWhenAccessedAtPath([this.getPropertyKey()], context)) + this.object.hasEffectsOnInteractionAtPath( + [this.getPropertyKey()], + { type: INTERACTION_ACCESSED }, + context + )) ); } diff --git a/src/ast/nodes/MetaProperty.ts b/src/ast/nodes/MetaProperty.ts index 5ec993c9bc0..9dbc2436959 100644 --- a/src/ast/nodes/MetaProperty.ts +++ b/src/ast/nodes/MetaProperty.ts @@ -4,8 +4,9 @@ import type { PluginDriver } from '../../utils/PluginDriver'; import { warnDeprecation } from '../../utils/error'; import type { GenerateCodeSnippets } from '../../utils/generateCodeSnippets'; import { dirname, normalize, relative } from '../../utils/path'; +import { INTERACTION_ACCESSED, NodeInteraction } from '../NodeInteractions'; import type ChildScope from '../scopes/ChildScope'; -import type { ObjectPathKey } from '../utils/PathTracker'; +import type { ObjectPath } from '../utils/PathTracker'; import type Identifier from './Identifier'; import MemberExpression from './MemberExpression'; import type * as NodeType from './NodeType'; @@ -52,8 +53,8 @@ export default class MetaProperty extends NodeBase { return false; } - hasEffectsWhenAccessedAtPath(path: readonly ObjectPathKey[]): boolean { - return path.length > 1; + hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { + return path.length > 1 || interaction.type !== INTERACTION_ACCESSED; } include(): void { diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index 5582606f156..bd56a83e5ab 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -2,9 +2,14 @@ import MagicString from 'magic-string'; import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import { renderCallArguments } from '../../utils/renderCallArguments'; import { RenderOptions } from '../../utils/renderHelpers'; -import type { CallOptions } from '../CallOptions'; import type { HasEffectsContext } from '../ExecutionContext'; import { InclusionContext } from '../ExecutionContext'; +import { + INTERACTION_ACCESSED, + INTERACTION_CALLED, + NodeInteraction, + NodeInteractionCalled +} from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; import { type ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; @@ -13,7 +18,7 @@ export default class NewExpression extends NodeBase { declare arguments: ExpressionNode[]; declare callee: ExpressionNode; declare type: NodeType.tNewExpression; - private declare callOptions: CallOptions; + private declare interaction: NodeInteractionCalled; hasEffects(context: HasEffectsContext): boolean { try { @@ -27,15 +32,15 @@ export default class NewExpression extends NodeBase { return false; return ( this.callee.hasEffects(context) || - this.callee.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.callOptions, context) + this.callee.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context) ); } finally { if (!this.deoptimized) this.applyDeoptimizations(); } } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - return path.length > 0; + hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { + return path.length > 0 || interaction.type !== INTERACTION_ACCESSED; } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { @@ -50,9 +55,10 @@ export default class NewExpression extends NodeBase { } initialise(): void { - this.callOptions = { + this.interaction = { args: this.arguments, thisArg: null, + type: INTERACTION_CALLED, withNew: true }; } diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index c8d6ddb3eb1..eeb273ce656 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -1,11 +1,10 @@ import type MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; -import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../NodeInteractions'; -import { NodeInteractionCalled } from '../NodeInteractions'; +import { NodeInteraction, NodeInteractionCalled } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, @@ -66,20 +65,12 @@ export default class ObjectExpression extends NodeBase implements DeoptimizableE ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return this.getObjectEntity().hasEffectsWhenAccessedAtPath(path, context); - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return this.getObjectEntity().hasEffectsWhenAssignedAtPath(path, context); - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - return this.getObjectEntity().hasEffectsWhenCalledAtPath(path, callOptions, context); + return this.getObjectEntity().hasEffectsOnInteractionAtPath(path, interaction, context); } render( diff --git a/src/ast/nodes/ObjectPattern.ts b/src/ast/nodes/ObjectPattern.ts index af21af5ec27..4e4a12a1216 100644 --- a/src/ast/nodes/ObjectPattern.ts +++ b/src/ast/nodes/ObjectPattern.ts @@ -1,4 +1,5 @@ import type { HasEffectsContext } from '../ExecutionContext'; +import { NodeInteractionAssigned } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath } from '../utils/PathTracker'; import type LocalVariable from '../variables/LocalVariable'; import type Variable from '../variables/Variable'; @@ -45,10 +46,14 @@ export default class ObjectPattern extends NodeBase implements PatternNode { } } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + hasEffectsOnInteractionAtPath( + path: ObjectPath, + interaction: NodeInteractionAssigned, + context: HasEffectsContext + ): boolean { if (path.length > 0) return true; for (const property of this.properties) { - if (property.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) return true; + if (property.hasEffectsOnInteractionAtPath(EMPTY_PATH, interaction, context)) return true; } return false; } diff --git a/src/ast/nodes/PropertyDefinition.ts b/src/ast/nodes/PropertyDefinition.ts index 7a30781bf12..0318e2f0eb1 100644 --- a/src/ast/nodes/PropertyDefinition.ts +++ b/src/ast/nodes/PropertyDefinition.ts @@ -1,8 +1,7 @@ -import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../NodeInteractions'; -import { NodeInteractionCalled } from '../NodeInteractions'; +import { NodeInteraction, NodeInteractionCalled } from '../NodeInteractions'; import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; import type PrivateIdentifier from './PrivateIdentifier'; @@ -58,20 +57,12 @@ export default class PropertyDefinition extends NodeBase { return this.key.hasEffects(context) || (this.static && !!this.value?.hasEffects(context)); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return !this.value || this.value.hasEffectsWhenAccessedAtPath(path, context); - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return !this.value || this.value.hasEffectsWhenAssignedAtPath(path, context); - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - return !this.value || this.value.hasEffectsWhenCalledAtPath(path, callOptions, context); + return !this.value || this.value.hasEffectsOnInteractionAtPath(path, interaction, context); } protected applyDeoptimizations() {} diff --git a/src/ast/nodes/RestElement.ts b/src/ast/nodes/RestElement.ts index b83fada446a..baad03a00c3 100644 --- a/src/ast/nodes/RestElement.ts +++ b/src/ast/nodes/RestElement.ts @@ -1,4 +1,5 @@ import type { HasEffectsContext } from '../ExecutionContext'; +import { NodeInteractionAssigned } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, UnknownKey } from '../utils/PathTracker'; import type LocalVariable from '../variables/LocalVariable'; import type Variable from '../variables/Variable'; @@ -28,8 +29,15 @@ export default class RestElement extends NodeBase implements PatternNode { path.length === 0 && this.argument.deoptimizePath(EMPTY_PATH); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return path.length > 0 || this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context); + hasEffectsOnInteractionAtPath( + path: ObjectPath, + interaction: NodeInteractionAssigned, + context: HasEffectsContext + ): boolean { + return ( + path.length > 0 || + this.argument.hasEffectsOnInteractionAtPath(EMPTY_PATH, interaction, context) + ); } markDeclarationReached(): void { diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index 7d3279f4eb6..d7f5aba5d28 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -7,10 +7,10 @@ import { type RenderOptions } from '../../utils/renderHelpers'; import { treeshakeNode } from '../../utils/treeshakeNode'; -import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../NodeInteractions'; +import { INTERACTION_ACCESSED, NodeInteraction } from '../NodeInteractions'; import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import ExpressionStatement from './ExpressionStatement'; import type * as NodeType from './NodeType'; @@ -56,28 +56,17 @@ export default class SequenceExpression extends NodeBase { return false; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return ( - path.length > 0 && - this.expressions[this.expressions.length - 1].hasEffectsWhenAccessedAtPath(path, context) - ); - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return this.expressions[this.expressions.length - 1].hasEffectsWhenAssignedAtPath( - path, - context - ); - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - return this.expressions[this.expressions.length - 1].hasEffectsWhenCalledAtPath( + if (path.length === 0 && interaction.type === INTERACTION_ACCESSED) { + return false; + } + return this.expressions[this.expressions.length - 1].hasEffectsOnInteractionAtPath( path, - callOptions, + interaction, context ); } diff --git a/src/ast/nodes/SpreadElement.ts b/src/ast/nodes/SpreadElement.ts index d2422e8c765..24d56fe48f7 100644 --- a/src/ast/nodes/SpreadElement.ts +++ b/src/ast/nodes/SpreadElement.ts @@ -1,6 +1,7 @@ import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../NodeInteractions'; +import { INTERACTION_ACCESSED } from '../NodeInteractions'; import { type ObjectPath, type PathTracker, UNKNOWN_PATH, UnknownKey } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; import { type ExpressionNode, NodeBase } from './shared/Node'; @@ -31,7 +32,11 @@ export default class SpreadElement extends NodeBase { this.argument.hasEffects(context) || (propertyReadSideEffects && (propertyReadSideEffects === 'always' || - this.argument.hasEffectsWhenAccessedAtPath(UNKNOWN_PATH, context))) + this.argument.hasEffectsOnInteractionAtPath( + UNKNOWN_PATH, + { type: INTERACTION_ACCESSED }, + context + ))) ); } diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index 197661c8216..10a7dfda304 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -47,7 +47,7 @@ export default class TaggedTemplateExpression extends CallExpressionBase { } return ( this.tag.hasEffects(context) || - this.tag.hasEffectsWhenCalledAtPath(EMPTY_PATH, this.interaction, context) + this.tag.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context) ); } finally { if (!this.deoptimized) this.applyDeoptimizations(); diff --git a/src/ast/nodes/TemplateLiteral.ts b/src/ast/nodes/TemplateLiteral.ts index 8ddc7dae135..098ea369724 100644 --- a/src/ast/nodes/TemplateLiteral.ts +++ b/src/ast/nodes/TemplateLiteral.ts @@ -1,7 +1,7 @@ import type MagicString from 'magic-string'; import type { RenderOptions } from '../../utils/renderHelpers'; -import { CallOptions } from '../CallOptions'; import { HasEffectsContext } from '../ExecutionContext'; +import { INTERACTION_ACCESSED, INTERACTION_CALLED, NodeInteraction } from '../NodeInteractions'; import type { ObjectPath } from '../utils/PathTracker'; import { getMemberReturnExpressionWhenCalled, @@ -39,17 +39,16 @@ export default class TemplateLiteral extends NodeBase { return getMemberReturnExpressionWhenCalled(literalStringMembers, path[0]); } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - return path.length > 1; - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - if (path.length === 1) { - return hasMemberEffectWhenCalled(literalStringMembers, path[0], callOptions, context); + if (interaction.type === INTERACTION_ACCESSED) { + return path.length > 1; + } + if (interaction.type === INTERACTION_CALLED && path.length === 1) { + return hasMemberEffectWhenCalled(literalStringMembers, path[0], interaction, context); } return true; } diff --git a/src/ast/nodes/ThisExpression.ts b/src/ast/nodes/ThisExpression.ts index 2cf1b8cbd06..0ae54d12be1 100644 --- a/src/ast/nodes/ThisExpression.ts +++ b/src/ast/nodes/ThisExpression.ts @@ -1,6 +1,7 @@ import type MagicString from 'magic-string'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeInteractionWithThisArg } from '../NodeInteractions'; +import type { NodeInteraction, NodeInteractionWithThisArg } from '../NodeInteractions'; +import { INTERACTION_ACCESSED } from '../NodeInteractions'; import ModuleScope from '../scopes/ModuleScope'; import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import type Variable from '../variables/Variable'; @@ -33,12 +34,15 @@ export default class ThisExpression extends NodeBase { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return path.length > 0 && this.variable.hasEffectsWhenAccessedAtPath(path, context); - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return this.variable.hasEffectsWhenAssignedAtPath(path, context); + hasEffectsOnInteractionAtPath( + path: ObjectPath, + interaction: NodeInteraction, + context: HasEffectsContext + ): boolean { + if (path.length === 0) { + return interaction.type !== INTERACTION_ACCESSED; + } + return this.variable.hasEffectsOnInteractionAtPath(path, interaction, context); } include(): void { diff --git a/src/ast/nodes/UnaryExpression.ts b/src/ast/nodes/UnaryExpression.ts index ae732027e02..ced92637fec 100644 --- a/src/ast/nodes/UnaryExpression.ts +++ b/src/ast/nodes/UnaryExpression.ts @@ -1,10 +1,11 @@ import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; +import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, NodeInteraction } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, type PathTracker } from '../utils/PathTracker'; import Identifier from './Identifier'; import type { LiteralValue } from './Literal'; import type * as NodeType from './NodeType'; -import { type LiteralValueOrUnknown, UnknownValue } from './shared/Expression'; +import { type LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from './shared/Expression'; import { type ExpressionNode, NodeBase } from './shared/Node'; const unaryOperators: { @@ -43,15 +44,18 @@ export default class UnaryExpression extends NodeBase { return ( this.argument.hasEffects(context) || (this.operator === 'delete' && - this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context)) + this.argument.hasEffectsOnInteractionAtPath( + EMPTY_PATH, + { type: INTERACTION_ASSIGNED, value: UNKNOWN_EXPRESSION }, + context + )) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - if (this.operator === 'void') { - return path.length > 0; - } - return path.length > 1; + hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { + return ( + interaction.type !== INTERACTION_ACCESSED || path.length > (this.operator === 'void' ? 0 : 1) + ); } protected applyDeoptimizations(): void { diff --git a/src/ast/nodes/UpdateExpression.ts b/src/ast/nodes/UpdateExpression.ts index 68a4c052718..7cdfe960dbf 100644 --- a/src/ast/nodes/UpdateExpression.ts +++ b/src/ast/nodes/UpdateExpression.ts @@ -6,9 +6,11 @@ import { renderSystemExportSequenceBeforeExpression } from '../../utils/systemJsRendering'; import type { HasEffectsContext } from '../ExecutionContext'; +import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, NodeInteraction } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath } from '../utils/PathTracker'; import Identifier from './Identifier'; import * as NodeType from './NodeType'; +import { UNKNOWN_EXPRESSION } from './shared/Expression'; import { type ExpressionNode, NodeBase } from './shared/Node'; export default class UpdateExpression extends NodeBase { @@ -21,12 +23,16 @@ export default class UpdateExpression extends NodeBase { if (!this.deoptimized) this.applyDeoptimizations(); return ( this.argument.hasEffects(context) || - this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, context) + this.argument.hasEffectsOnInteractionAtPath( + EMPTY_PATH, + { type: INTERACTION_ASSIGNED, value: UNKNOWN_EXPRESSION }, + context + ) ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - return path.length > 1; + hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { + return path.length > 1 || interaction.type !== INTERACTION_ACCESSED; } render(code: MagicString, options: RenderOptions): void { diff --git a/src/ast/nodes/VariableDeclaration.ts b/src/ast/nodes/VariableDeclaration.ts index cdd0b50578c..81f44d94611 100644 --- a/src/ast/nodes/VariableDeclaration.ts +++ b/src/ast/nodes/VariableDeclaration.ts @@ -49,7 +49,7 @@ export default class VariableDeclaration extends NodeBase { } } - hasEffectsWhenAssignedAtPath(): boolean { + hasEffectsOnInteractionAtPath(): boolean { return false; } diff --git a/src/ast/nodes/shared/CallExpressionBase.ts b/src/ast/nodes/shared/CallExpressionBase.ts index b5c42417d39..ee9177fe9fd 100644 --- a/src/ast/nodes/shared/CallExpressionBase.ts +++ b/src/ast/nodes/shared/CallExpressionBase.ts @@ -1,7 +1,12 @@ -import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext } from '../../ExecutionContext'; -import { NodeInteractionCalled, NodeInteractionWithThisArg } from '../../NodeInteractions'; +import { + INTERACTION_ASSIGNED, + INTERACTION_CALLED, + NodeInteraction, + NodeInteractionCalled, + NodeInteractionWithThisArg +} from '../../NodeInteractions'; import { type ObjectPath, type PathTracker, UNKNOWN_PATH } from '../../utils/PathTracker'; import { type ExpressionEntity, @@ -109,31 +114,30 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return ( - !context.accessed.trackEntityAtPathAndGetIfTracked(path, this) && - this.getReturnExpression().hasEffectsWhenAccessedAtPath(path, context) - ); - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return ( - !context.assigned.trackEntityAtPathAndGetIfTracked(path, this) && - this.getReturnExpression().hasEffectsWhenAssignedAtPath(path, context) - ); - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - return ( - !( - callOptions.withNew ? context.instantiated : context.called - ).trackEntityAtPathAndGetIfTracked(path, callOptions, this) && - this.getReturnExpression().hasEffectsWhenCalledAtPath(path, callOptions, context) - ); + const { type } = interaction; + if (type === INTERACTION_CALLED) { + if ( + (interaction.withNew + ? context.instantiated + : context.called + ).trackEntityAtPathAndGetIfTracked(path, interaction, this) + ) { + return false; + } + } else if ( + (type === INTERACTION_ASSIGNED + ? context.assigned + : context.accessed + ).trackEntityAtPathAndGetIfTracked(path, this) + ) { + return false; + } + return this.getReturnExpression().hasEffectsOnInteractionAtPath(path, interaction, context); } protected abstract getReturnExpression(recursionTracker?: PathTracker): ExpressionEntity; diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index e0fd19efc90..1d2061ee1b7 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -1,7 +1,11 @@ -import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; -import type { NodeInteractionCalled, NodeInteractionWithThisArg } from '../../NodeInteractions'; +import { + INTERACTION_CALLED, + NodeInteraction, + NodeInteractionCalled, + NodeInteractionWithThisArg +} from '../../NodeInteractions'; import ChildScope from '../../scopes/ChildScope'; import type Scope from '../../scopes/Scope'; import { @@ -78,29 +82,21 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { return initEffect || super.hasEffects(context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return this.getObjectEntity().hasEffectsWhenAccessedAtPath(path, context); - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return this.getObjectEntity().hasEffectsWhenAssignedAtPath(path, context); - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - if (path.length === 0) { + if (interaction.type === INTERACTION_CALLED && path.length === 0) { return ( - !callOptions.withNew || + !interaction.withNew || (this.classConstructor !== null - ? this.classConstructor.hasEffectsWhenCalledAtPath(EMPTY_PATH, callOptions, context) - : this.superClass?.hasEffectsWhenCalledAtPath(path, callOptions, context)) || + ? this.classConstructor.hasEffectsOnInteractionAtPath(path, interaction, context) + : this.superClass?.hasEffectsOnInteractionAtPath(path, interaction, context)) || false ); } else { - return this.getObjectEntity().hasEffectsWhenCalledAtPath(path, callOptions, context); + return this.getObjectEntity().hasEffectsOnInteractionAtPath(path, interaction, context); } } diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index 8b7d01edc2e..1b718c0382a 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -1,8 +1,11 @@ -import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { WritableEntity } from '../../Entity'; import { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; -import { NodeInteractionCalled, NodeInteractionWithThisArg } from '../../NodeInteractions'; +import { + NodeInteraction, + NodeInteractionCalled, + NodeInteractionWithThisArg +} from '../../NodeInteractions'; import { ObjectPath, PathTracker, UNKNOWN_PATH } from '../../utils/PathTracker'; import { LiteralValue } from '../Literal'; import SpreadElement from '../SpreadElement'; @@ -55,17 +58,9 @@ export class ExpressionEntity implements WritableEntity { return UNKNOWN_EXPRESSION; } - hasEffectsWhenAccessedAtPath(_path: ObjectPath, _context: HasEffectsContext): boolean { - return true; - } - - hasEffectsWhenAssignedAtPath(_path: ObjectPath, _context: HasEffectsContext): boolean { - return true; - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( _path: ObjectPath, - _callOptions: CallOptions, + _interaction: NodeInteraction, _context: HasEffectsContext ): boolean { return true; diff --git a/src/ast/nodes/shared/FunctionBase.ts b/src/ast/nodes/shared/FunctionBase.ts index d59189bd546..5354404e396 100644 --- a/src/ast/nodes/shared/FunctionBase.ts +++ b/src/ast/nodes/shared/FunctionBase.ts @@ -1,12 +1,18 @@ import type { NormalizedTreeshakingOptions } from '../../../rollup/types'; -import { type CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { BROKEN_FLOW_NONE, type HasEffectsContext, type InclusionContext } from '../../ExecutionContext'; -import { NO_ARGS, NodeInteractionCalled, NodeInteractionWithThisArg } from '../../NodeInteractions'; +import { + INTERACTION_ACCESSED, + INTERACTION_CALLED, + NO_ARGS, + NodeInteraction, + NodeInteractionCalled, + NodeInteractionWithThisArg +} from '../../NodeInteractions'; import ReturnValueScope from '../../scopes/ReturnValueScope'; import { type ObjectPath, PathTracker, UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker'; import BlockStatement from '../BlockStatement'; @@ -84,35 +90,31 @@ export default abstract class FunctionBase extends NodeBase { return this.scope.getReturnExpression(); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return this.getObjectEntity().hasEffectsWhenAccessedAtPath(path, context); - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return this.getObjectEntity().hasEffectsWhenAssignedAtPath(path, context); - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - if (path.length > 0) { - return this.getObjectEntity().hasEffectsWhenCalledAtPath(path, callOptions, context); + if (path.length > 0 || interaction.type !== INTERACTION_CALLED) { + return this.getObjectEntity().hasEffectsOnInteractionAtPath(path, interaction, context); } if (this.async) { const { propertyReadSideEffects } = this.context.options .treeshake as NormalizedTreeshakingOptions; const returnExpression = this.scope.getReturnExpression(); if ( - returnExpression.hasEffectsWhenCalledAtPath( + returnExpression.hasEffectsOnInteractionAtPath( ['then'], - { args: NO_ARGS, thisArg: null, withNew: false }, + { args: NO_ARGS, thisArg: null, type: INTERACTION_CALLED, withNew: false }, context ) || (propertyReadSideEffects && (propertyReadSideEffects === 'always' || - returnExpression.hasEffectsWhenAccessedAtPath(['then'], context))) + returnExpression.hasEffectsOnInteractionAtPath( + ['then'], + { type: INTERACTION_ACCESSED }, + context + ))) ) { return true; } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index d4d8d3f5f6d..e878009f9fc 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -1,6 +1,9 @@ -import { type CallOptions } from '../../CallOptions'; import { type HasEffectsContext, type InclusionContext } from '../../ExecutionContext'; -import { INTERACTION_CALLED, NodeInteractionWithThisArg } from '../../NodeInteractions'; +import { + INTERACTION_CALLED, + NodeInteraction, + NodeInteractionWithThisArg +} from '../../NodeInteractions'; import FunctionScope from '../../scopes/FunctionScope'; import { type ObjectPath, PathTracker } from '../../utils/PathTracker'; import BlockStatement from '../BlockStatement'; @@ -36,39 +39,41 @@ export default class FunctionNode extends FunctionBase { } } - hasEffects(): boolean { + hasEffects(context: HasEffectsContext): boolean { if (!this.deoptimized) this.applyDeoptimizations(); - return !!this.id?.hasEffects(); + return !!this.id?.hasEffects(context); } - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - if (super.hasEffectsWhenCalledAtPath(path, callOptions, context)) return true; - const thisInit = context.replacedVariableInits.get(this.scope.thisVariable); - context.replacedVariableInits.set( - this.scope.thisVariable, - callOptions.withNew - ? new ObjectEntity(Object.create(null), OBJECT_PROTOTYPE) - : UNKNOWN_EXPRESSION - ); - const { brokenFlow, ignore } = context; - context.ignore = { - breaks: false, - continues: false, - labels: new Set(), - returnYield: true - }; - if (this.body.hasEffects(context)) return true; - context.brokenFlow = brokenFlow; - if (thisInit) { - context.replacedVariableInits.set(this.scope.thisVariable, thisInit); - } else { - context.replacedVariableInits.delete(this.scope.thisVariable); + if (super.hasEffectsOnInteractionAtPath(path, interaction, context)) return true; + if (interaction.type === INTERACTION_CALLED) { + const thisInit = context.replacedVariableInits.get(this.scope.thisVariable); + context.replacedVariableInits.set( + this.scope.thisVariable, + interaction.withNew + ? new ObjectEntity(Object.create(null), OBJECT_PROTOTYPE) + : UNKNOWN_EXPRESSION + ); + const { brokenFlow, ignore } = context; + context.ignore = { + breaks: false, + continues: false, + labels: new Set(), + returnYield: true + }; + if (this.body.hasEffects(context)) return true; + context.brokenFlow = brokenFlow; + if (thisInit) { + context.replacedVariableInits.set(this.scope.thisVariable, thisInit); + } else { + context.replacedVariableInits.delete(this.scope.thisVariable); + } + context.ignore = ignore; } - context.ignore = ignore; return false; } diff --git a/src/ast/nodes/shared/MethodBase.ts b/src/ast/nodes/shared/MethodBase.ts index 10fc7112c06..ac0a3c6bbc1 100644 --- a/src/ast/nodes/shared/MethodBase.ts +++ b/src/ast/nodes/shared/MethodBase.ts @@ -1,4 +1,3 @@ -import { type CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext } from '../../ExecutionContext'; import { @@ -6,6 +5,7 @@ import { INTERACTION_ASSIGNED, INTERACTION_CALLED, NO_ARGS, + NodeInteraction, NodeInteractionCalled, NodeInteractionWithThisArg } from '../../NodeInteractions'; @@ -45,7 +45,6 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity path: ObjectPath, recursionTracker: PathTracker ): void { - // TODO Lukas cache and share interaction with hasEffects if (interaction.type === INTERACTION_ACCESSED && this.kind === 'get' && path.length === 0) { return this.value.deoptimizeThisOnInteractionAtPath( { args: NO_ARGS, thisArg: interaction.thisArg, type: INTERACTION_CALLED, withNew: false }, @@ -53,7 +52,6 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity recursionTracker ); } - // TODO Lukas cache and share interaction with hasEffects if (interaction.type === INTERACTION_ASSIGNED && this.kind === 'set' && path.length === 0) { return this.value.deoptimizeThisOnInteractionAtPath( { @@ -95,41 +93,38 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity return this.key.hasEffects(context); } - // TODO Lukas this should use the assigned thisArg - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - if (this.kind === 'get' && path.length === 0) { - return this.value.hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( + path: ObjectPath, + interaction: NodeInteraction, + context: HasEffectsContext + ): boolean { + if (this.kind === 'get' && interaction.type === INTERACTION_ACCESSED && path.length === 0) { + // TODO Lukas thisArg should be determined by parent alone + return this.value.hasEffectsOnInteractionAtPath( EMPTY_PATH, - { args: NO_ARGS, thisArg: null, withNew: false }, + { args: NO_ARGS, thisArg: null, type: INTERACTION_CALLED, withNew: false }, context ); } - return this.getAccessedValue().hasEffectsWhenAccessedAtPath(path, context); - } - - // TODO Lukas this should use the assigned value and thisArg - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - if (this.kind === 'set') { - return this.value.hasEffectsWhenCalledAtPath( + // setters are only called for empty paths + if (this.kind === 'set' && interaction.type === INTERACTION_ASSIGNED) { + // TODO Lukas thisArg should be determined by parent alone + return this.value.hasEffectsOnInteractionAtPath( EMPTY_PATH, - { args: NO_ARGS, thisArg: null, withNew: false }, + { + args: [interaction.value], + thisArg: null, + type: INTERACTION_CALLED, + withNew: false + }, context ); } - return this.getAccessedValue().hasEffectsWhenAssignedAtPath(path, context); - } - - hasEffectsWhenCalledAtPath( - path: ObjectPath, - callOptions: CallOptions, - context: HasEffectsContext - ): boolean { - return this.getAccessedValue().hasEffectsWhenCalledAtPath(path, callOptions, context); + return this.getAccessedValue().hasEffectsOnInteractionAtPath(path, interaction, context); } protected applyDeoptimizations() {} - // TODO Lukas this should get the thisArg as parameter from the interaction if accessed, null otherwise protected getAccessedValue(): ExpressionEntity { if (this.accessedValue === null) { if (this.kind === 'get') { diff --git a/src/ast/nodes/shared/MethodTypes.ts b/src/ast/nodes/shared/MethodTypes.ts index cd1298b60bd..0120fe1972d 100644 --- a/src/ast/nodes/shared/MethodTypes.ts +++ b/src/ast/nodes/shared/MethodTypes.ts @@ -1,9 +1,11 @@ -import { type CallOptions } from '../../CallOptions'; import type { HasEffectsContext } from '../../ExecutionContext'; import { + INTERACTION_ACCESSED, + INTERACTION_ASSIGNED, INTERACTION_CALLED, NO_ARGS, NodeInteraction, + NodeInteractionCalled, NodeInteractionWithThisArg } from '../../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_INTEGER_PATH } from '../../utils/PathTracker'; @@ -44,7 +46,7 @@ export class Method extends ExpressionEntity { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - interaction: NodeInteraction + interaction: NodeInteractionCalled ): ExpressionEntity { if (path.length > 0) { return UNKNOWN_EXPRESSION; @@ -57,43 +59,44 @@ export class Method extends ExpressionEntity { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - return path.length > 1; - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath): boolean { - return path.length > 0; - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - if ( - path.length > 0 || - (this.description.mutatesSelfAsArray === true && - callOptions.thisArg?.hasEffectsWhenAssignedAtPath(UNKNOWN_INTEGER_PATH, context)) - ) { + const { type } = interaction; + if (path.length > (type === INTERACTION_ACCESSED ? 1 : 0)) { return true; } - if (!this.description.callsArgs) { - return false; - } - for (const argIndex of this.description.callsArgs) { + if (type === INTERACTION_CALLED) { if ( - callOptions.args[argIndex]?.hasEffectsWhenCalledAtPath( - EMPTY_PATH, - { - args: NO_ARGS, - thisArg: null, - withNew: false - }, + this.description.mutatesSelfAsArray === true && + interaction.thisArg?.hasEffectsOnInteractionAtPath( + UNKNOWN_INTEGER_PATH, + { type: INTERACTION_ASSIGNED, value: UNKNOWN_EXPRESSION }, context ) ) { return true; } + if (this.description.callsArgs) { + for (const argIndex of this.description.callsArgs) { + if ( + interaction.args[argIndex]?.hasEffectsOnInteractionAtPath( + EMPTY_PATH, + { + args: NO_ARGS, + thisArg: null, + type: INTERACTION_CALLED, + withNew: false + }, + context + ) + ) { + return true; + } + } + } } return false; } diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index 14213e5e491..7497a8f914e 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -1,7 +1,6 @@ -import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../../ExecutionContext'; -import { NodeInteractionCalled } from '../../NodeInteractions'; +import { NodeInteraction, NodeInteractionCalled } from '../../NodeInteractions'; import type { ObjectPath, PathTracker } from '../../utils/PathTracker'; import { ExpressionEntity } from './Expression'; import type { IncludeChildren } from './Node'; @@ -32,27 +31,13 @@ export class MultiExpression extends ExpressionEntity { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - for (const expression of this.expressions) { - if (expression.hasEffectsWhenAccessedAtPath(path, context)) return true; - } - return false; - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - for (const expression of this.expressions) { - if (expression.hasEffectsWhenAssignedAtPath(path, context)) return true; - } - return false; - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { for (const expression of this.expressions) { - if (expression.hasEffectsWhenCalledAtPath(path, callOptions, context)) return true; + if (expression.hasEffectsOnInteractionAtPath(path, interaction, context)) return true; } return false; } diff --git a/src/ast/nodes/shared/ObjectEntity.ts b/src/ast/nodes/shared/ObjectEntity.ts index 0b070feaa05..a8368433c1e 100644 --- a/src/ast/nodes/shared/ObjectEntity.ts +++ b/src/ast/nodes/shared/ObjectEntity.ts @@ -1,9 +1,10 @@ -import { CallOptions } from '../../CallOptions'; import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { HasEffectsContext } from '../../ExecutionContext'; import { INTERACTION_ACCESSED, + INTERACTION_ASSIGNED, INTERACTION_CALLED, + NodeInteraction, NodeInteractionCalled, NodeInteractionWithThisArg } from '../../NodeInteractions'; @@ -275,111 +276,130 @@ export class ObjectEntity extends ExpressionEntity { return UNKNOWN_EXPRESSION; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + // TODO Lukas simplify + hasEffectsOnInteractionAtPath( + path: ObjectPath, + interaction: NodeInteraction, + context: HasEffectsContext + ): boolean { const [key, ...subPath] = path; - if (path.length > 1) { - if (typeof key !== 'string') { + switch (interaction.type) { + case INTERACTION_CALLED: { + const expressionAtPath = this.getMemberExpression(key); + if (expressionAtPath) { + return expressionAtPath.hasEffectsOnInteractionAtPath( + path.slice(1), + interaction, + context + ); + } + if (this.prototypeExpression) { + return this.prototypeExpression.hasEffectsOnInteractionAtPath(path, interaction, context); + } return true; } - const expressionAtPath = this.getMemberExpression(key); - if (expressionAtPath) { - return expressionAtPath.hasEffectsWhenAccessedAtPath(subPath, context); - } - if (this.prototypeExpression) { - return this.prototypeExpression.hasEffectsWhenAccessedAtPath(path, context); - } - return true; - } - - if (this.hasLostTrack) return true; - if (typeof key === 'string') { - if (this.propertiesAndGettersByKey[key]) { - const getters = this.gettersByKey[key]; - if (getters) { - for (const getter of getters) { - if (getter.hasEffectsWhenAccessedAtPath(subPath, context)) return true; + case INTERACTION_ACCESSED: { + if (path.length > 1) { + if (typeof key !== 'string') { + return true; + } + const expressionAtPath = this.getMemberExpression(key); + if (expressionAtPath) { + return expressionAtPath.hasEffectsOnInteractionAtPath(subPath, interaction, context); + } + if (this.prototypeExpression) { + return this.prototypeExpression.hasEffectsOnInteractionAtPath( + path, + interaction, + context + ); } - } - return false; - } - for (const getter of this.unmatchableGetters) { - if (getter.hasEffectsWhenAccessedAtPath(subPath, context)) { return true; } - } - } else { - for (const getters of Object.values(this.gettersByKey).concat([this.unmatchableGetters])) { - for (const getter of getters) { - if (getter.hasEffectsWhenAccessedAtPath(subPath, context)) return true; - } - } - } - if (this.prototypeExpression) { - return this.prototypeExpression.hasEffectsWhenAccessedAtPath(path, context); - } - return false; - } - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - const [key, ...subPath] = path; - if (path.length > 1) { - if (typeof key !== 'string') { - return true; - } - const expressionAtPath = this.getMemberExpression(key); - if (expressionAtPath) { - return expressionAtPath.hasEffectsWhenAssignedAtPath(subPath, context); - } - if (this.prototypeExpression) { - return this.prototypeExpression.hasEffectsWhenAssignedAtPath(path, context); - } - return true; - } - - if (key === UnknownNonAccessorKey) return false; - if (this.hasLostTrack) return true; - if (typeof key === 'string') { - if (this.propertiesAndSettersByKey[key]) { - const setters = this.settersByKey[key]; - if (setters) { - for (const setter of setters) { - if (setter.hasEffectsWhenAssignedAtPath(subPath, context)) return true; + if (this.hasLostTrack) return true; + if (typeof key === 'string') { + if (this.propertiesAndGettersByKey[key]) { + const getters = this.gettersByKey[key]; + if (getters) { + for (const getter of getters) { + if (getter.hasEffectsOnInteractionAtPath(subPath, interaction, context)) + return true; + } + } + return false; + } + for (const getter of this.unmatchableGetters) { + if (getter.hasEffectsOnInteractionAtPath(subPath, interaction, context)) { + return true; + } + } + } else { + for (const getters of Object.values(this.gettersByKey).concat([ + this.unmatchableGetters + ])) { + for (const getter of getters) { + if (getter.hasEffectsOnInteractionAtPath(subPath, interaction, context)) return true; + } } } + if (this.prototypeExpression) { + return this.prototypeExpression.hasEffectsOnInteractionAtPath(path, interaction, context); + } return false; } - for (const property of this.unmatchableSetters) { - if (property.hasEffectsWhenAssignedAtPath(subPath, context)) { + case INTERACTION_ASSIGNED: { + if (path.length > 1) { + if (typeof key !== 'string') { + return true; + } + const expressionAtPath = this.getMemberExpression(key); + if (expressionAtPath) { + return expressionAtPath.hasEffectsOnInteractionAtPath(subPath, interaction, context); + } + if (this.prototypeExpression) { + return this.prototypeExpression.hasEffectsOnInteractionAtPath( + path, + interaction, + context + ); + } return true; } - } - } else { - for (const setters of Object.values(this.settersByKey).concat([this.unmatchableSetters])) { - for (const setter of setters) { - if (setter.hasEffectsWhenAssignedAtPath(subPath, context)) return true; + + if (key === UnknownNonAccessorKey) return false; + if (this.hasLostTrack) return true; + if (typeof key === 'string') { + if (this.propertiesAndSettersByKey[key]) { + const setters = this.settersByKey[key]; + if (setters) { + for (const setter of setters) { + if (setter.hasEffectsOnInteractionAtPath(subPath, interaction, context)) + return true; + } + } + return false; + } + for (const property of this.unmatchableSetters) { + if (property.hasEffectsOnInteractionAtPath(subPath, interaction, context)) { + return true; + } + } + } else { + for (const setters of Object.values(this.settersByKey).concat([ + this.unmatchableSetters + ])) { + for (const setter of setters) { + if (setter.hasEffectsOnInteractionAtPath(subPath, interaction, context)) return true; + } + } + } + if (this.prototypeExpression) { + return this.prototypeExpression.hasEffectsOnInteractionAtPath(path, interaction, context); } + return false; } } - if (this.prototypeExpression) { - return this.prototypeExpression.hasEffectsWhenAssignedAtPath(path, context); - } - return false; - } - - hasEffectsWhenCalledAtPath( - path: ObjectPath, - callOptions: CallOptions, - context: HasEffectsContext - ): boolean { - const key = path[0]; - const expressionAtPath = this.getMemberExpression(key); - if (expressionAtPath) { - return expressionAtPath.hasEffectsWhenCalledAtPath(path.slice(1), callOptions, context); - } - if (this.prototypeExpression) { - return this.prototypeExpression.hasEffectsWhenCalledAtPath(path, callOptions, context); - } - return true; } private buildPropertyMaps(properties: readonly ObjectProperty[]): void { diff --git a/src/ast/nodes/shared/ObjectMember.ts b/src/ast/nodes/shared/ObjectMember.ts index 479960eec2d..e31287f6218 100644 --- a/src/ast/nodes/shared/ObjectMember.ts +++ b/src/ast/nodes/shared/ObjectMember.ts @@ -1,8 +1,7 @@ -import type { CallOptions } from '../../CallOptions'; import type { DeoptimizableEntity } from '../../DeoptimizableEntity'; import type { HasEffectsContext } from '../../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../../NodeInteractions'; -import { NodeInteractionCalled } from '../../NodeInteractions'; +import { NodeInteraction, NodeInteractionCalled } from '../../NodeInteractions'; import type { ObjectPath, PathTracker } from '../../utils/PathTracker'; import { ExpressionEntity, type LiteralValueOrUnknown } from './Expression'; @@ -49,19 +48,11 @@ export class ObjectMember extends ExpressionEntity { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return this.object.hasEffectsWhenAccessedAtPath([this.key, ...path], context); - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return this.object.hasEffectsWhenAssignedAtPath([this.key, ...path], context); - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - return this.object.hasEffectsWhenCalledAtPath([this.key, ...path], callOptions, context); + return this.object.hasEffectsOnInteractionAtPath([this.key, ...path], interaction, context); } } diff --git a/src/ast/nodes/shared/ObjectPrototype.ts b/src/ast/nodes/shared/ObjectPrototype.ts index 94e800ebe7c..d33972b25fc 100644 --- a/src/ast/nodes/shared/ObjectPrototype.ts +++ b/src/ast/nodes/shared/ObjectPrototype.ts @@ -1,4 +1,8 @@ -import { INTERACTION_CALLED, NodeInteractionWithThisArg } from '../../NodeInteractions'; +import { + INTERACTION_CALLED, + NodeInteraction, + NodeInteractionWithThisArg +} from '../../NodeInteractions'; import { ObjectPath, ObjectPathKey, UNKNOWN_PATH } from '../../utils/PathTracker'; import { ExpressionEntity, LiteralValueOrUnknown, UnknownValue } from './Expression'; import { @@ -32,12 +36,8 @@ const OBJECT_PROTOTYPE_FALLBACK: ExpressionEntity = return path.length === 1 && isInteger(path[0]) ? undefined : UnknownValue; } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - return path.length > 1; - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath): boolean { - return path.length > 1; + hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { + return path.length > 1 || interaction.type === INTERACTION_CALLED; } })(); diff --git a/src/ast/nodes/shared/knownGlobals.ts b/src/ast/nodes/shared/knownGlobals.ts index b42316d6bc3..a823d9a0b1f 100644 --- a/src/ast/nodes/shared/knownGlobals.ts +++ b/src/ast/nodes/shared/knownGlobals.ts @@ -1,14 +1,15 @@ /* eslint sort-keys: "off" */ -import { CallOptions } from '../../CallOptions'; import { HasEffectsContext } from '../../ExecutionContext'; -import { UNKNOWN_NON_ACCESSOR_PATH } from '../../utils/PathTracker'; +import { INTERACTION_ASSIGNED, NodeInteractionCalled } from '../../NodeInteractions'; import type { ObjectPath } from '../../utils/PathTracker'; +import { UNKNOWN_NON_ACCESSOR_PATH } from '../../utils/PathTracker'; +import { UNKNOWN_EXPRESSION } from './Expression'; const ValueProperties = Symbol('Value Properties'); interface ValueDescription { - hasEffectsWhenCalled(callOptions: CallOptions, context: HasEffectsContext): boolean; + hasEffectsWhenCalled(interaction: NodeInteractionCalled, context: HasEffectsContext): boolean; } interface GlobalDescription { @@ -46,10 +47,14 @@ const PF: GlobalDescription = { const MUTATES_ARG_WITHOUT_ACCESSOR: GlobalDescription = { __proto__: null, [ValueProperties]: { - hasEffectsWhenCalled(callOptions, context) { + hasEffectsWhenCalled({ args }, context) { return ( - !callOptions.args.length || - callOptions.args[0].hasEffectsWhenAssignedAtPath(UNKNOWN_NON_ACCESSOR_PATH, context) + !args.length || + args[0].hasEffectsOnInteractionAtPath( + UNKNOWN_NON_ACCESSOR_PATH, + { type: INTERACTION_ASSIGNED, value: UNKNOWN_EXPRESSION }, + context + ) ); } } diff --git a/src/ast/values.ts b/src/ast/values.ts index 39f9f5c1991..c5e22d1aecc 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -1,6 +1,11 @@ -import { type CallOptions } from './CallOptions'; import type { HasEffectsContext } from './ExecutionContext'; -import { NO_ARGS } from './NodeInteractions'; +import { + INTERACTION_ACCESSED, + INTERACTION_CALLED, + NO_ARGS, + NodeInteraction, + NodeInteractionCalled +} from './NodeInteractions'; import type { LiteralValue } from './nodes/Literal'; import { ExpressionEntity, UNKNOWN_EXPRESSION } from './nodes/shared/Expression'; import { @@ -11,7 +16,9 @@ import { } from './utils/PathTracker'; export interface MemberDescription { - hasEffectsWhenCalled: ((callOptions: CallOptions, context: HasEffectsContext) => boolean) | null; + hasEffectsWhenCalled: + | ((interaction: NodeInteractionCalled, context: HasEffectsContext) => boolean) + | null; returns: ExpressionEntity; } @@ -53,17 +60,16 @@ export const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = return UNKNOWN_EXPRESSION; } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - return path.length > 1; - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - if (path.length === 1) { - return hasMemberEffectWhenCalled(literalBooleanMembers, path[0], callOptions, context); + if (interaction.type === INTERACTION_ACCESSED) { + return path.length > 1; + } + if (interaction.type === INTERACTION_CALLED && path.length === 1) { + return hasMemberEffectWhenCalled(literalBooleanMembers, path[0], interaction, context); } return true; } @@ -85,17 +91,16 @@ export const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = return UNKNOWN_EXPRESSION; } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - return path.length > 1; - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - if (path.length === 1) { - return hasMemberEffectWhenCalled(literalNumberMembers, path[0], callOptions, context); + if (interaction.type === INTERACTION_ACCESSED) { + return path.length > 1; + } + if (interaction.type === INTERACTION_CALLED && path.length === 1) { + return hasMemberEffectWhenCalled(literalNumberMembers, path[0], interaction, context); } return true; } @@ -117,17 +122,16 @@ export const UNKNOWN_LITERAL_STRING: ExpressionEntity = return UNKNOWN_EXPRESSION; } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - return path.length > 1; - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - if (path.length === 1) { - return hasMemberEffectWhenCalled(literalStringMembers, path[0], callOptions, context); + if (interaction.type === INTERACTION_ACCESSED) { + return path.length > 1; + } + if (interaction.type === INTERACTION_CALLED && path.length === 1) { + return hasMemberEffectWhenCalled(literalStringMembers, path[0], interaction, context); } return true; } @@ -142,18 +146,19 @@ const returnsString: RawMemberDescription = { const stringReplace: RawMemberDescription = { value: { - hasEffectsWhenCalled(callOptions, context) { - const arg1 = callOptions.args[1]; + hasEffectsWhenCalled({ args }, context) { + const arg1 = args[1]; return ( - callOptions.args.length < 2 || + args.length < 2 || (typeof arg1.getLiteralValueAtPath(EMPTY_PATH, SHARED_RECURSION_TRACKER, { deoptimizeCache() {} }) === 'symbol' && - arg1.hasEffectsWhenCalledAtPath( + arg1.hasEffectsOnInteractionAtPath( EMPTY_PATH, { args: NO_ARGS, thisArg: null, + type: INTERACTION_CALLED, withNew: false }, context @@ -263,13 +268,13 @@ export function getLiteralMembersForValue export function hasMemberEffectWhenCalled( members: MemberDescriptions, memberName: ObjectPathKey, - callOptions: CallOptions, + interaction: NodeInteractionCalled, context: HasEffectsContext ): boolean { if (typeof memberName !== 'string' || !members[memberName]) { return true; } - return members[memberName].hasEffectsWhenCalled?.(callOptions, context) || false; + return members[memberName].hasEffectsWhenCalled?.(interaction, context) || false; } export function getMemberReturnExpressionWhenCalled( diff --git a/src/ast/variables/ArgumentsVariable.ts b/src/ast/variables/ArgumentsVariable.ts index 8826e29ac19..94ad7093def 100644 --- a/src/ast/variables/ArgumentsVariable.ts +++ b/src/ast/variables/ArgumentsVariable.ts @@ -1,6 +1,7 @@ import type { AstContext } from '../../Module'; +import { INTERACTION_ACCESSED, NodeInteraction } from '../NodeInteractions'; import { UNKNOWN_EXPRESSION } from '../nodes/shared/Expression'; -import type { ObjectPath } from '../utils/PathTracker'; +import { ObjectPath } from '../utils/PathTracker'; import LocalVariable from './LocalVariable'; export default class ArgumentsVariable extends LocalVariable { @@ -8,15 +9,7 @@ export default class ArgumentsVariable extends LocalVariable { super('arguments', null, UNKNOWN_EXPRESSION, context); } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - return path.length > 1; - } - - hasEffectsWhenAssignedAtPath(): boolean { - return true; - } - - hasEffectsWhenCalledAtPath(): boolean { - return true; + hasEffectsOnInteractionAtPath(path: ObjectPath, { type }: NodeInteraction): boolean { + return type !== INTERACTION_ACCESSED || path.length > 1; } } diff --git a/src/ast/variables/ExternalVariable.ts b/src/ast/variables/ExternalVariable.ts index 18f2f999cb9..e7fc1f760a5 100644 --- a/src/ast/variables/ExternalVariable.ts +++ b/src/ast/variables/ExternalVariable.ts @@ -1,4 +1,5 @@ import type ExternalModule from '../../ExternalModule'; +import { INTERACTION_ACCESSED, NodeInteraction } from '../NodeInteractions'; import type Identifier from '../nodes/Identifier'; import type { ObjectPath } from '../utils/PathTracker'; import Variable from './Variable'; @@ -21,8 +22,8 @@ export default class ExternalVariable extends Variable { } } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - return path.length > (this.isNamespace ? 1 : 0); + hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { + return interaction.type !== INTERACTION_ACCESSED || path.length > (this.isNamespace ? 1 : 0); } include(): void { diff --git a/src/ast/variables/GlobalVariable.ts b/src/ast/variables/GlobalVariable.ts index e79ecd421e5..d3959d8f905 100644 --- a/src/ast/variables/GlobalVariable.ts +++ b/src/ast/variables/GlobalVariable.ts @@ -1,6 +1,11 @@ -import { CallOptions } from '../CallOptions'; import { DeoptimizableEntity } from '../DeoptimizableEntity'; import { HasEffectsContext } from '../ExecutionContext'; +import { + INTERACTION_ACCESSED, + INTERACTION_ASSIGNED, + INTERACTION_CALLED, + NodeInteraction +} from '../NodeInteractions'; import { LiteralValueOrUnknown, UnknownTruthyValue, @@ -24,20 +29,24 @@ export default class GlobalVariable extends Variable { return getGlobalAtPath([this.name, ...path]) ? UnknownTruthyValue : UnknownValue; } - hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { - if (path.length === 0) { - // Technically, "undefined" is a global variable of sorts - return this.name !== 'undefined' && !getGlobalAtPath([this.name]); - } - return !getGlobalAtPath([this.name, ...path].slice(0, -1)); - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - const globalAtPath = getGlobalAtPath([this.name, ...path]); - return !globalAtPath || globalAtPath.hasEffectsWhenCalled(callOptions, context); + switch (interaction.type) { + case INTERACTION_ACCESSED: + if (path.length === 0) { + // Technically, "undefined" is a global variable of sorts + return this.name !== 'undefined' && !getGlobalAtPath([this.name]); + } + return !getGlobalAtPath([this.name, ...path].slice(0, -1)); + case INTERACTION_ASSIGNED: + return true; + case INTERACTION_CALLED: { + const globalAtPath = getGlobalAtPath([this.name, ...path]); + return !globalAtPath || globalAtPath.hasEffectsWhenCalled(interaction, context); + } + } } } diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index a4700a1cd33..863d9d36631 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -1,8 +1,13 @@ import Module, { AstContext } from '../../Module'; -import type { CallOptions } from '../CallOptions'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import { createInclusionContext, HasEffectsContext, InclusionContext } from '../ExecutionContext'; import type { NodeInteractionCalled, NodeInteractionWithThisArg } from '../NodeInteractions'; +import { + INTERACTION_ACCESSED, + INTERACTION_ASSIGNED, + INTERACTION_CALLED, + NodeInteraction +} from '../NodeInteractions'; import type ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration'; import type Identifier from '../nodes/Identifier'; import * as NodeType from '../nodes/NodeType'; @@ -141,33 +146,32 @@ export default class LocalVariable extends Variable { ); } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - if (this.isReassigned) return true; - return (this.init && - !context.accessed.trackEntityAtPathAndGetIfTracked(path, this) && - this.init.hasEffectsWhenAccessedAtPath(path, context))!; - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - if (this.included) return true; - if (path.length === 0) return false; - if (this.isReassigned) return true; - return (this.init && - !context.assigned.trackEntityAtPathAndGetIfTracked(path, this) && - this.init.hasEffectsWhenAssignedAtPath(path, context))!; - } - - hasEffectsWhenCalledAtPath( + hasEffectsOnInteractionAtPath( path: ObjectPath, - callOptions: CallOptions, + interaction: NodeInteraction, context: HasEffectsContext ): boolean { - if (this.isReassigned) return true; - return (this.init && - !( - callOptions.withNew ? context.instantiated : context.called - ).trackEntityAtPathAndGetIfTracked(path, callOptions, this) && - this.init.hasEffectsWhenCalledAtPath(path, callOptions, context))!; + switch (interaction.type) { + case INTERACTION_ACCESSED: + if (this.isReassigned) return true; + return (this.init && + !context.accessed.trackEntityAtPathAndGetIfTracked(path, this) && + this.init.hasEffectsOnInteractionAtPath(path, interaction, context))!; + case INTERACTION_ASSIGNED: + if (this.included) return true; + if (path.length === 0) return false; + if (this.isReassigned) return true; + return (this.init && + !context.assigned.trackEntityAtPathAndGetIfTracked(path, this) && + this.init.hasEffectsOnInteractionAtPath(path, interaction, context))!; + case INTERACTION_CALLED: + if (this.isReassigned) return true; + return (this.init && + !( + interaction.withNew ? context.instantiated : context.called + ).trackEntityAtPathAndGetIfTracked(path, interaction, this) && + this.init.hasEffectsOnInteractionAtPath(path, interaction, context))!; + } } include(): void { diff --git a/src/ast/variables/ThisVariable.ts b/src/ast/variables/ThisVariable.ts index 530c362955a..9586a57e22a 100644 --- a/src/ast/variables/ThisVariable.ts +++ b/src/ast/variables/ThisVariable.ts @@ -1,6 +1,6 @@ import type { AstContext } from '../../Module'; import type { HasEffectsContext } from '../ExecutionContext'; -import type { NodeInteractionWithThisArg } from '../NodeInteractions'; +import type { NodeInteraction, NodeInteractionWithThisArg } from '../NodeInteractions'; import { type ExpressionEntity, UNKNOWN_EXPRESSION } from '../nodes/shared/Expression'; import { DiscriminatedPathTracker, @@ -69,17 +69,14 @@ export default class ThisVariable extends LocalVariable { } } - hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { + hasEffectsOnInteractionAtPath( + path: ObjectPath, + interaction: NodeInteraction, + context: HasEffectsContext + ): boolean { return ( - this.getInit(context).hasEffectsWhenAccessedAtPath(path, context) || - super.hasEffectsWhenAccessedAtPath(path, context) - ); - } - - hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - return ( - this.getInit(context).hasEffectsWhenAssignedAtPath(path, context) || - super.hasEffectsWhenAssignedAtPath(path, context) + this.getInit(context).hasEffectsOnInteractionAtPath(path, interaction, context) || + super.hasEffectsOnInteractionAtPath(path, interaction, context) ); } diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index 51575176de6..fabf0ffe49e 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -1,6 +1,7 @@ import type ExternalModule from '../../ExternalModule'; import type Module from '../../Module'; import type { HasEffectsContext } from '../ExecutionContext'; +import { INTERACTION_ACCESSED, NodeInteraction } from '../NodeInteractions'; import type Identifier from '../nodes/Identifier'; import { ExpressionEntity } from '../nodes/shared/Expression'; import type { ObjectPath } from '../utils/PathTracker'; @@ -36,8 +37,15 @@ export default class Variable extends ExpressionEntity { return this.renderBaseName ? `${this.renderBaseName}${getPropertyAccess(name)}` : name; } - hasEffectsWhenAccessedAtPath(path: ObjectPath, _context: HasEffectsContext): boolean { - return path.length > 0; + hasEffectsOnInteractionAtPath( + path: ObjectPath, + interaction: NodeInteraction, + _context: HasEffectsContext + ): boolean { + if (interaction.type === INTERACTION_ACCESSED) { + return path.length > 0; + } + return true; } /** From 6503fd09a852ecb96461b78b615f83b483ebbe2a Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 6 Jun 2022 06:08:54 +0200 Subject: [PATCH 08/13] Fix accessor handling for loops and updated expressions --- src/ast/NodeInteractions.ts | 17 +++++- src/ast/nodes/AssignmentExpression.ts | 48 ++++++++-------- src/ast/nodes/BinaryExpression.ts | 4 +- src/ast/nodes/ForInStatement.ts | 47 +++++++++++----- src/ast/nodes/ForOfStatement.ts | 25 +++++++-- src/ast/nodes/Identifier.ts | 7 +-- src/ast/nodes/MemberExpression.ts | 32 ++++++----- src/ast/nodes/MetaProperty.ts | 4 +- src/ast/nodes/NewExpression.ts | 4 +- src/ast/nodes/SpreadElement.ts | 4 +- src/ast/nodes/UnaryExpression.ts | 16 +++--- src/ast/nodes/UpdateExpression.ts | 55 ++++++++++++++----- src/ast/nodes/shared/ClassNode.ts | 6 +- src/ast/nodes/shared/FunctionBase.ts | 4 +- src/ast/nodes/shared/MethodTypes.ts | 8 +-- src/ast/nodes/shared/ObjectPrototype.ts | 4 +- src/ast/nodes/shared/knownGlobals.ts | 5 +- src/ast/variables/ExternalVariable.ts | 4 +- src/ast/variables/Variable.ts | 7 +-- .../samples/for-in-accessors/_config.js | 3 + .../function/samples/for-in-accessors/main.js | 10 ++++ .../samples/for-of-accessors/_config.js | 3 + .../function/samples/for-of-accessors/main.js | 10 ++++ .../update-expression-accessors/_config.js | 3 + .../update-expression-accessors/main.js | 16 ++++++ 25 files changed, 231 insertions(+), 115 deletions(-) create mode 100644 test/function/samples/for-in-accessors/_config.js create mode 100644 test/function/samples/for-in-accessors/main.js create mode 100644 test/function/samples/for-of-accessors/_config.js create mode 100644 test/function/samples/for-of-accessors/main.js create mode 100644 test/function/samples/update-expression-accessors/_config.js create mode 100644 test/function/samples/update-expression-accessors/main.js diff --git a/src/ast/NodeInteractions.ts b/src/ast/NodeInteractions.ts index 062e87e6996..5bb458703df 100644 --- a/src/ast/NodeInteractions.ts +++ b/src/ast/NodeInteractions.ts @@ -1,5 +1,5 @@ import SpreadElement from './nodes/SpreadElement'; -import { ExpressionEntity } from './nodes/shared/Expression'; +import { ExpressionEntity, UNKNOWN_EXPRESSION } from './nodes/shared/Expression'; // TODO Lukas for usages and see if caching makes sense export const INTERACTION_ACCESSED = 0; @@ -7,14 +7,27 @@ export const INTERACTION_ASSIGNED = 1; export const INTERACTION_CALLED = 2; export interface NodeInteractionAccessed { + thisArg: ExpressionEntity | null; type: typeof INTERACTION_ACCESSED; } +export const NODE_INTERACTION_ACCESS: NodeInteractionAccessed = { + thisArg: null, + type: INTERACTION_ACCESSED +}; + export interface NodeInteractionAssigned { + thisArg: ExpressionEntity | null; type: typeof INTERACTION_ASSIGNED; value: ExpressionEntity; } +export const NODE_INTERACTION_UNKNOWN_ASSIGNMENT: NodeInteractionAssigned = { + thisArg: null, + type: INTERACTION_ASSIGNED, + value: UNKNOWN_EXPRESSION +}; + export interface NodeInteractionCalled { args: (ExpressionEntity | SpreadElement)[]; thisArg: ExpressionEntity | null; @@ -24,11 +37,9 @@ export interface NodeInteractionCalled { export const NO_ARGS = []; -// TODO Lukas destructure interactions where they are not forwarded export type NodeInteraction = | NodeInteractionAccessed | NodeInteractionAssigned | NodeInteractionCalled; -// TODO Lukas getters and setters can only every have their actual parent as thisArg -> do not add them to the interaction but get them statically export type NodeInteractionWithThisArg = NodeInteraction & { thisArg: ExpressionEntity }; diff --git a/src/ast/nodes/AssignmentExpression.ts b/src/ast/nodes/AssignmentExpression.ts index efa29316ffc..eeebcfa90b7 100644 --- a/src/ast/nodes/AssignmentExpression.ts +++ b/src/ast/nodes/AssignmentExpression.ts @@ -17,7 +17,12 @@ import { type HasEffectsContext, type InclusionContext } from '../ExecutionContext'; -import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, NodeInteraction } from '../NodeInteractions'; +import { + INTERACTION_ACCESSED, + INTERACTION_ASSIGNED, + NodeInteraction, + NodeInteractionAssigned +} from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import type Variable from '../variables/Variable'; import Identifier from './Identifier'; @@ -27,6 +32,7 @@ import ObjectPattern from './ObjectPattern'; import { type ExpressionNode, type IncludeChildren, NodeBase } from './shared/Node'; import type { PatternNode } from './shared/Pattern'; +// TODO during initialise, call a method on the ME target to set the assignment value export default class AssignmentExpression extends NodeBase { declare left: ExpressionNode | PatternNode; declare operator: @@ -45,22 +51,19 @@ export default class AssignmentExpression extends NodeBase { | '**='; declare right: ExpressionNode; declare type: NodeType.tAssignmentExpression; + private declare interaction: NodeInteractionAssigned; hasEffects(context: HasEffectsContext): boolean { - if (!this.deoptimized) this.applyDeoptimizations(); - const { right, left } = this; + const { deoptimized, left, right } = this; + if (!deoptimized) this.applyDeoptimizations(); // MemberExpressions do not access the property before assignments if the // operator is '='. Moreover, they imply a "this" value for setters. return ( right.hasEffects(context) || (left instanceof MemberExpression - ? left.hasEffectsAsAssignmentTarget(context, this.operator !== '=', right) + ? left.hasEffectsAsAssignmentTarget(context, this.operator !== '=') : left.hasEffects(context) || - left.hasEffectsOnInteractionAtPath( - EMPTY_PATH, - { type: INTERACTION_ASSIGNED, value: right }, - context - )) + left.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context)) ); } @@ -81,10 +84,10 @@ export default class AssignmentExpression extends NodeBase { } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { - if (!this.deoptimized) this.applyDeoptimizations(); + const { deoptimized, left, right, operator } = this; + if (!deoptimized) this.applyDeoptimizations(); this.included = true; let hasEffectsContext; - const { left, right, operator } = this; const isMemberExpression = left instanceof MemberExpression; if ( includeChildrenRecursively || @@ -92,21 +95,12 @@ export default class AssignmentExpression extends NodeBase { left.included || ((hasEffectsContext = createHasEffectsContext()), isMemberExpression - ? left.hasEffectsAsAssignmentTarget(hasEffectsContext, false, right) + ? left.hasEffectsAsAssignmentTarget(hasEffectsContext, false) : left.hasEffects(hasEffectsContext) || - left.hasEffectsOnInteractionAtPath( - EMPTY_PATH, - { type: INTERACTION_ASSIGNED, value: right }, - hasEffectsContext - )) + left.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, hasEffectsContext)) ) { if (isMemberExpression) { - left.includeAsAssignmentTarget( - context, - includeChildrenRecursively, - operator !== '=', - right - ); + left.includeAsAssignmentTarget(context, includeChildrenRecursively, operator !== '='); } else { left.include(context, includeChildrenRecursively); } @@ -114,6 +108,14 @@ export default class AssignmentExpression extends NodeBase { right.include(context, includeChildrenRecursively); } + initialise(): void { + const { left, right } = this; + this.interaction = { thisArg: null, type: INTERACTION_ASSIGNED, value: right }; + if (left instanceof MemberExpression) { + left.setAssignedValue(right); + } + } + render( code: MagicString, options: RenderOptions, diff --git a/src/ast/nodes/BinaryExpression.ts b/src/ast/nodes/BinaryExpression.ts index 8cd19d7b39c..d1167831f79 100644 --- a/src/ast/nodes/BinaryExpression.ts +++ b/src/ast/nodes/BinaryExpression.ts @@ -84,8 +84,8 @@ export default class BinaryExpression extends NodeBase implements DeoptimizableE return super.hasEffects(context); } - hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { - return interaction.type !== INTERACTION_ACCESSED || path.length > 1; + hasEffectsOnInteractionAtPath(path: ObjectPath, { type }: NodeInteraction): boolean { + return type !== INTERACTION_ACCESSED || path.length > 1; } render( diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index 9c3d9196458..8cccd3d76b1 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -1,10 +1,11 @@ import type MagicString from 'magic-string'; import { NO_SEMICOLON, type RenderOptions } from '../../utils/renderHelpers'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import { INTERACTION_ASSIGNED } from '../NodeInteractions'; +import { NODE_INTERACTION_UNKNOWN_ASSIGNMENT } from '../NodeInteractions'; import BlockScope from '../scopes/BlockScope'; import type Scope from '../scopes/Scope'; import { EMPTY_PATH } from '../utils/PathTracker'; +import MemberExpression from './MemberExpression'; import type * as NodeType from './NodeType'; import type VariableDeclaration from './VariableDeclaration'; import { UNKNOWN_EXPRESSION } from './shared/Expression'; @@ -18,7 +19,7 @@ import type { PatternNode } from './shared/Pattern'; export default class ForInStatement extends StatementBase { declare body: StatementNode; - declare left: VariableDeclaration | PatternNode; + declare left: VariableDeclaration | PatternNode | MemberExpression; declare right: ExpressionNode; declare type: NodeType.tForInStatement; @@ -27,16 +28,19 @@ export default class ForInStatement extends StatementBase { } hasEffects(context: HasEffectsContext): boolean { - if (!this.deoptimized) this.applyDeoptimizations(); + const { deoptimized, left, right } = this; + if (!deoptimized) this.applyDeoptimizations(); if ( - (this.left && - (this.left.hasEffects(context) || - this.left.hasEffectsOnInteractionAtPath( - EMPTY_PATH, - { type: INTERACTION_ASSIGNED, value: UNKNOWN_EXPRESSION }, - context - ))) || - (this.right && this.right.hasEffects(context)) + (left && + (left instanceof MemberExpression + ? left.hasEffectsAsAssignmentTarget(context, false) + : left.hasEffects(context) || + left.hasEffectsOnInteractionAtPath( + EMPTY_PATH, + NODE_INTERACTION_UNKNOWN_ASSIGNMENT, + context + ))) || + (right && right.hasEffects(context)) ) return true; const { @@ -53,15 +57,28 @@ export default class ForInStatement extends StatementBase { } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { - if (!this.deoptimized) this.applyDeoptimizations(); + const { body, deoptimized, left, right } = this; + if (!deoptimized) this.applyDeoptimizations(); this.included = true; - this.left.include(context, includeChildrenRecursively || true); - this.right.include(context, includeChildrenRecursively); + const includeLeftChildren = includeChildrenRecursively || true; + if (left instanceof MemberExpression) { + left.includeAsAssignmentTarget(context, includeLeftChildren, false); + } else { + left.include(context, includeLeftChildren); + } + right.include(context, includeChildrenRecursively); const { brokenFlow } = context; - this.body.include(context, includeChildrenRecursively, { asSingleStatement: true }); + body.include(context, includeChildrenRecursively, { asSingleStatement: true }); context.brokenFlow = brokenFlow; } + initialise() { + const { left } = this; + if (left instanceof MemberExpression) { + left.setAssignedValue(UNKNOWN_EXPRESSION); + } + } + render(code: MagicString, options: RenderOptions): void { this.left.render(code, options, NO_SEMICOLON); this.right.render(code, options, NO_SEMICOLON); diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index 8d35fd4e136..b8244a66db5 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -4,8 +4,10 @@ import type { InclusionContext } from '../ExecutionContext'; import BlockScope from '../scopes/BlockScope'; import type Scope from '../scopes/Scope'; import { EMPTY_PATH } from '../utils/PathTracker'; +import MemberExpression from './MemberExpression'; import type * as NodeType from './NodeType'; import type VariableDeclaration from './VariableDeclaration'; +import { UNKNOWN_EXPRESSION } from './shared/Expression'; import { type ExpressionNode, type IncludeChildren, @@ -17,7 +19,7 @@ import type { PatternNode } from './shared/Pattern'; export default class ForOfStatement extends StatementBase { declare await: boolean; declare body: StatementNode; - declare left: VariableDeclaration | PatternNode; + declare left: VariableDeclaration | PatternNode | MemberExpression; declare right: ExpressionNode; declare type: NodeType.tForOfStatement; @@ -32,15 +34,28 @@ export default class ForOfStatement extends StatementBase { } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { - if (!this.deoptimized) this.applyDeoptimizations(); + const { body, deoptimized, left, right } = this; + if (!deoptimized) this.applyDeoptimizations(); this.included = true; - this.left.include(context, includeChildrenRecursively || true); - this.right.include(context, includeChildrenRecursively); + const includeLeftChildren = includeChildrenRecursively || true; + if (left instanceof MemberExpression) { + left.includeAsAssignmentTarget(context, includeLeftChildren, false); + } else { + left.include(context, includeLeftChildren); + } + right.include(context, includeChildrenRecursively); const { brokenFlow } = context; - this.body.include(context, includeChildrenRecursively, { asSingleStatement: true }); + body.include(context, includeChildrenRecursively, { asSingleStatement: true }); context.brokenFlow = brokenFlow; } + initialise() { + const { left } = this; + if (left instanceof MemberExpression) { + left.setAssignedValue(UNKNOWN_EXPRESSION); + } + } + render(code: MagicString, options: RenderOptions): void { this.left.render(code, options, NO_SEMICOLON); this.right.render(code, options, NO_SEMICOLON); diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 50d1abd9af7..73bb9f90f59 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -10,6 +10,7 @@ import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, INTERACTION_CALLED, + NODE_INTERACTION_ACCESS, NodeInteraction, NodeInteractionCalled } from '../NodeInteractions'; @@ -139,11 +140,7 @@ export default class Identifier extends NodeBase implements PatternNode { return ( (this.context.options.treeshake as NormalizedTreeshakingOptions).unknownGlobalSideEffects && this.variable instanceof GlobalVariable && - this.variable.hasEffectsOnInteractionAtPath( - EMPTY_PATH, - { type: INTERACTION_ACCESSED }, - context - ) + this.variable.hasEffectsOnInteractionAtPath(EMPTY_PATH, NODE_INTERACTION_ACCESS, context) ); } diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 87f48c74b31..0df3ffeaa66 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -10,6 +10,8 @@ import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, NodeInteraction, + NodeInteractionAccessed, + NodeInteractionAssigned, NodeInteractionCalled, NodeInteractionWithThisArg } from '../NodeInteractions'; @@ -95,7 +97,9 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE declare propertyKey: ObjectPathKey | null; declare type: NodeType.tMemberExpression; variable: Variable | null = null; + private declare accessInteraction: NodeInteractionAccessed & { thisArg: ExpressionEntity }; private assignmentDeoptimized = false; + private declare assignmentInteraction: NodeInteractionAssigned & { thisArg: ExpressionEntity }; private bound = false; private expressionsToBeDeoptimized: DeoptimizableEntity[] = []; private replacement: string | null = null; @@ -228,17 +232,13 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } - hasEffectsAsAssignmentTarget( - context: HasEffectsContext, - checkAccess: boolean, - value: ExpressionEntity - ): boolean { - if (!this.assignmentDeoptimized) this.applyAssignmentDeoptimization(value); + hasEffectsAsAssignmentTarget(context: HasEffectsContext, checkAccess: boolean): boolean { + if (!this.assignmentDeoptimized) this.applyAssignmentDeoptimization(); return ( this.property.hasEffects(context) || this.object.hasEffects(context) || (checkAccess && this.hasAccessEffect(context)) || - this.hasEffectsOnInteractionAtPath(EMPTY_PATH, { type: INTERACTION_ASSIGNED, value }, context) + this.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.assignmentInteraction, context) ); } @@ -271,10 +271,9 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE includeAsAssignmentTarget( context: InclusionContext, includeChildrenRecursively: IncludeChildren, - deoptimizeAccess: boolean, - value: ExpressionEntity + deoptimizeAccess: boolean ): void { - if (!this.assignmentDeoptimized) this.applyAssignmentDeoptimization(value); + if (!this.assignmentDeoptimized) this.applyAssignmentDeoptimization(); if (deoptimizeAccess) { this.include(context, includeChildrenRecursively); } else { @@ -295,6 +294,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE initialise(): void { this.propertyKey = getResolvablePropertyKey(this); + this.accessInteraction = { thisArg: this.object, type: INTERACTION_ACCESSED }; } render( @@ -325,6 +325,10 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } } + setAssignedValue(value: ExpressionEntity) { + this.assignmentInteraction = { thisArg: this.object, type: INTERACTION_ASSIGNED, value }; + } + protected applyDeoptimizations(): void { this.deoptimized = true; const { propertyReadSideEffects } = this.context.options @@ -337,7 +341,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ) { const propertyKey = this.getPropertyKey(); this.object.deoptimizeThisOnInteractionAtPath( - { thisArg: this.object, type: INTERACTION_ACCESSED }, + this.accessInteraction, [propertyKey], SHARED_RECURSION_TRACKER ); @@ -345,7 +349,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } } - private applyAssignmentDeoptimization(value: ExpressionEntity): void { + private applyAssignmentDeoptimization(): void { this.assignmentDeoptimized = true; const { propertyReadSideEffects } = this.context.options .treeshake as NormalizedTreeshakingOptions; @@ -356,7 +360,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE !(this.variable || this.replacement) ) { this.object.deoptimizeThisOnInteractionAtPath( - { thisArg: this.object, type: INTERACTION_ASSIGNED, value }, + this.assignmentInteraction, [this.getPropertyKey()], SHARED_RECURSION_TRACKER ); @@ -400,7 +404,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE (propertyReadSideEffects === 'always' || this.object.hasEffectsOnInteractionAtPath( [this.getPropertyKey()], - { type: INTERACTION_ACCESSED }, + this.accessInteraction, context )) ); diff --git a/src/ast/nodes/MetaProperty.ts b/src/ast/nodes/MetaProperty.ts index 9dbc2436959..9b0c8a5f729 100644 --- a/src/ast/nodes/MetaProperty.ts +++ b/src/ast/nodes/MetaProperty.ts @@ -53,8 +53,8 @@ export default class MetaProperty extends NodeBase { return false; } - hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { - return path.length > 1 || interaction.type !== INTERACTION_ACCESSED; + hasEffectsOnInteractionAtPath(path: ObjectPath, { type }: NodeInteraction): boolean { + return path.length > 1 || type !== INTERACTION_ACCESSED; } include(): void { diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index bd56a83e5ab..e12915475e5 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -39,8 +39,8 @@ export default class NewExpression extends NodeBase { } } - hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { - return path.length > 0 || interaction.type !== INTERACTION_ACCESSED; + hasEffectsOnInteractionAtPath(path: ObjectPath, { type }: NodeInteraction): boolean { + return path.length > 0 || type !== INTERACTION_ACCESSED; } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { diff --git a/src/ast/nodes/SpreadElement.ts b/src/ast/nodes/SpreadElement.ts index 24d56fe48f7..0a9c4f31505 100644 --- a/src/ast/nodes/SpreadElement.ts +++ b/src/ast/nodes/SpreadElement.ts @@ -1,7 +1,7 @@ import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../NodeInteractions'; -import { INTERACTION_ACCESSED } from '../NodeInteractions'; +import { NODE_INTERACTION_ACCESS } from '../NodeInteractions'; import { type ObjectPath, type PathTracker, UNKNOWN_PATH, UnknownKey } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; import { type ExpressionNode, NodeBase } from './shared/Node'; @@ -34,7 +34,7 @@ export default class SpreadElement extends NodeBase { (propertyReadSideEffects === 'always' || this.argument.hasEffectsOnInteractionAtPath( UNKNOWN_PATH, - { type: INTERACTION_ACCESSED }, + NODE_INTERACTION_ACCESS, context ))) ); diff --git a/src/ast/nodes/UnaryExpression.ts b/src/ast/nodes/UnaryExpression.ts index ced92637fec..617458b276b 100644 --- a/src/ast/nodes/UnaryExpression.ts +++ b/src/ast/nodes/UnaryExpression.ts @@ -1,11 +1,15 @@ import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext } from '../ExecutionContext'; -import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, NodeInteraction } from '../NodeInteractions'; +import { + INTERACTION_ACCESSED, + NODE_INTERACTION_UNKNOWN_ASSIGNMENT, + NodeInteraction +} from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, type PathTracker } from '../utils/PathTracker'; import Identifier from './Identifier'; import type { LiteralValue } from './Literal'; import type * as NodeType from './NodeType'; -import { type LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from './shared/Expression'; +import { type LiteralValueOrUnknown, UnknownValue } from './shared/Expression'; import { type ExpressionNode, NodeBase } from './shared/Node'; const unaryOperators: { @@ -46,16 +50,14 @@ export default class UnaryExpression extends NodeBase { (this.operator === 'delete' && this.argument.hasEffectsOnInteractionAtPath( EMPTY_PATH, - { type: INTERACTION_ASSIGNED, value: UNKNOWN_EXPRESSION }, + NODE_INTERACTION_UNKNOWN_ASSIGNMENT, context )) ); } - hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { - return ( - interaction.type !== INTERACTION_ACCESSED || path.length > (this.operator === 'void' ? 0 : 1) - ); + hasEffectsOnInteractionAtPath(path: ObjectPath, { type }: NodeInteraction): boolean { + return type !== INTERACTION_ACCESSED || path.length > (this.operator === 'void' ? 0 : 1); } protected applyDeoptimizations(): void { diff --git a/src/ast/nodes/UpdateExpression.ts b/src/ast/nodes/UpdateExpression.ts index 7cdfe960dbf..aad8dfa6f7d 100644 --- a/src/ast/nodes/UpdateExpression.ts +++ b/src/ast/nodes/UpdateExpression.ts @@ -5,34 +5,61 @@ import { renderSystemExportSequenceAfterExpression, renderSystemExportSequenceBeforeExpression } from '../../utils/systemJsRendering'; -import type { HasEffectsContext } from '../ExecutionContext'; -import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, NodeInteraction } from '../NodeInteractions'; +import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; +import { + INTERACTION_ACCESSED, + NODE_INTERACTION_UNKNOWN_ASSIGNMENT, + NodeInteraction, + NodeInteractionAssigned +} from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath } from '../utils/PathTracker'; import Identifier from './Identifier'; +import MemberExpression from './MemberExpression'; import * as NodeType from './NodeType'; import { UNKNOWN_EXPRESSION } from './shared/Expression'; -import { type ExpressionNode, NodeBase } from './shared/Node'; +import { type ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; export default class UpdateExpression extends NodeBase { declare argument: ExpressionNode; declare operator: '++' | '--'; declare prefix: boolean; declare type: NodeType.tUpdateExpression; + private declare interaction: NodeInteractionAssigned; + // TODO Lukas make .hasEffectsAsAssignmentTarget a function on all nodes that defaults to hasEffects || ... ? What about includeAsAssignmentTarget? hasEffects(context: HasEffectsContext): boolean { - if (!this.deoptimized) this.applyDeoptimizations(); - return ( - this.argument.hasEffects(context) || - this.argument.hasEffectsOnInteractionAtPath( - EMPTY_PATH, - { type: INTERACTION_ASSIGNED, value: UNKNOWN_EXPRESSION }, - context - ) - ); + const { deoptimized, argument } = this; + if (!deoptimized) this.applyDeoptimizations(); + return argument instanceof MemberExpression + ? argument.hasEffectsAsAssignmentTarget(context, true) + : argument.hasEffects(context) || + argument.hasEffectsOnInteractionAtPath( + EMPTY_PATH, + NODE_INTERACTION_UNKNOWN_ASSIGNMENT, + context + ); + } + + hasEffectsOnInteractionAtPath(path: ObjectPath, { type }: NodeInteraction): boolean { + return path.length > 1 || type !== INTERACTION_ACCESSED; } - hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { - return path.length > 1 || interaction.type !== INTERACTION_ACCESSED; + include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { + const { deoptimized, argument } = this; + if (!deoptimized) this.applyDeoptimizations(); + this.included = true; + if (argument instanceof MemberExpression) { + argument.includeAsAssignmentTarget(context, includeChildrenRecursively, true); + } else { + argument.include(context, includeChildrenRecursively); + } + } + + initialise() { + const { argument } = this; + if (argument instanceof MemberExpression) { + argument.setAssignedValue(UNKNOWN_EXPRESSION); + } } render(code: MagicString, options: RenderOptions): void { diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index 1d2061ee1b7..06f54ee3db7 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -47,10 +47,10 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { deoptimizeThisOnInteractionAtPath( interaction: NodeInteractionWithThisArg, - _path: ObjectPath, - _recursionTracker: PathTracker + path: ObjectPath, + recursionTracker: PathTracker ): void { - this.getObjectEntity().deoptimizeThisOnInteractionAtPath(interaction, _path, _recursionTracker); + this.getObjectEntity().deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); } getLiteralValueAtPath( diff --git a/src/ast/nodes/shared/FunctionBase.ts b/src/ast/nodes/shared/FunctionBase.ts index 5354404e396..6bd2c4d0152 100644 --- a/src/ast/nodes/shared/FunctionBase.ts +++ b/src/ast/nodes/shared/FunctionBase.ts @@ -105,14 +105,14 @@ export default abstract class FunctionBase extends NodeBase { if ( returnExpression.hasEffectsOnInteractionAtPath( ['then'], - { args: NO_ARGS, thisArg: null, type: INTERACTION_CALLED, withNew: false }, + { args: NO_ARGS, thisArg: returnExpression, type: INTERACTION_CALLED, withNew: false }, context ) || (propertyReadSideEffects && (propertyReadSideEffects === 'always' || returnExpression.hasEffectsOnInteractionAtPath( ['then'], - { type: INTERACTION_ACCESSED }, + { thisArg: returnExpression, type: INTERACTION_ACCESSED }, context ))) ) { diff --git a/src/ast/nodes/shared/MethodTypes.ts b/src/ast/nodes/shared/MethodTypes.ts index 0120fe1972d..bebc12f7376 100644 --- a/src/ast/nodes/shared/MethodTypes.ts +++ b/src/ast/nodes/shared/MethodTypes.ts @@ -1,9 +1,9 @@ import type { HasEffectsContext } from '../../ExecutionContext'; import { INTERACTION_ACCESSED, - INTERACTION_ASSIGNED, INTERACTION_CALLED, NO_ARGS, + NODE_INTERACTION_UNKNOWN_ASSIGNMENT, NodeInteraction, NodeInteractionCalled, NodeInteractionWithThisArg @@ -46,7 +46,7 @@ export class Method extends ExpressionEntity { getReturnExpressionWhenCalledAtPath( path: ObjectPath, - interaction: NodeInteractionCalled + { thisArg }: NodeInteractionCalled ): ExpressionEntity { if (path.length > 0) { return UNKNOWN_EXPRESSION; @@ -54,7 +54,7 @@ export class Method extends ExpressionEntity { return ( this.description.returnsPrimitive || (this.description.returns === 'self' - ? interaction.thisArg || UNKNOWN_EXPRESSION + ? thisArg || UNKNOWN_EXPRESSION : this.description.returns()) ); } @@ -73,7 +73,7 @@ export class Method extends ExpressionEntity { this.description.mutatesSelfAsArray === true && interaction.thisArg?.hasEffectsOnInteractionAtPath( UNKNOWN_INTEGER_PATH, - { type: INTERACTION_ASSIGNED, value: UNKNOWN_EXPRESSION }, + NODE_INTERACTION_UNKNOWN_ASSIGNMENT, context ) ) { diff --git a/src/ast/nodes/shared/ObjectPrototype.ts b/src/ast/nodes/shared/ObjectPrototype.ts index d33972b25fc..36fea3a829e 100644 --- a/src/ast/nodes/shared/ObjectPrototype.ts +++ b/src/ast/nodes/shared/ObjectPrototype.ts @@ -36,8 +36,8 @@ const OBJECT_PROTOTYPE_FALLBACK: ExpressionEntity = return path.length === 1 && isInteger(path[0]) ? undefined : UnknownValue; } - hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { - return path.length > 1 || interaction.type === INTERACTION_CALLED; + hasEffectsOnInteractionAtPath(path: ObjectPath, { type }: NodeInteraction): boolean { + return path.length > 1 || type === INTERACTION_CALLED; } })(); diff --git a/src/ast/nodes/shared/knownGlobals.ts b/src/ast/nodes/shared/knownGlobals.ts index a823d9a0b1f..bffb9c98813 100644 --- a/src/ast/nodes/shared/knownGlobals.ts +++ b/src/ast/nodes/shared/knownGlobals.ts @@ -1,10 +1,9 @@ /* eslint sort-keys: "off" */ import { HasEffectsContext } from '../../ExecutionContext'; -import { INTERACTION_ASSIGNED, NodeInteractionCalled } from '../../NodeInteractions'; +import { NODE_INTERACTION_UNKNOWN_ASSIGNMENT, NodeInteractionCalled } from '../../NodeInteractions'; import type { ObjectPath } from '../../utils/PathTracker'; import { UNKNOWN_NON_ACCESSOR_PATH } from '../../utils/PathTracker'; -import { UNKNOWN_EXPRESSION } from './Expression'; const ValueProperties = Symbol('Value Properties'); @@ -52,7 +51,7 @@ const MUTATES_ARG_WITHOUT_ACCESSOR: GlobalDescription = { !args.length || args[0].hasEffectsOnInteractionAtPath( UNKNOWN_NON_ACCESSOR_PATH, - { type: INTERACTION_ASSIGNED, value: UNKNOWN_EXPRESSION }, + NODE_INTERACTION_UNKNOWN_ASSIGNMENT, context ) ); diff --git a/src/ast/variables/ExternalVariable.ts b/src/ast/variables/ExternalVariable.ts index e7fc1f760a5..ba27368b170 100644 --- a/src/ast/variables/ExternalVariable.ts +++ b/src/ast/variables/ExternalVariable.ts @@ -22,8 +22,8 @@ export default class ExternalVariable extends Variable { } } - hasEffectsOnInteractionAtPath(path: ObjectPath, interaction: NodeInteraction): boolean { - return interaction.type !== INTERACTION_ACCESSED || path.length > (this.isNamespace ? 1 : 0); + hasEffectsOnInteractionAtPath(path: ObjectPath, { type }: NodeInteraction): boolean { + return type !== INTERACTION_ACCESSED || path.length > (this.isNamespace ? 1 : 0); } include(): void { diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index fabf0ffe49e..3895d81503a 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -39,13 +39,10 @@ export default class Variable extends ExpressionEntity { hasEffectsOnInteractionAtPath( path: ObjectPath, - interaction: NodeInteraction, + { type }: NodeInteraction, _context: HasEffectsContext ): boolean { - if (interaction.type === INTERACTION_ACCESSED) { - return path.length > 0; - } - return true; + return type !== INTERACTION_ACCESSED || path.length > 0; } /** diff --git a/test/function/samples/for-in-accessors/_config.js b/test/function/samples/for-in-accessors/_config.js new file mode 100644 index 00000000000..70c608f68b2 --- /dev/null +++ b/test/function/samples/for-in-accessors/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'deoptimizes "this" for accessors triggered by for-in loops' +}; diff --git a/test/function/samples/for-in-accessors/main.js b/test/function/samples/for-in-accessors/main.js new file mode 100644 index 00000000000..e5194d0d9ee --- /dev/null +++ b/test/function/samples/for-in-accessors/main.js @@ -0,0 +1,10 @@ +const obj = { + setter: false, + set foo(value) { + this.setter = true; + } +}; + +for (obj.foo in {x:1}); + +assert.ok(obj.setter ? true : false); diff --git a/test/function/samples/for-of-accessors/_config.js b/test/function/samples/for-of-accessors/_config.js new file mode 100644 index 00000000000..e54f1f0ac4c --- /dev/null +++ b/test/function/samples/for-of-accessors/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'deoptimizes "this" for accessors triggered by for-of loops' +}; diff --git a/test/function/samples/for-of-accessors/main.js b/test/function/samples/for-of-accessors/main.js new file mode 100644 index 00000000000..b47bc702e82 --- /dev/null +++ b/test/function/samples/for-of-accessors/main.js @@ -0,0 +1,10 @@ +const obj = { + setter: false, + set foo(value) { + this.setter = true; + } +}; + +for (obj.foo of [1]); + +assert.ok(obj.setter ? true : false); diff --git a/test/function/samples/update-expression-accessors/_config.js b/test/function/samples/update-expression-accessors/_config.js new file mode 100644 index 00000000000..f271e4ae349 --- /dev/null +++ b/test/function/samples/update-expression-accessors/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'deoptimizes "this" for accessors triggered by update expressions' +}; diff --git a/test/function/samples/update-expression-accessors/main.js b/test/function/samples/update-expression-accessors/main.js new file mode 100644 index 00000000000..a8658cd7bd4 --- /dev/null +++ b/test/function/samples/update-expression-accessors/main.js @@ -0,0 +1,16 @@ +const obj = { + getter: false, + setter: false, + get foo() { + this.getter = true; + return 0; + }, + set foo(value) { + this.setter = true; + } +}; + +obj.foo++; + +assert.ok(obj.getter ? true : false); +assert.ok(obj.setter ? true : false); From b2dc098b10420bd2f8cc7e1a5ae06b7dcb02344b Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 6 Jun 2022 06:59:06 +0200 Subject: [PATCH 09/13] Simplify assignment handling --- src/ast/nodes/AssignmentExpression.ts | 38 ++---------- src/ast/nodes/ForInStatement.ts | 26 +------- src/ast/nodes/ForOfStatement.ts | 12 +--- src/ast/nodes/MemberExpression.ts | 3 +- src/ast/nodes/UpdateExpression.ts | 29 ++------- src/ast/nodes/shared/Node.ts | 88 ++++++++++++++++++++++----- 6 files changed, 90 insertions(+), 106 deletions(-) diff --git a/src/ast/nodes/AssignmentExpression.ts b/src/ast/nodes/AssignmentExpression.ts index eeebcfa90b7..bafb062c1e2 100644 --- a/src/ast/nodes/AssignmentExpression.ts +++ b/src/ast/nodes/AssignmentExpression.ts @@ -17,22 +17,15 @@ import { type HasEffectsContext, type InclusionContext } from '../ExecutionContext'; -import { - INTERACTION_ACCESSED, - INTERACTION_ASSIGNED, - NodeInteraction, - NodeInteractionAssigned -} from '../NodeInteractions'; +import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, NodeInteraction } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import type Variable from '../variables/Variable'; import Identifier from './Identifier'; -import MemberExpression from './MemberExpression'; import * as NodeType from './NodeType'; import ObjectPattern from './ObjectPattern'; import { type ExpressionNode, type IncludeChildren, NodeBase } from './shared/Node'; import type { PatternNode } from './shared/Pattern'; -// TODO during initialise, call a method on the ME target to set the assignment value export default class AssignmentExpression extends NodeBase { declare left: ExpressionNode | PatternNode; declare operator: @@ -51,19 +44,14 @@ export default class AssignmentExpression extends NodeBase { | '**='; declare right: ExpressionNode; declare type: NodeType.tAssignmentExpression; - private declare interaction: NodeInteractionAssigned; hasEffects(context: HasEffectsContext): boolean { const { deoptimized, left, right } = this; if (!deoptimized) this.applyDeoptimizations(); // MemberExpressions do not access the property before assignments if the - // operator is '='. Moreover, they imply a "this" value for setters. + // operator is '='. return ( - right.hasEffects(context) || - (left instanceof MemberExpression - ? left.hasEffectsAsAssignmentTarget(context, this.operator !== '=') - : left.hasEffects(context) || - left.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context)) + right.hasEffects(context) || left.hasEffectsAsAssignmentTarget(context, this.operator !== '=') ); } @@ -87,33 +75,19 @@ export default class AssignmentExpression extends NodeBase { const { deoptimized, left, right, operator } = this; if (!deoptimized) this.applyDeoptimizations(); this.included = true; - let hasEffectsContext; - const isMemberExpression = left instanceof MemberExpression; if ( includeChildrenRecursively || operator !== '=' || left.included || - ((hasEffectsContext = createHasEffectsContext()), - isMemberExpression - ? left.hasEffectsAsAssignmentTarget(hasEffectsContext, false) - : left.hasEffects(hasEffectsContext) || - left.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, hasEffectsContext)) + left.hasEffectsAsAssignmentTarget(createHasEffectsContext(), false) ) { - if (isMemberExpression) { - left.includeAsAssignmentTarget(context, includeChildrenRecursively, operator !== '='); - } else { - left.include(context, includeChildrenRecursively); - } + left.includeAsAssignmentTarget(context, includeChildrenRecursively, operator !== '='); } right.include(context, includeChildrenRecursively); } initialise(): void { - const { left, right } = this; - this.interaction = { thisArg: null, type: INTERACTION_ASSIGNED, value: right }; - if (left instanceof MemberExpression) { - left.setAssignedValue(right); - } + this.left.setAssignedValue(this.right); } render( diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index 8cccd3d76b1..ca66952cb89 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -1,7 +1,6 @@ import type MagicString from 'magic-string'; import { NO_SEMICOLON, type RenderOptions } from '../../utils/renderHelpers'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; -import { NODE_INTERACTION_UNKNOWN_ASSIGNMENT } from '../NodeInteractions'; import BlockScope from '../scopes/BlockScope'; import type Scope from '../scopes/Scope'; import { EMPTY_PATH } from '../utils/PathTracker'; @@ -30,18 +29,7 @@ export default class ForInStatement extends StatementBase { hasEffects(context: HasEffectsContext): boolean { const { deoptimized, left, right } = this; if (!deoptimized) this.applyDeoptimizations(); - if ( - (left && - (left instanceof MemberExpression - ? left.hasEffectsAsAssignmentTarget(context, false) - : left.hasEffects(context) || - left.hasEffectsOnInteractionAtPath( - EMPTY_PATH, - NODE_INTERACTION_UNKNOWN_ASSIGNMENT, - context - ))) || - (right && right.hasEffects(context)) - ) + if (left?.hasEffectsAsAssignmentTarget(context, false) || right?.hasEffects(context)) return true; const { brokenFlow, @@ -60,12 +48,7 @@ export default class ForInStatement extends StatementBase { const { body, deoptimized, left, right } = this; if (!deoptimized) this.applyDeoptimizations(); this.included = true; - const includeLeftChildren = includeChildrenRecursively || true; - if (left instanceof MemberExpression) { - left.includeAsAssignmentTarget(context, includeLeftChildren, false); - } else { - left.include(context, includeLeftChildren); - } + left.includeAsAssignmentTarget(context, includeChildrenRecursively || true, false); right.include(context, includeChildrenRecursively); const { brokenFlow } = context; body.include(context, includeChildrenRecursively, { asSingleStatement: true }); @@ -73,10 +56,7 @@ export default class ForInStatement extends StatementBase { } initialise() { - const { left } = this; - if (left instanceof MemberExpression) { - left.setAssignedValue(UNKNOWN_EXPRESSION); - } + this.left.setAssignedValue(UNKNOWN_EXPRESSION); } render(code: MagicString, options: RenderOptions): void { diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index b8244a66db5..edca00c7d5d 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -37,12 +37,7 @@ export default class ForOfStatement extends StatementBase { const { body, deoptimized, left, right } = this; if (!deoptimized) this.applyDeoptimizations(); this.included = true; - const includeLeftChildren = includeChildrenRecursively || true; - if (left instanceof MemberExpression) { - left.includeAsAssignmentTarget(context, includeLeftChildren, false); - } else { - left.include(context, includeLeftChildren); - } + left.includeAsAssignmentTarget(context, includeChildrenRecursively || true, false); right.include(context, includeChildrenRecursively); const { brokenFlow } = context; body.include(context, includeChildrenRecursively, { asSingleStatement: true }); @@ -50,10 +45,7 @@ export default class ForOfStatement extends StatementBase { } initialise() { - const { left } = this; - if (left instanceof MemberExpression) { - left.setAssignedValue(UNKNOWN_EXPRESSION); - } + this.left.setAssignedValue(UNKNOWN_EXPRESSION); } render(code: MagicString, options: RenderOptions): void { diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 0df3ffeaa66..8d6ad0e65d3 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -97,9 +97,9 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE declare propertyKey: ObjectPathKey | null; declare type: NodeType.tMemberExpression; variable: Variable | null = null; + protected declare assignmentInteraction: NodeInteractionAssigned & { thisArg: ExpressionEntity }; private declare accessInteraction: NodeInteractionAccessed & { thisArg: ExpressionEntity }; private assignmentDeoptimized = false; - private declare assignmentInteraction: NodeInteractionAssigned & { thisArg: ExpressionEntity }; private bound = false; private expressionsToBeDeoptimized: DeoptimizableEntity[] = []; private replacement: string | null = null; @@ -233,6 +233,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } hasEffectsAsAssignmentTarget(context: HasEffectsContext, checkAccess: boolean): boolean { + if (checkAccess && !this.deoptimized) this.applyDeoptimizations(); if (!this.assignmentDeoptimized) this.applyAssignmentDeoptimization(); return ( this.property.hasEffects(context) || diff --git a/src/ast/nodes/UpdateExpression.ts b/src/ast/nodes/UpdateExpression.ts index aad8dfa6f7d..141768649f6 100644 --- a/src/ast/nodes/UpdateExpression.ts +++ b/src/ast/nodes/UpdateExpression.ts @@ -8,13 +8,11 @@ import { import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { INTERACTION_ACCESSED, - NODE_INTERACTION_UNKNOWN_ASSIGNMENT, NodeInteraction, NodeInteractionAssigned } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath } from '../utils/PathTracker'; import Identifier from './Identifier'; -import MemberExpression from './MemberExpression'; import * as NodeType from './NodeType'; import { UNKNOWN_EXPRESSION } from './shared/Expression'; import { type ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; @@ -26,18 +24,9 @@ export default class UpdateExpression extends NodeBase { declare type: NodeType.tUpdateExpression; private declare interaction: NodeInteractionAssigned; - // TODO Lukas make .hasEffectsAsAssignmentTarget a function on all nodes that defaults to hasEffects || ... ? What about includeAsAssignmentTarget? hasEffects(context: HasEffectsContext): boolean { - const { deoptimized, argument } = this; - if (!deoptimized) this.applyDeoptimizations(); - return argument instanceof MemberExpression - ? argument.hasEffectsAsAssignmentTarget(context, true) - : argument.hasEffects(context) || - argument.hasEffectsOnInteractionAtPath( - EMPTY_PATH, - NODE_INTERACTION_UNKNOWN_ASSIGNMENT, - context - ); + if (!this.deoptimized) this.applyDeoptimizations(); + return this.argument.hasEffectsAsAssignmentTarget(context, true); } hasEffectsOnInteractionAtPath(path: ObjectPath, { type }: NodeInteraction): boolean { @@ -45,21 +34,13 @@ export default class UpdateExpression extends NodeBase { } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) { - const { deoptimized, argument } = this; - if (!deoptimized) this.applyDeoptimizations(); + if (!this.deoptimized) this.applyDeoptimizations(); this.included = true; - if (argument instanceof MemberExpression) { - argument.includeAsAssignmentTarget(context, includeChildrenRecursively, true); - } else { - argument.include(context, includeChildrenRecursively); - } + this.argument.includeAsAssignmentTarget(context, includeChildrenRecursively, true); } initialise() { - const { argument } = this; - if (argument instanceof MemberExpression) { - argument.setAssignedValue(UNKNOWN_EXPRESSION); - } + this.argument.setAssignedValue(UNKNOWN_EXPRESSION); } render(code: MagicString, options: RenderOptions): void { diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index cabbc7f0813..656a1008c74 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -10,9 +10,10 @@ import { type HasEffectsContext, type InclusionContext } from '../../ExecutionContext'; +import { INTERACTION_ASSIGNED, NodeInteractionAssigned } from '../../NodeInteractions'; import { getAndCreateKeys, keys } from '../../keys'; import type ChildScope from '../../scopes/ChildScope'; -import { UNKNOWN_PATH } from '../../utils/PathTracker'; +import { EMPTY_PATH, UNKNOWN_PATH } from '../../utils/PathTracker'; import type Variable from '../../variables/Variable'; import * as NodeType from '../NodeType'; import { ExpressionEntity, InclusionOptions } from './Expression'; @@ -44,22 +45,32 @@ export interface Node extends Entity { ): void; /** - * Called once all nodes have been initialised and the scopes have been populated. + * Called once all nodes have been initialised and the scopes have been + * populated. */ bind(): void; /** - * Determine if this Node would have an effect on the bundle. - * This is usually true for already included nodes. Exceptions are e.g. break statements - * which only have an effect if their surrounding loop or switch statement is included. + * Determine if this Node would have an effect on the bundle. This is usually + * true for already included nodes. Exceptions are e.g. break statements which + * only have an effect if their surrounding loop or switch statement is + * included. * The options pass on information like this about the current execution path. */ hasEffects(context: HasEffectsContext): boolean; /** - * Includes the node in the bundle. If the flag is not set, children are usually included - * 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. + * Special version of hasEffects for assignment left-hand sides which ensures + * that accessor effects are checked as well. This is necessary to do from the + * child so that member expressions can use the correct thisArg value. + * setAssignedValue needs to be called during initialise to use this. + */ + hasEffectsAsAssignmentTarget(context: HasEffectsContext, checkAccess: boolean): boolean; + + /** + * Includes the node in the bundle. If the flag is not set, children are + * usually included 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( context: InclusionContext, @@ -67,13 +78,33 @@ export interface Node extends Entity { options?: InclusionOptions ): void; + /** + * Special version of include for assignment left-hand sides which ensures + * that accessors are handled correctly. This is necessary to do from the + * child so that member expressions can use the correct thisArg value. + * setAssignedValue needs to be called during initialise to use this. + */ + includeAsAssignmentTarget( + context: InclusionContext, + includeChildrenRecursively: IncludeChildren, + deoptimizeAccess: boolean + ): void; + render(code: MagicString, options: RenderOptions, nodeRenderOptions?: NodeRenderOptions): void; /** - * Start a new execution path to determine if this node has an effect on the bundle and - * should therefore be included. Included nodes should always be included again in subsequent - * visits as the inclusion of additional variables may require the inclusion of more child - * nodes in e.g. block statements. + * Sets the assigned value e.g. for assignment expression left. This must be + * called during initialise in case hasEffects/includeAsAssignmentTarget are + * used. + */ + setAssignedValue(value: ExpressionEntity): void; + + /** + * Start a new execution path to determine if this node has an effect on the + * bundle and should therefore be included. Included nodes should always be + * included again in subsequent visits as the inclusion of additional + * variables may require the inclusion of more child nodes in e.g. block + * statements. */ shouldBeIncluded(context: InclusionContext): boolean; } @@ -92,10 +123,16 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { declare scope: ChildScope; declare start: number; declare type: keyof typeof NodeType; - // Nodes can apply custom deoptimizations once they become part of the - // executed code. To do this, they must initialize this as false, implement - // applyDeoptimizations and call this from include and hasEffects if they - // have custom handlers + /** + * This will be populated during initialise if setAssignedValue is called. + */ + protected declare assignmentInteraction: NodeInteractionAssigned; + /** + * Nodes can apply custom deoptimizations once they become part of the + * executed code. To do this, they must initialize this as false, implement + * applyDeoptimizations and call this from include and hasEffects if they have + * custom handlers + */ protected deoptimized = false; constructor( @@ -159,6 +196,13 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { return false; } + hasEffectsAsAssignmentTarget(context: HasEffectsContext, _checkAccess: boolean): boolean { + return ( + this.hasEffects(context) || + this.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.assignmentInteraction, context) + ); + } + include( context: InclusionContext, includeChildrenRecursively: IncludeChildren, @@ -179,6 +223,14 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { } } + includeAsAssignmentTarget( + context: InclusionContext, + includeChildrenRecursively: IncludeChildren, + _deoptimizeAccess: boolean + ) { + this.include(context, includeChildrenRecursively); + } + /** * Override to perform special initialisation steps after the scope is initialised */ @@ -236,6 +288,10 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { } } + setAssignedValue(value: ExpressionEntity): void { + this.assignmentInteraction = { thisArg: null, type: INTERACTION_ASSIGNED, value }; + } + shouldBeIncluded(context: InclusionContext): boolean { return this.included || (!context.brokenFlow && this.hasEffects(createHasEffectsContext())); } From a3e4baa8b5e25b8e9d984faf637c00059851c775 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 6 Jun 2022 14:26:29 +0200 Subject: [PATCH 10/13] Change assignment to use args property --- src/ast/NodeInteractions.ts | 10 ++++++---- src/ast/nodes/MemberExpression.ts | 6 +++++- src/ast/nodes/shared/CallExpressionBase.ts | 2 +- src/ast/nodes/shared/MethodBase.ts | 22 +++++++++++++++------- src/ast/nodes/shared/Node.ts | 2 +- src/ast/variables/LocalVariable.ts | 2 +- src/ast/variables/ThisVariable.ts | 2 +- 7 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/ast/NodeInteractions.ts b/src/ast/NodeInteractions.ts index 5bb458703df..e73af1a122e 100644 --- a/src/ast/NodeInteractions.ts +++ b/src/ast/NodeInteractions.ts @@ -17,19 +17,21 @@ export const NODE_INTERACTION_ACCESS: NodeInteractionAccessed = { }; export interface NodeInteractionAssigned { + args: readonly [ExpressionEntity]; thisArg: ExpressionEntity | null; type: typeof INTERACTION_ASSIGNED; - value: ExpressionEntity; } +export const UNKNOWN_ARG = [UNKNOWN_EXPRESSION] as const; + export const NODE_INTERACTION_UNKNOWN_ASSIGNMENT: NodeInteractionAssigned = { + args: UNKNOWN_ARG, thisArg: null, - type: INTERACTION_ASSIGNED, - value: UNKNOWN_EXPRESSION + type: INTERACTION_ASSIGNED }; export interface NodeInteractionCalled { - args: (ExpressionEntity | SpreadElement)[]; + args: readonly (ExpressionEntity | SpreadElement)[]; thisArg: ExpressionEntity | null; type: typeof INTERACTION_CALLED; withNew: boolean; diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 8d6ad0e65d3..6eb0cfcd932 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -327,7 +327,11 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } setAssignedValue(value: ExpressionEntity) { - this.assignmentInteraction = { thisArg: this.object, type: INTERACTION_ASSIGNED, value }; + this.assignmentInteraction = { + args: [value], + thisArg: this.object, + type: INTERACTION_ASSIGNED + }; } protected applyDeoptimizations(): void { diff --git a/src/ast/nodes/shared/CallExpressionBase.ts b/src/ast/nodes/shared/CallExpressionBase.ts index ee9177fe9fd..ad09dcf8c3e 100644 --- a/src/ast/nodes/shared/CallExpressionBase.ts +++ b/src/ast/nodes/shared/CallExpressionBase.ts @@ -125,7 +125,7 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo (interaction.withNew ? context.instantiated : context.called - ).trackEntityAtPathAndGetIfTracked(path, interaction, this) + ).trackEntityAtPathAndGetIfTracked(path, interaction.args, this) ) { return false; } diff --git a/src/ast/nodes/shared/MethodBase.ts b/src/ast/nodes/shared/MethodBase.ts index ac0a3c6bbc1..a8bdb505166 100644 --- a/src/ast/nodes/shared/MethodBase.ts +++ b/src/ast/nodes/shared/MethodBase.ts @@ -47,7 +47,12 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity ): void { if (interaction.type === INTERACTION_ACCESSED && this.kind === 'get' && path.length === 0) { return this.value.deoptimizeThisOnInteractionAtPath( - { args: NO_ARGS, thisArg: interaction.thisArg, type: INTERACTION_CALLED, withNew: false }, + { + args: NO_ARGS, + thisArg: interaction.thisArg, + type: INTERACTION_CALLED, + withNew: false + }, EMPTY_PATH, recursionTracker ); @@ -55,7 +60,7 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity if (interaction.type === INTERACTION_ASSIGNED && this.kind === 'set' && path.length === 0) { return this.value.deoptimizeThisOnInteractionAtPath( { - args: [interaction.value], + args: interaction.args, thisArg: interaction.thisArg, type: INTERACTION_CALLED, withNew: false @@ -99,21 +104,24 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity context: HasEffectsContext ): boolean { if (this.kind === 'get' && interaction.type === INTERACTION_ACCESSED && path.length === 0) { - // TODO Lukas thisArg should be determined by parent alone return this.value.hasEffectsOnInteractionAtPath( EMPTY_PATH, - { args: NO_ARGS, thisArg: null, type: INTERACTION_CALLED, withNew: false }, + { + args: NO_ARGS, + thisArg: interaction.thisArg, + type: INTERACTION_CALLED, + withNew: false + }, context ); } // setters are only called for empty paths if (this.kind === 'set' && interaction.type === INTERACTION_ASSIGNED) { - // TODO Lukas thisArg should be determined by parent alone return this.value.hasEffectsOnInteractionAtPath( EMPTY_PATH, { - args: [interaction.value], - thisArg: null, + args: interaction.args, + thisArg: interaction.thisArg, type: INTERACTION_CALLED, withNew: false }, diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index 656a1008c74..cb5337284f5 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -289,7 +289,7 @@ export class NodeBase extends ExpressionEntity implements ExpressionNode { } setAssignedValue(value: ExpressionEntity): void { - this.assignmentInteraction = { thisArg: null, type: INTERACTION_ASSIGNED, value }; + this.assignmentInteraction = { args: [value], thisArg: null, type: INTERACTION_ASSIGNED }; } shouldBeIncluded(context: InclusionContext): boolean { diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 863d9d36631..628c3a35acb 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -169,7 +169,7 @@ export default class LocalVariable extends Variable { return (this.init && !( interaction.withNew ? context.instantiated : context.called - ).trackEntityAtPathAndGetIfTracked(path, interaction, this) && + ).trackEntityAtPathAndGetIfTracked(path, interaction.args, this) && this.init.hasEffectsOnInteractionAtPath(path, interaction, context))!; } } diff --git a/src/ast/variables/ThisVariable.ts b/src/ast/variables/ThisVariable.ts index 9586a57e22a..cd049b81f27 100644 --- a/src/ast/variables/ThisVariable.ts +++ b/src/ast/variables/ThisVariable.ts @@ -58,7 +58,7 @@ export default class ThisVariable extends LocalVariable { if ( !this.thisDeoptimizations.trackEntityAtPathAndGetIfTracked( path, - interaction, + interaction.type, interaction.thisArg ) ) { From 981498e33784004cb1e2d819b39342a1ab3569bd Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Mon, 6 Jun 2022 14:58:44 +0200 Subject: [PATCH 11/13] Simplify ObjectEntity effect handling --- src/ast/nodes/MemberExpression.ts | 1 - src/ast/nodes/shared/ObjectEntity.ts | 145 +++++++-------------------- 2 files changed, 34 insertions(+), 112 deletions(-) diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 6eb0cfcd932..93922eb0e2c 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -88,7 +88,6 @@ function getStringFromPath(path: PathWithPositions): string { return pathString; } -// TODO Lukas if we check each access separately, we do not need to check getters when accessing a nested property in MethodBase, verify export default class MemberExpression extends NodeBase implements DeoptimizableEntity { declare computed: boolean; declare object: ExpressionNode | Super; diff --git a/src/ast/nodes/shared/ObjectEntity.ts b/src/ast/nodes/shared/ObjectEntity.ts index a8368433c1e..efbc2026427 100644 --- a/src/ast/nodes/shared/ObjectEntity.ts +++ b/src/ast/nodes/shared/ObjectEntity.ts @@ -2,7 +2,6 @@ import { DeoptimizableEntity } from '../../DeoptimizableEntity'; import { HasEffectsContext } from '../../ExecutionContext'; import { INTERACTION_ACCESSED, - INTERACTION_ASSIGNED, INTERACTION_CALLED, NodeInteraction, NodeInteractionCalled, @@ -255,11 +254,11 @@ export class ObjectEntity extends ExpressionEntity { if (path.length === 0) { return UNKNOWN_EXPRESSION; } - const key = path[0]; + const [key, ...subPath] = path; const expressionAtPath = this.getMemberExpressionAndTrackDeopt(key, origin); if (expressionAtPath) { return expressionAtPath.getReturnExpressionWhenCalledAtPath( - path.slice(1), + subPath, interaction, recursionTracker, origin @@ -276,130 +275,54 @@ export class ObjectEntity extends ExpressionEntity { return UNKNOWN_EXPRESSION; } - // TODO Lukas simplify hasEffectsOnInteractionAtPath( path: ObjectPath, interaction: NodeInteraction, context: HasEffectsContext ): boolean { const [key, ...subPath] = path; - switch (interaction.type) { - case INTERACTION_CALLED: { - const expressionAtPath = this.getMemberExpression(key); - if (expressionAtPath) { - return expressionAtPath.hasEffectsOnInteractionAtPath( - path.slice(1), - interaction, - context - ); - } - if (this.prototypeExpression) { - return this.prototypeExpression.hasEffectsOnInteractionAtPath(path, interaction, context); - } - return true; + if (subPath.length || interaction.type === INTERACTION_CALLED) { + const expressionAtPath = this.getMemberExpression(key); + if (expressionAtPath) { + return expressionAtPath.hasEffectsOnInteractionAtPath(subPath, interaction, context); } - case INTERACTION_ACCESSED: { - if (path.length > 1) { - if (typeof key !== 'string') { - return true; - } - const expressionAtPath = this.getMemberExpression(key); - if (expressionAtPath) { - return expressionAtPath.hasEffectsOnInteractionAtPath(subPath, interaction, context); - } - if (this.prototypeExpression) { - return this.prototypeExpression.hasEffectsOnInteractionAtPath( - path, - interaction, - context - ); - } - return true; - } - - if (this.hasLostTrack) return true; - if (typeof key === 'string') { - if (this.propertiesAndGettersByKey[key]) { - const getters = this.gettersByKey[key]; - if (getters) { - for (const getter of getters) { - if (getter.hasEffectsOnInteractionAtPath(subPath, interaction, context)) - return true; - } - } - return false; - } - for (const getter of this.unmatchableGetters) { - if (getter.hasEffectsOnInteractionAtPath(subPath, interaction, context)) { - return true; - } - } - } else { - for (const getters of Object.values(this.gettersByKey).concat([ - this.unmatchableGetters - ])) { - for (const getter of getters) { - if (getter.hasEffectsOnInteractionAtPath(subPath, interaction, context)) return true; - } + if (this.prototypeExpression) { + return this.prototypeExpression.hasEffectsOnInteractionAtPath(path, interaction, context); + } + return true; + } + if (key === UnknownNonAccessorKey) return false; + if (this.hasLostTrack) return true; + const [propertiesAndAccessorsByKey, accessorsByKey, unmatchableAccessors] = + interaction.type === INTERACTION_ACCESSED + ? [this.propertiesAndGettersByKey, this.gettersByKey, this.unmatchableGetters] + : [this.propertiesAndSettersByKey, this.settersByKey, this.unmatchableSetters]; + if (typeof key === 'string') { + if (propertiesAndAccessorsByKey[key]) { + const accessors = accessorsByKey[key]; + if (accessors) { + for (const accessor of accessors) { + if (accessor.hasEffectsOnInteractionAtPath(subPath, interaction, context)) return true; } } - if (this.prototypeExpression) { - return this.prototypeExpression.hasEffectsOnInteractionAtPath(path, interaction, context); - } return false; } - case INTERACTION_ASSIGNED: { - if (path.length > 1) { - if (typeof key !== 'string') { - return true; - } - const expressionAtPath = this.getMemberExpression(key); - if (expressionAtPath) { - return expressionAtPath.hasEffectsOnInteractionAtPath(subPath, interaction, context); - } - if (this.prototypeExpression) { - return this.prototypeExpression.hasEffectsOnInteractionAtPath( - path, - interaction, - context - ); - } + for (const accessor of unmatchableAccessors) { + if (accessor.hasEffectsOnInteractionAtPath(subPath, interaction, context)) { return true; } - - if (key === UnknownNonAccessorKey) return false; - if (this.hasLostTrack) return true; - if (typeof key === 'string') { - if (this.propertiesAndSettersByKey[key]) { - const setters = this.settersByKey[key]; - if (setters) { - for (const setter of setters) { - if (setter.hasEffectsOnInteractionAtPath(subPath, interaction, context)) - return true; - } - } - return false; - } - for (const property of this.unmatchableSetters) { - if (property.hasEffectsOnInteractionAtPath(subPath, interaction, context)) { - return true; - } - } - } else { - for (const setters of Object.values(this.settersByKey).concat([ - this.unmatchableSetters - ])) { - for (const setter of setters) { - if (setter.hasEffectsOnInteractionAtPath(subPath, interaction, context)) return true; - } - } - } - if (this.prototypeExpression) { - return this.prototypeExpression.hasEffectsOnInteractionAtPath(path, interaction, context); + } + } else { + for (const accessors of Object.values(accessorsByKey).concat([unmatchableAccessors])) { + for (const accessor of accessors) { + if (accessor.hasEffectsOnInteractionAtPath(subPath, interaction, context)) return true; } - return false; } } + if (this.prototypeExpression) { + return this.prototypeExpression.hasEffectsOnInteractionAtPath(path, interaction, context); + } + return false; } private buildPropertyMaps(properties: readonly ObjectProperty[]): void { From e8247bde59967404ac8992ae80dba1d23863c38f Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Tue, 7 Jun 2022 06:33:46 +0200 Subject: [PATCH 12/13] Cache remaining interactions that may be cached --- src/ast/NodeInteractions.ts | 13 +++++++++++-- src/ast/nodes/Identifier.ts | 8 ++++++-- src/ast/nodes/SpreadElement.ts | 4 ++-- src/ast/nodes/shared/FunctionBase.ts | 8 ++++---- src/ast/nodes/shared/MethodBase.ts | 3 ++- src/ast/nodes/shared/MethodTypes.ts | 9 ++------- src/ast/values.ts | 13 ++----------- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/ast/NodeInteractions.ts b/src/ast/NodeInteractions.ts index e73af1a122e..35113cb5b0f 100644 --- a/src/ast/NodeInteractions.ts +++ b/src/ast/NodeInteractions.ts @@ -1,7 +1,6 @@ import SpreadElement from './nodes/SpreadElement'; import { ExpressionEntity, UNKNOWN_EXPRESSION } from './nodes/shared/Expression'; -// TODO Lukas for usages and see if caching makes sense export const INTERACTION_ACCESSED = 0; export const INTERACTION_ASSIGNED = 1; export const INTERACTION_CALLED = 2; @@ -11,7 +10,7 @@ export interface NodeInteractionAccessed { type: typeof INTERACTION_ACCESSED; } -export const NODE_INTERACTION_ACCESS: NodeInteractionAccessed = { +export const NODE_INTERACTION_UNKNOWN_ACCESS: NodeInteractionAccessed = { thisArg: null, type: INTERACTION_ACCESSED }; @@ -39,6 +38,16 @@ export interface NodeInteractionCalled { export const NO_ARGS = []; +// While this is technically a call without arguments, we can compare against +// this reference in places where precise values or thisArg would make a +// difference +export const NODE_INTERACTION_UNKNOWN_CALL: NodeInteractionCalled = { + args: NO_ARGS, + thisArg: null, + type: INTERACTION_CALLED, + withNew: false +}; + export type NodeInteraction = | NodeInteractionAccessed | NodeInteractionAssigned diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 73bb9f90f59..b4ddde25c0f 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -10,7 +10,7 @@ import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, INTERACTION_CALLED, - NODE_INTERACTION_ACCESS, + NODE_INTERACTION_UNKNOWN_ACCESS, NodeInteraction, NodeInteractionCalled } from '../NodeInteractions'; @@ -140,7 +140,11 @@ export default class Identifier extends NodeBase implements PatternNode { return ( (this.context.options.treeshake as NormalizedTreeshakingOptions).unknownGlobalSideEffects && this.variable instanceof GlobalVariable && - this.variable.hasEffectsOnInteractionAtPath(EMPTY_PATH, NODE_INTERACTION_ACCESS, context) + this.variable.hasEffectsOnInteractionAtPath( + EMPTY_PATH, + NODE_INTERACTION_UNKNOWN_ACCESS, + context + ) ); } diff --git a/src/ast/nodes/SpreadElement.ts b/src/ast/nodes/SpreadElement.ts index 0a9c4f31505..4a863c1b785 100644 --- a/src/ast/nodes/SpreadElement.ts +++ b/src/ast/nodes/SpreadElement.ts @@ -1,7 +1,7 @@ import type { NormalizedTreeshakingOptions } from '../../rollup/types'; import type { HasEffectsContext } from '../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../NodeInteractions'; -import { NODE_INTERACTION_ACCESS } from '../NodeInteractions'; +import { NODE_INTERACTION_UNKNOWN_ACCESS } from '../NodeInteractions'; import { type ObjectPath, type PathTracker, UNKNOWN_PATH, UnknownKey } from '../utils/PathTracker'; import type * as NodeType from './NodeType'; import { type ExpressionNode, NodeBase } from './shared/Node'; @@ -34,7 +34,7 @@ export default class SpreadElement extends NodeBase { (propertyReadSideEffects === 'always' || this.argument.hasEffectsOnInteractionAtPath( UNKNOWN_PATH, - NODE_INTERACTION_ACCESS, + NODE_INTERACTION_UNKNOWN_ACCESS, context ))) ); diff --git a/src/ast/nodes/shared/FunctionBase.ts b/src/ast/nodes/shared/FunctionBase.ts index 6bd2c4d0152..a1517cb7e08 100644 --- a/src/ast/nodes/shared/FunctionBase.ts +++ b/src/ast/nodes/shared/FunctionBase.ts @@ -6,9 +6,9 @@ import { type InclusionContext } from '../../ExecutionContext'; import { - INTERACTION_ACCESSED, INTERACTION_CALLED, - NO_ARGS, + NODE_INTERACTION_UNKNOWN_ACCESS, + NODE_INTERACTION_UNKNOWN_CALL, NodeInteraction, NodeInteractionCalled, NodeInteractionWithThisArg @@ -105,14 +105,14 @@ export default abstract class FunctionBase extends NodeBase { if ( returnExpression.hasEffectsOnInteractionAtPath( ['then'], - { args: NO_ARGS, thisArg: returnExpression, type: INTERACTION_CALLED, withNew: false }, + NODE_INTERACTION_UNKNOWN_CALL, context ) || (propertyReadSideEffects && (propertyReadSideEffects === 'always' || returnExpression.hasEffectsOnInteractionAtPath( ['then'], - { thisArg: returnExpression, type: INTERACTION_ACCESSED }, + NODE_INTERACTION_UNKNOWN_ACCESS, context ))) ) { diff --git a/src/ast/nodes/shared/MethodBase.ts b/src/ast/nodes/shared/MethodBase.ts index a8bdb505166..fd76f898643 100644 --- a/src/ast/nodes/shared/MethodBase.ts +++ b/src/ast/nodes/shared/MethodBase.ts @@ -5,6 +5,7 @@ import { INTERACTION_ASSIGNED, INTERACTION_CALLED, NO_ARGS, + NODE_INTERACTION_UNKNOWN_CALL, NodeInteraction, NodeInteractionCalled, NodeInteractionWithThisArg @@ -139,7 +140,7 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity this.accessedValue = UNKNOWN_EXPRESSION; return (this.accessedValue = this.value.getReturnExpressionWhenCalledAtPath( EMPTY_PATH, - { args: NO_ARGS, thisArg: null, type: INTERACTION_CALLED, withNew: false }, + NODE_INTERACTION_UNKNOWN_CALL, SHARED_RECURSION_TRACKER, this )); diff --git a/src/ast/nodes/shared/MethodTypes.ts b/src/ast/nodes/shared/MethodTypes.ts index bebc12f7376..993ebbc91c1 100644 --- a/src/ast/nodes/shared/MethodTypes.ts +++ b/src/ast/nodes/shared/MethodTypes.ts @@ -2,8 +2,8 @@ import type { HasEffectsContext } from '../../ExecutionContext'; import { INTERACTION_ACCESSED, INTERACTION_CALLED, - NO_ARGS, NODE_INTERACTION_UNKNOWN_ASSIGNMENT, + NODE_INTERACTION_UNKNOWN_CALL, NodeInteraction, NodeInteractionCalled, NodeInteractionWithThisArg @@ -84,12 +84,7 @@ export class Method extends ExpressionEntity { if ( interaction.args[argIndex]?.hasEffectsOnInteractionAtPath( EMPTY_PATH, - { - args: NO_ARGS, - thisArg: null, - type: INTERACTION_CALLED, - withNew: false - }, + NODE_INTERACTION_UNKNOWN_CALL, context ) ) { diff --git a/src/ast/values.ts b/src/ast/values.ts index c5e22d1aecc..c180143921c 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -2,7 +2,7 @@ import type { HasEffectsContext } from './ExecutionContext'; import { INTERACTION_ACCESSED, INTERACTION_CALLED, - NO_ARGS, + NODE_INTERACTION_UNKNOWN_CALL, NodeInteraction, NodeInteractionCalled } from './NodeInteractions'; @@ -153,16 +153,7 @@ const stringReplace: RawMemberDescription = { (typeof arg1.getLiteralValueAtPath(EMPTY_PATH, SHARED_RECURSION_TRACKER, { deoptimizeCache() {} }) === 'symbol' && - arg1.hasEffectsOnInteractionAtPath( - EMPTY_PATH, - { - args: NO_ARGS, - thisArg: null, - type: INTERACTION_CALLED, - withNew: false - }, - context - )) + arg1.hasEffectsOnInteractionAtPath(EMPTY_PATH, NODE_INTERACTION_UNKNOWN_CALL, context)) ); }, returns: UNKNOWN_LITERAL_STRING From 605c701d5d447940480b6e74af367f1d8b6f4626 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Tue, 7 Jun 2022 07:07:21 +0200 Subject: [PATCH 13/13] Improve coverage --- src/ast/nodes/AssignmentExpression.ts | 10 +--------- src/ast/nodes/AssignmentPattern.ts | 16 ++-------------- src/ast/nodes/ForInStatement.ts | 3 +-- src/ast/nodes/ObjectPattern.ts | 5 +++-- src/ast/nodes/SequenceExpression.ts | 5 +---- 5 files changed, 8 insertions(+), 31 deletions(-) diff --git a/src/ast/nodes/AssignmentExpression.ts b/src/ast/nodes/AssignmentExpression.ts index bafb062c1e2..a985ddc272a 100644 --- a/src/ast/nodes/AssignmentExpression.ts +++ b/src/ast/nodes/AssignmentExpression.ts @@ -17,7 +17,7 @@ import { type HasEffectsContext, type InclusionContext } from '../ExecutionContext'; -import { INTERACTION_ACCESSED, INTERACTION_ASSIGNED, NodeInteraction } from '../NodeInteractions'; +import { NodeInteraction } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import type Variable from '../variables/Variable'; import Identifier from './Identifier'; @@ -60,14 +60,6 @@ export default class AssignmentExpression extends NodeBase { interaction: NodeInteraction, context: HasEffectsContext ): boolean { - if (path.length === 0) { - if (interaction.type === INTERACTION_ACCESSED) { - return false; - } - if (interaction.type === INTERACTION_ASSIGNED) { - return true; - } - } return this.right.hasEffectsOnInteractionAtPath(path, interaction, context); } diff --git a/src/ast/nodes/AssignmentPattern.ts b/src/ast/nodes/AssignmentPattern.ts index fdfc06a4d3c..14ef36a46b0 100644 --- a/src/ast/nodes/AssignmentPattern.ts +++ b/src/ast/nodes/AssignmentPattern.ts @@ -2,14 +2,13 @@ import type MagicString from 'magic-string'; import { BLANK } from '../../utils/blank'; import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers'; import type { HasEffectsContext } from '../ExecutionContext'; -import { InclusionContext } from '../ExecutionContext'; import { NodeInteractionAssigned } from '../NodeInteractions'; import { EMPTY_PATH, type ObjectPath, UNKNOWN_PATH } from '../utils/PathTracker'; import type LocalVariable from '../variables/LocalVariable'; import type Variable from '../variables/Variable'; import type * as NodeType from './NodeType'; import type { ExpressionEntity } from './shared/Expression'; -import { type ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; +import { type ExpressionNode, NodeBase } from './shared/Node'; import type { PatternNode } from './shared/Pattern'; export default class AssignmentPattern extends NodeBase implements PatternNode { @@ -42,13 +41,6 @@ export default class AssignmentPattern extends NodeBase implements PatternNode { ); } - include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { - if (!this.deoptimized) this.applyDeoptimizations(); - this.included = true; - this.left.include(context, includeChildrenRecursively); - this.right.include(context, includeChildrenRecursively); - } - markDeclarationReached(): void { this.left.markDeclarationReached(); } @@ -59,11 +51,7 @@ export default class AssignmentPattern extends NodeBase implements PatternNode { { isShorthandProperty }: NodeRenderOptions = BLANK ): void { this.left.render(code, options, { isShorthandProperty }); - if (this.right.included) { - this.right.render(code, options); - } else { - code.remove(this.left.end, this.end); - } + this.right.render(code, options); } protected applyDeoptimizations(): void { diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index ca66952cb89..8ca62b46738 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -29,8 +29,7 @@ export default class ForInStatement extends StatementBase { hasEffects(context: HasEffectsContext): boolean { const { deoptimized, left, right } = this; if (!deoptimized) this.applyDeoptimizations(); - if (left?.hasEffectsAsAssignmentTarget(context, false) || right?.hasEffects(context)) - return true; + if (left.hasEffectsAsAssignmentTarget(context, false) || right.hasEffects(context)) return true; const { brokenFlow, ignore: { breaks, continues } diff --git a/src/ast/nodes/ObjectPattern.ts b/src/ast/nodes/ObjectPattern.ts index 4e4a12a1216..42fa761eba4 100644 --- a/src/ast/nodes/ObjectPattern.ts +++ b/src/ast/nodes/ObjectPattern.ts @@ -47,11 +47,12 @@ export default class ObjectPattern extends NodeBase implements PatternNode { } hasEffectsOnInteractionAtPath( - path: ObjectPath, + // At the moment, this is only triggered for assignment left-hand sides, + // where the path is empty + _path: ObjectPath, interaction: NodeInteractionAssigned, context: HasEffectsContext ): boolean { - if (path.length > 0) return true; for (const property of this.properties) { if (property.hasEffectsOnInteractionAtPath(EMPTY_PATH, interaction, context)) return true; } diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index d7f5aba5d28..b2f5a1b48e4 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -10,7 +10,7 @@ import { treeshakeNode } from '../../utils/treeshakeNode'; import type { DeoptimizableEntity } from '../DeoptimizableEntity'; import type { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import type { NodeInteractionWithThisArg } from '../NodeInteractions'; -import { INTERACTION_ACCESSED, NodeInteraction } from '../NodeInteractions'; +import { NodeInteraction } from '../NodeInteractions'; import type { ObjectPath, PathTracker } from '../utils/PathTracker'; import ExpressionStatement from './ExpressionStatement'; import type * as NodeType from './NodeType'; @@ -61,9 +61,6 @@ export default class SequenceExpression extends NodeBase { interaction: NodeInteraction, context: HasEffectsContext ): boolean { - if (path.length === 0 && interaction.type === INTERACTION_ACCESSED) { - return false; - } return this.expressions[this.expressions.length - 1].hasEffectsOnInteractionAtPath( path, interaction,