main
md 192 lines 6.54 KB
Rendered Raw
1 # validateContextVariableLValues
2
3 ## File
4 `src/Validation/ValidateContextVariableLValues.ts`
5
6 ## Purpose
7 This validation pass ensures that all load/store references to a given named identifier are consistent with the "kind" of that variable (normal local variable or context variable). Context variables are variables that are captured by closures and require special handling for correct closure semantics.
8
9 The pass prevents mixing context variable operations (`DeclareContext`, `StoreContext`, `LoadContext`) with local variable operations (`DeclareLocal`, `StoreLocal`, `LoadLocal`, `Destructure`) on the same identifier.
10
11 ## Input Invariants
12 - The function has been lowered to HIR
13 - All instructions have been categorized by kind
14 - Nested function expressions have been lowered
15
16 ## Validation Rules
17
18 ### Rule 1: Consistent Variable Kind
19 All references to the same identifier must use consistent load/store operations:
20 - Context variables must only use `DeclareContext`, `StoreContext`, `LoadContext`
21 - Local variables must only use `DeclareLocal`, `StoreLocal`, `LoadLocal`
22
23 **Error (Invariant violation):**
24 ```
25 Expected all references to a variable to be consistently local or context references
26 Identifier [place] is referenced as a [kind] variable, but was previously referenced as a [prev.kind] variable
27 ```
28
29 ### Rule 2: No Destructuring of Context Variables
30 Context variables cannot be destructured using the `Destructure` instruction.
31
32 **Error (Todo):**
33 ```
34 Support destructuring of context variables
35 ```
36
37 ### Rule 3: Unhandled Instruction Variants
38 If an instruction has lvalues that the pass does not handle, it throws a Todo error.
39
40 **Error (Todo):**
41 ```
42 ValidateContextVariableLValues: unhandled instruction variant
43 Handle '[kind]' lvalues
44 ```
45
46 ## Algorithm
47
48 ### Phase 1: Initialize Tracking
49 ```typescript
50 const identifierKinds: Map<IdentifierId, {place: Place, kind: 'local' | 'context' | 'destructure'}> = new Map();
51 ```
52
53 ### Phase 2: Visit All Instructions
54 The pass iterates through all blocks and instructions, categorizing each based on its kind:
55
56 ```typescript
57 for (const [, block] of fn.body.blocks) {
58 for (const instr of block.instructions) {
59 switch (value.kind) {
60 case 'DeclareContext':
61 case 'StoreContext':
62 visit(identifierKinds, value.lvalue.place, 'context');
63 break;
64 case 'LoadContext':
65 visit(identifierKinds, value.place, 'context');
66 break;
67 case 'StoreLocal':
68 case 'DeclareLocal':
69 visit(identifierKinds, value.lvalue.place, 'local');
70 break;
71 case 'LoadLocal':
72 visit(identifierKinds, value.place, 'local');
73 break;
74 case 'PostfixUpdate':
75 case 'PrefixUpdate':
76 visit(identifierKinds, value.lvalue, 'local');
77 break;
78 case 'Destructure':
79 for (const lvalue of eachPatternOperand(value.lvalue.pattern)) {
80 visit(identifierKinds, lvalue, 'destructure');
81 }
82 break;
83 case 'ObjectMethod':
84 case 'FunctionExpression':
85 // Recursively validate nested functions
86 validateContextVariableLValuesImpl(value.loweredFunc.func, identifierKinds);
87 break;
88 }
89 }
90 }
91 ```
92
93 ### Phase 3: Check Consistency
94 For each place visited, the `visit` function checks if the identifier was previously seen with a different kind:
95
96 ```typescript
97 function visit(identifiers, place, kind) {
98 const prev = identifiers.get(place.identifier.id);
99 if (prev !== undefined) {
100 const wasContext = prev.kind === 'context';
101 const isContext = kind === 'context';
102 if (wasContext !== isContext) {
103 // Check for destructuring of context variable
104 if (prev.kind === 'destructure' || kind === 'destructure') {
105 CompilerError.throwTodo({
106 reason: `Support destructuring of context variables`,
107 ...
108 });
109 }
110 // Invariant violation: inconsistent variable kinds
111 CompilerError.invariant(false, {
112 reason: 'Expected all references to be consistently local or context references',
113 ...
114 });
115 }
116 }
117 identifiers.set(place.identifier.id, {place, kind});
118 }
119 ```
120
121 ## Edge Cases
122
123 ### Nested Function Expressions
124 The validation recursively processes nested function expressions and object methods, sharing the same `identifierKinds` map. This ensures that a variable captured by a nested function is consistently treated as a context variable throughout the entire function hierarchy.
125
126 ### Destructuring Patterns
127 Each operand in a destructure pattern is visited individually, marked as 'destructure' kind. If the same identifier was previously used as a context variable, a Todo error is thrown since destructuring of context variables is not yet supported.
128
129 ### Update Expressions
130 Both `PostfixUpdate` (e.g., `x++`) and `PrefixUpdate` (e.g., `++x`) are treated as local variable operations.
131
132 ## TODOs
133
134 1. **Destructuring of context variables** - Currently not supported:
135 ```typescript
136 CompilerError.throwTodo({
137 reason: `Support destructuring of context variables`,
138 ...
139 });
140 ```
141
142 2. **Unhandled instruction variants** - Some instruction types with lvalues may not be handled:
143 ```typescript
144 CompilerError.throwTodo({
145 reason: 'ValidateContextVariableLValues: unhandled instruction variant',
146 description: `Handle '${value.kind}' lvalues`,
147 ...
148 });
149 ```
150
151 ## Example
152
153 ### Fixture: `error.todo-for-of-loop-with-context-variable-iterator.js`
154
155 **Input:**
156 ```javascript
157 import {useHook} from 'shared-runtime';
158
159 function Component(props) {
160 const data = useHook();
161 const items = [];
162 // NOTE: `item` is a context variable because it's reassigned and also referenced
163 // within a closure, the `onClick` handler of each item
164 for (let item of props.data) {
165 item = item ?? {}; // reassignment to force a context variable
166 items.push(
167 <div key={item.id} onClick={() => data.set(item)}>
168 {item.id}
169 </div>
170 );
171 }
172 return <div>{items}</div>;
173 }
174 ```
175
176 **Error:**
177 ```
178 Todo: Support non-trivial for..of inits
179
180 error.todo-for-of-loop-with-context-variable-iterator.ts:8:2
181 6 | // NOTE: `item` is a context variable because it's reassigned and also referenced
182 7 | // within a closure, the `onClick` handler of each item
183 > 8 | for (let item of props.data) {
184 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
185 > 9 | item = item ?? {}; // reassignment to force a context variable
186 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
187 ...
188 > 15 | }
189 | ^^^^ Support non-trivial for..of inits
190 ```
191
192 Note: This particular error comes from an earlier pass (lowering), but demonstrates the kind of context variable scenarios that this validation is designed to catch.