Skip to content

Commit 5d81532

Browse files
fix: add this option to context.ignore (#4842)
* feat: add this option to context.ignore * Track this deoptimizations * test: tweak test description --------- Co-authored-by: Lukas Taegert-Atkinson <[email protected]>
1 parent e0d9681 commit 5d81532

10 files changed

Lines changed: 81 additions & 14 deletions

File tree

src/ast/ExecutionContext.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface ExecutionContextIgnore {
88
continues: boolean;
99
labels: Set<string>;
1010
returnYield: boolean;
11+
this: boolean;
1112
}
1213

1314
export const BROKEN_FLOW_NONE = 0;
@@ -51,7 +52,8 @@ export function createHasEffectsContext(): HasEffectsContext {
5152
breaks: false,
5253
continues: false,
5354
labels: new Set(),
54-
returnYield: false
55+
returnYield: false,
56+
this: false
5557
},
5658
includedLabels: new Set(),
5759
instantiated: new DiscriminatedPathTracker(),

src/ast/nodes/ArrowFunctionExpression.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ export default class ArrowFunctionExpression extends FunctionBase {
4343
breaks: false,
4444
continues: false,
4545
labels: new Set(),
46-
returnYield: true
46+
returnYield: true,
47+
this: false
4748
};
4849
if (this.body.hasEffects(context)) return true;
4950
context.ignore = ignore;

src/ast/nodes/shared/FunctionNode.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@ export default class FunctionNode extends FunctionBase {
2020
declare preventChildBlockScope: true;
2121
declare scope: FunctionScope;
2222
protected objectEntity: ObjectEntity | null = null;
23+
private declare constructedEntity: ObjectEntity;
2324

2425
createScope(parentScope: FunctionScope): void {
2526
this.scope = new FunctionScope(parentScope, this.context);
27+
this.constructedEntity = new ObjectEntity(Object.create(null), OBJECT_PROTOTYPE);
28+
// This makes sure that all deoptimizations of "this" are applied to the
29+
// constructed entity.
30+
this.scope.thisVariable.addEntityToBeDeoptimized(this.constructedEntity);
2631
}
2732

2833
deoptimizeThisOnInteractionAtPath(
@@ -51,16 +56,15 @@ export default class FunctionNode extends FunctionBase {
5156
const thisInit = context.replacedVariableInits.get(this.scope.thisVariable);
5257
context.replacedVariableInits.set(
5358
this.scope.thisVariable,
54-
interaction.withNew
55-
? new ObjectEntity(Object.create(null), OBJECT_PROTOTYPE)
56-
: UNKNOWN_EXPRESSION
59+
interaction.withNew ? this.constructedEntity : UNKNOWN_EXPRESSION
5760
);
5861
const { brokenFlow, ignore, replacedVariableInits } = context;
5962
context.ignore = {
6063
breaks: false,
6164
continues: false,
6265
labels: new Set(),
63-
returnYield: true
66+
returnYield: true,
67+
this: interaction.withNew
6468
};
6569
if (this.body.hasEffects(context)) return true;
6670
context.brokenFlow = brokenFlow;

src/ast/variables/ThisVariable.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,15 @@ export default class ThisVariable extends LocalVariable {
7474
interaction: NodeInteraction,
7575
context: HasEffectsContext
7676
): boolean {
77-
return (
78-
this.getInit(context).hasEffectsOnInteractionAtPath(path, interaction, context) ||
79-
super.hasEffectsOnInteractionAtPath(path, interaction, context)
80-
);
81-
}
82-
83-
private getInit(context: HasEffectsContext): ExpressionEntity {
84-
return context.replacedVariableInits.get(this) || UNKNOWN_EXPRESSION;
77+
const replacedVariableInit = context.replacedVariableInits.get(this);
78+
if (replacedVariableInit) {
79+
return (
80+
replacedVariableInit.hasEffectsOnInteractionAtPath(path, interaction, context) ||
81+
// If the surrounding function is included, all mutations of "this" must
82+
// be counted as side effects, which is what this second line does.
83+
(!context.ignore.this && super.hasEffectsOnInteractionAtPath(path, interaction, context))
84+
);
85+
}
86+
return UNKNOWN_EXPRESSION.hasEffectsOnInteractionAtPath(path, interaction, context);
8587
}
8688
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
description:
3+
'When a constructor is called with the new keyword, changing its this has no side effects'
4+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class Main {
2+
constructor() {
3+
this.a = 1;
4+
}
5+
}
6+
const b = new Main();
7+
assert.ok(b);
8+
9+
function Foo() {
10+
this.a = 1;
11+
}
12+
const d = new Foo();
13+
assert.ok(d);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class Main {
2+
constructor() {
3+
this.a = 1;
4+
}
5+
}
6+
const a = new Main(); // removed
7+
const b = new Main();
8+
assert.ok(b);
9+
10+
function Foo() {
11+
this.a = 1;
12+
}
13+
14+
const c = new Foo(); // removed
15+
const d = new Foo();
16+
assert.ok(d);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
description: 'detects side effects when mutating "this" in a constructor'
3+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class Main {
2+
constructor() {
3+
this.a = 1;
4+
}
5+
}
6+
const b = new Main();
7+
assert.ok(b);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
let effect = false;
2+
3+
class Foo {
4+
constructor() {
5+
Object.defineProperty(this, 'x', {
6+
set() {
7+
effect = true;
8+
}
9+
});
10+
this.x = 2;
11+
}
12+
}
13+
14+
new Foo();
15+
assert.ok(effect);

0 commit comments

Comments
 (0)