-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
ClassNode.ts
190 lines (177 loc) · 5.79 KB
/
ClassNode.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import type { DeoptimizableEntity } from '../../DeoptimizableEntity';
import type { HasEffectsContext, InclusionContext } from '../../ExecutionContext';
import {
INTERACTION_CALLED,
NodeInteraction,
NodeInteractionCalled,
NodeInteractionWithThisArg
} from '../../NodeInteractions';
import ChildScope from '../../scopes/ChildScope';
import type Scope from '../../scopes/Scope';
import {
EMPTY_PATH,
type ObjectPath,
type PathTracker,
SHARED_RECURSION_TRACKER,
TestInstanceof,
UNKNOWN_PATH,
UnknownKey
} from '../../utils/PathTracker';
import type ClassBody from '../ClassBody';
import Identifier from '../Identifier';
import type Literal from '../Literal';
import MethodDefinition from '../MethodDefinition';
import { type ExpressionEntity, type LiteralValueOrUnknown } from './Expression';
import { type ExpressionNode, type IncludeChildren, NodeBase } from './Node';
import { ObjectEntity, type ObjectProperty } from './ObjectEntity';
import { ObjectMember } from './ObjectMember';
import { OBJECT_PROTOTYPE } from './ObjectPrototype';
export default class ClassNode extends NodeBase implements DeoptimizableEntity {
declare body: ClassBody;
declare id: Identifier | null;
declare superClass: ExpressionNode | null;
private declare classConstructor: MethodDefinition | null;
private objectEntity: ObjectEntity | null = null;
createScope(parentScope: Scope): void {
this.scope = new ChildScope(parentScope);
}
deoptimizeCache(): void {
this.getObjectEntity().deoptimizeAllProperties();
}
deoptimizePath(path: ObjectPath): void {
this.getObjectEntity().deoptimizePath(path);
}
deoptimizeThisOnInteractionAtPath(
interaction: NodeInteractionWithThisArg,
path: ObjectPath,
recursionTracker: PathTracker
): void {
this.getObjectEntity().deoptimizeThisOnInteractionAtPath(interaction, path, recursionTracker);
}
getLiteralValueAtPath(
path: ObjectPath,
recursionTracker: PathTracker,
origin: DeoptimizableEntity
): LiteralValueOrUnknown {
return this.getObjectEntity().getLiteralValueAtPath(path, recursionTracker, origin);
}
getReturnExpressionWhenCalledAtPath(
path: ObjectPath,
interaction: NodeInteractionCalled,
recursionTracker: PathTracker,
origin: DeoptimizableEntity
): ExpressionEntity {
return this.getObjectEntity().getReturnExpressionWhenCalledAtPath(
path,
interaction,
recursionTracker,
origin
);
}
hasEffects(context: HasEffectsContext): boolean {
if (!this.deoptimized) this.applyDeoptimizations();
const initEffect = this.superClass?.hasEffects(context) || this.body.hasEffects(context);
this.id?.markDeclarationReached();
return initEffect || super.hasEffects(context);
}
hasEffectsOnInteractionAtPath(
path: ObjectPath,
interaction: NodeInteraction,
context: HasEffectsContext
): boolean {
if (interaction.type === INTERACTION_CALLED && path.length === 0) {
return (
!interaction.withNew ||
(this.classConstructor !== null
? this.classConstructor.hasEffectsOnInteractionAtPath(path, interaction, context)
: this.superClass?.hasEffectsOnInteractionAtPath(path, interaction, context)) ||
false
);
} else if (path[0] === TestInstanceof) {
return false;
} else {
return this.getObjectEntity().hasEffectsOnInteractionAtPath(path, interaction, context);
}
}
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
if (!this.deoptimized) this.applyDeoptimizations();
this.included = true;
this.superClass?.include(context, includeChildrenRecursively);
this.body.include(context, includeChildrenRecursively);
if (this.id) {
this.id.markDeclarationReached();
this.id.include();
}
}
initialise(): void {
this.id?.declare('class', this);
for (const method of this.body.body) {
if (method instanceof MethodDefinition && method.kind === 'constructor') {
this.classConstructor = method;
return;
}
}
this.classConstructor = null;
}
protected applyDeoptimizations(): void {
this.deoptimized = true;
for (const definition of this.body.body) {
if (
!(
definition.static ||
(definition instanceof MethodDefinition && definition.kind === 'constructor')
)
) {
// Calls to methods are not tracked, ensure that the return value is deoptimized
definition.deoptimizePath(UNKNOWN_PATH);
}
}
this.context.requestTreeshakingPass();
}
private getObjectEntity(): ObjectEntity {
if (this.objectEntity !== null) {
return this.objectEntity;
}
const staticProperties: ObjectProperty[] = [];
const dynamicMethods: ObjectProperty[] = [];
for (const definition of this.body.body) {
const properties = definition.static ? staticProperties : dynamicMethods;
const definitionKind = (definition as MethodDefinition | { kind: undefined }).kind;
// Note that class fields do not end up on the prototype
if (properties === dynamicMethods && !definitionKind) continue;
const kind = definitionKind === 'set' || definitionKind === 'get' ? definitionKind : 'init';
let key: string;
if (definition.computed) {
const keyValue = definition.key.getLiteralValueAtPath(
EMPTY_PATH,
SHARED_RECURSION_TRACKER,
this
);
if (typeof keyValue === 'symbol') {
properties.push({ key: UnknownKey, kind, property: definition });
continue;
} else {
key = String(keyValue);
}
} else {
key =
definition.key instanceof Identifier
? definition.key.name
: String((definition.key as Literal).value);
}
properties.push({ key, kind, property: definition });
}
staticProperties.unshift({
key: 'prototype',
kind: 'init',
property: new ObjectEntity(
dynamicMethods,
this.superClass ? new ObjectMember(this.superClass, 'prototype') : OBJECT_PROTOTYPE
)
});
return (this.objectEntity = new ObjectEntity(
staticProperties,
this.superClass || OBJECT_PROTOTYPE
));
}
}