main
rs 452 lines 15.9 KB
Raw
1 // Copyright (c) Meta Platforms, Inc. and affiliates.
2 //
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the root directory of this source tree.
5
6 //! RenameVariables — renames variables for output, assigns unique names,
7 //! handles SSA renames.
8 //!
9 //! Corresponds to `src/ReactiveScopes/RenameVariables.ts`.
10
11 use rustc_hash::{FxHashMap, FxHashSet};
12
13 use react_compiler_hir::DeclarationId;
14 use react_compiler_hir::EvaluationOrder;
15 use react_compiler_hir::FunctionId;
16 use react_compiler_hir::IdentifierName;
17 use react_compiler_hir::InstructionValue;
18 use react_compiler_hir::ParamPattern;
19 use react_compiler_hir::Place;
20 use react_compiler_hir::PrunedReactiveScopeBlock;
21 use react_compiler_hir::ReactiveBlock;
22 use react_compiler_hir::ReactiveFunction;
23 use react_compiler_hir::ReactiveScopeBlock;
24 use react_compiler_hir::ReactiveValue;
25 use react_compiler_hir::environment::Environment;
26
27 use crate::visitors::ReactiveFunctionVisitor;
28 use crate::visitors::{self};
29
30 // =============================================================================
31 // Scopes
32 // =============================================================================
33
34 struct Scopes {
35 seen: FxHashMap<DeclarationId, IdentifierName>,
36 stack: Vec<FxHashMap<String, DeclarationId>>,
37 globals: FxHashSet<String>,
38 names: FxHashSet<String>,
39 }
40
41 impl Scopes {
42 fn new(globals: FxHashSet<String>) -> Self {
43 Self {
44 seen: FxHashMap::default(),
45 stack: vec![FxHashMap::default()],
46 globals,
47 names: FxHashSet::default(),
48 }
49 }
50
51 fn visit_identifier(
52 &mut self,
53 identifier_id: react_compiler_hir::IdentifierId,
54 env: &Environment,
55 ) {
56 let identifier = &env.identifiers[identifier_id.0 as usize];
57 let original_name = match &identifier.name {
58 Some(name) => name.clone(),
59 None => return,
60 };
61 let declaration_id = identifier.declaration_id;
62
63 if self.seen.contains_key(&declaration_id) {
64 return;
65 }
66
67 let original_value = original_name.value().to_string();
68 let is_promoted = matches!(original_name, IdentifierName::Promoted(_));
69 let is_promoted_temp = is_promoted && original_value.starts_with("#t");
70 let is_promoted_jsx = is_promoted && original_value.starts_with("#T");
71
72 let mut name: String;
73 let mut id: u32 = 0;
74 if is_promoted_temp {
75 name = format!("t{}", id);
76 id += 1;
77 } else if is_promoted_jsx {
78 name = format!("T{}", id);
79 id += 1;
80 } else {
81 name = original_value.clone();
82 }
83
84 while self.lookup(&name).is_some() || self.globals.contains(&name) {
85 if is_promoted_temp {
86 name = format!("t{}", id);
87 id += 1;
88 } else if is_promoted_jsx {
89 name = format!("T{}", id);
90 id += 1;
91 } else {
92 name = format!("{}${}", original_value, id);
93 id += 1;
94 }
95 }
96
97 let identifier_name = IdentifierName::Named(name.clone());
98 self.seen.insert(declaration_id, identifier_name);
99 self.stack
100 .last_mut()
101 .unwrap()
102 .insert(name.clone(), declaration_id);
103 self.names.insert(name);
104 }
105
106 fn lookup(&self, name: &str) -> Option<DeclarationId> {
107 for scope in self.stack.iter().rev() {
108 if let Some(id) = scope.get(name) {
109 return Some(*id);
110 }
111 }
112 None
113 }
114
115 fn enter(&mut self) {
116 self.stack.push(FxHashMap::default());
117 }
118
119 fn leave(&mut self) {
120 self.stack.pop();
121 }
122 }
123
124 // =============================================================================
125 // Visitor — TS: `class Visitor extends ReactiveFunctionVisitor<Scopes>`
126 // =============================================================================
127
128 struct Visitor<'a> {
129 env: &'a Environment,
130 }
131
132 impl ReactiveFunctionVisitor for Visitor<'_> {
133 type State = Scopes;
134
135 fn env(&self) -> &Environment {
136 self.env
137 }
138
139 /// TS: `visitParam(place, state) { state.visit(place.identifier) }`
140 fn visit_param(&self, place: &Place, state: &mut Scopes) {
141 state.visit_identifier(place.identifier, self.env);
142 }
143
144 /// TS: `visitLValue(_id, lvalue, state) { state.visit(lvalue.identifier) }`
145 fn visit_lvalue(&self, _id: EvaluationOrder, lvalue: &Place, state: &mut Scopes) {
146 state.visit_identifier(lvalue.identifier, self.env);
147 }
148
149 /// TS: `visitPlace(_id, place, state) { state.visit(place.identifier) }`
150 fn visit_place(&self, _id: EvaluationOrder, place: &Place, state: &mut Scopes) {
151 state.visit_identifier(place.identifier, self.env);
152 }
153
154 /// TS: `visitBlock(block, state) { state.enter(() => { this.traverseBlock(block, state) }) }`
155 fn visit_block(&self, block: &ReactiveBlock, state: &mut Scopes) {
156 state.enter();
157 self.traverse_block(block, state);
158 state.leave();
159 }
160
161 /// TS: `visitPrunedScope(scopeBlock, state) { this.traverseBlock(scopeBlock.instructions, state) }`
162 /// No enter/leave — names assigned inside pruned scopes remain visible in
163 /// the enclosing scope, preventing name reuse.
164 fn visit_pruned_scope(&self, scope: &PrunedReactiveScopeBlock, state: &mut Scopes) {
165 self.traverse_block(&scope.instructions, state);
166 }
167
168 /// TS: `visitScope(scope, state) { for (const [_, decl] of scope.scope.declarations) state.visit(decl.identifier); this.traverseScope(scope, state) }`
169 fn visit_scope(&self, scope: &ReactiveScopeBlock, state: &mut Scopes) {
170 let scope_data = &self.env.scopes[scope.scope.0 as usize];
171 let decl_ids: Vec<react_compiler_hir::IdentifierId> = scope_data
172 .declarations
173 .iter()
174 .map(|(_, d)| d.identifier)
175 .collect();
176 for id in decl_ids {
177 state.visit_identifier(id, self.env);
178 }
179 self.traverse_scope(scope, state);
180 }
181
182 /// TS: `visitValue(id, value, state) { this.traverseValue(id, value, state); if (value.kind === 'FunctionExpression' || value.kind === 'ObjectMethod') this.visitHirFunction(value.loweredFunc.func, state) }`
183 fn visit_value(&self, id: EvaluationOrder, value: &ReactiveValue, state: &mut Scopes) {
184 self.traverse_value(id, value, state);
185 if let ReactiveValue::Instruction(iv) = value {
186 match iv {
187 InstructionValue::FunctionExpression { lowered_func, .. }
188 | InstructionValue::ObjectMethod { lowered_func, .. } => {
189 self.visit_hir_function(lowered_func.func, state);
190 }
191 _ => {}
192 }
193 }
194 }
195 }
196
197 // =============================================================================
198 // Public entry point
199 // =============================================================================
200
201 /// Renames variables for output — assigns unique names, handles SSA renames.
202 /// Returns a Set of all unique variable names used.
203 /// TS: `renameVariables`
204 pub fn rename_variables(func: &mut ReactiveFunction, env: &mut Environment) -> FxHashSet<String> {
205 rename_variables_with_parent(func, env, None)
206 }
207
208 fn rename_variables_with_parent(
209 func: &mut ReactiveFunction,
210 env: &mut Environment,
211 parent_names: Option<&FxHashSet<String>>,
212 ) -> FxHashSet<String> {
213 let globals = collect_referenced_globals(&func.body, env);
214
215 // Phase 1: Use ReactiveFunctionVisitor to compute the rename mapping.
216 // This collects DeclarationId -> IdentifierName without mutating env.
217 let mut scopes = Scopes::new(globals.clone());
218 // If parent names are provided (for outlined functions), pre-populate
219 // the scope stack so that parameter names don't collide with parent
220 // variables. In the TS compiler, outlined functions are placed in the
221 // parent function body and processed within the parent's scope context.
222 if let Some(parent) = parent_names {
223 scopes.enter();
224 for name in parent {
225 scopes
226 .stack
227 .last_mut()
228 .unwrap()
229 .insert(name.clone(), DeclarationId(u32::MAX));
230 scopes.names.insert(name.clone());
231 }
232 }
233 rename_variables_impl(func, &Visitor { env }, &mut scopes);
234
235 // Phase 2: Apply the computed renames to all identifiers in env.
236 for identifier in env.identifiers.iter_mut() {
237 if let Some(mapped_name) = scopes.seen.get(&identifier.declaration_id) {
238 if identifier.name.is_some() {
239 identifier.name = Some(mapped_name.clone());
240 }
241 }
242 }
243
244 let mut result: FxHashSet<String> = scopes.names;
245 result.extend(globals);
246 result
247 }
248
249 /// TS: `renameVariablesImpl`
250 fn rename_variables_impl(func: &ReactiveFunction, visitor: &Visitor, scopes: &mut Scopes) {
251 scopes.enter();
252 for param in &func.params {
253 let place = match param {
254 ParamPattern::Place(p) => p,
255 ParamPattern::Spread(s) => &s.place,
256 };
257 visitor.visit_param(place, scopes);
258 }
259 visitors::visit_reactive_function(func, visitor, scopes);
260 scopes.leave();
261 }
262
263 // =============================================================================
264 // CollectReferencedGlobals
265 // =============================================================================
266
267 /// Collects all globally referenced names from the reactive function.
268 /// TS: `collectReferencedGlobals`
269 fn collect_referenced_globals(block: &ReactiveBlock, env: &Environment) -> FxHashSet<String> {
270 let mut globals = FxHashSet::default();
271 collect_globals_block(block, &mut globals, env);
272 globals
273 }
274
275 fn collect_globals_block(
276 block: &ReactiveBlock,
277 globals: &mut FxHashSet<String>,
278 env: &Environment,
279 ) {
280 for stmt in block {
281 match stmt {
282 react_compiler_hir::ReactiveStatement::Instruction(instr) => {
283 collect_globals_value(&instr.value, globals, env);
284 }
285 react_compiler_hir::ReactiveStatement::Scope(scope) => {
286 collect_globals_block(&scope.instructions, globals, env);
287 }
288 react_compiler_hir::ReactiveStatement::PrunedScope(scope) => {
289 collect_globals_block(&scope.instructions, globals, env);
290 }
291 react_compiler_hir::ReactiveStatement::Terminal(terminal) => {
292 collect_globals_terminal(terminal, globals, env);
293 }
294 }
295 }
296 }
297
298 fn collect_globals_value(
299 value: &ReactiveValue,
300 globals: &mut FxHashSet<String>,
301 env: &Environment,
302 ) {
303 match value {
304 ReactiveValue::Instruction(iv) => {
305 if let InstructionValue::LoadGlobal { binding, .. } = iv {
306 globals.insert(binding.name().to_string());
307 }
308 // Visit inner functions
309 match iv {
310 InstructionValue::FunctionExpression { lowered_func, .. }
311 | InstructionValue::ObjectMethod { lowered_func, .. } => {
312 collect_globals_hir_function(lowered_func.func, globals, env);
313 }
314 _ => {}
315 }
316 }
317 ReactiveValue::SequenceExpression {
318 instructions,
319 value: inner,
320 ..
321 } => {
322 for instr in instructions {
323 collect_globals_value(&instr.value, globals, env);
324 }
325 collect_globals_value(inner, globals, env);
326 }
327 ReactiveValue::ConditionalExpression {
328 test,
329 consequent,
330 alternate,
331 ..
332 } => {
333 collect_globals_value(test, globals, env);
334 collect_globals_value(consequent, globals, env);
335 collect_globals_value(alternate, globals, env);
336 }
337 ReactiveValue::LogicalExpression { left, right, .. } => {
338 collect_globals_value(left, globals, env);
339 collect_globals_value(right, globals, env);
340 }
341 ReactiveValue::OptionalExpression { value: inner, .. } => {
342 collect_globals_value(inner, globals, env);
343 }
344 }
345 }
346
347 /// Recursively collects LoadGlobal names from an inner HIR function.
348 fn collect_globals_hir_function(
349 func_id: FunctionId,
350 globals: &mut FxHashSet<String>,
351 env: &Environment,
352 ) {
353 let inner_func = &env.functions[func_id.0 as usize];
354 let block_ids: Vec<_> = inner_func.body.blocks.keys().copied().collect();
355 for block_id in block_ids {
356 let inner_func = &env.functions[func_id.0 as usize];
357 let block = &inner_func.body.blocks[&block_id];
358 for instr_id in &block.instructions {
359 let instr = &inner_func.instructions[instr_id.0 as usize];
360 if let InstructionValue::LoadGlobal { binding, .. } = &instr.value {
361 globals.insert(binding.name().to_string());
362 }
363 // Recurse into nested function expressions
364 match &instr.value {
365 InstructionValue::FunctionExpression { lowered_func, .. }
366 | InstructionValue::ObjectMethod { lowered_func, .. } => {
367 collect_globals_hir_function(lowered_func.func, globals, env);
368 }
369 _ => {}
370 }
371 }
372 }
373 }
374
375 fn collect_globals_terminal(
376 stmt: &react_compiler_hir::ReactiveTerminalStatement,
377 globals: &mut FxHashSet<String>,
378 env: &Environment,
379 ) {
380 match &stmt.terminal {
381 react_compiler_hir::ReactiveTerminal::Break { .. }
382 | react_compiler_hir::ReactiveTerminal::Continue { .. } => {}
383 react_compiler_hir::ReactiveTerminal::Return { .. }
384 | react_compiler_hir::ReactiveTerminal::Throw { .. } => {}
385 react_compiler_hir::ReactiveTerminal::For {
386 init,
387 test,
388 update,
389 loop_block,
390 ..
391 } => {
392 collect_globals_value(init, globals, env);
393 collect_globals_value(test, globals, env);
394 collect_globals_block(loop_block, globals, env);
395 if let Some(update) = update {
396 collect_globals_value(update, globals, env);
397 }
398 }
399 react_compiler_hir::ReactiveTerminal::ForOf {
400 init,
401 test,
402 loop_block,
403 ..
404 } => {
405 collect_globals_value(init, globals, env);
406 collect_globals_value(test, globals, env);
407 collect_globals_block(loop_block, globals, env);
408 }
409 react_compiler_hir::ReactiveTerminal::ForIn {
410 init, loop_block, ..
411 } => {
412 collect_globals_value(init, globals, env);
413 collect_globals_block(loop_block, globals, env);
414 }
415 react_compiler_hir::ReactiveTerminal::DoWhile {
416 loop_block, test, ..
417 } => {
418 collect_globals_block(loop_block, globals, env);
419 collect_globals_value(test, globals, env);
420 }
421 react_compiler_hir::ReactiveTerminal::While {
422 test, loop_block, ..
423 } => {
424 collect_globals_value(test, globals, env);
425 collect_globals_block(loop_block, globals, env);
426 }
427 react_compiler_hir::ReactiveTerminal::If {
428 consequent,
429 alternate,
430 ..
431 } => {
432 collect_globals_block(consequent, globals, env);
433 if let Some(alt) = alternate {
434 collect_globals_block(alt, globals, env);
435 }
436 }
437 react_compiler_hir::ReactiveTerminal::Switch { cases, .. } => {
438 for case in cases {
439 if let Some(block) = &case.block {
440 collect_globals_block(block, globals, env);
441 }
442 }
443 }
444 react_compiler_hir::ReactiveTerminal::Label { block, .. } => {
445 collect_globals_block(block, globals, env);
446 }
447 react_compiler_hir::ReactiveTerminal::Try { block, handler, .. } => {
448 collect_globals_block(block, globals, env);
449 collect_globals_block(handler, globals, env);
450 }
451 }
452 }