diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index fc292214745..39adbedc28e 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -11,6 +11,7 @@ import { ObjectPath, ObjectPathKey, PathTracker, + UNKNOWN_PATH, UnknownKey } from '../utils/PathTracker'; import { LiteralValueOrUnknown, UnknownValue } from '../values'; @@ -77,6 +78,7 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE private bound = false; private expressionsToBeDeoptimized: DeoptimizableEntity[] = []; + private hasDeoptimizedPath = false; private replacement: string | null = null; addExportedVariables(): void {} @@ -112,6 +114,10 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } deoptimizeCache() { + this.propertyKey = UnknownKey; + if (this.hasDeoptimizedPath) { + this.object.deoptimizePath(UNKNOWN_PATH); + } for (const expression of this.expressionsToBeDeoptimized) { expression.deoptimizeCache(); } @@ -124,7 +130,12 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE this.variable.deoptimizePath(path); } else { if (this.propertyKey === null) this.analysePropertyKey(); - this.object.deoptimizePath([this.propertyKey as ObjectPathKey, ...path]); + if (this.propertyKey === UnknownKey) { + this.object.deoptimizePath(UNKNOWN_PATH); + } else { + this.hasDeoptimizedPath = true; + this.object.deoptimizePath([this.propertyKey as ObjectPathKey, ...path]); + } } } diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index 0d8fb2b61de..eca3e4d8c3d 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -36,11 +36,12 @@ interface PropertyMap { }; } -export default class ObjectExpression extends NodeBase { +export default class ObjectExpression extends NodeBase implements DeoptimizableEntity { properties!: (Property | SpreadElement)[]; type!: NodeType.tObjectExpression; private deoptimizedPaths = new Set(); + // We collect deoptimization information if we can resolve a computed property access private expressionsToBeDeoptimized = new Map(); private hasUnknownDeoptimizedProperty = false; diff --git a/test/function/samples/deoptimize-cached-prop/_config.js b/test/function/samples/deoptimize-cached-prop/_config.js new file mode 100644 index 00000000000..5bffd221ee2 --- /dev/null +++ b/test/function/samples/deoptimize-cached-prop/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'deoptimizes cached properties and return values if necessary (#3264)' +}; diff --git a/test/function/samples/deoptimize-cached-prop/main.js b/test/function/samples/deoptimize-cached-prop/main.js new file mode 100644 index 00000000000..ecc7966a341 --- /dev/null +++ b/test/function/samples/deoptimize-cached-prop/main.js @@ -0,0 +1,24 @@ +const obj1 = {}; +let prop = 'x'; + +updateProp(); + +obj1[prop] = true; + +assert.strictEqual(obj1.y ? 'WORKING' : 'BUG', 'WORKING'); + +function updateProp() { + prop = 'y'; +} + +const obj2 = {}; +obj2[getResult().prop] = 1; + +assert.equal(obj2.foo ? 'WORKING' : 'BUG', 'WORKING'); + +function getResult() { + const result = {}; + result.prop = 'foo'; + return result; +} +