From 8daf05e68fd9bd42887b71346d332c99f3f76695 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Sun, 20 Nov 2022 08:08:32 +0100 Subject: [PATCH] Add pureness flag to return expressions --- src/ast/nodes/ArrayExpression.ts | 2 +- src/ast/nodes/CallExpression.ts | 6 ++-- src/ast/nodes/ConditionalExpression.ts | 33 ++++++++++++---------- src/ast/nodes/Identifier.ts | 2 +- src/ast/nodes/Literal.ts | 8 ++++-- src/ast/nodes/LogicalExpression.ts | 23 +++++++++++---- src/ast/nodes/MemberExpression.ts | 8 +++--- src/ast/nodes/ObjectExpression.ts | 2 +- src/ast/nodes/PropertyDefinition.ts | 6 ++-- src/ast/nodes/TaggedTemplateExpression.ts | 8 +++--- src/ast/nodes/TemplateLiteral.ts | 8 ++++-- src/ast/nodes/shared/CallExpressionBase.ts | 31 +++++++++++--------- src/ast/nodes/shared/ClassNode.ts | 2 +- src/ast/nodes/shared/Expression.ts | 9 ++++-- src/ast/nodes/shared/FunctionBase.ts | 8 +++--- src/ast/nodes/shared/MethodBase.ts | 26 +++++++++-------- src/ast/nodes/shared/MethodTypes.ts | 17 +++++------ src/ast/nodes/shared/MultiExpression.ts | 21 ++++++++++---- src/ast/nodes/shared/ObjectEntity.ts | 7 +++-- src/ast/nodes/shared/ObjectMember.ts | 2 +- src/ast/values.ts | 30 +++++++++++++------- src/ast/variables/LocalVariable.ts | 7 +++-- 22 files changed, 161 insertions(+), 105 deletions(-) diff --git a/src/ast/nodes/ArrayExpression.ts b/src/ast/nodes/ArrayExpression.ts index 3ae8d39199f..cddbdd70117 100644 --- a/src/ast/nodes/ArrayExpression.ts +++ b/src/ast/nodes/ArrayExpression.ts @@ -49,7 +49,7 @@ export default class ArrayExpression extends NodeBase { interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { return this.getObjectEntity().getReturnExpressionWhenCalledAtPath( path, interaction, diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 0d915d6ed22..7f7226a9e5e 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -20,7 +20,7 @@ import type * as NodeType from './NodeType'; import type SpreadElement from './SpreadElement'; import type Super from './Super'; import CallExpressionBase from './shared/CallExpressionBase'; -import { type ExpressionEntity, UNKNOWN_EXPRESSION } from './shared/Expression'; +import { type ExpressionEntity, UNKNOWN_RETURN_EXPRESSION } from './shared/Expression'; import { type ExpressionNode, INCLUDE_PARAMETERS, type IncludeChildren } from './shared/Node'; export default class CallExpression extends CallExpressionBase implements DeoptimizableEntity { @@ -120,9 +120,9 @@ export default class CallExpression extends CallExpressionBase implements Deopti protected getReturnExpression( recursionTracker: PathTracker = SHARED_RECURSION_TRACKER - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { if (this.returnExpression === null) { - this.returnExpression = UNKNOWN_EXPRESSION; + this.returnExpression = UNKNOWN_RETURN_EXPRESSION; return (this.returnExpression = this.callee.getReturnExpressionWhenCalledAtPath( EMPTY_PATH, this.interaction, diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index 25d838ede85..f605dd11bd6 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -80,23 +80,26 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { const usedBranch = this.getUsedBranch(); if (!usedBranch) - return new MultiExpression([ - this.consequent.getReturnExpressionWhenCalledAtPath( - path, - interaction, - recursionTracker, - origin - ), - this.alternate.getReturnExpressionWhenCalledAtPath( - path, - interaction, - recursionTracker, - origin - ) - ]); + return [ + new MultiExpression([ + this.consequent.getReturnExpressionWhenCalledAtPath( + path, + interaction, + recursionTracker, + origin + )[0], + this.alternate.getReturnExpressionWhenCalledAtPath( + path, + interaction, + recursionTracker, + origin + )[0] + ]), + false + ]; this.expressionsToBeDeoptimized.push(origin); return usedBranch.getReturnExpressionWhenCalledAtPath( path, diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 05d6744e7b4..fd623f79003 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -132,7 +132,7 @@ export default class Identifier extends NodeBase implements PatternNode { interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { return this.getVariableRespectingTDZ()!.getReturnExpressionWhenCalledAtPath( path, interaction, diff --git a/src/ast/nodes/Literal.ts b/src/ast/nodes/Literal.ts index 247fbf276f8..5259ada5ef2 100644 --- a/src/ast/nodes/Literal.ts +++ b/src/ast/nodes/Literal.ts @@ -17,7 +17,7 @@ import type * as NodeType from './NodeType'; import { type ExpressionEntity, type LiteralValueOrUnknown, - UNKNOWN_EXPRESSION, + UNKNOWN_RETURN_EXPRESSION, UnknownValue } from './shared/Expression'; import { type GenericEsTreeNode, NodeBase } from './shared/Node'; @@ -50,8 +50,10 @@ export default class Literal extends Node return this.value; } - getReturnExpressionWhenCalledAtPath(path: ObjectPath): ExpressionEntity { - if (path.length !== 1) return UNKNOWN_EXPRESSION; + getReturnExpressionWhenCalledAtPath( + path: ObjectPath + ): [expression: ExpressionEntity, isPure: boolean] { + if (path.length !== 1) return UNKNOWN_RETURN_EXPRESSION; return getMemberReturnExpressionWhenCalled(this.members, path[0]); } diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index 28bd3b5dd53..cbc8625e436 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -93,13 +93,26 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { const usedBranch = this.getUsedBranch(); if (!usedBranch) - return new MultiExpression([ - this.left.getReturnExpressionWhenCalledAtPath(path, interaction, recursionTracker, origin), - this.right.getReturnExpressionWhenCalledAtPath(path, interaction, recursionTracker, origin) - ]); + return [ + new MultiExpression([ + this.left.getReturnExpressionWhenCalledAtPath( + path, + interaction, + recursionTracker, + origin + )[0], + this.right.getReturnExpressionWhenCalledAtPath( + path, + interaction, + recursionTracker, + origin + )[0] + ]), + false + ]; this.expressionsToBeDeoptimized.push(origin); return usedBranch.getReturnExpressionWhenCalledAtPath( path, diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index b9137cd9199..6590b6aaabd 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -38,7 +38,7 @@ import type Super from './Super'; import { type ExpressionEntity, type LiteralValueOrUnknown, - UNKNOWN_EXPRESSION, + UNKNOWN_RETURN_EXPRESSION, UnknownValue } from './shared/Expression'; import { type ExpressionNode, type IncludeChildren, NodeBase } from './shared/Node'; @@ -197,7 +197,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { if (this.variable) { return this.variable.getReturnExpressionWhenCalledAtPath( path, @@ -207,7 +207,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE ); } if (this.isUndefined) { - return UNDEFINED_EXPRESSION; + return [UNDEFINED_EXPRESSION, false]; } this.expressionsToBeDeoptimized.push(origin); if (path.length < MAX_PATH_DEPTH) { @@ -218,7 +218,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE origin ); } - return UNKNOWN_EXPRESSION; + return UNKNOWN_RETURN_EXPRESSION; } hasEffects(context: HasEffectsContext): boolean { diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index 5ac588c45ab..31c13d146b2 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -59,7 +59,7 @@ export default class ObjectExpression extends NodeBase implements DeoptimizableE interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { return this.getObjectEntity().getReturnExpressionWhenCalledAtPath( path, interaction, diff --git a/src/ast/nodes/PropertyDefinition.ts b/src/ast/nodes/PropertyDefinition.ts index b4bcfc42876..6debb640378 100644 --- a/src/ast/nodes/PropertyDefinition.ts +++ b/src/ast/nodes/PropertyDefinition.ts @@ -11,7 +11,7 @@ import type PrivateIdentifier from './PrivateIdentifier'; import { type ExpressionEntity, type LiteralValueOrUnknown, - UNKNOWN_EXPRESSION, + UNKNOWN_RETURN_EXPRESSION, UnknownValue } from './shared/Expression'; import { type ExpressionNode, NodeBase } from './shared/Node'; @@ -50,10 +50,10 @@ export default class PropertyDefinition extends NodeBase { interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { return this.value ? this.value.getReturnExpressionWhenCalledAtPath(path, interaction, recursionTracker, origin) - : UNKNOWN_EXPRESSION; + : UNKNOWN_RETURN_EXPRESSION; } hasEffects(context: HasEffectsContext): boolean { diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index 15287b8f71d..70d3a84172a 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -12,7 +12,7 @@ import * as NodeType from './NodeType'; import type TemplateLiteral from './TemplateLiteral'; import CallExpressionBase from './shared/CallExpressionBase'; import type { ExpressionEntity } from './shared/Expression'; -import { UNKNOWN_EXPRESSION } from './shared/Expression'; +import { UNKNOWN_EXPRESSION, UNKNOWN_RETURN_EXPRESSION } from './shared/Expression'; import type { ExpressionNode, IncludeChildren } from './shared/Node'; export default class TaggedTemplateExpression extends CallExpressionBase { @@ -56,7 +56,7 @@ export default class TaggedTemplateExpression extends CallExpressionBase { this.quasi.include(context, includeChildrenRecursively); } this.tag.includeCallArguments(context, this.interaction.args); - const returnExpression = this.getReturnExpression(); + const [returnExpression] = this.getReturnExpression(); if (!returnExpression.included) { returnExpression.include(context, false); } @@ -94,9 +94,9 @@ export default class TaggedTemplateExpression extends CallExpressionBase { protected getReturnExpression( recursionTracker: PathTracker = SHARED_RECURSION_TRACKER - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { if (this.returnExpression === null) { - this.returnExpression = UNKNOWN_EXPRESSION; + this.returnExpression = UNKNOWN_RETURN_EXPRESSION; return (this.returnExpression = this.tag.getReturnExpressionWhenCalledAtPath( EMPTY_PATH, this.interaction, diff --git a/src/ast/nodes/TemplateLiteral.ts b/src/ast/nodes/TemplateLiteral.ts index a07d212ef9f..dee0b0ecaf4 100644 --- a/src/ast/nodes/TemplateLiteral.ts +++ b/src/ast/nodes/TemplateLiteral.ts @@ -12,7 +12,7 @@ import { import type * as NodeType from './NodeType'; import type TemplateElement from './TemplateElement'; import type { ExpressionEntity, LiteralValueOrUnknown } from './shared/Expression'; -import { UNKNOWN_EXPRESSION, UnknownValue } from './shared/Expression'; +import { UNKNOWN_RETURN_EXPRESSION, UnknownValue } from './shared/Expression'; import { type ExpressionNode, NodeBase } from './shared/Node'; export default class TemplateLiteral extends NodeBase { @@ -29,9 +29,11 @@ export default class TemplateLiteral extends NodeBase { return this.quasis[0].value.cooked; } - getReturnExpressionWhenCalledAtPath(path: ObjectPath): ExpressionEntity { + getReturnExpressionWhenCalledAtPath( + path: ObjectPath + ): [expression: ExpressionEntity, isPure: boolean] { if (path.length !== 1) { - return UNKNOWN_EXPRESSION; + return UNKNOWN_RETURN_EXPRESSION; } return getMemberReturnExpressionWhenCalled(literalStringMembers, path[0]); } diff --git a/src/ast/nodes/shared/CallExpressionBase.ts b/src/ast/nodes/shared/CallExpressionBase.ts index 0af6a709aac..7ce7c9c3a15 100644 --- a/src/ast/nodes/shared/CallExpressionBase.ts +++ b/src/ast/nodes/shared/CallExpressionBase.ts @@ -11,19 +11,20 @@ import { type ExpressionEntity, type LiteralValueOrUnknown, UNKNOWN_EXPRESSION, + UNKNOWN_RETURN_EXPRESSION, UnknownValue } from './Expression'; import { NodeBase } from './Node'; export default abstract class CallExpressionBase extends NodeBase implements DeoptimizableEntity { protected declare interaction: NodeInteractionCalled; - protected returnExpression: ExpressionEntity | null = null; + protected returnExpression: [expression: ExpressionEntity, isPure: boolean] | null = null; private readonly deoptimizableDependentExpressions: DeoptimizableEntity[] = []; private readonly expressionsToBeDeoptimized = new Set(); deoptimizeCache(): void { - if (this.returnExpression !== UNKNOWN_EXPRESSION) { - this.returnExpression = UNKNOWN_EXPRESSION; + if (this.returnExpression?.[0] !== UNKNOWN_EXPRESSION) { + this.returnExpression = UNKNOWN_RETURN_EXPRESSION; for (const expression of this.deoptimizableDependentExpressions) { expression.deoptimizeCache(); } @@ -40,7 +41,7 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo ) { return; } - const returnExpression = this.getReturnExpression(); + const [returnExpression] = this.getReturnExpression(); if (returnExpression !== UNKNOWN_EXPRESSION) { returnExpression.deoptimizePath(path); } @@ -51,7 +52,9 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo path: ObjectPath, recursionTracker: PathTracker ): void { - const returnExpression = this.getReturnExpression(recursionTracker); + const [returnExpression, isPure] = this.getReturnExpression(recursionTracker); + // TODO Lukas test + if (isPure) return; if (returnExpression === UNKNOWN_EXPRESSION) { interaction.thisArg.deoptimizePath(UNKNOWN_PATH); } else { @@ -72,7 +75,7 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { - const returnExpression = this.getReturnExpression(recursionTracker); + const [returnExpression] = this.getReturnExpression(recursionTracker); if (returnExpression === UNKNOWN_EXPRESSION) { return UnknownValue; } @@ -92,10 +95,10 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { - const returnExpression = this.getReturnExpression(recursionTracker); - if (this.returnExpression === UNKNOWN_EXPRESSION) { - return UNKNOWN_EXPRESSION; + ): [expression: ExpressionEntity, isPure: boolean] { + const [returnExpression] = this.getReturnExpression(recursionTracker); + if (returnExpression === UNKNOWN_EXPRESSION) { + return UNKNOWN_RETURN_EXPRESSION; } return recursionTracker.withTrackedEntityAtPath( path, @@ -109,7 +112,7 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo origin ); }, - UNKNOWN_EXPRESSION + UNKNOWN_RETURN_EXPRESSION ); } @@ -138,8 +141,10 @@ export default abstract class CallExpressionBase extends NodeBase implements Deo ) { return false; } - return this.getReturnExpression().hasEffectsOnInteractionAtPath(path, interaction, context); + return this.getReturnExpression()[0].hasEffectsOnInteractionAtPath(path, interaction, context); } - protected abstract getReturnExpression(recursionTracker?: PathTracker): ExpressionEntity; + protected abstract getReturnExpression( + recursionTracker?: PathTracker + ): [expression: ExpressionEntity, isPure: boolean]; } diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index 996520efef4..d9a7ab72c56 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -66,7 +66,7 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity { interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { return this.getObjectEntity().getReturnExpressionWhenCalledAtPath( path, interaction, diff --git a/src/ast/nodes/shared/Expression.ts b/src/ast/nodes/shared/Expression.ts index 7edd783c960..04a802d706d 100644 --- a/src/ast/nodes/shared/Expression.ts +++ b/src/ast/nodes/shared/Expression.ts @@ -59,8 +59,8 @@ export class ExpressionEntity implements WritableEntity { _interaction: NodeInteractionCalled, _recursionTracker: PathTracker, _origin: DeoptimizableEntity - ): ExpressionEntity { - return UNKNOWN_EXPRESSION; + ): [expression: ExpressionEntity, isPure: boolean] { + return UNKNOWN_RETURN_EXPRESSION; } hasEffectsOnInteractionAtPath( @@ -95,3 +95,8 @@ export class ExpressionEntity implements WritableEntity { export const UNKNOWN_EXPRESSION: ExpressionEntity = new (class UnknownExpression extends ExpressionEntity {})(); + +export const UNKNOWN_RETURN_EXPRESSION: [expression: ExpressionEntity, isPure: boolean] = [ + UNKNOWN_EXPRESSION, + false +]; diff --git a/src/ast/nodes/shared/FunctionBase.ts b/src/ast/nodes/shared/FunctionBase.ts index eb8095d98df..74aab33d9a8 100644 --- a/src/ast/nodes/shared/FunctionBase.ts +++ b/src/ast/nodes/shared/FunctionBase.ts @@ -23,7 +23,7 @@ import * as NodeType from '../NodeType'; import RestElement from '../RestElement'; import type SpreadElement from '../SpreadElement'; import type { ExpressionEntity, LiteralValueOrUnknown } from './Expression'; -import { UNKNOWN_EXPRESSION } from './Expression'; +import { UNKNOWN_EXPRESSION, UNKNOWN_RETURN_EXPRESSION } from './Expression'; import { type ExpressionNode, type GenericEsTreeNode, @@ -74,7 +74,7 @@ export default abstract class FunctionBase extends NodeBase { interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { if (path.length > 0) { return this.getObjectEntity().getReturnExpressionWhenCalledAtPath( path, @@ -89,9 +89,9 @@ export default abstract class FunctionBase extends NodeBase { this.scope.getReturnExpression().deoptimizePath(UNKNOWN_PATH); this.context.requestTreeshakingPass(); } - return UNKNOWN_EXPRESSION; + return UNKNOWN_RETURN_EXPRESSION; } - return this.scope.getReturnExpression(); + return [this.scope.getReturnExpression(), false]; } hasEffectsOnInteractionAtPath( diff --git a/src/ast/nodes/shared/MethodBase.ts b/src/ast/nodes/shared/MethodBase.ts index 10e8c6d7858..170019af869 100644 --- a/src/ast/nodes/shared/MethodBase.ts +++ b/src/ast/nodes/shared/MethodBase.ts @@ -22,7 +22,7 @@ import type PrivateIdentifier from '../PrivateIdentifier'; import { type ExpressionEntity, type LiteralValueOrUnknown, - UNKNOWN_EXPRESSION + UNKNOWN_RETURN_EXPRESSION } from './Expression'; import { type ExpressionNode, NodeBase } from './Node'; import type { PatternNode } from './Pattern'; @@ -33,14 +33,14 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity declare kind: 'constructor' | 'method' | 'init' | 'get' | 'set'; declare value: ExpressionNode | (ExpressionNode & PatternNode); - private accessedValue: ExpressionEntity | null = null; + private accessedValue: [expression: ExpressionEntity, isPure: boolean] | null = null; // As getter properties directly receive their values from fixed function // expressions, there is no known situation where a getter is deoptimized. deoptimizeCache(): void {} deoptimizePath(path: ObjectPath): void { - this.getAccessedValue().deoptimizePath(path); + this.getAccessedValue()[0].deoptimizePath(path); } deoptimizeThisOnInteractionAtPath( @@ -72,7 +72,11 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity recursionTracker ); } - this.getAccessedValue().deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker); + this.getAccessedValue()[0].deoptimizeThisOnInteractionAtPath( + interaction, + path, + recursionTracker + ); } getLiteralValueAtPath( @@ -80,7 +84,7 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { - return this.getAccessedValue().getLiteralValueAtPath(path, recursionTracker, origin); + return this.getAccessedValue()[0].getLiteralValueAtPath(path, recursionTracker, origin); } getReturnExpressionWhenCalledAtPath( @@ -88,8 +92,8 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { - return this.getAccessedValue().getReturnExpressionWhenCalledAtPath( + ): [expression: ExpressionEntity, isPure: boolean] { + return this.getAccessedValue()[0].getReturnExpressionWhenCalledAtPath( path, interaction, recursionTracker, @@ -131,15 +135,15 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity context ); } - return this.getAccessedValue().hasEffectsOnInteractionAtPath(path, interaction, context); + return this.getAccessedValue()[0].hasEffectsOnInteractionAtPath(path, interaction, context); } protected applyDeoptimizations() {} - protected getAccessedValue(): ExpressionEntity { + protected getAccessedValue(): [expression: ExpressionEntity, isPure: boolean] { if (this.accessedValue === null) { if (this.kind === 'get') { - this.accessedValue = UNKNOWN_EXPRESSION; + this.accessedValue = UNKNOWN_RETURN_EXPRESSION; return (this.accessedValue = this.value.getReturnExpressionWhenCalledAtPath( EMPTY_PATH, NODE_INTERACTION_UNKNOWN_CALL, @@ -147,7 +151,7 @@ export default class MethodBase extends NodeBase implements DeoptimizableEntity this )); } else { - return (this.accessedValue = this.value); + return (this.accessedValue = [this.value, false]); } } return this.accessedValue; diff --git a/src/ast/nodes/shared/MethodTypes.ts b/src/ast/nodes/shared/MethodTypes.ts index 3c54cd738ad..2facd9162e3 100644 --- a/src/ast/nodes/shared/MethodTypes.ts +++ b/src/ast/nodes/shared/MethodTypes.ts @@ -16,7 +16,7 @@ import { UNKNOWN_LITERAL_NUMBER, UNKNOWN_LITERAL_STRING } from '../../values'; -import { ExpressionEntity, UNKNOWN_EXPRESSION } from './Expression'; +import { ExpressionEntity, UNKNOWN_EXPRESSION, UNKNOWN_RETURN_EXPRESSION } from './Expression'; type MethodDescription = { callsArgs: number[] | null; @@ -49,16 +49,17 @@ export class Method extends ExpressionEntity { getReturnExpressionWhenCalledAtPath( path: ObjectPath, { thisArg }: NodeInteractionCalled - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { if (path.length > 0) { - return UNKNOWN_EXPRESSION; + return UNKNOWN_RETURN_EXPRESSION; } - return ( + return [ this.description.returnsPrimitive || - (this.description.returns === 'self' - ? thisArg || UNKNOWN_EXPRESSION - : this.description.returns()) - ); + (this.description.returns === 'self' + ? thisArg || UNKNOWN_EXPRESSION + : this.description.returns()), + false + ]; } hasEffectsOnInteractionAtPath( diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index 61369b12e1a..e5442452f10 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -22,12 +22,21 @@ export class MultiExpression extends ExpressionEntity { interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { - return new MultiExpression( - this.expressions.map(expression => - expression.getReturnExpressionWhenCalledAtPath(path, interaction, recursionTracker, origin) - ) - ); + ): [expression: ExpressionEntity, isPure: boolean] { + return [ + new MultiExpression( + this.expressions.map( + expression => + expression.getReturnExpressionWhenCalledAtPath( + path, + interaction, + recursionTracker, + origin + )[0] + ) + ), + false + ]; } hasEffectsOnInteractionAtPath( diff --git a/src/ast/nodes/shared/ObjectEntity.ts b/src/ast/nodes/shared/ObjectEntity.ts index b8b9986ff11..4ee27f728f9 100644 --- a/src/ast/nodes/shared/ObjectEntity.ts +++ b/src/ast/nodes/shared/ObjectEntity.ts @@ -18,6 +18,7 @@ import type { LiteralValueOrUnknown } from './Expression'; import { ExpressionEntity, UNKNOWN_EXPRESSION, + UNKNOWN_RETURN_EXPRESSION, UnknownTruthyValue, UnknownValue } from './Expression'; @@ -250,9 +251,9 @@ export class ObjectEntity extends ExpressionEntity { interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { if (path.length === 0) { - return UNKNOWN_EXPRESSION; + return UNKNOWN_RETURN_EXPRESSION; } const [key, ...subPath] = path; const expressionAtPath = this.getMemberExpressionAndTrackDeopt(key, origin); @@ -272,7 +273,7 @@ export class ObjectEntity extends ExpressionEntity { origin ); } - return UNKNOWN_EXPRESSION; + return UNKNOWN_RETURN_EXPRESSION; } hasEffectsOnInteractionAtPath( diff --git a/src/ast/nodes/shared/ObjectMember.ts b/src/ast/nodes/shared/ObjectMember.ts index 5204129407a..266540b0e30 100644 --- a/src/ast/nodes/shared/ObjectMember.ts +++ b/src/ast/nodes/shared/ObjectMember.ts @@ -42,7 +42,7 @@ export class ObjectMember extends ExpressionEntity { interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { return this.object.getReturnExpressionWhenCalledAtPath( [this.key, ...path], interaction, diff --git a/src/ast/values.ts b/src/ast/values.ts index b74f5152e72..bb0297421aa 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -6,7 +6,11 @@ import { NODE_INTERACTION_UNKNOWN_CALL } from './NodeInteractions'; import type { LiteralValue } from './nodes/Literal'; -import { ExpressionEntity, UNKNOWN_EXPRESSION } from './nodes/shared/Expression'; +import { + ExpressionEntity, + UNKNOWN_EXPRESSION, + UNKNOWN_RETURN_EXPRESSION +} from './nodes/shared/Expression'; import { EMPTY_PATH, type ObjectPath, @@ -52,11 +56,13 @@ const returnsUnknown: RawMemberDescription = { export const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = new (class UnknownBoolean extends ExpressionEntity { - getReturnExpressionWhenCalledAtPath(path: ObjectPath): ExpressionEntity { + getReturnExpressionWhenCalledAtPath( + path: ObjectPath + ): [expression: ExpressionEntity, isPure: boolean] { if (path.length === 1) { return getMemberReturnExpressionWhenCalled(literalBooleanMembers, path[0]); } - return UNKNOWN_EXPRESSION; + return UNKNOWN_RETURN_EXPRESSION; } hasEffectsOnInteractionAtPath( @@ -83,11 +89,13 @@ const returnsBoolean: RawMemberDescription = { export const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = new (class UnknownNumber extends ExpressionEntity { - getReturnExpressionWhenCalledAtPath(path: ObjectPath): ExpressionEntity { + getReturnExpressionWhenCalledAtPath( + path: ObjectPath + ): [expression: ExpressionEntity, isPure: boolean] { if (path.length === 1) { return getMemberReturnExpressionWhenCalled(literalNumberMembers, path[0]); } - return UNKNOWN_EXPRESSION; + return UNKNOWN_RETURN_EXPRESSION; } hasEffectsOnInteractionAtPath( @@ -114,11 +122,13 @@ const returnsNumber: RawMemberDescription = { export const UNKNOWN_LITERAL_STRING: ExpressionEntity = new (class UnknownString extends ExpressionEntity { - getReturnExpressionWhenCalledAtPath(path: ObjectPath): ExpressionEntity { + getReturnExpressionWhenCalledAtPath( + path: ObjectPath + ): [expression: ExpressionEntity, isPure: boolean] { if (path.length === 1) { return getMemberReturnExpressionWhenCalled(literalStringMembers, path[0]); } - return UNKNOWN_EXPRESSION; + return UNKNOWN_RETURN_EXPRESSION; } hasEffectsOnInteractionAtPath( @@ -277,7 +287,7 @@ export function hasMemberEffectWhenCalled( export function getMemberReturnExpressionWhenCalled( members: MemberDescriptions, memberName: ObjectPathKey -): ExpressionEntity { - if (typeof memberName !== 'string' || !members[memberName]) return UNKNOWN_EXPRESSION; - return members[memberName].returns; +): [expression: ExpressionEntity, isPure: boolean] { + if (typeof memberName !== 'string' || !members[memberName]) return UNKNOWN_RETURN_EXPRESSION; + return [members[memberName].returns, false]; } diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 420763678a7..e8dffb2d88a 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -21,6 +21,7 @@ import { type ExpressionEntity, type LiteralValueOrUnknown, UNKNOWN_EXPRESSION, + UNKNOWN_RETURN_EXPRESSION, UnknownValue } from '../nodes/shared/Expression'; import type { Node } from '../nodes/shared/Node'; @@ -131,9 +132,9 @@ export default class LocalVariable extends Variable { interaction: NodeInteractionCalled, recursionTracker: PathTracker, origin: DeoptimizableEntity - ): ExpressionEntity { + ): [expression: ExpressionEntity, isPure: boolean] { if (this.isReassigned || !this.init) { - return UNKNOWN_EXPRESSION; + return UNKNOWN_RETURN_EXPRESSION; } return recursionTracker.withTrackedEntityAtPath( path, @@ -147,7 +148,7 @@ export default class LocalVariable extends Variable { origin ); }, - UNKNOWN_EXPRESSION + UNKNOWN_RETURN_EXPRESSION ); }