5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
+import {CompilerError} from '..';
9
import {
10
DeclarationId,
11
InstructionKind,
28
visitReactiveFunction(fn, new Visitor(), hoistedIdentifiers);
29
}
30
30
-type HoistedIdentifiers = Map<DeclarationId, InstructionKind>;
31
+const REWRITTEN_HOISTED_CONST: unique symbol = Symbol(
32
+ 'REWRITTEN_HOISTED_CONST',
33
+);
34
+const REWRITTEN_HOISTED_LET: unique symbol = Symbol('REWRITTEN_HOISTED_LET');
35
+
36
+type HoistedIdentifiers = Map<
37
+ DeclarationId,
38
+ | InstructionKind
39
+ | typeof REWRITTEN_HOISTED_CONST
40
+ | typeof REWRITTEN_HOISTED_LET
41
+>;
42
43
class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
44
override transformInstruction(
46
state: HoistedIdentifiers,
47
): Transformed<ReactiveStatement> {
48
this.visitInstruction(instruction, state);
49
+
50
+ /**
51
+ * Remove hoisted declarations to preserve TDZ
52
+ */
53
if (
54
instruction.value.kind === 'DeclareContext' &&
55
instruction.value.lvalue.kind === 'HoistedConst'
83
return {kind: 'remove'};
84
}
85
71
- if (
72
- instruction.value.kind === 'StoreContext' &&
73
- state.has(instruction.value.lvalue.place.identifier.declarationId)
74
- ) {
86
+ if (instruction.value.kind === 'StoreContext') {
87
const kind = state.get(
88
instruction.value.lvalue.place.identifier.declarationId,
77
- )!;
78
- return {
79
- kind: 'replace',
80
- value: {
81
- kind: 'instruction',
82
- instruction: {
83
- ...instruction,
89
+ );
90
+ if (kind != null) {
91
+ CompilerError.invariant(kind !== REWRITTEN_HOISTED_CONST, {
92
+ reason: 'Expected exactly one store to a hoisted const variable',
93
+ loc: instruction.loc,
94
+ });
95
+ if (
96
+ kind === InstructionKind.Const ||
97
+ kind === InstructionKind.Function
98
+ ) {
99
+ state.set(
100
+ instruction.value.lvalue.place.identifier.declarationId,
101
+ REWRITTEN_HOISTED_CONST,
102
+ );
103
+ return {
104
+ kind: 'replace',
105
value: {
85
- ...instruction.value,
86
- lvalue: {
87
- ...instruction.value.lvalue,
88
- kind,
106
+ kind: 'instruction',
107
+ instruction: {
108
+ ...instruction,
109
+ value: {
110
+ ...instruction.value,
111
+ lvalue: {
112
+ ...instruction.value.lvalue,
113
+ kind,
114
+ },
115
+ type: null,
116
+ kind: 'StoreLocal',
117
+ },
118
},
90
- type: null,
91
- kind: 'StoreLocal',
119
},
93
- },
94
- },
95
- };
120
+ };
121
+ } else if (kind !== REWRITTEN_HOISTED_LET) {
122
+ /**
123
+ * Context variables declared with let may have reassignments. Only
124
+ * insert a `DeclareContext` for the first encountered `StoreContext`
125
+ * instruction.
126
+ */
127
+ state.set(
128
+ instruction.value.lvalue.place.identifier.declarationId,
129
+ REWRITTEN_HOISTED_LET,
130
+ );
131
+ return {
132
+ kind: 'replace-many',
133
+ value: [
134
+ {
135
+ kind: 'instruction',
136
+ instruction: {
137
+ id: instruction.id,
138
+ lvalue: null,
139
+ value: {
140
+ kind: 'DeclareContext',
141
+ lvalue: {
142
+ kind: InstructionKind.Let,
143
+ place: {...instruction.value.lvalue.place},
144
+ },
145
+ loc: instruction.value.loc,
146
+ },
147
+ loc: instruction.loc,
148
+ },
149
+ },
150
+ {kind: 'instruction', instruction},
151
+ ],
152
+ };
153
+ }
154
+ }
155
}
156
157
return {kind: 'keep'};