diff --git a/src/ast/nodes/shared/knownGlobals.ts b/src/ast/nodes/shared/knownGlobals.ts index 3b8c05979f0..b42316d6bc3 100644 --- a/src/ast/nodes/shared/knownGlobals.ts +++ b/src/ast/nodes/shared/knownGlobals.ts @@ -1,15 +1,14 @@ /* eslint sort-keys: "off" */ +import { CallOptions } from '../../CallOptions'; +import { HasEffectsContext } from '../../ExecutionContext'; +import { UNKNOWN_NON_ACCESSOR_PATH } from '../../utils/PathTracker'; import type { ObjectPath } from '../../utils/PathTracker'; const ValueProperties = Symbol('Value Properties'); interface ValueDescription { - // Denotes a proper function except for the side effects listed explicitly - function: boolean; - // This assumes that mutation itself cannot have side effects and that this - // does not trigger setters or getters - mutatesArg1: boolean; + hasEffectsWhenCalled(callOptions: CallOptions, context: HasEffectsContext): boolean; } interface GlobalDescription { @@ -18,8 +17,17 @@ interface GlobalDescription { __proto__: null; } -const PURE: ValueDescription = { function: true, mutatesArg1: false }; -const IMPURE: ValueDescription = { function: false, mutatesArg1: false }; +const PURE: ValueDescription = { + hasEffectsWhenCalled() { + return false; + } +}; + +const IMPURE: ValueDescription = { + hasEffectsWhenCalled() { + return true; + } +}; // We use shortened variables to reduce file size here /* OBJECT */ @@ -37,7 +45,14 @@ const PF: GlobalDescription = { /* FUNCTION THAT MUTATES FIRST ARG WITHOUT TRIGGERING ACCESSORS */ const MUTATES_ARG_WITHOUT_ACCESSOR: GlobalDescription = { __proto__: null, - [ValueProperties]: { function: true, mutatesArg1: true } + [ValueProperties]: { + hasEffectsWhenCalled(callOptions, context) { + return ( + !callOptions.args.length || + callOptions.args[0].hasEffectsWhenAssignedAtPath(UNKNOWN_NON_ACCESSOR_PATH, context) + ); + } + } }; /* CONSTRUCTOR */ diff --git a/src/ast/variables/GlobalVariable.ts b/src/ast/variables/GlobalVariable.ts index 813011e2106..7d6928c5f9c 100644 --- a/src/ast/variables/GlobalVariable.ts +++ b/src/ast/variables/GlobalVariable.ts @@ -2,14 +2,16 @@ import { CallOptions } from '../CallOptions'; import { HasEffectsContext } from '../ExecutionContext'; import { getGlobalAtPath } from '../nodes/shared/knownGlobals'; import type { ObjectPath } from '../utils/PathTracker'; -import { UNKNOWN_NON_ACCESSOR_PATH } from '../utils/PathTracker'; import Variable from './Variable'; export default class GlobalVariable extends Variable { + // Ensure we use live-bindings for globals as we do not know if they have + // been reassigned isReassigned = true; hasEffectsWhenAccessedAtPath(path: ObjectPath): boolean { if (path.length === 0) { + // Technically, "undefined" is a global variable of sorts return this.name !== 'undefined' && getGlobalAtPath([this.name]) === null; } return getGlobalAtPath([this.name, ...path].slice(0, -1)) === null; @@ -21,12 +23,6 @@ export default class GlobalVariable extends Variable { context: HasEffectsContext ): boolean { const globalAtPath = getGlobalAtPath([this.name, ...path]); - return ( - globalAtPath === null || - !globalAtPath.function || - (globalAtPath.mutatesArg1 && - (!callOptions.args.length || - callOptions.args[0].hasEffectsWhenAssignedAtPath(UNKNOWN_NON_ACCESSOR_PATH, context))) - ); + return globalAtPath === null || globalAtPath.hasEffectsWhenCalled(callOptions, context); } }