main
rs 4,461 lines 182 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 //! Code generation pass: converts a `ReactiveFunction` tree back into a Babel-compatible
7 //! AST with memoization (useMemoCache) wired in.
8 //!
9 //! This is the final pass in the compilation pipeline.
10 //!
11 //! Corresponds to `src/ReactiveScopes/CodegenReactiveFunction.ts` in the TS compiler.
12
13 use rustc_hash::{FxHashMap, FxHashSet};
14
15 use react_compiler_ast::common::BaseNode;
16 use react_compiler_ast::common::Position as AstPosition;
17 use react_compiler_ast::common::RawNode;
18 use react_compiler_ast::common::SourceLocation as AstSourceLocation;
19 use react_compiler_ast::expressions::ArrowFunctionBody;
20 use react_compiler_ast::expressions::Expression;
21 use react_compiler_ast::expressions::Identifier as AstIdentifier;
22 use react_compiler_ast::expressions::{self as ast_expr};
23 use react_compiler_ast::jsx::JSXAttribute as AstJSXAttribute;
24 use react_compiler_ast::jsx::JSXAttributeItem;
25 use react_compiler_ast::jsx::JSXAttributeName;
26 use react_compiler_ast::jsx::JSXAttributeValue;
27 use react_compiler_ast::jsx::JSXChild;
28 use react_compiler_ast::jsx::JSXClosingElement;
29 use react_compiler_ast::jsx::JSXClosingFragment;
30 use react_compiler_ast::jsx::JSXElement;
31 use react_compiler_ast::jsx::JSXElementName;
32 use react_compiler_ast::jsx::JSXExpressionContainer;
33 use react_compiler_ast::jsx::JSXExpressionContainerExpr;
34 use react_compiler_ast::jsx::JSXFragment;
35 use react_compiler_ast::jsx::JSXIdentifier;
36 use react_compiler_ast::jsx::JSXMemberExprObject;
37 use react_compiler_ast::jsx::JSXMemberExpression;
38 use react_compiler_ast::jsx::JSXNamespacedName;
39 use react_compiler_ast::jsx::JSXOpeningElement;
40 use react_compiler_ast::jsx::JSXOpeningFragment;
41 use react_compiler_ast::jsx::JSXSpreadAttribute;
42 use react_compiler_ast::jsx::JSXText;
43 use react_compiler_ast::literals::BooleanLiteral;
44 use react_compiler_ast::literals::NullLiteral;
45 use react_compiler_ast::literals::NumericLiteral;
46 use react_compiler_ast::literals::RegExpLiteral as AstRegExpLiteral;
47 use react_compiler_ast::literals::StringLiteral;
48 use react_compiler_ast::literals::TemplateElement;
49 use react_compiler_ast::literals::TemplateElementValue;
50 use react_compiler_ast::operators::AssignmentOperator;
51 use react_compiler_ast::operators::BinaryOperator as AstBinaryOperator;
52 use react_compiler_ast::operators::LogicalOperator as AstLogicalOperator;
53 use react_compiler_ast::operators::UnaryOperator as AstUnaryOperator;
54 use react_compiler_ast::operators::UpdateOperator as AstUpdateOperator;
55 use react_compiler_ast::patterns::ArrayPattern as AstArrayPattern;
56 use react_compiler_ast::patterns::ObjectPatternProp;
57 use react_compiler_ast::patterns::ObjectPatternProperty;
58 use react_compiler_ast::patterns::PatternLike;
59 use react_compiler_ast::patterns::RestElement;
60 use react_compiler_ast::statements::BlockStatement;
61 use react_compiler_ast::statements::BreakStatement;
62 use react_compiler_ast::statements::CatchClause;
63 use react_compiler_ast::statements::ContinueStatement;
64 use react_compiler_ast::statements::DebuggerStatement;
65 use react_compiler_ast::statements::Directive;
66 use react_compiler_ast::statements::DirectiveLiteral;
67 use react_compiler_ast::statements::DoWhileStatement;
68 use react_compiler_ast::statements::EmptyStatement;
69 use react_compiler_ast::statements::ExpressionStatement;
70 use react_compiler_ast::statements::ForInStatement;
71 use react_compiler_ast::statements::ForInit;
72 use react_compiler_ast::statements::ForOfStatement;
73 use react_compiler_ast::statements::ForStatement;
74 use react_compiler_ast::statements::FunctionDeclaration;
75 use react_compiler_ast::statements::IfStatement;
76 use react_compiler_ast::statements::LabeledStatement;
77 use react_compiler_ast::statements::ReturnStatement;
78 use react_compiler_ast::statements::Statement;
79 use react_compiler_ast::statements::SwitchCase;
80 use react_compiler_ast::statements::SwitchStatement;
81 use react_compiler_ast::statements::ThrowStatement;
82 use react_compiler_ast::statements::TryStatement;
83 use react_compiler_ast::statements::UnknownStatement;
84 use react_compiler_ast::statements::VariableDeclaration;
85 use react_compiler_ast::statements::VariableDeclarationKind;
86 use react_compiler_ast::statements::VariableDeclarator;
87 use react_compiler_ast::statements::WhileStatement;
88 use react_compiler_ast::statements::is_known_statement_type;
89 use react_compiler_diagnostics::CompilerDiagnostic;
90 use react_compiler_diagnostics::CompilerDiagnosticDetail;
91 use react_compiler_diagnostics::CompilerError;
92 use react_compiler_diagnostics::CompilerErrorDetail;
93 use react_compiler_diagnostics::ErrorCategory;
94 use react_compiler_diagnostics::SourceLocation as DiagSourceLocation;
95 use react_compiler_hir::ArrayElement;
96 use react_compiler_hir::ArrayPattern;
97 use react_compiler_hir::BlockId;
98 use react_compiler_hir::DeclarationId;
99 use react_compiler_hir::FunctionExpressionType;
100 use react_compiler_hir::IdentifierId;
101 use react_compiler_hir::InstructionKind;
102 use react_compiler_hir::InstructionValue;
103 use react_compiler_hir::JsxAttribute;
104 use react_compiler_hir::JsxTag;
105 use react_compiler_hir::LogicalOperator;
106 use react_compiler_hir::ObjectPattern;
107 use react_compiler_hir::ObjectPropertyKey;
108 use react_compiler_hir::ObjectPropertyOrSpread;
109 use react_compiler_hir::ObjectPropertyType;
110 use react_compiler_hir::ParamPattern;
111 use react_compiler_hir::Pattern;
112 use react_compiler_hir::Place;
113 use react_compiler_hir::PlaceOrSpread;
114 use react_compiler_hir::PrimitiveValue;
115 use react_compiler_hir::PropertyLiteral;
116 use react_compiler_hir::ScopeId;
117 use react_compiler_hir::SpreadPattern;
118 use react_compiler_hir::environment::Environment;
119 use react_compiler_hir::reactive::PrunedReactiveScopeBlock;
120 use react_compiler_hir::reactive::ReactiveBlock;
121 use react_compiler_hir::reactive::ReactiveFunction;
122 use react_compiler_hir::reactive::ReactiveInstruction;
123 use react_compiler_hir::reactive::ReactiveScopeBlock;
124 use react_compiler_hir::reactive::ReactiveStatement;
125 use react_compiler_hir::reactive::ReactiveTerminal;
126 use react_compiler_hir::reactive::ReactiveTerminalTargetKind;
127 use react_compiler_hir::reactive::ReactiveValue;
128
129 use crate::build_reactive_function::build_reactive_function;
130 use crate::prune_hoisted_contexts::prune_hoisted_contexts;
131 use crate::prune_unused_labels::prune_unused_labels;
132 use crate::prune_unused_lvalues::prune_unused_lvalues;
133 use crate::rename_variables::rename_variables;
134 use crate::visitors::ReactiveFunctionVisitor;
135 use crate::visitors::visit_reactive_function;
136
137 // =============================================================================
138 // Public API
139 // =============================================================================
140
141 pub const MEMO_CACHE_SENTINEL: &str = "react.memo_cache_sentinel";
142 pub const EARLY_RETURN_SENTINEL: &str = "react.early_return_sentinel";
143
144 /// FBT tags whose children get special codegen treatment.
145 const SINGLE_CHILD_FBT_TAGS: &[&str] = &["fbt:param", "fbs:param"];
146
147 /// Result of code generation for a single function.
148 pub struct CodegenFunction {
149 pub loc: Option<DiagSourceLocation>,
150 pub id: Option<AstIdentifier>,
151 pub name_hint: Option<String>,
152 pub params: Vec<PatternLike>,
153 pub body: BlockStatement,
154 pub generator: bool,
155 pub is_async: bool,
156 pub memo_slots_used: u32,
157 pub memo_blocks: u32,
158 pub memo_values: u32,
159 pub pruned_memo_blocks: u32,
160 pub pruned_memo_values: u32,
161 pub outlined: Vec<OutlinedFunction>,
162 }
163
164 impl std::fmt::Debug for CodegenFunction {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 f.debug_struct("CodegenFunction")
167 .field("memo_slots_used", &self.memo_slots_used)
168 .field("memo_blocks", &self.memo_blocks)
169 .field("memo_values", &self.memo_values)
170 .field("pruned_memo_blocks", &self.pruned_memo_blocks)
171 .field("pruned_memo_values", &self.pruned_memo_values)
172 .finish()
173 }
174 }
175
176 /// An outlined function extracted during compilation.
177 pub struct OutlinedFunction {
178 pub func: CodegenFunction,
179 pub fn_type: Option<react_compiler_hir::ReactFunctionType>,
180 }
181
182 /// Top-level entry point: generates code for a reactive function.
183 /// Computes the Fast Refresh source hash used to bust the memo cache when the
184 /// source file changes. Matches the TS compiler's
185 /// `createHmac('sha256', code).digest('hex')`: an HMAC-SHA256 keyed by the
186 /// source code, hashing empty data.
187 fn source_file_hash(code: &str) -> String {
188 hmac_sha256::HMAC::mac(b"", code.as_bytes())
189 .iter()
190 .map(|b| format!("{b:02x}"))
191 .collect()
192 }
193
194 pub fn codegen_function(
195 func: &ReactiveFunction,
196 env: &mut Environment,
197 unique_identifiers: FxHashSet<String>,
198 fbt_operands: FxHashSet<IdentifierId>,
199 ) -> Result<CodegenFunction, CompilerError> {
200 let fn_name = func.id.as_deref().unwrap_or("[[ anonymous ]]");
201 let mut cx = Context::new(env, fn_name.to_string(), unique_identifiers, fbt_operands);
202
203 // Fast Refresh: compute source hash and reserve a cache slot if enabled
204 let fast_refresh_state: Option<(u32, String)> =
205 if cx.env.config.enable_reset_cache_on_source_file_changes == Some(true) {
206 if let Some(ref code) = cx.env.code {
207 let hash = source_file_hash(code);
208 let cache_index = cx.alloc_cache_index(); // Reserve slot 0 for the hash check
209 Some((cache_index, hash))
210 } else {
211 None
212 }
213 } else {
214 None
215 };
216
217 let mut compiled = codegen_reactive_function(&mut cx, func)?;
218
219 // enableEmitHookGuards: wrap entire function body in try/finally with
220 // $dispatcherGuard(PushHookGuard=0) / $dispatcherGuard(PopHookGuard=1).
221 // Per-hook-call wrapping is done inline during codegen (CallExpression/MethodCall).
222 if cx.env.hook_guard_name.is_some()
223 && cx.env.output_mode == react_compiler_hir::environment::OutputMode::Client
224 {
225 let guard_name = cx.env.hook_guard_name.as_ref().unwrap().clone();
226 let body_stmts = std::mem::replace(&mut compiled.body.body, Vec::new());
227 compiled.body.body = vec![create_function_body_hook_guard(
228 &guard_name,
229 body_stmts,
230 0,
231 1,
232 )];
233 }
234
235 let cache_count = compiled.memo_slots_used;
236 if cache_count != 0 {
237 let mut preface: Vec<Statement> = Vec::new();
238 let cache_name = cx.synthesize_name("$");
239
240 // const $ = useMemoCache(N)
241 preface.push(Statement::VariableDeclaration(VariableDeclaration {
242 base: BaseNode::typed("VariableDeclaration"),
243 declarations: vec![VariableDeclarator {
244 base: BaseNode::typed("VariableDeclarator"),
245 id: PatternLike::Identifier(make_identifier(&cache_name)),
246 init: Some(Box::new(Expression::CallExpression(
247 ast_expr::CallExpression {
248 base: BaseNode::typed("CallExpression"),
249 callee: Box::new(Expression::Identifier(make_identifier("useMemoCache"))),
250 arguments: vec![Expression::NumericLiteral(NumericLiteral {
251 base: BaseNode::typed("NumericLiteral"),
252 value: cache_count as f64,
253 extra: None,
254 })],
255 type_parameters: None,
256 type_arguments: None,
257 optional: None,
258 },
259 ))),
260 definite: None,
261 }],
262 kind: VariableDeclarationKind::Const,
263 declare: None,
264 }));
265
266 // Fast Refresh: emit cache invalidation check after useMemoCache
267 if let Some((cache_index, ref hash)) = fast_refresh_state {
268 let index_var = cx.synthesize_name("$i");
269 // if ($[cacheIndex] !== "hash") { for (let $i = 0; $i < N; $i += 1) { $[$i] = Symbol.for("react.memo_cache_sentinel"); } $[cacheIndex] = "hash"; }
270 preface.push(Statement::IfStatement(IfStatement {
271 base: BaseNode::typed("IfStatement"),
272 test: Box::new(Expression::BinaryExpression(ast_expr::BinaryExpression {
273 base: BaseNode::typed("BinaryExpression"),
274 operator: AstBinaryOperator::StrictNeq,
275 left: Box::new(Expression::MemberExpression(ast_expr::MemberExpression {
276 base: BaseNode::typed("MemberExpression"),
277 object: Box::new(Expression::Identifier(make_identifier(&cache_name))),
278 property: Box::new(Expression::NumericLiteral(NumericLiteral {
279 base: BaseNode::typed("NumericLiteral"),
280 value: cache_index as f64,
281 extra: None,
282 })),
283 computed: true,
284 })),
285 right: Box::new(Expression::StringLiteral(StringLiteral {
286 base: BaseNode::typed("StringLiteral"),
287 value: hash.clone().into(),
288 })),
289 })),
290 consequent: Box::new(Statement::BlockStatement(BlockStatement {
291 base: BaseNode::typed("BlockStatement"),
292 body: vec![
293 // for (let $i = 0; $i < N; $i += 1) { $[$i] = Symbol.for("react.memo_cache_sentinel"); }
294 Statement::ForStatement(ForStatement {
295 base: BaseNode::typed("ForStatement"),
296 init: Some(Box::new(ForInit::VariableDeclaration(
297 VariableDeclaration {
298 base: BaseNode::typed("VariableDeclaration"),
299 declarations: vec![VariableDeclarator {
300 base: BaseNode::typed("VariableDeclarator"),
301 id: PatternLike::Identifier(make_identifier(&index_var)),
302 init: Some(Box::new(Expression::NumericLiteral(
303 NumericLiteral {
304 base: BaseNode::typed("NumericLiteral"),
305 value: 0.0,
306 extra: None,
307 },
308 ))),
309 definite: None,
310 }],
311 kind: VariableDeclarationKind::Let,
312 declare: None,
313 },
314 ))),
315 test: Some(Box::new(Expression::BinaryExpression(
316 ast_expr::BinaryExpression {
317 base: BaseNode::typed("BinaryExpression"),
318 operator: AstBinaryOperator::Lt,
319 left: Box::new(Expression::Identifier(make_identifier(
320 &index_var,
321 ))),
322 right: Box::new(Expression::NumericLiteral(NumericLiteral {
323 base: BaseNode::typed("NumericLiteral"),
324 value: cache_count as f64,
325 extra: None,
326 })),
327 },
328 ))),
329 update: Some(Box::new(Expression::AssignmentExpression(
330 ast_expr::AssignmentExpression {
331 base: BaseNode::typed("AssignmentExpression"),
332 operator: AssignmentOperator::AddAssign,
333 left: Box::new(PatternLike::Identifier(make_identifier(
334 &index_var,
335 ))),
336 right: Box::new(Expression::NumericLiteral(NumericLiteral {
337 base: BaseNode::typed("NumericLiteral"),
338 value: 1.0,
339 extra: None,
340 })),
341 },
342 ))),
343 body: Box::new(Statement::BlockStatement(BlockStatement {
344 base: BaseNode::typed("BlockStatement"),
345 body: vec![Statement::ExpressionStatement(ExpressionStatement {
346 base: BaseNode::typed("ExpressionStatement"),
347 expression: Box::new(Expression::AssignmentExpression(
348 ast_expr::AssignmentExpression {
349 base: BaseNode::typed("AssignmentExpression"),
350 operator: AssignmentOperator::Assign,
351 left: Box::new(PatternLike::MemberExpression(
352 ast_expr::MemberExpression {
353 base: BaseNode::typed("MemberExpression"),
354 object: Box::new(Expression::Identifier(
355 make_identifier(&cache_name),
356 )),
357 property: Box::new(Expression::Identifier(
358 make_identifier(&index_var),
359 )),
360 computed: true,
361 },
362 )),
363 right: Box::new(Expression::CallExpression(
364 ast_expr::CallExpression {
365 base: BaseNode::typed("CallExpression"),
366 callee: Box::new(Expression::MemberExpression(
367 ast_expr::MemberExpression {
368 base: BaseNode::typed(
369 "MemberExpression",
370 ),
371 object: Box::new(
372 Expression::Identifier(
373 make_identifier("Symbol"),
374 ),
375 ),
376 property: Box::new(
377 Expression::Identifier(
378 make_identifier("for"),
379 ),
380 ),
381 computed: false,
382 },
383 )),
384 arguments: vec![Expression::StringLiteral(
385 StringLiteral {
386 base: BaseNode::typed("StringLiteral"),
387 value: MEMO_CACHE_SENTINEL
388 .to_string()
389 .into(),
390 },
391 )],
392 type_parameters: None,
393 type_arguments: None,
394 optional: None,
395 },
396 )),
397 },
398 )),
399 })],
400 directives: Vec::new(),
401 })),
402 }),
403 // $[cacheIndex] = "hash"
404 Statement::ExpressionStatement(ExpressionStatement {
405 base: BaseNode::typed("ExpressionStatement"),
406 expression: Box::new(Expression::AssignmentExpression(
407 ast_expr::AssignmentExpression {
408 base: BaseNode::typed("AssignmentExpression"),
409 operator: AssignmentOperator::Assign,
410 left: Box::new(PatternLike::MemberExpression(
411 ast_expr::MemberExpression {
412 base: BaseNode::typed("MemberExpression"),
413 object: Box::new(Expression::Identifier(
414 make_identifier(&cache_name),
415 )),
416 property: Box::new(Expression::NumericLiteral(
417 NumericLiteral {
418 base: BaseNode::typed("NumericLiteral"),
419 value: cache_index as f64,
420 extra: None,
421 },
422 )),
423 computed: true,
424 },
425 )),
426 right: Box::new(Expression::StringLiteral(StringLiteral {
427 base: BaseNode::typed("StringLiteral"),
428 value: hash.clone().into(),
429 })),
430 },
431 )),
432 }),
433 ],
434 directives: Vec::new(),
435 })),
436 alternate: None,
437 }));
438 }
439
440 // Insert preface at the beginning of the body
441 let mut new_body = preface;
442 new_body.append(&mut compiled.body.body);
443 compiled.body.body = new_body;
444 }
445
446 // Instrument forget: emit instrumentation call at the top of the function body
447 let emit_instrument_forget = cx.env.config.enable_emit_instrument_forget.clone();
448 if let Some(ref instrument_config) = emit_instrument_forget {
449 if func.id.is_some()
450 && cx.env.output_mode == react_compiler_hir::environment::OutputMode::Client
451 {
452 // Use pre-resolved import names from environment (set by program-level code)
453 let instrument_fn_local = cx
454 .env
455 .instrument_fn_name
456 .clone()
457 .unwrap_or_else(|| instrument_config.fn_.import_specifier_name.clone());
458 let instrument_gating_local = cx.env.instrument_gating_name.clone();
459
460 // Build the gating condition
461 let gating_expr: Option<Expression> =
462 instrument_gating_local.map(|name| Expression::Identifier(make_identifier(&name)));
463 let global_gating_expr: Option<Expression> = instrument_config
464 .global_gating
465 .as_ref()
466 .map(|g| Expression::Identifier(make_identifier(g)));
467
468 let if_test = match (gating_expr, global_gating_expr) {
469 (Some(gating), Some(global)) => {
470 Expression::LogicalExpression(ast_expr::LogicalExpression {
471 base: BaseNode::typed("LogicalExpression"),
472 operator: AstLogicalOperator::And,
473 left: Box::new(global),
474 right: Box::new(gating),
475 })
476 }
477 (Some(gating), None) => gating,
478 (None, Some(global)) => global,
479 (None, None) => unreachable!(
480 "InstrumentationConfig requires at least one of gating or globalGating"
481 ),
482 };
483
484 let fn_name_str = func.id.as_deref().unwrap_or("");
485 let filename_str = cx.env.filename.as_deref().unwrap_or("");
486
487 let instrument_call = Statement::IfStatement(IfStatement {
488 base: BaseNode::typed("IfStatement"),
489 test: Box::new(if_test),
490 consequent: Box::new(Statement::ExpressionStatement(ExpressionStatement {
491 base: BaseNode::typed("ExpressionStatement"),
492 expression: Box::new(Expression::CallExpression(ast_expr::CallExpression {
493 base: BaseNode::typed("CallExpression"),
494 callee: Box::new(Expression::Identifier(make_identifier(
495 &instrument_fn_local,
496 ))),
497 arguments: vec![
498 Expression::StringLiteral(StringLiteral {
499 base: BaseNode::typed("StringLiteral"),
500 value: fn_name_str.to_string().into(),
501 }),
502 Expression::StringLiteral(StringLiteral {
503 base: BaseNode::typed("StringLiteral"),
504 value: filename_str.to_string().into(),
505 }),
506 ],
507 type_parameters: None,
508 type_arguments: None,
509 optional: None,
510 })),
511 })),
512 alternate: None,
513 });
514 compiled.body.body.insert(0, instrument_call);
515 }
516 }
517
518 // Process outlined functions.
519 // Use clone (not take) to match TS behavior: getOutlinedFunctions() returns
520 // a reference, so outlined functions persist on the environment and are also
521 // available to the parent function's codegen. The inner function codegen
522 // processes them here, and the parent/top-level codegen processes them again.
523 let outlined_entries = cx.env.get_outlined_functions().to_vec();
524 let mut outlined: Vec<OutlinedFunction> = Vec::new();
525 for entry in outlined_entries {
526 let reactive_fn = build_reactive_function(&entry.func, cx.env)?;
527 let mut reactive_fn_mut = reactive_fn;
528 prune_unused_labels(&mut reactive_fn_mut, cx.env)?;
529 prune_unused_lvalues(&mut reactive_fn_mut, cx.env);
530 prune_hoisted_contexts(&mut reactive_fn_mut, cx.env)?;
531
532 let identifiers = rename_variables(&mut reactive_fn_mut, cx.env);
533 let mut outlined_cx = Context::new(
534 cx.env,
535 reactive_fn_mut
536 .id
537 .as_deref()
538 .unwrap_or("[[ anonymous ]]")
539 .to_string(),
540 identifiers,
541 cx.fbt_operands.clone(),
542 );
543 let codegen = codegen_reactive_function(&mut outlined_cx, &reactive_fn_mut)?;
544 outlined.push(OutlinedFunction {
545 func: codegen,
546 fn_type: entry.fn_type,
547 });
548 }
549 compiled.outlined = outlined;
550
551 Ok(compiled)
552 }
553
554 // =============================================================================
555 // Context
556 // =============================================================================
557
558 #[derive(Clone)]
559 enum ExpressionOrJsxText {
560 Expression(Expression),
561 JsxText(JSXText),
562 }
563
564 /// The entry a write to [`Temporaries`] displaced, kept so the write can be
565 /// undone.
566 ///
567 /// The expression is boxed because `ExpressionOrJsxText` is ~900 bytes (it
568 /// inlines an `Expression`). Unboxed, the undo log would be a `Vec` of
569 /// ~900-byte slots that are almost always `Absent`, costing more peak heap than
570 /// the copy it replaces on shallow functions. Boxed, an entry is 16 bytes and
571 /// only allocates when a write actually displaces a buffered expression.
572 enum Displaced {
573 /// The key was not present before the write.
574 Absent,
575 /// The key was present as a declared temporary with no buffered value.
576 Empty,
577 /// The key was present with this buffered value.
578 Value(Box<ExpressionOrJsxText>),
579 }
580
581 /// A position in a [`Temporaries`] undo log, produced by [`Temporaries::mark`].
582 #[derive(Clone, Copy)]
583 struct TempMark(usize);
584
585 /// Expressions buffered for temporaries that have not been emitted yet, plus an
586 /// undo log allowing a nested block or scope to be codegen'd and its additions
587 /// discarded.
588 ///
589 /// The TypeScript implementation snapshots this with `new Map(cx.temp)`, which
590 /// is a *shallow* copy: it duplicates references, not the AST nodes behind them.
591 /// The equivalent Rust `.clone()` deep-copies every buffered `Expression` tree,
592 /// which made codegen quadratic in component size and dominated both allocation
593 /// volume and peak heap.
594 ///
595 /// `TS CodegenReactiveFunction.codegenBlock` asserts that pre-existing entries
596 /// are never mutated ("Expected temporary value to be unchanged"), so a
597 /// snapshot's only job is to discard entries added by the nested block. Because
598 /// entries are only ever inserted (never removed, nor mutated in place),
599 /// rewinding an insert log restores the map exactly, with no copying.
600 ///
601 /// All writes go through [`Temporaries::set`] so the log cannot drift out of
602 /// sync with the map.
603 #[derive(Default)]
604 struct Temporaries {
605 values: FxHashMap<DeclarationId, Option<ExpressionOrJsxText>>,
606 journal: Vec<(DeclarationId, Displaced)>,
607 }
608
609 impl Temporaries {
610 fn get(&self, declaration_id: DeclarationId) -> Option<&Option<ExpressionOrJsxText>> {
611 self.values.get(&declaration_id)
612 }
613
614 fn contains_key(&self, declaration_id: DeclarationId) -> bool {
615 self.values.contains_key(&declaration_id)
616 }
617
618 /// Buffers `value` for `declaration_id`, journaling the displaced entry.
619 /// `HashMap::insert` returns that entry by move, so journaling costs no
620 /// clones.
621 fn set(&mut self, declaration_id: DeclarationId, value: Option<ExpressionOrJsxText>) {
622 let displaced = match self.values.insert(declaration_id, value) {
623 None => Displaced::Absent,
624 Some(None) => Displaced::Empty,
625 Some(Some(previous)) => Displaced::Value(Box::new(previous)),
626 };
627 self.journal.push((declaration_id, displaced));
628 }
629
630 /// Marks the current state, for a later [`Temporaries::rewind`].
631 fn mark(&self) -> TempMark {
632 TempMark(self.journal.len())
633 }
634
635 /// Restores the state captured by `mark`, discarding every write since.
636 fn rewind(&mut self, mark: TempMark) {
637 while self.journal.len() > mark.0 {
638 let (declaration_id, displaced) = self.journal.pop().unwrap();
639 match displaced {
640 Displaced::Absent => {
641 self.values.remove(&declaration_id);
642 }
643 Displaced::Empty => {
644 self.values.insert(declaration_id, None);
645 }
646 Displaced::Value(previous) => {
647 self.values.insert(declaration_id, Some(*previous));
648 }
649 }
650 }
651 }
652
653 /// Hands the buffered expressions to a nested function's context, which may
654 /// read them but must not leak its own additions back out.
655 ///
656 /// The borrower gets a fresh log, so [`Temporaries::reclaim`] can undo
657 /// exactly the borrower's writes rather than the lender's whole history.
658 fn lend(&mut self) -> Temporaries {
659 Temporaries {
660 values: std::mem::take(&mut self.values),
661 journal: Vec::new(),
662 }
663 }
664
665 /// Takes back expressions handed out by [`Temporaries::lend`], discarding
666 /// every write the borrower made.
667 fn reclaim(&mut self, mut lent: Temporaries) {
668 lent.rewind(TempMark(0));
669 self.values = lent.values;
670 }
671 }
672
673 struct Context<'env> {
674 env: &'env mut Environment,
675 #[allow(dead_code)]
676 fn_name: String,
677 next_cache_index: u32,
678 declarations: FxHashSet<DeclarationId>,
679 temp: Temporaries,
680 object_methods: FxHashMap<
681 IdentifierId,
682 (
683 InstructionValue,
684 Option<react_compiler_diagnostics::SourceLocation>,
685 ),
686 >,
687 unique_identifiers: FxHashSet<String>,
688 fbt_operands: FxHashSet<IdentifierId>,
689 synthesized_names: FxHashMap<String, String>,
690 }
691
692 impl<'env> Context<'env> {
693 fn new(
694 env: &'env mut Environment,
695 fn_name: String,
696 unique_identifiers: FxHashSet<String>,
697 fbt_operands: FxHashSet<IdentifierId>,
698 ) -> Self {
699 Context {
700 env,
701 fn_name,
702 next_cache_index: 0,
703 declarations: FxHashSet::default(),
704 temp: Temporaries::default(),
705 object_methods: FxHashMap::default(),
706 unique_identifiers,
707 fbt_operands,
708 synthesized_names: FxHashMap::default(),
709 }
710 }
711
712 fn alloc_cache_index(&mut self) -> u32 {
713 let idx = self.next_cache_index;
714 self.next_cache_index += 1;
715 idx
716 }
717
718 fn declare(&mut self, identifier_id: IdentifierId) {
719 let ident = &self.env.identifiers[identifier_id.0 as usize];
720 self.declarations.insert(ident.declaration_id);
721 }
722
723 fn has_declared(&self, identifier_id: IdentifierId) -> bool {
724 let ident = &self.env.identifiers[identifier_id.0 as usize];
725 self.declarations.contains(&ident.declaration_id)
726 }
727
728 fn synthesize_name(&mut self, name: &str) -> String {
729 if let Some(prev) = self.synthesized_names.get(name) {
730 return prev.clone();
731 }
732 let mut validated = name.to_string();
733 let mut index = 0u32;
734 while self.unique_identifiers.contains(&validated) {
735 validated = format!("{name}{index}");
736 index += 1;
737 }
738 self.unique_identifiers.insert(validated.clone());
739 self.synthesized_names
740 .insert(name.to_string(), validated.clone());
741 validated
742 }
743
744 fn record_error(&mut self, detail: CompilerErrorDetail) -> Result<(), CompilerError> {
745 self.env.record_error(detail)
746 }
747 }
748
749 // =============================================================================
750 // Core codegen functions
751 // =============================================================================
752
753 fn codegen_reactive_function(
754 cx: &mut Context,
755 func: &ReactiveFunction,
756 ) -> Result<CodegenFunction, CompilerError> {
757 // Register parameters
758 for param in &func.params {
759 let place = match param {
760 ParamPattern::Place(p) => p,
761 ParamPattern::Spread(sp) => &sp.place,
762 };
763 let declaration_id = cx.env.identifiers[place.identifier.0 as usize].declaration_id;
764 cx.temp.set(declaration_id, None);
765 cx.declare(place.identifier);
766 }
767
768 let params: Vec<PatternLike> = func
769 .params
770 .iter()
771 .map(|p| convert_parameter(p, cx.env))
772 .collect::<Result<_, _>>()?;
773 let mut body = codegen_block(cx, &func.body)?;
774
775 // Add directives
776 body.directives = func
777 .directives
778 .iter()
779 .map(|d| Directive {
780 base: BaseNode::typed("Directive"),
781 value: DirectiveLiteral {
782 base: BaseNode::typed("DirectiveLiteral"),
783 value: d.clone(),
784 },
785 })
786 .collect();
787
788 // Remove trailing `return undefined`
789 if let Some(last) = body.body.last() {
790 if matches!(last, Statement::ReturnStatement(ret) if ret.argument.is_none()) {
791 body.body.pop();
792 }
793 }
794
795 // Count memo blocks
796 let (memo_blocks, memo_values, pruned_memo_blocks, pruned_memo_values) =
797 count_memo_blocks(func, cx.env);
798
799 Ok(CodegenFunction {
800 loc: func.loc,
801 id: func.id.as_ref().map(|name| make_identifier(name)),
802 name_hint: func.name_hint.clone(),
803 params,
804 body,
805 generator: func.generator,
806 is_async: func.is_async,
807 memo_slots_used: cx.next_cache_index,
808 memo_blocks,
809 memo_values,
810 pruned_memo_blocks,
811 pruned_memo_values,
812 outlined: Vec::new(),
813 })
814 }
815
816 fn convert_parameter(
817 param: &ParamPattern,
818 env: &Environment,
819 ) -> Result<PatternLike, CompilerError> {
820 match param {
821 ParamPattern::Place(place) => Ok(PatternLike::Identifier(convert_identifier(
822 place.identifier,
823 env,
824 )?)),
825 ParamPattern::Spread(spread) => Ok(PatternLike::RestElement(RestElement {
826 base: BaseNode::typed("RestElement"),
827 argument: Box::new(PatternLike::Identifier(convert_identifier(
828 spread.place.identifier,
829 env,
830 )?)),
831 type_annotation: None,
832 decorators: None,
833 })),
834 }
835 }
836
837 // =============================================================================
838 // Block codegen
839 // =============================================================================
840
841 fn codegen_block(cx: &mut Context, block: &ReactiveBlock) -> Result<BlockStatement, CompilerError> {
842 let mark = cx.temp.mark();
843 let result = codegen_block_no_reset(cx, block)?;
844 cx.temp.rewind(mark);
845 Ok(result)
846 }
847
848 fn codegen_block_no_reset(
849 cx: &mut Context,
850 block: &ReactiveBlock,
851 ) -> Result<BlockStatement, CompilerError> {
852 let mut statements: Vec<Statement> = Vec::new();
853 for item in block {
854 match item {
855 ReactiveStatement::Instruction(instr) => {
856 if let Some(stmt) = codegen_instruction_nullable(cx, instr)? {
857 statements.push(stmt);
858 }
859 }
860 ReactiveStatement::PrunedScope(PrunedReactiveScopeBlock { instructions, .. }) => {
861 let scope_block = codegen_block_no_reset(cx, instructions)?;
862 statements.extend(scope_block.body);
863 }
864 ReactiveStatement::Scope(ReactiveScopeBlock {
865 scope,
866 instructions,
867 }) => {
868 let mark = cx.temp.mark();
869 codegen_reactive_scope(cx, &mut statements, *scope, instructions)?;
870 cx.temp.rewind(mark);
871 }
872 ReactiveStatement::Terminal(term_stmt) => {
873 let stmt = codegen_terminal(cx, &term_stmt.terminal)?;
874 let Some(stmt) = stmt else {
875 continue;
876 };
877 if let Some(ref label) = term_stmt.label {
878 if !label.implicit {
879 let inner = if let Statement::BlockStatement(bs) = &stmt {
880 if bs.body.len() == 1 {
881 bs.body[0].clone()
882 } else {
883 stmt
884 }
885 } else {
886 stmt
887 };
888 statements.push(Statement::LabeledStatement(LabeledStatement {
889 base: BaseNode::typed("LabeledStatement"),
890 label: make_identifier(&codegen_label(label.id)),
891 body: Box::new(inner),
892 }));
893 } else if let Statement::BlockStatement(bs) = stmt {
894 statements.extend(bs.body);
895 } else {
896 statements.push(stmt);
897 }
898 } else if let Statement::BlockStatement(bs) = stmt {
899 statements.extend(bs.body);
900 } else {
901 statements.push(stmt);
902 }
903 }
904 }
905 }
906 Ok(BlockStatement {
907 base: BaseNode::typed("BlockStatement"),
908 body: statements,
909 directives: Vec::new(),
910 })
911 }
912
913 // =============================================================================
914 // Reactive scope codegen (memoization)
915 // =============================================================================
916
917 fn codegen_reactive_scope(
918 cx: &mut Context,
919 statements: &mut Vec<Statement>,
920 scope_id: ScopeId,
921 block: &ReactiveBlock,
922 ) -> Result<(), CompilerError> {
923 // Clone scope data upfront to avoid holding a borrow on cx.env
924 let scope_deps = cx.env.scopes[scope_id.0 as usize].dependencies.clone();
925 let scope_decls = cx.env.scopes[scope_id.0 as usize].declarations.clone();
926 let scope_reassignments = cx.env.scopes[scope_id.0 as usize].reassignments.clone();
927
928 let mut cache_store_stmts: Vec<Statement> = Vec::new();
929 let mut cache_load_stmts: Vec<Statement> = Vec::new();
930 let mut cache_loads: Vec<(AstIdentifier, u32, Expression)> = Vec::new();
931 let mut change_exprs: Vec<Expression> = Vec::new();
932
933 // Sort dependencies
934 let mut deps = scope_deps;
935 deps.sort_by(|a, b| compare_scope_dependency(a, b, cx.env));
936
937 for dep in &deps {
938 let index = cx.alloc_cache_index();
939 let cache_name = cx.synthesize_name("$");
940 let comparison = Expression::BinaryExpression(ast_expr::BinaryExpression {
941 base: BaseNode::typed("BinaryExpression"),
942 operator: AstBinaryOperator::StrictNeq,
943 left: Box::new(Expression::MemberExpression(ast_expr::MemberExpression {
944 base: BaseNode::typed("MemberExpression"),
945 object: Box::new(Expression::Identifier(make_identifier(&cache_name))),
946 property: Box::new(Expression::NumericLiteral(NumericLiteral {
947 base: BaseNode::typed("NumericLiteral"),
948 value: index as f64,
949 extra: None,
950 })),
951 computed: true,
952 })),
953 right: Box::new(codegen_dependency(cx, dep)?),
954 });
955 change_exprs.push(comparison);
956
957 // Store dependency value into cache
958 let dep_value = codegen_dependency(cx, dep)?;
959 cache_store_stmts.push(Statement::ExpressionStatement(ExpressionStatement {
960 base: BaseNode::typed("ExpressionStatement"),
961 expression: Box::new(Expression::AssignmentExpression(
962 ast_expr::AssignmentExpression {
963 base: BaseNode::typed("AssignmentExpression"),
964 operator: AssignmentOperator::Assign,
965 left: Box::new(PatternLike::MemberExpression(ast_expr::MemberExpression {
966 base: BaseNode::typed("MemberExpression"),
967 object: Box::new(Expression::Identifier(make_identifier(&cache_name))),
968 property: Box::new(Expression::NumericLiteral(NumericLiteral {
969 base: BaseNode::typed("NumericLiteral"),
970 value: index as f64,
971 extra: None,
972 })),
973 computed: true,
974 })),
975 right: Box::new(dep_value),
976 },
977 )),
978 }));
979 }
980
981 let mut first_output_index: Option<u32> = None;
982
983 // Sort declarations
984 let mut decls = scope_decls;
985 decls.sort_by(|(_id_a, a), (_id_b, b)| compare_scope_declaration(a, b, cx.env));
986
987 for (_ident_id, decl) in &decls {
988 let index = cx.alloc_cache_index();
989 if first_output_index.is_none() {
990 first_output_index = Some(index);
991 }
992
993 let ident = &cx.env.identifiers[decl.identifier.0 as usize];
994 invariant(
995 ident.name.is_some(),
996 &format!(
997 "Expected scope declaration identifier to be named, id={}",
998 decl.identifier.0
999 ),
1000 None,
1001 )?;
1002
1003 let name = convert_identifier(decl.identifier, cx.env)?;
1004 if !cx.has_declared(decl.identifier) {
1005 statements.push(Statement::VariableDeclaration(VariableDeclaration {
1006 base: BaseNode::typed("VariableDeclaration"),
1007 declarations: vec![make_var_declarator(
1008 PatternLike::Identifier(name.clone()),
1009 None,
1010 )],
1011 kind: VariableDeclarationKind::Let,
1012 declare: None,
1013 }));
1014 }
1015 cache_loads.push((name.clone(), index, Expression::Identifier(name.clone())));
1016 cx.declare(decl.identifier);
1017 }
1018
1019 for reassignment_id in scope_reassignments {
1020 let index = cx.alloc_cache_index();
1021 if first_output_index.is_none() {
1022 first_output_index = Some(index);
1023 }
1024 let name = convert_identifier(reassignment_id, cx.env)?;
1025 cache_loads.push((name.clone(), index, Expression::Identifier(name)));
1026 }
1027
1028 // Build test condition
1029 let test_condition = if change_exprs.is_empty() {
1030 let first_idx = first_output_index.ok_or_else(|| {
1031 invariant_err("Expected scope to have at least one declaration", None)
1032 })?;
1033 let cache_name = cx.synthesize_name("$");
1034 Expression::BinaryExpression(ast_expr::BinaryExpression {
1035 base: BaseNode::typed("BinaryExpression"),
1036 operator: AstBinaryOperator::StrictEq,
1037 left: Box::new(Expression::MemberExpression(ast_expr::MemberExpression {
1038 base: BaseNode::typed("MemberExpression"),
1039 object: Box::new(Expression::Identifier(make_identifier(&cache_name))),
1040 property: Box::new(Expression::NumericLiteral(NumericLiteral {
1041 base: BaseNode::typed("NumericLiteral"),
1042 value: first_idx as f64,
1043 extra: None,
1044 })),
1045 computed: true,
1046 })),
1047 right: Box::new(symbol_for(MEMO_CACHE_SENTINEL)),
1048 })
1049 } else {
1050 change_exprs
1051 .into_iter()
1052 .reduce(|acc, expr| {
1053 Expression::LogicalExpression(ast_expr::LogicalExpression {
1054 base: BaseNode::typed("LogicalExpression"),
1055 operator: AstLogicalOperator::Or,
1056 left: Box::new(acc),
1057 right: Box::new(expr),
1058 })
1059 })
1060 .unwrap()
1061 };
1062
1063 let mut computation_block = codegen_block(cx, block)?;
1064
1065 // Build cache store and load statements for declarations
1066 for (name, index, value) in &cache_loads {
1067 let cache_name = cx.synthesize_name("$");
1068 cache_store_stmts.push(Statement::ExpressionStatement(ExpressionStatement {
1069 base: BaseNode::typed("ExpressionStatement"),
1070 expression: Box::new(Expression::AssignmentExpression(
1071 ast_expr::AssignmentExpression {
1072 base: BaseNode::typed("AssignmentExpression"),
1073 operator: AssignmentOperator::Assign,
1074 left: Box::new(PatternLike::MemberExpression(ast_expr::MemberExpression {
1075 base: BaseNode::typed("MemberExpression"),
1076 object: Box::new(Expression::Identifier(make_identifier(&cache_name))),
1077 property: Box::new(Expression::NumericLiteral(NumericLiteral {
1078 base: BaseNode::typed("NumericLiteral"),
1079 value: *index as f64,
1080 extra: None,
1081 })),
1082 computed: true,
1083 })),
1084 right: Box::new(value.clone()),
1085 },
1086 )),
1087 }));
1088 cache_load_stmts.push(Statement::ExpressionStatement(ExpressionStatement {
1089 base: BaseNode::typed("ExpressionStatement"),
1090 expression: Box::new(Expression::AssignmentExpression(
1091 ast_expr::AssignmentExpression {
1092 base: BaseNode::typed("AssignmentExpression"),
1093 operator: AssignmentOperator::Assign,
1094 left: Box::new(PatternLike::Identifier(name.clone())),
1095 right: Box::new(Expression::MemberExpression(ast_expr::MemberExpression {
1096 base: BaseNode::typed("MemberExpression"),
1097 object: Box::new(Expression::Identifier(make_identifier(&cache_name))),
1098 property: Box::new(Expression::NumericLiteral(NumericLiteral {
1099 base: BaseNode::typed("NumericLiteral"),
1100 value: *index as f64,
1101 extra: None,
1102 })),
1103 computed: true,
1104 })),
1105 },
1106 )),
1107 }));
1108 }
1109
1110 computation_block.body.extend(cache_store_stmts);
1111
1112 let memo_stmt = Statement::IfStatement(IfStatement {
1113 base: BaseNode::typed("IfStatement"),
1114 test: Box::new(test_condition),
1115 consequent: Box::new(Statement::BlockStatement(computation_block)),
1116 alternate: Some(Box::new(Statement::BlockStatement(BlockStatement {
1117 base: BaseNode::typed("BlockStatement"),
1118 body: cache_load_stmts,
1119 directives: Vec::new(),
1120 }))),
1121 });
1122 statements.push(memo_stmt);
1123
1124 // Handle early return
1125 let early_return_value = cx.env.scopes[scope_id.0 as usize]
1126 .early_return_value
1127 .clone();
1128 if let Some(ref early_return) = early_return_value {
1129 let early_ident = &cx.env.identifiers[early_return.value.0 as usize];
1130 let name = match &early_ident.name {
1131 Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(),
1132 Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(),
1133 None => {
1134 return Err(invariant_err(
1135 "Expected early return value to be promoted to a named variable",
1136 early_return.loc,
1137 ));
1138 }
1139 };
1140 statements.push(Statement::IfStatement(IfStatement {
1141 base: BaseNode::typed("IfStatement"),
1142 test: Box::new(Expression::BinaryExpression(ast_expr::BinaryExpression {
1143 base: BaseNode::typed("BinaryExpression"),
1144 operator: AstBinaryOperator::StrictNeq,
1145 left: Box::new(Expression::Identifier(make_identifier(&name))),
1146 right: Box::new(symbol_for(EARLY_RETURN_SENTINEL)),
1147 })),
1148 consequent: Box::new(Statement::BlockStatement(BlockStatement {
1149 base: BaseNode::typed("BlockStatement"),
1150 body: vec![Statement::ReturnStatement(ReturnStatement {
1151 base: BaseNode::typed("ReturnStatement"),
1152 argument: Some(Box::new(Expression::Identifier(make_identifier(&name)))),
1153 })],
1154 directives: Vec::new(),
1155 })),
1156 alternate: None,
1157 }));
1158 }
1159
1160 Ok(())
1161 }
1162
1163 // =============================================================================
1164 // Terminal codegen
1165 // =============================================================================
1166
1167 fn codegen_terminal(
1168 cx: &mut Context,
1169 terminal: &ReactiveTerminal,
1170 ) -> Result<Option<Statement>, CompilerError> {
1171 match terminal {
1172 ReactiveTerminal::Break {
1173 target,
1174 target_kind,
1175 loc,
1176 ..
1177 } => {
1178 if *target_kind == ReactiveTerminalTargetKind::Implicit {
1179 return Ok(None);
1180 }
1181 Ok(Some(Statement::BreakStatement(BreakStatement {
1182 base: base_node_with_loc("BreakStatement", *loc),
1183 label: if *target_kind == ReactiveTerminalTargetKind::Labeled {
1184 Some(make_identifier(&codegen_label(*target)))
1185 } else {
1186 None
1187 },
1188 })))
1189 }
1190 ReactiveTerminal::Continue {
1191 target,
1192 target_kind,
1193 loc,
1194 ..
1195 } => {
1196 if *target_kind == ReactiveTerminalTargetKind::Implicit {
1197 return Ok(None);
1198 }
1199 Ok(Some(Statement::ContinueStatement(ContinueStatement {
1200 base: base_node_with_loc("ContinueStatement", *loc),
1201 label: if *target_kind == ReactiveTerminalTargetKind::Labeled {
1202 Some(make_identifier(&codegen_label(*target)))
1203 } else {
1204 None
1205 },
1206 })))
1207 }
1208 ReactiveTerminal::Return { value, loc, .. } => {
1209 let expr = codegen_place_to_expression(cx, value)?;
1210 if let Expression::Identifier(ref ident) = expr {
1211 if ident.name == "undefined" {
1212 return Ok(Some(Statement::ReturnStatement(ReturnStatement {
1213 base: base_node_with_loc("ReturnStatement", *loc),
1214 argument: None,
1215 })));
1216 }
1217 }
1218 Ok(Some(Statement::ReturnStatement(ReturnStatement {
1219 base: base_node_with_loc("ReturnStatement", *loc),
1220 argument: Some(Box::new(expr)),
1221 })))
1222 }
1223 ReactiveTerminal::Throw { value, loc, .. } => {
1224 let expr = codegen_place_to_expression(cx, value)?;
1225 Ok(Some(Statement::ThrowStatement(ThrowStatement {
1226 base: base_node_with_loc("ThrowStatement", *loc),
1227 argument: Box::new(expr),
1228 })))
1229 }
1230 ReactiveTerminal::If {
1231 test,
1232 consequent,
1233 alternate,
1234 loc,
1235 ..
1236 } => {
1237 let test_expr = codegen_place_to_expression(cx, test)?;
1238 let consequent_block = codegen_block(cx, consequent)?;
1239 let alternate_stmt = if let Some(alt) = alternate {
1240 let block = codegen_block(cx, alt)?;
1241 if block.body.is_empty() {
1242 None
1243 } else {
1244 Some(Box::new(Statement::BlockStatement(block)))
1245 }
1246 } else {
1247 None
1248 };
1249 Ok(Some(Statement::IfStatement(IfStatement {
1250 base: base_node_with_loc("IfStatement", *loc),
1251 test: Box::new(test_expr),
1252 consequent: Box::new(Statement::BlockStatement(consequent_block)),
1253 alternate: alternate_stmt,
1254 })))
1255 }
1256 ReactiveTerminal::Switch {
1257 test, cases, loc, ..
1258 } => {
1259 let test_expr = codegen_place_to_expression(cx, test)?;
1260 let switch_cases: Vec<SwitchCase> = cases
1261 .iter()
1262 .map(|case| {
1263 let test = case
1264 .test
1265 .as_ref()
1266 .map(|t| codegen_place_to_expression(cx, t))
1267 .transpose()?;
1268 let block = case
1269 .block
1270 .as_ref()
1271 .map(|b| codegen_block(cx, b))
1272 .transpose()?;
1273 let consequent = match block {
1274 Some(b) if b.body.is_empty() => Vec::new(),
1275 Some(b) => vec![Statement::BlockStatement(b)],
1276 None => Vec::new(),
1277 };
1278 Ok(SwitchCase {
1279 base: BaseNode::typed("SwitchCase"),
1280 test: test.map(Box::new),
1281 consequent,
1282 })
1283 })
1284 .collect::<Result<_, CompilerError>>()?;
1285 Ok(Some(Statement::SwitchStatement(SwitchStatement {
1286 base: base_node_with_loc("SwitchStatement", *loc),
1287 discriminant: Box::new(test_expr),
1288 cases: switch_cases,
1289 })))
1290 }
1291 ReactiveTerminal::DoWhile {
1292 loop_block,
1293 test,
1294 loc,
1295 ..
1296 } => {
1297 let test_expr = codegen_instruction_value_to_expression(cx, test)?;
1298 let body = codegen_block(cx, loop_block)?;
1299 Ok(Some(Statement::DoWhileStatement(DoWhileStatement {
1300 base: base_node_with_loc("DoWhileStatement", *loc),
1301 test: Box::new(test_expr),
1302 body: Box::new(Statement::BlockStatement(body)),
1303 })))
1304 }
1305 ReactiveTerminal::While {
1306 test,
1307 loop_block,
1308 loc,
1309 ..
1310 } => {
1311 let test_expr = codegen_instruction_value_to_expression(cx, test)?;
1312 let body = codegen_block(cx, loop_block)?;
1313 Ok(Some(Statement::WhileStatement(WhileStatement {
1314 base: base_node_with_loc("WhileStatement", *loc),
1315 test: Box::new(test_expr),
1316 body: Box::new(Statement::BlockStatement(body)),
1317 })))
1318 }
1319 ReactiveTerminal::For {
1320 init,
1321 test,
1322 update,
1323 loop_block,
1324 loc,
1325 ..
1326 } => {
1327 let init_val = codegen_for_init(cx, init)?;
1328 let test_expr = codegen_instruction_value_to_expression(cx, test)?;
1329 let update_expr = update
1330 .as_ref()
1331 .map(|u| codegen_instruction_value_to_expression(cx, u))
1332 .transpose()?;
1333 let body = codegen_block(cx, loop_block)?;
1334 Ok(Some(Statement::ForStatement(ForStatement {
1335 base: base_node_with_loc("ForStatement", *loc),
1336 init: init_val.map(|v| Box::new(v)),
1337 test: Some(Box::new(test_expr)),
1338 update: update_expr.map(Box::new),
1339 body: Box::new(Statement::BlockStatement(body)),
1340 })))
1341 }
1342 ReactiveTerminal::ForIn {
1343 init,
1344 loop_block,
1345 loc,
1346 ..
1347 } => codegen_for_in(cx, init, loop_block, *loc),
1348 ReactiveTerminal::ForOf {
1349 init,
1350 test,
1351 loop_block,
1352 loc,
1353 ..
1354 } => codegen_for_of(cx, init, test, loop_block, *loc),
1355 ReactiveTerminal::Label { block, .. } => {
1356 let body = codegen_block(cx, block)?;
1357 Ok(Some(Statement::BlockStatement(body)))
1358 }
1359 ReactiveTerminal::Try {
1360 block,
1361 handler_binding,
1362 handler,
1363 loc,
1364 ..
1365 } => {
1366 let catch_param = match handler_binding.as_ref() {
1367 Some(binding) => {
1368 let declaration_id =
1369 cx.env.identifiers[binding.identifier.0 as usize].declaration_id;
1370 cx.temp.set(declaration_id, None);
1371 Some(PatternLike::Identifier(convert_identifier(
1372 binding.identifier,
1373 cx.env,
1374 )?))
1375 }
1376 None => None,
1377 };
1378 let try_block = codegen_block(cx, block)?;
1379 let handler_block = codegen_block(cx, handler)?;
1380 Ok(Some(Statement::TryStatement(TryStatement {
1381 base: base_node_with_loc("TryStatement", *loc),
1382 block: try_block,
1383 handler: Some(CatchClause {
1384 base: BaseNode::typed("CatchClause"),
1385 param: catch_param,
1386 body: handler_block,
1387 }),
1388 finalizer: None,
1389 })))
1390 }
1391 }
1392 }
1393
1394 fn codegen_for_in(
1395 cx: &mut Context,
1396 init: &ReactiveValue,
1397 loop_block: &ReactiveBlock,
1398 loc: Option<DiagSourceLocation>,
1399 ) -> Result<Option<Statement>, CompilerError> {
1400 let ReactiveValue::SequenceExpression { instructions, .. } = init else {
1401 return Err(invariant_err(
1402 "Expected a sequence expression init for for..in",
1403 None,
1404 ));
1405 };
1406 if instructions.len() != 2 {
1407 cx.record_error(CompilerErrorDetail {
1408 category: ErrorCategory::Todo,
1409 reason: "Support non-trivial for..in inits".to_string(),
1410 description: None,
1411 loc,
1412 suggestions: None,
1413 })?;
1414 return Ok(Some(Statement::EmptyStatement(EmptyStatement {
1415 base: BaseNode::typed("EmptyStatement"),
1416 })));
1417 }
1418 let iterable_collection = &instructions[0];
1419 let iterable_item = &instructions[1];
1420 let instr_value = get_instruction_value(&iterable_item.value)?;
1421 let (lval, var_decl_kind) = extract_for_in_of_lval(cx, instr_value, "for..in", loc)?;
1422 let right = codegen_instruction_value_to_expression(cx, &iterable_collection.value)?;
1423 let body = codegen_block(cx, loop_block)?;
1424 Ok(Some(Statement::ForInStatement(ForInStatement {
1425 base: base_node_with_loc("ForInStatement", loc),
1426 left: Box::new(
1427 react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(VariableDeclaration {
1428 base: BaseNode::typed("VariableDeclaration"),
1429 declarations: vec![VariableDeclarator {
1430 base: BaseNode::typed("VariableDeclarator"),
1431 id: lval,
1432 init: None,
1433 definite: None,
1434 }],
1435 kind: var_decl_kind,
1436 declare: None,
1437 }),
1438 ),
1439 right: Box::new(right),
1440 body: Box::new(Statement::BlockStatement(body)),
1441 })))
1442 }
1443
1444 fn codegen_for_of(
1445 cx: &mut Context,
1446 init: &ReactiveValue,
1447 test: &ReactiveValue,
1448 loop_block: &ReactiveBlock,
1449 loc: Option<DiagSourceLocation>,
1450 ) -> Result<Option<Statement>, CompilerError> {
1451 // Validate init is SequenceExpression with single GetIterator instruction
1452 let ReactiveValue::SequenceExpression {
1453 instructions: init_instrs,
1454 ..
1455 } = init
1456 else {
1457 return Err(invariant_err(
1458 "Expected a sequence expression init for for..of",
1459 None,
1460 ));
1461 };
1462 if init_instrs.len() != 1 {
1463 return Err(invariant_err(
1464 "Expected a single-expression sequence expression init for for..of",
1465 None,
1466 ));
1467 }
1468 let get_iter_value = get_instruction_value(&init_instrs[0].value)?;
1469 let InstructionValue::GetIterator { collection, .. } = get_iter_value else {
1470 return Err(invariant_err("Expected GetIterator in for..of init", None));
1471 };
1472
1473 let ReactiveValue::SequenceExpression {
1474 instructions: test_instrs,
1475 ..
1476 } = test
1477 else {
1478 return Err(invariant_err(
1479 "Expected a sequence expression test for for..of",
1480 None,
1481 ));
1482 };
1483 if test_instrs.len() != 2 {
1484 cx.record_error(CompilerErrorDetail {
1485 category: ErrorCategory::Todo,
1486 reason: "Support non-trivial for..of inits".to_string(),
1487 description: None,
1488 loc,
1489 suggestions: None,
1490 })?;
1491 return Ok(Some(Statement::EmptyStatement(EmptyStatement {
1492 base: BaseNode::typed("EmptyStatement"),
1493 })));
1494 }
1495 let iterable_item = &test_instrs[1];
1496 let instr_value = get_instruction_value(&iterable_item.value)?;
1497 let (lval, var_decl_kind) = extract_for_in_of_lval(cx, instr_value, "for..of", loc)?;
1498
1499 let right = codegen_place_to_expression(cx, collection)?;
1500 let body = codegen_block(cx, loop_block)?;
1501 Ok(Some(Statement::ForOfStatement(ForOfStatement {
1502 base: base_node_with_loc("ForOfStatement", loc),
1503 left: Box::new(
1504 react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(VariableDeclaration {
1505 base: BaseNode::typed("VariableDeclaration"),
1506 declarations: vec![VariableDeclarator {
1507 base: BaseNode::typed("VariableDeclarator"),
1508 id: lval,
1509 init: None,
1510 definite: None,
1511 }],
1512 kind: var_decl_kind,
1513 declare: None,
1514 }),
1515 ),
1516 right: Box::new(right),
1517 body: Box::new(Statement::BlockStatement(body)),
1518 is_await: false,
1519 })))
1520 }
1521
1522 /// Extract lval and declaration kind from a for-in/for-of iterable item instruction.
1523 fn extract_for_in_of_lval(
1524 cx: &mut Context,
1525 instr_value: &InstructionValue,
1526 context_name: &str,
1527 loc: Option<DiagSourceLocation>,
1528 ) -> Result<(PatternLike, VariableDeclarationKind), CompilerError> {
1529 let (lval, kind) = match instr_value {
1530 InstructionValue::StoreLocal { lvalue, .. } => (
1531 codegen_lvalue(cx, &LvalueRef::Place(&lvalue.place))?,
1532 lvalue.kind,
1533 ),
1534 InstructionValue::Destructure { lvalue, .. } => (
1535 codegen_lvalue(cx, &LvalueRef::Pattern(&lvalue.pattern))?,
1536 lvalue.kind,
1537 ),
1538 InstructionValue::StoreContext { .. } => {
1539 cx.record_error(CompilerErrorDetail {
1540 category: ErrorCategory::Todo,
1541 reason: format!("Support non-trivial {} inits", context_name),
1542 description: None,
1543 loc,
1544 suggestions: None,
1545 })?;
1546 return Ok((
1547 PatternLike::Identifier(make_identifier("_")),
1548 VariableDeclarationKind::Let,
1549 ));
1550 }
1551 _ => {
1552 return Err(invariant_err(
1553 &format!(
1554 "Expected a StoreLocal or Destructure in {} collection, found {:?}",
1555 context_name,
1556 std::mem::discriminant(instr_value)
1557 ),
1558 None,
1559 ));
1560 }
1561 };
1562 let var_decl_kind = match kind {
1563 InstructionKind::Const => VariableDeclarationKind::Const,
1564 InstructionKind::Let => VariableDeclarationKind::Let,
1565 _ => {
1566 return Err(invariant_err(
1567 &format!(
1568 "Unexpected {:?} variable in {} collection",
1569 kind, context_name
1570 ),
1571 None,
1572 ));
1573 }
1574 };
1575 Ok((lval, var_decl_kind))
1576 }
1577
1578 fn codegen_for_init(
1579 cx: &mut Context,
1580 init: &ReactiveValue,
1581 ) -> Result<Option<ForInit>, CompilerError> {
1582 if let ReactiveValue::SequenceExpression { instructions, .. } = init {
1583 let block_items: Vec<ReactiveStatement> = instructions
1584 .iter()
1585 .map(|i| ReactiveStatement::Instruction(i.clone()))
1586 .collect();
1587 let body = codegen_block(cx, &block_items)?.body;
1588 let mut declarators: Vec<VariableDeclarator> = Vec::new();
1589 let mut kind = VariableDeclarationKind::Const;
1590 for instr in body {
1591 // Check if this is an assignment that can be folded into the last declarator
1592 if let Statement::ExpressionStatement(ref expr_stmt) = instr {
1593 if let Expression::AssignmentExpression(ref assign) = *expr_stmt.expression {
1594 if matches!(assign.operator, AssignmentOperator::Assign) {
1595 if let PatternLike::Identifier(ref left_ident) = *assign.left {
1596 if let Some(top) = declarators.last_mut() {
1597 if let PatternLike::Identifier(ref top_ident) = top.id {
1598 if top_ident.name == left_ident.name && top.init.is_none() {
1599 top.init = Some(assign.right.clone());
1600 continue;
1601 }
1602 }
1603 }
1604 }
1605 }
1606 }
1607 }
1608
1609 if let Statement::VariableDeclaration(var_decl) = instr {
1610 match var_decl.kind {
1611 VariableDeclarationKind::Let | VariableDeclarationKind::Const => {}
1612 _ => {
1613 return Err(invariant_err(
1614 "Expected a let or const variable declaration",
1615 None,
1616 ));
1617 }
1618 }
1619 if matches!(var_decl.kind, VariableDeclarationKind::Let) {
1620 kind = VariableDeclarationKind::Let;
1621 }
1622 declarators.extend(var_decl.declarations);
1623 } else {
1624 let stmt_type = get_statement_type_name(&instr);
1625 let stmt_loc = get_statement_loc(&instr);
1626 let reason = "Expected a variable declaration".to_string();
1627 let mut err = CompilerError::new();
1628 err.push_diagnostic(
1629 CompilerDiagnostic::new(
1630 ErrorCategory::Invariant,
1631 reason.clone(),
1632 Some(format!("Got {}", stmt_type)),
1633 )
1634 .with_detail(CompilerDiagnosticDetail::Error {
1635 loc: stmt_loc,
1636 message: Some(reason),
1637 identifier_name: None,
1638 }),
1639 );
1640 return Err(err);
1641 }
1642 }
1643 if declarators.is_empty() {
1644 return Err(invariant_err(
1645 "Expected a variable declaration in for-init",
1646 None,
1647 ));
1648 }
1649 Ok(Some(ForInit::VariableDeclaration(VariableDeclaration {
1650 base: BaseNode::typed("VariableDeclaration"),
1651 declarations: declarators,
1652 kind,
1653 declare: None,
1654 })))
1655 } else {
1656 let expr = codegen_instruction_value_to_expression(cx, init)?;
1657 Ok(Some(ForInit::Expression(Box::new(expr))))
1658 }
1659 }
1660
1661 // =============================================================================
1662 // Instruction codegen
1663 // =============================================================================
1664
1665 /// How statement-position codegen disposes of an `UnsupportedNode`'s
1666 /// `original_node`. See [`codegen_unsupported_original_node`].
1667 enum UnsupportedOriginalNode {
1668 /// Emit this statement directly (early return).
1669 Statement(Statement),
1670 /// Flow through the general expression codegen path so the instruction's
1671 /// lvalue temporary is bound/registered.
1672 ExpressionCodegen,
1673 }
1674
1675 /// Discriminate an `UnsupportedNode`'s `original_node` by its `type` tag.
1676 ///
1677 /// Lowering serializes typed `Expression`/`Statement`/`PatternLike` bailout
1678 /// nodes, plus the raw nodes of `Statement::Unknown` (whose tags are
1679 /// unmodeled by construction). Dispatch accordingly:
1680 ///
1681 /// - Modeled statement tag: parse the typed statement and emit it directly.
1682 /// A parse failure here is a serialize/deserialize asymmetry, surfaced as
1683 /// an invariant rather than degraded.
1684 /// - Tag parseable as `Expression` or `PatternLike` (both enums are strict,
1685 /// no catch-all): expression codegen. Patterns (e.g. `ObjectPattern`
1686 /// destructuring targets) keep their existing placeholder fallback there.
1687 /// - Anything else is an unmodeled tag, producible only by the
1688 /// unknown-statement lowering bailout — i.e. it came from a statement
1689 /// position — so preserve it verbatim as `Statement::Unknown`, matching
1690 /// the TS codegen's `return node` for non-expressions.
1691 fn codegen_unsupported_original_node(
1692 node: &serde_json::Value,
1693 ) -> Result<UnsupportedOriginalNode, CompilerError> {
1694 let tag = node.get("type").and_then(serde_json::Value::as_str);
1695 if tag.is_some_and(is_known_statement_type) {
1696 let stmt: Statement = serde_json::from_value(node.clone()).map_err(|e| {
1697 invariant_err(
1698 &format!("Failed to deserialize original AST node: {}", e),
1699 None,
1700 )
1701 })?;
1702 return Ok(UnsupportedOriginalNode::Statement(stmt));
1703 }
1704 if serde_json::from_value::<Expression>(node.clone()).is_ok()
1705 || serde_json::from_value::<PatternLike>(node.clone()).is_ok()
1706 {
1707 return Ok(UnsupportedOriginalNode::ExpressionCodegen);
1708 }
1709 let unknown = UnknownStatement::from_raw(RawNode::from_value(node)).map_err(|e| {
1710 invariant_err(
1711 &format!("Failed to read unsupported original AST node: {}", e),
1712 None,
1713 )
1714 })?;
1715 Ok(UnsupportedOriginalNode::Statement(Statement::Unknown(
1716 unknown,
1717 )))
1718 }
1719
1720 fn codegen_instruction_nullable(
1721 cx: &mut Context,
1722 instr: &ReactiveInstruction,
1723 ) -> Result<Option<Statement>, CompilerError> {
1724 // Only check specific InstructionValue kinds for the base Instruction variant
1725 if let ReactiveValue::Instruction(ref value) = instr.value {
1726 match value {
1727 InstructionValue::StoreLocal { .. }
1728 | InstructionValue::StoreContext { .. }
1729 | InstructionValue::Destructure { .. }
1730 | InstructionValue::DeclareLocal { .. }
1731 | InstructionValue::DeclareContext { .. } => {
1732 return codegen_store_or_declare(cx, instr, value);
1733 }
1734 InstructionValue::StartMemoize { .. } | InstructionValue::FinishMemoize { .. } => {
1735 return Ok(None);
1736 }
1737 InstructionValue::Debugger { .. } => {
1738 return Ok(Some(Statement::DebuggerStatement(DebuggerStatement {
1739 base: base_node_with_loc("DebuggerStatement", instr.loc),
1740 })));
1741 }
1742 InstructionValue::UnsupportedNode {
1743 original_node: Some(node),
1744 ..
1745 } => {
1746 // Statement-vs-expression discrimination must be explicit by
1747 // `type` tag: `Statement`'s deserializer has a tolerant
1748 // `Statement::Unknown` catch-all, so "does it deserialize as
1749 // a Statement?" succeeds for ANY tagged object and would
1750 // emit expression nodes as raw statements, orphaning their
1751 // lvalue temporaries (the regression the explicit dispatch
1752 // below prevents; TS codegen's equivalent check is
1753 // `if (!t.isExpression(node)) return node; value = node`).
1754 match codegen_unsupported_original_node(node)? {
1755 UnsupportedOriginalNode::Statement(stmt) => return Ok(Some(stmt)),
1756 UnsupportedOriginalNode::ExpressionCodegen => {
1757 // Expression (or pattern) node — fall through to the
1758 // general codegen path which handles lvalue binding
1759 // and temporary registration.
1760 }
1761 }
1762 }
1763 InstructionValue::ObjectMethod { loc, .. } => {
1764 invariant(
1765 instr.lvalue.is_some(),
1766 "Expected object methods to have a temp lvalue",
1767 None,
1768 )?;
1769 let lvalue = instr.lvalue.as_ref().unwrap();
1770 cx.object_methods
1771 .insert(lvalue.identifier, (value.clone(), *loc));
1772 return Ok(None);
1773 }
1774 _ => {} // fall through to general codegen
1775 }
1776 }
1777 // General case: codegen the full ReactiveValue
1778 let expr_value = codegen_instruction_value(cx, &instr.value)?;
1779 let stmt = codegen_instruction(cx, instr, expr_value)?;
1780 if matches!(stmt, Statement::EmptyStatement(_)) {
1781 Ok(None)
1782 } else {
1783 Ok(Some(stmt))
1784 }
1785 }
1786
1787 fn codegen_store_or_declare(
1788 cx: &mut Context,
1789 instr: &ReactiveInstruction,
1790 value: &InstructionValue,
1791 ) -> Result<Option<Statement>, CompilerError> {
1792 match value {
1793 InstructionValue::StoreLocal {
1794 lvalue, value: val, ..
1795 } => {
1796 let mut kind = lvalue.kind;
1797 if cx.has_declared(lvalue.place.identifier) {
1798 kind = InstructionKind::Reassign;
1799 }
1800 let rhs = codegen_place_to_expression(cx, val)?;
1801 emit_store(cx, instr, kind, &LvalueRef::Place(&lvalue.place), Some(rhs))
1802 }
1803 InstructionValue::StoreContext {
1804 lvalue, value: val, ..
1805 } => {
1806 let rhs = codegen_place_to_expression(cx, val)?;
1807 emit_store(
1808 cx,
1809 instr,
1810 lvalue.kind,
1811 &LvalueRef::Place(&lvalue.place),
1812 Some(rhs),
1813 )
1814 }
1815 InstructionValue::DeclareLocal { lvalue, .. }
1816 | InstructionValue::DeclareContext { lvalue, .. } => {
1817 if cx.has_declared(lvalue.place.identifier) {
1818 return Ok(None);
1819 }
1820 emit_store(
1821 cx,
1822 instr,
1823 lvalue.kind,
1824 &LvalueRef::Place(&lvalue.place),
1825 None,
1826 )
1827 }
1828 InstructionValue::Destructure {
1829 lvalue, value: val, ..
1830 } => {
1831 let kind = lvalue.kind;
1832 // Register temporaries for unnamed pattern operands
1833 for place in react_compiler_hir::visitors::each_pattern_operand(&lvalue.pattern) {
1834 let ident = &cx.env.identifiers[place.identifier.0 as usize];
1835 let declaration_id = ident.declaration_id;
1836 let is_unnamed = ident.name.is_none();
1837 if kind != InstructionKind::Reassign && is_unnamed {
1838 cx.temp.set(declaration_id, None);
1839 }
1840 }
1841 let rhs = codegen_place_to_expression(cx, val)?;
1842 emit_store(
1843 cx,
1844 instr,
1845 kind,
1846 &LvalueRef::Pattern(&lvalue.pattern),
1847 Some(rhs),
1848 )
1849 }
1850 _ => unreachable!(),
1851 }
1852 }
1853
1854 fn emit_store(
1855 cx: &mut Context,
1856 instr: &ReactiveInstruction,
1857 kind: InstructionKind,
1858 lvalue: &LvalueRef,
1859 value: Option<Expression>,
1860 ) -> Result<Option<Statement>, CompilerError> {
1861 match kind {
1862 InstructionKind::Const => {
1863 // Invariant: Const declarations cannot also have an outer lvalue
1864 // (i.e., cannot be referenced as an expression)
1865 if instr.lvalue.is_some() {
1866 return Err(invariant_err_with_detail_message(
1867 "Const declaration cannot be referenced as an expression",
1868 "this is Const",
1869 instr.loc,
1870 ));
1871 }
1872 let lval = codegen_lvalue(cx, lvalue)?;
1873 Ok(Some(Statement::VariableDeclaration(VariableDeclaration {
1874 base: base_node_with_loc("VariableDeclaration", instr.loc),
1875 declarations: vec![make_var_declarator(lval, value)],
1876 kind: VariableDeclarationKind::Const,
1877 declare: None,
1878 })))
1879 }
1880 InstructionKind::Function => {
1881 let lval = codegen_lvalue(cx, lvalue)?;
1882 let PatternLike::Identifier(fn_id) = lval else {
1883 return Err(invariant_err(
1884 "Expected an identifier as function declaration lvalue",
1885 None,
1886 ));
1887 };
1888 let Some(rhs) = value else {
1889 return Err(invariant_err(
1890 "Expected a function value for function declaration",
1891 None,
1892 ));
1893 };
1894 match rhs {
1895 Expression::FunctionExpression(func_expr) => {
1896 Ok(Some(Statement::FunctionDeclaration(FunctionDeclaration {
1897 base: base_node_with_loc("FunctionDeclaration", instr.loc),
1898 id: Some(fn_id),
1899 params: func_expr.params,
1900 body: func_expr.body,
1901 generator: func_expr.generator,
1902 is_async: func_expr.is_async,
1903 declare: None,
1904 return_type: None,
1905 type_parameters: None,
1906 predicate: None,
1907 component_declaration: false,
1908 hook_declaration: false,
1909 })))
1910 }
1911 _ => Err(invariant_err(
1912 "Expected a function expression for function declaration",
1913 None,
1914 )),
1915 }
1916 }
1917 InstructionKind::Let => {
1918 // Invariant: Let declarations cannot also have an outer lvalue
1919 if instr.lvalue.is_some() {
1920 return Err(invariant_err_with_detail_message(
1921 "Const declaration cannot be referenced as an expression",
1922 "this is Let",
1923 instr.loc,
1924 ));
1925 }
1926 let lval = codegen_lvalue(cx, lvalue)?;
1927 Ok(Some(Statement::VariableDeclaration(VariableDeclaration {
1928 base: base_node_with_loc("VariableDeclaration", instr.loc),
1929 declarations: vec![make_var_declarator(lval, value)],
1930 kind: VariableDeclarationKind::Let,
1931 declare: None,
1932 })))
1933 }
1934 InstructionKind::Reassign => {
1935 let Some(rhs) = value else {
1936 return Err(invariant_err("Expected a value for reassignment", None));
1937 };
1938 let lval = codegen_lvalue(cx, lvalue)?;
1939 let expr = Expression::AssignmentExpression(ast_expr::AssignmentExpression {
1940 base: BaseNode::typed("AssignmentExpression"),
1941 operator: AssignmentOperator::Assign,
1942 left: Box::new(lval),
1943 right: Box::new(rhs),
1944 });
1945 if let Some(ref lvalue_place) = instr.lvalue {
1946 let is_store_context = matches!(
1947 &instr.value,
1948 ReactiveValue::Instruction(InstructionValue::StoreContext { .. })
1949 );
1950 if !is_store_context {
1951 let declaration_id =
1952 cx.env.identifiers[lvalue_place.identifier.0 as usize].declaration_id;
1953 cx.temp
1954 .set(declaration_id, Some(ExpressionOrJsxText::Expression(expr)));
1955 return Ok(None);
1956 } else {
1957 let stmt =
1958 codegen_instruction(cx, instr, ExpressionOrJsxText::Expression(expr))?;
1959 if matches!(stmt, Statement::EmptyStatement(_)) {
1960 return Ok(None);
1961 }
1962 return Ok(Some(stmt));
1963 }
1964 }
1965 Ok(Some(Statement::ExpressionStatement(ExpressionStatement {
1966 base: base_node_with_loc("ExpressionStatement", instr.loc),
1967 expression: Box::new(expr),
1968 })))
1969 }
1970 InstructionKind::Catch => Ok(Some(Statement::EmptyStatement(EmptyStatement {
1971 base: BaseNode::typed("EmptyStatement"),
1972 }))),
1973 InstructionKind::HoistedLet
1974 | InstructionKind::HoistedConst
1975 | InstructionKind::HoistedFunction => Err(invariant_err(
1976 &format!(
1977 "Expected {:?} to have been pruned in PruneHoistedContexts",
1978 kind
1979 ),
1980 None,
1981 )),
1982 }
1983 }
1984
1985 fn codegen_instruction(
1986 cx: &mut Context,
1987 instr: &ReactiveInstruction,
1988 value: ExpressionOrJsxText,
1989 ) -> Result<Statement, CompilerError> {
1990 let Some(ref lvalue) = instr.lvalue else {
1991 let expr = convert_value_to_expression(value);
1992 return Ok(Statement::ExpressionStatement(ExpressionStatement {
1993 base: base_node_with_loc("ExpressionStatement", instr.loc),
1994 expression: Box::new(expr),
1995 }));
1996 };
1997 let ident = &cx.env.identifiers[lvalue.identifier.0 as usize];
1998 let declaration_id = ident.declaration_id;
1999 if ident.name.is_none() {
2000 // temporary
2001 cx.temp.set(declaration_id, Some(value));
2002 return Ok(Statement::EmptyStatement(EmptyStatement {
2003 base: BaseNode::typed("EmptyStatement"),
2004 }));
2005 }
2006 let expr_value = convert_value_to_expression(value);
2007 if cx.has_declared(lvalue.identifier) {
2008 Ok(Statement::ExpressionStatement(ExpressionStatement {
2009 base: base_node_with_loc("ExpressionStatement", instr.loc),
2010 expression: Box::new(Expression::AssignmentExpression(
2011 ast_expr::AssignmentExpression {
2012 base: BaseNode::typed("AssignmentExpression"),
2013 operator: AssignmentOperator::Assign,
2014 left: Box::new(PatternLike::Identifier(convert_identifier(
2015 lvalue.identifier,
2016 cx.env,
2017 )?)),
2018 right: Box::new(expr_value),
2019 },
2020 )),
2021 }))
2022 } else {
2023 Ok(Statement::VariableDeclaration(VariableDeclaration {
2024 base: base_node_with_loc("VariableDeclaration", instr.loc),
2025 declarations: vec![make_var_declarator(
2026 PatternLike::Identifier(convert_identifier(lvalue.identifier, cx.env)?),
2027 Some(expr_value),
2028 )],
2029 kind: VariableDeclarationKind::Const,
2030 declare: None,
2031 }))
2032 }
2033 }
2034
2035 // =============================================================================
2036 // Instruction value codegen
2037 // =============================================================================
2038
2039 fn codegen_instruction_value_to_expression(
2040 cx: &mut Context,
2041 instr_value: &ReactiveValue,
2042 ) -> Result<Expression, CompilerError> {
2043 let value = codegen_instruction_value(cx, instr_value)?;
2044 Ok(convert_value_to_expression(value))
2045 }
2046
2047 fn codegen_instruction_value(
2048 cx: &mut Context,
2049 instr_value: &ReactiveValue,
2050 ) -> Result<ExpressionOrJsxText, CompilerError> {
2051 match instr_value {
2052 ReactiveValue::Instruction(iv) => {
2053 let mut result = codegen_base_instruction_value(cx, iv)?;
2054 // Propagate instrValue.loc to the generated expression, matching TS:
2055 // if (instrValue.loc != null && instrValue.loc != GeneratedSource) {
2056 // value.loc = instrValue.loc;
2057 // }
2058 if let Some(loc) = iv.loc() {
2059 apply_loc_to_value(&mut result, *loc);
2060 }
2061 Ok(result)
2062 }
2063 ReactiveValue::LogicalExpression {
2064 operator,
2065 left,
2066 right,
2067 ..
2068 } => {
2069 let left_expr = codegen_instruction_value_to_expression(cx, left)?;
2070 let right_expr = codegen_instruction_value_to_expression(cx, right)?;
2071 Ok(ExpressionOrJsxText::Expression(
2072 Expression::LogicalExpression(ast_expr::LogicalExpression {
2073 base: BaseNode::typed("LogicalExpression"),
2074 operator: convert_logical_operator(operator),
2075 left: Box::new(left_expr),
2076 right: Box::new(right_expr),
2077 }),
2078 ))
2079 }
2080 ReactiveValue::ConditionalExpression {
2081 test,
2082 consequent,
2083 alternate,
2084 ..
2085 } => {
2086 let test_expr = codegen_instruction_value_to_expression(cx, test)?;
2087 let cons_expr = codegen_instruction_value_to_expression(cx, consequent)?;
2088 let alt_expr = codegen_instruction_value_to_expression(cx, alternate)?;
2089 Ok(ExpressionOrJsxText::Expression(
2090 Expression::ConditionalExpression(ast_expr::ConditionalExpression {
2091 base: BaseNode::typed("ConditionalExpression"),
2092 test: Box::new(test_expr),
2093 consequent: Box::new(cons_expr),
2094 alternate: Box::new(alt_expr),
2095 }),
2096 ))
2097 }
2098 ReactiveValue::SequenceExpression {
2099 instructions,
2100 value,
2101 ..
2102 } => {
2103 let block_items: Vec<ReactiveStatement> = instructions
2104 .iter()
2105 .map(|i| ReactiveStatement::Instruction(i.clone()))
2106 .collect();
2107 let body = codegen_block_no_reset(cx, &block_items)?.body;
2108 let mut expressions: Vec<Expression> = Vec::new();
2109 for stmt in body {
2110 match stmt {
2111 Statement::ExpressionStatement(es) => {
2112 expressions.push(*es.expression);
2113 }
2114 Statement::VariableDeclaration(ref var_decl) => {
2115 let _declarator = &var_decl.declarations[0];
2116 cx.record_error(CompilerErrorDetail {
2117 category: ErrorCategory::Todo,
2118 reason: format!(
2119 "(CodegenReactiveFunction::codegenInstructionValue) Cannot declare variables in a value block"
2120 ),
2121 description: None,
2122 loc: None,
2123 suggestions: None,
2124 })?;
2125 expressions.push(Expression::StringLiteral(StringLiteral {
2126 base: BaseNode::typed("StringLiteral"),
2127 value: format!("TODO handle declaration").into(),
2128 }));
2129 }
2130 _ => {
2131 cx.record_error(CompilerErrorDetail {
2132 category: ErrorCategory::Todo,
2133 reason: format!(
2134 "(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of statement to expression"
2135 ),
2136 description: None,
2137 loc: None,
2138 suggestions: None,
2139 })?;
2140 expressions.push(Expression::StringLiteral(StringLiteral {
2141 base: BaseNode::typed("StringLiteral"),
2142 value: format!("TODO handle statement").into(),
2143 }));
2144 }
2145 }
2146 }
2147 let final_expr = codegen_instruction_value_to_expression(cx, value)?;
2148 if expressions.is_empty() {
2149 Ok(ExpressionOrJsxText::Expression(final_expr))
2150 } else {
2151 expressions.push(final_expr);
2152 Ok(ExpressionOrJsxText::Expression(
2153 Expression::SequenceExpression(ast_expr::SequenceExpression {
2154 base: BaseNode::typed("SequenceExpression"),
2155 expressions,
2156 }),
2157 ))
2158 }
2159 }
2160 ReactiveValue::OptionalExpression {
2161 value, optional, ..
2162 } => {
2163 let opt_value = codegen_instruction_value_to_expression(cx, value)?;
2164 match opt_value {
2165 Expression::OptionalCallExpression(oce) => Ok(ExpressionOrJsxText::Expression(
2166 Expression::OptionalCallExpression(ast_expr::OptionalCallExpression {
2167 base: BaseNode::typed("OptionalCallExpression"),
2168 callee: oce.callee,
2169 arguments: oce.arguments,
2170 optional: *optional,
2171 type_parameters: oce.type_parameters,
2172 type_arguments: oce.type_arguments,
2173 }),
2174 )),
2175 Expression::CallExpression(ce) => Ok(ExpressionOrJsxText::Expression(
2176 Expression::OptionalCallExpression(ast_expr::OptionalCallExpression {
2177 base: BaseNode::typed("OptionalCallExpression"),
2178 callee: ce.callee,
2179 arguments: ce.arguments,
2180 optional: *optional,
2181 type_parameters: None,
2182 type_arguments: None,
2183 }),
2184 )),
2185 Expression::OptionalMemberExpression(ome) => Ok(ExpressionOrJsxText::Expression(
2186 Expression::OptionalMemberExpression(ast_expr::OptionalMemberExpression {
2187 base: BaseNode::typed("OptionalMemberExpression"),
2188 object: ome.object,
2189 property: ome.property,
2190 computed: ome.computed,
2191 optional: *optional,
2192 }),
2193 )),
2194 Expression::MemberExpression(me) => Ok(ExpressionOrJsxText::Expression(
2195 Expression::OptionalMemberExpression(ast_expr::OptionalMemberExpression {
2196 base: BaseNode::typed("OptionalMemberExpression"),
2197 object: me.object,
2198 property: me.property,
2199 computed: me.computed,
2200 optional: *optional,
2201 }),
2202 )),
2203 other => Err(invariant_err(
2204 &format!(
2205 "Expected optional value to resolve to call or member expression, got {:?}",
2206 std::mem::discriminant(&other)
2207 ),
2208 None,
2209 )),
2210 }
2211 }
2212 }
2213 }
2214
2215 fn codegen_base_instruction_value(
2216 cx: &mut Context,
2217 iv: &InstructionValue,
2218 ) -> Result<ExpressionOrJsxText, CompilerError> {
2219 match iv {
2220 InstructionValue::Primitive { value, loc } => Ok(ExpressionOrJsxText::Expression(
2221 codegen_primitive_value(value, *loc),
2222 )),
2223 InstructionValue::BinaryExpression {
2224 operator,
2225 left,
2226 right,
2227 ..
2228 } => {
2229 let left_expr = codegen_place_to_expression(cx, left)?;
2230 let right_expr = codegen_place_to_expression(cx, right)?;
2231 Ok(ExpressionOrJsxText::Expression(
2232 Expression::BinaryExpression(ast_expr::BinaryExpression {
2233 base: BaseNode::typed("BinaryExpression"),
2234 operator: convert_binary_operator(operator),
2235 left: Box::new(left_expr),
2236 right: Box::new(right_expr),
2237 }),
2238 ))
2239 }
2240 InstructionValue::UnaryExpression {
2241 operator, value, ..
2242 } => {
2243 let arg = codegen_place_to_expression(cx, value)?;
2244 Ok(ExpressionOrJsxText::Expression(
2245 Expression::UnaryExpression(ast_expr::UnaryExpression {
2246 base: BaseNode::typed("UnaryExpression"),
2247 operator: convert_unary_operator(operator),
2248 prefix: true,
2249 argument: Box::new(arg),
2250 }),
2251 ))
2252 }
2253 InstructionValue::LoadLocal { place, .. } | InstructionValue::LoadContext { place, .. } => {
2254 let expr = codegen_place_to_expression(cx, place)?;
2255 Ok(ExpressionOrJsxText::Expression(expr))
2256 }
2257 InstructionValue::LoadGlobal { binding, .. } => Ok(ExpressionOrJsxText::Expression(
2258 Expression::Identifier(make_identifier(binding.name())),
2259 )),
2260 InstructionValue::CallExpression {
2261 callee,
2262 args,
2263 loc: _,
2264 } => {
2265 let callee_expr = codegen_place_to_expression(cx, callee)?;
2266 let arguments = args
2267 .iter()
2268 .map(|arg| codegen_argument(cx, arg))
2269 .collect::<Result<_, _>>()?;
2270 let call_expr = Expression::CallExpression(ast_expr::CallExpression {
2271 base: BaseNode::typed("CallExpression"),
2272 callee: Box::new(callee_expr),
2273 arguments,
2274 type_parameters: None,
2275 type_arguments: None,
2276 optional: None,
2277 });
2278 // enableEmitHookGuards: wrap hook calls in try/finally IIFE
2279 let result = maybe_wrap_hook_call(cx, call_expr, callee.identifier);
2280 Ok(ExpressionOrJsxText::Expression(result))
2281 }
2282 InstructionValue::MethodCall {
2283 receiver: _,
2284 property,
2285 args,
2286 loc: _,
2287 } => {
2288 let member_expr = codegen_place_to_expression(cx, property)?;
2289 // Invariant: MethodCall::property must resolve to a MemberExpression
2290 if !matches!(
2291 member_expr,
2292 Expression::MemberExpression(_) | Expression::OptionalMemberExpression(_)
2293 ) {
2294 let expr_type = match &member_expr {
2295 Expression::Identifier(_) => "Identifier",
2296 _ => "unknown",
2297 };
2298 {
2299 let msg = format!("Got: '{}'", expr_type);
2300 let mut err = CompilerError::new();
2301 err.push_diagnostic(
2302 CompilerDiagnostic::new(
2303 ErrorCategory::Invariant,
2304 "[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression",
2305 None,
2306 )
2307 .with_detail(CompilerDiagnosticDetail::Error {
2308 loc: property.loc,
2309 message: Some(msg),
2310 identifier_name: None,
2311 }),
2312 );
2313 return Err(err);
2314 }
2315 }
2316 let arguments = args
2317 .iter()
2318 .map(|arg| codegen_argument(cx, arg))
2319 .collect::<Result<_, _>>()?;
2320 let call_expr = Expression::CallExpression(ast_expr::CallExpression {
2321 base: BaseNode::typed("CallExpression"),
2322 callee: Box::new(member_expr),
2323 arguments,
2324 type_parameters: None,
2325 type_arguments: None,
2326 optional: None,
2327 });
2328 // enableEmitHookGuards: wrap hook method calls in try/finally IIFE
2329 let result = maybe_wrap_hook_call(cx, call_expr, property.identifier);
2330 Ok(ExpressionOrJsxText::Expression(result))
2331 }
2332 InstructionValue::NewExpression { callee, args, .. } => {
2333 let callee_expr = codegen_place_to_expression(cx, callee)?;
2334 let arguments = args
2335 .iter()
2336 .map(|arg| codegen_argument(cx, arg))
2337 .collect::<Result<_, _>>()?;
2338 Ok(ExpressionOrJsxText::Expression(Expression::NewExpression(
2339 ast_expr::NewExpression {
2340 base: BaseNode::typed("NewExpression"),
2341 callee: Box::new(callee_expr),
2342 arguments,
2343 type_parameters: None,
2344 type_arguments: None,
2345 },
2346 )))
2347 }
2348 InstructionValue::ArrayExpression { elements, .. } => {
2349 let elems: Vec<Option<Expression>> = elements
2350 .iter()
2351 .map(|el| match el {
2352 ArrayElement::Place(place) => Ok(Some(codegen_place_to_expression(cx, place)?)),
2353 ArrayElement::Spread(spread) => {
2354 let arg = codegen_place_to_expression(cx, &spread.place)?;
2355 Ok(Some(Expression::SpreadElement(ast_expr::SpreadElement {
2356 base: BaseNode::typed("SpreadElement"),
2357 argument: Box::new(arg),
2358 })))
2359 }
2360 ArrayElement::Hole => Ok(None),
2361 })
2362 .collect::<Result<_, CompilerError>>()?;
2363 Ok(ExpressionOrJsxText::Expression(
2364 Expression::ArrayExpression(ast_expr::ArrayExpression {
2365 base: BaseNode::typed("ArrayExpression"),
2366 elements: elems,
2367 }),
2368 ))
2369 }
2370 InstructionValue::ObjectExpression { properties, .. } => {
2371 codegen_object_expression(cx, properties)
2372 }
2373 InstructionValue::PropertyLoad {
2374 object, property, ..
2375 } => {
2376 let obj = codegen_place_to_expression(cx, object)?;
2377 let (prop, computed) = property_literal_to_expression(property);
2378 Ok(ExpressionOrJsxText::Expression(
2379 Expression::MemberExpression(ast_expr::MemberExpression {
2380 base: BaseNode::typed("MemberExpression"),
2381 object: Box::new(obj),
2382 property: Box::new(prop),
2383 computed,
2384 }),
2385 ))
2386 }
2387 InstructionValue::PropertyStore {
2388 object,
2389 property,
2390 value,
2391 ..
2392 } => {
2393 let obj = codegen_place_to_expression(cx, object)?;
2394 let (prop, computed) = property_literal_to_expression(property);
2395 let val = codegen_place_to_expression(cx, value)?;
2396 Ok(ExpressionOrJsxText::Expression(
2397 Expression::AssignmentExpression(ast_expr::AssignmentExpression {
2398 base: BaseNode::typed("AssignmentExpression"),
2399 operator: AssignmentOperator::Assign,
2400 left: Box::new(PatternLike::MemberExpression(ast_expr::MemberExpression {
2401 base: BaseNode::typed("MemberExpression"),
2402 object: Box::new(obj),
2403 property: Box::new(prop),
2404 computed,
2405 })),
2406 right: Box::new(val),
2407 }),
2408 ))
2409 }
2410 InstructionValue::PropertyDelete {
2411 object, property, ..
2412 } => {
2413 let obj = codegen_place_to_expression(cx, object)?;
2414 let (prop, computed) = property_literal_to_expression(property);
2415 Ok(ExpressionOrJsxText::Expression(
2416 Expression::UnaryExpression(ast_expr::UnaryExpression {
2417 base: BaseNode::typed("UnaryExpression"),
2418 operator: AstUnaryOperator::Delete,
2419 prefix: true,
2420 argument: Box::new(Expression::MemberExpression(ast_expr::MemberExpression {
2421 base: BaseNode::typed("MemberExpression"),
2422 object: Box::new(obj),
2423 property: Box::new(prop),
2424 computed,
2425 })),
2426 }),
2427 ))
2428 }
2429 InstructionValue::ComputedLoad {
2430 object, property, ..
2431 } => {
2432 let obj = codegen_place_to_expression(cx, object)?;
2433 let prop = codegen_place_to_expression(cx, property)?;
2434 Ok(ExpressionOrJsxText::Expression(
2435 Expression::MemberExpression(ast_expr::MemberExpression {
2436 base: BaseNode::typed("MemberExpression"),
2437 object: Box::new(obj),
2438 property: Box::new(prop),
2439 computed: true,
2440 }),
2441 ))
2442 }
2443 InstructionValue::ComputedStore {
2444 object,
2445 property,
2446 value,
2447 ..
2448 } => {
2449 let obj = codegen_place_to_expression(cx, object)?;
2450 let prop = codegen_place_to_expression(cx, property)?;
2451 let val = codegen_place_to_expression(cx, value)?;
2452 Ok(ExpressionOrJsxText::Expression(
2453 Expression::AssignmentExpression(ast_expr::AssignmentExpression {
2454 base: BaseNode::typed("AssignmentExpression"),
2455 operator: AssignmentOperator::Assign,
2456 left: Box::new(PatternLike::MemberExpression(ast_expr::MemberExpression {
2457 base: BaseNode::typed("MemberExpression"),
2458 object: Box::new(obj),
2459 property: Box::new(prop),
2460 computed: true,
2461 })),
2462 right: Box::new(val),
2463 }),
2464 ))
2465 }
2466 InstructionValue::ComputedDelete {
2467 object, property, ..
2468 } => {
2469 let obj = codegen_place_to_expression(cx, object)?;
2470 let prop = codegen_place_to_expression(cx, property)?;
2471 Ok(ExpressionOrJsxText::Expression(
2472 Expression::UnaryExpression(ast_expr::UnaryExpression {
2473 base: BaseNode::typed("UnaryExpression"),
2474 operator: AstUnaryOperator::Delete,
2475 prefix: true,
2476 argument: Box::new(Expression::MemberExpression(ast_expr::MemberExpression {
2477 base: BaseNode::typed("MemberExpression"),
2478 object: Box::new(obj),
2479 property: Box::new(prop),
2480 computed: true,
2481 })),
2482 }),
2483 ))
2484 }
2485 InstructionValue::RegExpLiteral { pattern, flags, .. } => Ok(
2486 ExpressionOrJsxText::Expression(Expression::RegExpLiteral(AstRegExpLiteral {
2487 base: BaseNode::typed("RegExpLiteral"),
2488 pattern: pattern.clone(),
2489 flags: flags.clone(),
2490 })),
2491 ),
2492 InstructionValue::MetaProperty { meta, property, .. } => Ok(
2493 ExpressionOrJsxText::Expression(Expression::MetaProperty(ast_expr::MetaProperty {
2494 base: BaseNode::typed("MetaProperty"),
2495 meta: make_identifier(meta),
2496 property: make_identifier(property),
2497 })),
2498 ),
2499 InstructionValue::Await { value, .. } => {
2500 let arg = codegen_place_to_expression(cx, value)?;
2501 Ok(ExpressionOrJsxText::Expression(
2502 Expression::AwaitExpression(ast_expr::AwaitExpression {
2503 base: BaseNode::typed("AwaitExpression"),
2504 argument: Box::new(arg),
2505 }),
2506 ))
2507 }
2508 InstructionValue::GetIterator { collection, .. } => {
2509 let expr = codegen_place_to_expression(cx, collection)?;
2510 Ok(ExpressionOrJsxText::Expression(expr))
2511 }
2512 InstructionValue::IteratorNext { iterator, .. } => {
2513 let expr = codegen_place_to_expression(cx, iterator)?;
2514 Ok(ExpressionOrJsxText::Expression(expr))
2515 }
2516 InstructionValue::NextPropertyOf { value, .. } => {
2517 let expr = codegen_place_to_expression(cx, value)?;
2518 Ok(ExpressionOrJsxText::Expression(expr))
2519 }
2520 InstructionValue::PostfixUpdate {
2521 operation, lvalue, ..
2522 } => {
2523 let arg = codegen_place_to_expression(cx, lvalue)?;
2524 Ok(ExpressionOrJsxText::Expression(
2525 Expression::UpdateExpression(ast_expr::UpdateExpression {
2526 base: BaseNode::typed("UpdateExpression"),
2527 operator: convert_update_operator(operation),
2528 argument: Box::new(arg),
2529 prefix: false,
2530 }),
2531 ))
2532 }
2533 InstructionValue::PrefixUpdate {
2534 operation, lvalue, ..
2535 } => {
2536 let arg = codegen_place_to_expression(cx, lvalue)?;
2537 Ok(ExpressionOrJsxText::Expression(
2538 Expression::UpdateExpression(ast_expr::UpdateExpression {
2539 base: BaseNode::typed("UpdateExpression"),
2540 operator: convert_update_operator(operation),
2541 argument: Box::new(arg),
2542 prefix: true,
2543 }),
2544 ))
2545 }
2546 InstructionValue::StoreLocal { lvalue, value, .. } => {
2547 invariant(
2548 lvalue.kind == InstructionKind::Reassign,
2549 "Unexpected StoreLocal in codegenInstructionValue",
2550 None,
2551 )?;
2552 let lval = codegen_lvalue(cx, &LvalueRef::Place(&lvalue.place))?;
2553 let rhs = codegen_place_to_expression(cx, value)?;
2554 Ok(ExpressionOrJsxText::Expression(
2555 Expression::AssignmentExpression(ast_expr::AssignmentExpression {
2556 base: BaseNode::typed("AssignmentExpression"),
2557 operator: AssignmentOperator::Assign,
2558 left: Box::new(lval),
2559 right: Box::new(rhs),
2560 }),
2561 ))
2562 }
2563 InstructionValue::StoreGlobal { name, value, .. } => {
2564 let rhs = codegen_place_to_expression(cx, value)?;
2565 Ok(ExpressionOrJsxText::Expression(
2566 Expression::AssignmentExpression(ast_expr::AssignmentExpression {
2567 base: BaseNode::typed("AssignmentExpression"),
2568 operator: AssignmentOperator::Assign,
2569 left: Box::new(PatternLike::Identifier(make_identifier(name))),
2570 right: Box::new(rhs),
2571 }),
2572 ))
2573 }
2574 InstructionValue::FunctionExpression {
2575 name,
2576 name_hint,
2577 lowered_func,
2578 expr_type,
2579 ..
2580 } => codegen_function_expression(cx, name, name_hint, lowered_func, expr_type),
2581 InstructionValue::TaggedTemplateExpression { tag, value, .. } => {
2582 let tag_expr = codegen_place_to_expression(cx, tag)?;
2583 Ok(ExpressionOrJsxText::Expression(
2584 Expression::TaggedTemplateExpression(ast_expr::TaggedTemplateExpression {
2585 base: BaseNode::typed("TaggedTemplateExpression"),
2586 tag: Box::new(tag_expr),
2587 quasi: ast_expr::TemplateLiteral {
2588 base: BaseNode::typed("TemplateLiteral"),
2589 quasis: vec![TemplateElement {
2590 base: BaseNode::typed("TemplateElement"),
2591 value: TemplateElementValue {
2592 raw: value.raw.clone(),
2593 cooked: value.cooked.clone(),
2594 },
2595 tail: true,
2596 }],
2597 expressions: Vec::new(),
2598 },
2599 type_parameters: None,
2600 }),
2601 ))
2602 }
2603 InstructionValue::TemplateLiteral {
2604 subexprs, quasis, ..
2605 } => {
2606 let exprs: Vec<Expression> = subexprs
2607 .iter()
2608 .map(|p| codegen_place_to_expression(cx, p))
2609 .collect::<Result<_, _>>()?;
2610 let template_elems: Vec<TemplateElement> = quasis
2611 .iter()
2612 .enumerate()
2613 .map(|(i, q)| TemplateElement {
2614 base: BaseNode::typed("TemplateElement"),
2615 value: TemplateElementValue {
2616 raw: q.raw.clone(),
2617 cooked: q.cooked.clone(),
2618 },
2619 tail: i == quasis.len() - 1,
2620 })
2621 .collect();
2622 Ok(ExpressionOrJsxText::Expression(
2623 Expression::TemplateLiteral(ast_expr::TemplateLiteral {
2624 base: BaseNode::typed("TemplateLiteral"),
2625 quasis: template_elems,
2626 expressions: exprs,
2627 }),
2628 ))
2629 }
2630 InstructionValue::TypeCastExpression {
2631 value,
2632 type_annotation_kind,
2633 type_annotation,
2634 ..
2635 } => {
2636 let expr = codegen_place_to_expression(cx, value)?;
2637 let wrapped = match (type_annotation_kind.as_deref(), type_annotation) {
2638 (Some("satisfies"), Some(ta)) => {
2639 let mut ta = ta.clone();
2640 apply_renames_to_json(&mut ta, &cx.env.renames, &cx.env.reference_node_ids);
2641 Expression::TSSatisfiesExpression(ast_expr::TSSatisfiesExpression {
2642 base: BaseNode::typed("TSSatisfiesExpression"),
2643 expression: Box::new(expr),
2644 type_annotation: RawNode::from_value(&ta),
2645 })
2646 }
2647 (Some("as"), Some(ta)) => {
2648 let mut ta = ta.clone();
2649 apply_renames_to_json(&mut ta, &cx.env.renames, &cx.env.reference_node_ids);
2650 Expression::TSAsExpression(ast_expr::TSAsExpression {
2651 base: BaseNode::typed("TSAsExpression"),
2652 expression: Box::new(expr),
2653 type_annotation: RawNode::from_value(&ta),
2654 })
2655 }
2656 (Some("cast"), Some(ta)) => {
2657 let mut ta = ta.clone();
2658 apply_renames_to_json(&mut ta, &cx.env.renames, &cx.env.reference_node_ids);
2659 Expression::TypeCastExpression(ast_expr::TypeCastExpression {
2660 base: BaseNode::typed("TypeCastExpression"),
2661 expression: Box::new(expr),
2662 type_annotation: RawNode::from_value(&ta),
2663 })
2664 }
2665 _ => expr,
2666 };
2667 Ok(ExpressionOrJsxText::Expression(wrapped))
2668 }
2669 InstructionValue::JSXText { value, loc } => Ok(ExpressionOrJsxText::JsxText(JSXText {
2670 base: base_node_with_loc("JSXText", *loc),
2671 value: value.clone(),
2672 })),
2673 InstructionValue::JsxExpression {
2674 tag,
2675 props,
2676 children,
2677 loc,
2678 opening_loc,
2679 closing_loc,
2680 } => codegen_jsx_expression(cx, tag, props, children, *loc, *opening_loc, *closing_loc),
2681 InstructionValue::JsxFragment { children, .. } => {
2682 let child_elems: Vec<JSXChild> = children
2683 .iter()
2684 .map(|child| codegen_jsx_element(cx, child))
2685 .collect::<Result<_, _>>()?;
2686 Ok(ExpressionOrJsxText::Expression(Expression::JSXFragment(
2687 JSXFragment {
2688 base: BaseNode::typed("JSXFragment"),
2689 opening_fragment: JSXOpeningFragment {
2690 base: BaseNode::typed("JSXOpeningFragment"),
2691 },
2692 closing_fragment: JSXClosingFragment {
2693 base: BaseNode::typed("JSXClosingFragment"),
2694 },
2695 children: child_elems,
2696 },
2697 )))
2698 }
2699 InstructionValue::UnsupportedNode {
2700 original_node,
2701 node_type,
2702 ..
2703 } => {
2704 // Try to deserialize the original AST node from JSON (mirrors statement-level handler)
2705 match original_node {
2706 Some(node) => {
2707 match serde_json::from_value::<Expression>(node.clone()) {
2708 Ok(expr) => Ok(ExpressionOrJsxText::Expression(expr)),
2709 Err(_) => {
2710 // Not a valid expression — fall back to placeholder
2711 Ok(ExpressionOrJsxText::Expression(Expression::Identifier(
2712 make_identifier(&format!(
2713 "__unsupported_{}",
2714 node_type.as_deref().unwrap_or("unknown")
2715 )),
2716 )))
2717 }
2718 }
2719 }
2720 None => {
2721 // No original node available — fall back to placeholder
2722 Ok(ExpressionOrJsxText::Expression(Expression::Identifier(
2723 make_identifier(&format!(
2724 "__unsupported_{}",
2725 node_type.as_deref().unwrap_or("unknown")
2726 )),
2727 )))
2728 }
2729 }
2730 }
2731 InstructionValue::StartMemoize { .. }
2732 | InstructionValue::FinishMemoize { .. }
2733 | InstructionValue::Debugger { .. }
2734 | InstructionValue::DeclareLocal { .. }
2735 | InstructionValue::DeclareContext { .. }
2736 | InstructionValue::Destructure { .. }
2737 | InstructionValue::ObjectMethod { .. }
2738 | InstructionValue::StoreContext { .. } => Err(invariant_err(
2739 &format!(
2740 "Unexpected {:?} in codegenInstructionValue",
2741 std::mem::discriminant(iv)
2742 ),
2743 None,
2744 )),
2745 }
2746 }
2747
2748 // =============================================================================
2749 // Function expression codegen
2750 // =============================================================================
2751
2752 fn codegen_function_expression(
2753 cx: &mut Context,
2754 name: &Option<String>,
2755 name_hint: &Option<String>,
2756 lowered_func: &react_compiler_hir::LoweredFunction,
2757 expr_type: &FunctionExpressionType,
2758 ) -> Result<ExpressionOrJsxText, CompilerError> {
2759 let func = &cx.env.functions[lowered_func.func.0 as usize];
2760 let reactive_fn = build_reactive_function(func, cx.env)?;
2761 let mut reactive_fn_mut = reactive_fn;
2762 prune_unused_labels(&mut reactive_fn_mut, cx.env)?;
2763 prune_unused_lvalues(&mut reactive_fn_mut, cx.env);
2764 prune_hoisted_contexts(&mut reactive_fn_mut, cx.env)?;
2765
2766 let mut inner_cx = Context::new(
2767 cx.env,
2768 reactive_fn_mut
2769 .id
2770 .as_deref()
2771 .unwrap_or("[[ anonymous ]]")
2772 .to_string(),
2773 cx.unique_identifiers.clone(),
2774 cx.fbt_operands.clone(),
2775 );
2776 // The inner function reads the enclosing temporaries but must not leak its
2777 // own back out. Lend the map to `inner_cx` and rewind its writes on the way
2778 // out, rather than deep-cloning every buffered expression tree. The map is
2779 // restored on the error path too, so `cx` is never left empty.
2780 inner_cx.temp = cx.temp.lend();
2781
2782 let fn_result = codegen_reactive_function(&mut inner_cx, &reactive_fn_mut);
2783
2784 cx.temp.reclaim(std::mem::take(&mut inner_cx.temp));
2785 let fn_result = fn_result?;
2786
2787 let value = match expr_type {
2788 FunctionExpressionType::ArrowFunctionExpression => {
2789 let mut body: ArrowFunctionBody =
2790 ArrowFunctionBody::BlockStatement(fn_result.body.clone());
2791 // Optimize single-return arrow functions
2792 if fn_result.body.body.len() == 1 && reactive_fn_mut.directives.is_empty() {
2793 if let Statement::ReturnStatement(ret) = &fn_result.body.body[0] {
2794 if let Some(ref arg) = ret.argument {
2795 body = ArrowFunctionBody::Expression(arg.clone());
2796 }
2797 }
2798 }
2799 let is_expression = matches!(body, ArrowFunctionBody::Expression(_));
2800 Expression::ArrowFunctionExpression(ast_expr::ArrowFunctionExpression {
2801 base: BaseNode::typed("ArrowFunctionExpression"),
2802 params: fn_result.params,
2803 body: Box::new(body),
2804 id: None,
2805 generator: false,
2806 is_async: fn_result.is_async,
2807 expression: Some(is_expression),
2808 return_type: None,
2809 type_parameters: None,
2810 predicate: None,
2811 })
2812 }
2813 _ => Expression::FunctionExpression(ast_expr::FunctionExpression {
2814 base: BaseNode::typed("FunctionExpression"),
2815 params: fn_result.params,
2816 body: fn_result.body,
2817 id: name.as_ref().map(|n| make_identifier(n)),
2818 generator: fn_result.generator,
2819 is_async: fn_result.is_async,
2820 return_type: None,
2821 type_parameters: None,
2822 predicate: None,
2823 }),
2824 };
2825
2826 // Handle enableNameAnonymousFunctions
2827 if cx.env.config.enable_name_anonymous_functions && name.is_none() && name_hint.is_some() {
2828 let hint = name_hint.as_ref().unwrap();
2829 let wrapped = Expression::MemberExpression(ast_expr::MemberExpression {
2830 base: BaseNode::typed("MemberExpression"),
2831 object: Box::new(Expression::ObjectExpression(ast_expr::ObjectExpression {
2832 base: BaseNode::typed("ObjectExpression"),
2833 properties: vec![ast_expr::ObjectExpressionProperty::ObjectProperty(
2834 ast_expr::ObjectProperty {
2835 base: BaseNode::typed("ObjectProperty"),
2836 key: Box::new(Expression::StringLiteral(StringLiteral {
2837 base: BaseNode::typed("StringLiteral"),
2838 value: hint.clone().into(),
2839 })),
2840 value: Box::new(value),
2841 computed: false,
2842 shorthand: false,
2843 decorators: None,
2844 method: None,
2845 },
2846 )],
2847 })),
2848 property: Box::new(Expression::StringLiteral(StringLiteral {
2849 base: BaseNode::typed("StringLiteral"),
2850 value: hint.clone().into(),
2851 })),
2852 computed: true,
2853 });
2854 return Ok(ExpressionOrJsxText::Expression(wrapped));
2855 }
2856
2857 Ok(ExpressionOrJsxText::Expression(value))
2858 }
2859
2860 // =============================================================================
2861 // Object expression codegen
2862 // =============================================================================
2863
2864 fn codegen_object_expression(
2865 cx: &mut Context,
2866 properties: &[ObjectPropertyOrSpread],
2867 ) -> Result<ExpressionOrJsxText, CompilerError> {
2868 let mut ast_properties: Vec<ast_expr::ObjectExpressionProperty> = Vec::new();
2869 for prop in properties {
2870 match prop {
2871 ObjectPropertyOrSpread::Property(obj_prop) => {
2872 let key = codegen_object_property_key(cx, &obj_prop.key)?;
2873 match obj_prop.property_type {
2874 ObjectPropertyType::Property => {
2875 let value = codegen_place_to_expression(cx, &obj_prop.place)?;
2876 let is_shorthand = matches!(&key, Expression::Identifier(k_id)
2877 if matches!(&value, Expression::Identifier(v_id) if v_id.name == k_id.name));
2878 ast_properties.push(ast_expr::ObjectExpressionProperty::ObjectProperty(
2879 ast_expr::ObjectProperty {
2880 base: BaseNode::typed("ObjectProperty"),
2881 key: Box::new(key),
2882 value: Box::new(value),
2883 computed: matches!(
2884 obj_prop.key,
2885 ObjectPropertyKey::Computed { .. }
2886 ),
2887 shorthand: is_shorthand,
2888 decorators: None,
2889 method: None,
2890 },
2891 ));
2892 }
2893 ObjectPropertyType::Method => {
2894 let method_data = cx.object_methods.get(&obj_prop.place.identifier);
2895 let method_data = method_data.cloned();
2896 let Some((InstructionValue::ObjectMethod { lowered_func, .. }, _)) =
2897 method_data
2898 else {
2899 return Err(invariant_err("Expected ObjectMethod instruction", None));
2900 };
2901
2902 let func = &cx.env.functions[lowered_func.func.0 as usize];
2903 let reactive_fn = build_reactive_function(func, cx.env)?;
2904 let mut reactive_fn_mut = reactive_fn;
2905 prune_unused_labels(&mut reactive_fn_mut, cx.env)?;
2906 prune_unused_lvalues(&mut reactive_fn_mut, cx.env);
2907
2908 let mut inner_cx = Context::new(
2909 cx.env,
2910 reactive_fn_mut
2911 .id
2912 .as_deref()
2913 .unwrap_or("[[ anonymous ]]")
2914 .to_string(),
2915 cx.unique_identifiers.clone(),
2916 cx.fbt_operands.clone(),
2917 );
2918 inner_cx.temp = cx.temp.lend();
2919
2920 let fn_result = codegen_reactive_function(&mut inner_cx, &reactive_fn_mut);
2921
2922 cx.temp.reclaim(std::mem::take(&mut inner_cx.temp));
2923 let fn_result = fn_result?;
2924
2925 ast_properties.push(ast_expr::ObjectExpressionProperty::ObjectMethod(
2926 ast_expr::ObjectMethod {
2927 base: BaseNode::typed("ObjectMethod"),
2928 method: true,
2929 kind: ast_expr::ObjectMethodKind::Method,
2930 key: Box::new(key),
2931 params: fn_result.params,
2932 body: fn_result.body,
2933 computed: matches!(
2934 obj_prop.key,
2935 ObjectPropertyKey::Computed { .. }
2936 ),
2937 id: None,
2938 generator: fn_result.generator,
2939 is_async: fn_result.is_async,
2940 decorators: None,
2941 return_type: None,
2942 type_parameters: None,
2943 predicate: None,
2944 },
2945 ));
2946 }
2947 }
2948 }
2949 ObjectPropertyOrSpread::Spread(spread) => {
2950 let arg = codegen_place_to_expression(cx, &spread.place)?;
2951 ast_properties.push(ast_expr::ObjectExpressionProperty::SpreadElement(
2952 ast_expr::SpreadElement {
2953 base: BaseNode::typed("SpreadElement"),
2954 argument: Box::new(arg),
2955 },
2956 ));
2957 }
2958 }
2959 }
2960 Ok(ExpressionOrJsxText::Expression(
2961 Expression::ObjectExpression(ast_expr::ObjectExpression {
2962 base: BaseNode::typed("ObjectExpression"),
2963 properties: ast_properties,
2964 }),
2965 ))
2966 }
2967
2968 fn codegen_object_property_key(
2969 cx: &mut Context,
2970 key: &ObjectPropertyKey,
2971 ) -> Result<Expression, CompilerError> {
2972 match key {
2973 ObjectPropertyKey::String { name } => Ok(Expression::StringLiteral(StringLiteral {
2974 base: BaseNode::typed("StringLiteral"),
2975 value: name.clone().into(),
2976 })),
2977 ObjectPropertyKey::Identifier { name } => Ok(Expression::Identifier(make_identifier(name))),
2978 ObjectPropertyKey::Computed { name } => {
2979 let expr = codegen_place(cx, name)?;
2980 match expr {
2981 ExpressionOrJsxText::Expression(e) => Ok(e),
2982 ExpressionOrJsxText::JsxText(_) => Err(invariant_err(
2983 "Expected object property key to be an expression",
2984 None,
2985 )),
2986 }
2987 }
2988 ObjectPropertyKey::Number { name } => Ok(Expression::NumericLiteral(NumericLiteral {
2989 base: BaseNode::typed("NumericLiteral"),
2990 value: name.value(),
2991 extra: None,
2992 })),
2993 }
2994 }
2995
2996 // =============================================================================
2997 // JSX codegen
2998 // =============================================================================
2999
3000 fn codegen_jsx_expression(
3001 cx: &mut Context,
3002 tag: &JsxTag,
3003 props: &[JsxAttribute],
3004 children: &Option<Vec<Place>>,
3005 loc: Option<DiagSourceLocation>,
3006 opening_loc: Option<DiagSourceLocation>,
3007 closing_loc: Option<DiagSourceLocation>,
3008 ) -> Result<ExpressionOrJsxText, CompilerError> {
3009 let mut attributes: Vec<JSXAttributeItem> = Vec::new();
3010 for attr in props {
3011 attributes.push(codegen_jsx_attribute(cx, attr)?);
3012 }
3013
3014 let (tag_value, _tag_loc) = match tag {
3015 JsxTag::Place(place) => (codegen_place_to_expression(cx, place)?, place.loc),
3016 JsxTag::Builtin(builtin) => (
3017 Expression::StringLiteral(StringLiteral {
3018 base: BaseNode::typed("StringLiteral"),
3019 value: builtin.name.clone().into(),
3020 }),
3021 None,
3022 ),
3023 };
3024
3025 let jsx_tag = expression_to_jsx_tag(&tag_value, jsx_tag_loc(tag))?;
3026
3027 let is_fbt_tag = if let Expression::StringLiteral(ref s) = tag_value {
3028 s.value
3029 .as_str()
3030 .is_some_and(|v| SINGLE_CHILD_FBT_TAGS.contains(&v))
3031 } else {
3032 false
3033 };
3034
3035 let child_nodes = if is_fbt_tag {
3036 children
3037 .as_ref()
3038 .map(|c| {
3039 c.iter()
3040 .map(|child| codegen_jsx_fbt_child_element(cx, child))
3041 .collect::<Result<Vec<_>, _>>()
3042 })
3043 .transpose()?
3044 .unwrap_or_default()
3045 } else {
3046 children
3047 .as_ref()
3048 .map(|c| {
3049 c.iter()
3050 .map(|child| codegen_jsx_element(cx, child))
3051 .collect::<Result<Vec<_>, _>>()
3052 })
3053 .transpose()?
3054 .unwrap_or_default()
3055 };
3056
3057 let is_self_closing = children.is_none();
3058
3059 let element = JSXElement {
3060 base: base_node_with_loc("JSXElement", loc),
3061 opening_element: JSXOpeningElement {
3062 base: base_node_with_loc("JSXOpeningElement", opening_loc),
3063 name: jsx_tag.clone(),
3064 attributes,
3065 self_closing: is_self_closing,
3066 type_parameters: None,
3067 },
3068 closing_element: if !is_self_closing {
3069 Some(JSXClosingElement {
3070 base: base_node_with_loc("JSXClosingElement", closing_loc),
3071 name: jsx_tag,
3072 })
3073 } else {
3074 None
3075 },
3076 children: child_nodes,
3077 self_closing: if is_self_closing { Some(true) } else { None },
3078 };
3079
3080 Ok(ExpressionOrJsxText::Expression(Expression::JSXElement(
3081 Box::new(element),
3082 )))
3083 }
3084
3085 const JSX_TEXT_CHILD_REQUIRES_EXPR_CONTAINER_PATTERN: &[char] = &['<', '>', '&', '{', '}'];
3086 const STRING_REQUIRES_EXPR_CONTAINER_CHARS: &str = "\"\\";
3087
3088 fn string_requires_expr_container(s: &str) -> bool {
3089 for c in s.chars() {
3090 if STRING_REQUIRES_EXPR_CONTAINER_CHARS.contains(c) {
3091 return true;
3092 }
3093 // Check for control chars and non-basic-latin
3094 let code = c as u32;
3095 if code <= 0x1F || code == 0x7F || (code >= 0x80 && code <= 0x9F) || (code >= 0xA0) {
3096 return true;
3097 }
3098 }
3099 false
3100 }
3101
3102 fn codegen_jsx_attribute(
3103 cx: &mut Context,
3104 attr: &JsxAttribute,
3105 ) -> Result<JSXAttributeItem, CompilerError> {
3106 match attr {
3107 JsxAttribute::Attribute { name, place } => {
3108 let prop_name = if name.contains(':') {
3109 let parts: Vec<&str> = name.splitn(2, ':').collect();
3110 JSXAttributeName::JSXNamespacedName(JSXNamespacedName {
3111 base: BaseNode::typed("JSXNamespacedName"),
3112 namespace: JSXIdentifier {
3113 base: BaseNode::typed("JSXIdentifier"),
3114 name: parts[0].to_string(),
3115 },
3116 name: JSXIdentifier {
3117 base: BaseNode::typed("JSXIdentifier"),
3118 name: parts[1].to_string(),
3119 },
3120 })
3121 } else {
3122 JSXAttributeName::JSXIdentifier(JSXIdentifier {
3123 base: BaseNode::typed("JSXIdentifier"),
3124 name: name.clone(),
3125 })
3126 };
3127
3128 let inner_value = codegen_place_to_expression(cx, place)?;
3129 let attr_value = match &inner_value {
3130 Expression::StringLiteral(s) => {
3131 if string_requires_expr_container(&s.value.to_marker_string())
3132 && !cx.fbt_operands.contains(&place.identifier)
3133 {
3134 Some(JSXAttributeValue::JSXExpressionContainer(
3135 JSXExpressionContainer {
3136 base: base_node_with_loc("JSXExpressionContainer", place.loc),
3137 expression: JSXExpressionContainerExpr::Expression(Box::new(
3138 inner_value,
3139 )),
3140 },
3141 ))
3142 } else {
3143 // Preserve loc from the inner StringLiteral (or fall back to
3144 // the place's loc) so downstream plugins (e.g., babel-plugin-fbt)
3145 // can read loc on attribute values.
3146 let base = if s.base.loc.is_some() {
3147 s.base.clone()
3148 } else {
3149 base_node_with_loc("StringLiteral", place.loc)
3150 };
3151 Some(JSXAttributeValue::StringLiteral(StringLiteral {
3152 base,
3153 value: s.value.clone(),
3154 }))
3155 }
3156 }
3157 _ => Some(JSXAttributeValue::JSXExpressionContainer(
3158 JSXExpressionContainer {
3159 base: base_node_with_loc("JSXExpressionContainer", place.loc),
3160 expression: JSXExpressionContainerExpr::Expression(Box::new(inner_value)),
3161 },
3162 )),
3163 };
3164 Ok(JSXAttributeItem::JSXAttribute(AstJSXAttribute {
3165 base: base_node_with_loc("JSXAttribute", place.loc),
3166 name: prop_name,
3167 value: attr_value,
3168 }))
3169 }
3170 JsxAttribute::SpreadAttribute { argument } => {
3171 let expr = codegen_place_to_expression(cx, argument)?;
3172 Ok(JSXAttributeItem::JSXSpreadAttribute(JSXSpreadAttribute {
3173 base: BaseNode::typed("JSXSpreadAttribute"),
3174 argument: Box::new(expr),
3175 }))
3176 }
3177 }
3178 }
3179
3180 fn codegen_jsx_element(cx: &mut Context, place: &Place) -> Result<JSXChild, CompilerError> {
3181 let loc = place.loc;
3182 let value = codegen_place(cx, place)?;
3183 match value {
3184 ExpressionOrJsxText::JsxText(text) => {
3185 if text
3186 .value
3187 .contains(JSX_TEXT_CHILD_REQUIRES_EXPR_CONTAINER_PATTERN)
3188 {
3189 Ok(JSXChild::JSXExpressionContainer(JSXExpressionContainer {
3190 base: base_node_with_loc("JSXExpressionContainer", loc),
3191 expression: JSXExpressionContainerExpr::Expression(Box::new(
3192 Expression::StringLiteral(StringLiteral {
3193 base: base_node_with_loc("StringLiteral", loc),
3194 value: text.value.clone().into(),
3195 }),
3196 )),
3197 }))
3198 } else {
3199 Ok(JSXChild::JSXText(text))
3200 }
3201 }
3202 ExpressionOrJsxText::Expression(Expression::JSXElement(elem)) => {
3203 Ok(JSXChild::JSXElement(elem))
3204 }
3205 ExpressionOrJsxText::Expression(Expression::JSXFragment(frag)) => {
3206 Ok(JSXChild::JSXFragment(frag))
3207 }
3208 ExpressionOrJsxText::Expression(expr) => {
3209 Ok(JSXChild::JSXExpressionContainer(JSXExpressionContainer {
3210 base: base_node_with_loc("JSXExpressionContainer", loc),
3211 expression: JSXExpressionContainerExpr::Expression(Box::new(expr)),
3212 }))
3213 }
3214 }
3215 }
3216
3217 fn codegen_jsx_fbt_child_element(
3218 cx: &mut Context,
3219 place: &Place,
3220 ) -> Result<JSXChild, CompilerError> {
3221 let loc = place.loc;
3222 let value = codegen_place(cx, place)?;
3223 match value {
3224 ExpressionOrJsxText::JsxText(text) => Ok(JSXChild::JSXText(text)),
3225 ExpressionOrJsxText::Expression(Expression::JSXElement(elem)) => {
3226 Ok(JSXChild::JSXElement(elem))
3227 }
3228 ExpressionOrJsxText::Expression(expr) => {
3229 Ok(JSXChild::JSXExpressionContainer(JSXExpressionContainer {
3230 base: base_node_with_loc("JSXExpressionContainer", loc),
3231 expression: JSXExpressionContainerExpr::Expression(Box::new(expr)),
3232 }))
3233 }
3234 }
3235 }
3236
3237 fn expression_to_jsx_tag(
3238 expr: &Expression,
3239 loc: Option<DiagSourceLocation>,
3240 ) -> Result<JSXElementName, CompilerError> {
3241 match expr {
3242 Expression::Identifier(ident) => Ok(JSXElementName::JSXIdentifier(JSXIdentifier {
3243 base: base_node_with_loc("JSXIdentifier", loc),
3244 name: ident.name.clone(),
3245 })),
3246 Expression::MemberExpression(me) => Ok(JSXElementName::JSXMemberExpression(
3247 convert_member_expression_to_jsx(me)?,
3248 )),
3249 Expression::StringLiteral(s) => {
3250 // JSX tag names are identifier-shaped; the marker form preserves
3251 // the pre-JsString behavior for pathological values.
3252 let tag_text = s.value.to_marker_string();
3253 if tag_text.contains(':') {
3254 let parts: Vec<&str> = tag_text.splitn(2, ':').collect();
3255 Ok(JSXElementName::JSXNamespacedName(JSXNamespacedName {
3256 base: base_node_with_loc("JSXNamespacedName", loc),
3257 namespace: JSXIdentifier {
3258 base: base_node_with_loc("JSXIdentifier", loc),
3259 name: parts[0].to_string(),
3260 },
3261 name: JSXIdentifier {
3262 base: base_node_with_loc("JSXIdentifier", loc),
3263 name: parts[1].to_string(),
3264 },
3265 }))
3266 } else {
3267 Ok(JSXElementName::JSXIdentifier(JSXIdentifier {
3268 base: base_node_with_loc("JSXIdentifier", loc),
3269 name: tag_text,
3270 }))
3271 }
3272 }
3273 _ => Err(invariant_err(
3274 &format!("Expected JSX tag to be an identifier or string"),
3275 None,
3276 )),
3277 }
3278 }
3279
3280 fn convert_member_expression_to_jsx(
3281 me: &ast_expr::MemberExpression,
3282 ) -> Result<JSXMemberExpression, CompilerError> {
3283 let Expression::Identifier(ref prop_ident) = *me.property else {
3284 return Err(invariant_err(
3285 "Expected JSX member expression property to be a string",
3286 None,
3287 ));
3288 };
3289 let property = JSXIdentifier {
3290 base: BaseNode::typed("JSXIdentifier"),
3291 name: prop_ident.name.clone(),
3292 };
3293 match &*me.object {
3294 Expression::Identifier(ident) => Ok(JSXMemberExpression {
3295 base: BaseNode::typed("JSXMemberExpression"),
3296 object: Box::new(JSXMemberExprObject::JSXIdentifier(JSXIdentifier {
3297 base: BaseNode::typed("JSXIdentifier"),
3298 name: ident.name.clone(),
3299 })),
3300 property,
3301 }),
3302 Expression::MemberExpression(inner_me) => {
3303 let inner = convert_member_expression_to_jsx(inner_me)?;
3304 Ok(JSXMemberExpression {
3305 base: BaseNode::typed("JSXMemberExpression"),
3306 object: Box::new(JSXMemberExprObject::JSXMemberExpression(Box::new(inner))),
3307 property,
3308 })
3309 }
3310 _ => Err(invariant_err(
3311 "Expected JSX member expression to be an identifier or nested member expression",
3312 None,
3313 )),
3314 }
3315 }
3316
3317 // =============================================================================
3318 // Pattern codegen (lvalues)
3319 // =============================================================================
3320
3321 enum LvalueRef<'a> {
3322 Place(&'a Place),
3323 Pattern(&'a Pattern),
3324 Spread(&'a SpreadPattern),
3325 }
3326
3327 fn codegen_lvalue(cx: &mut Context, pattern: &LvalueRef) -> Result<PatternLike, CompilerError> {
3328 match pattern {
3329 LvalueRef::Place(place) => Ok(PatternLike::Identifier(convert_identifier(
3330 place.identifier,
3331 cx.env,
3332 )?)),
3333 LvalueRef::Pattern(pat) => match pat {
3334 Pattern::Array(arr) => codegen_array_pattern(cx, arr),
3335 Pattern::Object(obj) => codegen_object_pattern(cx, obj),
3336 },
3337 LvalueRef::Spread(spread) => {
3338 let inner = codegen_lvalue(cx, &LvalueRef::Place(&spread.place))?;
3339 Ok(PatternLike::RestElement(RestElement {
3340 base: BaseNode::typed("RestElement"),
3341 argument: Box::new(inner),
3342 type_annotation: None,
3343 decorators: None,
3344 }))
3345 }
3346 }
3347 }
3348
3349 fn codegen_array_pattern(
3350 cx: &mut Context,
3351 pattern: &ArrayPattern,
3352 ) -> Result<PatternLike, CompilerError> {
3353 let elements: Vec<Option<PatternLike>> = pattern
3354 .items
3355 .iter()
3356 .map(|item| match item {
3357 react_compiler_hir::ArrayPatternElement::Place(place) => {
3358 Ok(Some(codegen_lvalue(cx, &LvalueRef::Place(place))?))
3359 }
3360 react_compiler_hir::ArrayPatternElement::Spread(spread) => {
3361 Ok(Some(codegen_lvalue(cx, &LvalueRef::Spread(spread))?))
3362 }
3363 react_compiler_hir::ArrayPatternElement::Hole => Ok(None),
3364 })
3365 .collect::<Result<_, CompilerError>>()?;
3366 Ok(PatternLike::ArrayPattern(AstArrayPattern {
3367 base: base_node_with_loc("ArrayPattern", pattern.loc),
3368 elements,
3369 type_annotation: None,
3370 decorators: None,
3371 }))
3372 }
3373
3374 fn codegen_object_pattern(
3375 cx: &mut Context,
3376 pattern: &ObjectPattern,
3377 ) -> Result<PatternLike, CompilerError> {
3378 let properties: Vec<ObjectPatternProperty> = pattern
3379 .properties
3380 .iter()
3381 .map(|prop| match prop {
3382 ObjectPropertyOrSpread::Property(obj_prop) => {
3383 let key = codegen_object_property_key(cx, &obj_prop.key)?;
3384 let value = codegen_lvalue(cx, &LvalueRef::Place(&obj_prop.place))?;
3385 let is_shorthand = matches!(&key, Expression::Identifier(k_id)
3386 if matches!(&value, PatternLike::Identifier(v_id) if v_id.name == k_id.name));
3387 Ok(ObjectPatternProperty::ObjectProperty(ObjectPatternProp {
3388 base: BaseNode::typed("ObjectProperty"),
3389 key: Box::new(key),
3390 value: Box::new(value),
3391 computed: matches!(obj_prop.key, ObjectPropertyKey::Computed { .. }),
3392 shorthand: is_shorthand,
3393 decorators: None,
3394 method: None,
3395 }))
3396 }
3397 ObjectPropertyOrSpread::Spread(spread) => {
3398 let inner = codegen_lvalue(cx, &LvalueRef::Place(&spread.place))?;
3399 Ok(ObjectPatternProperty::RestElement(RestElement {
3400 base: BaseNode::typed("RestElement"),
3401 argument: Box::new(inner),
3402 type_annotation: None,
3403 decorators: None,
3404 }))
3405 }
3406 })
3407 .collect::<Result<_, CompilerError>>()?;
3408 Ok(PatternLike::ObjectPattern(
3409 react_compiler_ast::patterns::ObjectPattern {
3410 base: base_node_with_loc("ObjectPattern", pattern.loc),
3411 properties,
3412 type_annotation: None,
3413 decorators: None,
3414 },
3415 ))
3416 }
3417
3418 // =============================================================================
3419 // Place / identifier codegen
3420 // =============================================================================
3421
3422 fn codegen_place_to_expression(
3423 cx: &mut Context,
3424 place: &Place,
3425 ) -> Result<Expression, CompilerError> {
3426 let value = codegen_place(cx, place)?;
3427 Ok(convert_value_to_expression(value))
3428 }
3429
3430 fn codegen_place(cx: &mut Context, place: &Place) -> Result<ExpressionOrJsxText, CompilerError> {
3431 let ident = &cx.env.identifiers[place.identifier.0 as usize];
3432 if let Some(tmp) = cx.temp.get(ident.declaration_id) {
3433 if let Some(val) = tmp {
3434 return Ok(val.clone());
3435 }
3436 // tmp is None — means declared but no temp value, fall through
3437 }
3438 // Check if it's an unnamed identifier without a temp
3439 if ident.name.is_none() && !cx.temp.contains_key(ident.declaration_id) {
3440 return Err(invariant_err(
3441 &format!(
3442 "[Codegen] No value found for temporary, identifier id={}",
3443 place.identifier.0
3444 ),
3445 place.loc,
3446 ));
3447 }
3448 let mut ast_ident = convert_identifier(place.identifier, cx.env)?;
3449 // Override identifier loc with place.loc, matching TS: identifier.loc = place.loc
3450 if let Some(loc) = place.loc {
3451 ast_ident.base.loc = Some(AstSourceLocation {
3452 start: AstPosition {
3453 line: loc.start.line,
3454 column: loc.start.column,
3455 index: None,
3456 },
3457 end: AstPosition {
3458 line: loc.end.line,
3459 column: loc.end.column,
3460 index: None,
3461 },
3462 filename: None,
3463 identifier_name: None,
3464 });
3465 }
3466 Ok(ExpressionOrJsxText::Expression(Expression::Identifier(
3467 ast_ident,
3468 )))
3469 }
3470
3471 fn convert_identifier(
3472 identifier_id: IdentifierId,
3473 env: &Environment,
3474 ) -> Result<AstIdentifier, CompilerError> {
3475 let ident = &env.identifiers[identifier_id.0 as usize];
3476 let name = match &ident.name {
3477 Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(),
3478 Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(),
3479 None => {
3480 // Use CompilerDiagnostic (with details array) to match TS CompilerError.invariant()
3481 // which creates a CompilerDiagnostic with details: [{kind: "error", loc, message}].
3482 let reason =
3483 "Expected temporaries to be promoted to named identifiers in an earlier pass"
3484 .to_string();
3485 let description = format!("identifier {} is unnamed", identifier_id.0);
3486 let mut err = CompilerError::new();
3487 err.push_diagnostic(
3488 CompilerDiagnostic::new(
3489 ErrorCategory::Invariant,
3490 reason.clone(),
3491 Some(description),
3492 )
3493 .with_detail(CompilerDiagnosticDetail::Error {
3494 loc: None,
3495 message: Some(reason),
3496 identifier_name: None,
3497 }),
3498 );
3499 return Err(err);
3500 }
3501 };
3502 Ok(make_identifier_with_loc(&name, ident.loc))
3503 }
3504
3505 fn codegen_argument(cx: &mut Context, arg: &PlaceOrSpread) -> Result<Expression, CompilerError> {
3506 match arg {
3507 PlaceOrSpread::Place(place) => codegen_place_to_expression(cx, place),
3508 PlaceOrSpread::Spread(spread) => {
3509 let expr = codegen_place_to_expression(cx, &spread.place)?;
3510 Ok(Expression::SpreadElement(ast_expr::SpreadElement {
3511 base: BaseNode::typed("SpreadElement"),
3512 argument: Box::new(expr),
3513 }))
3514 }
3515 }
3516 }
3517
3518 // =============================================================================
3519 // Dependency codegen
3520 // =============================================================================
3521
3522 fn codegen_dependency(
3523 cx: &mut Context,
3524 dep: &react_compiler_hir::ReactiveScopeDependency,
3525 ) -> Result<Expression, CompilerError> {
3526 let mut object: Expression =
3527 Expression::Identifier(convert_identifier(dep.identifier, cx.env)?);
3528 if !dep.path.is_empty() {
3529 let has_optional = dep.path.iter().any(|p| p.optional);
3530 for path_entry in &dep.path {
3531 let (property, is_computed) = property_literal_to_expression(&path_entry.property);
3532 if has_optional {
3533 object = Expression::OptionalMemberExpression(ast_expr::OptionalMemberExpression {
3534 base: BaseNode::typed("OptionalMemberExpression"),
3535 object: Box::new(object),
3536 property: Box::new(property),
3537 computed: is_computed,
3538 optional: path_entry.optional,
3539 });
3540 } else {
3541 object = Expression::MemberExpression(ast_expr::MemberExpression {
3542 base: BaseNode::typed("MemberExpression"),
3543 object: Box::new(object),
3544 property: Box::new(property),
3545 computed: is_computed,
3546 });
3547 }
3548 }
3549 }
3550 Ok(object)
3551 }
3552
3553 // =============================================================================
3554 // CountMemoBlockVisitor — uses ReactiveFunctionVisitor trait
3555 // =============================================================================
3556
3557 /// Counts memo blocks and pruned memo blocks in a reactive function.
3558 /// TS: `class CountMemoBlockVisitor extends ReactiveFunctionVisitor<void>`
3559 struct CountMemoBlockVisitor<'a> {
3560 env: &'a Environment,
3561 }
3562
3563 struct CountMemoBlockState {
3564 memo_blocks: u32,
3565 memo_values: u32,
3566 pruned_memo_blocks: u32,
3567 pruned_memo_values: u32,
3568 }
3569
3570 impl<'a> ReactiveFunctionVisitor for CountMemoBlockVisitor<'a> {
3571 type State = CountMemoBlockState;
3572
3573 fn env(&self) -> &Environment {
3574 self.env
3575 }
3576
3577 fn visit_scope(&self, scope_block: &ReactiveScopeBlock, state: &mut CountMemoBlockState) {
3578 state.memo_blocks += 1;
3579 let scope = &self.env.scopes[scope_block.scope.0 as usize];
3580 state.memo_values += scope.declarations.len() as u32;
3581 self.traverse_scope(scope_block, state);
3582 }
3583
3584 fn visit_pruned_scope(
3585 &self,
3586 scope_block: &PrunedReactiveScopeBlock,
3587 state: &mut CountMemoBlockState,
3588 ) {
3589 state.pruned_memo_blocks += 1;
3590 let scope = &self.env.scopes[scope_block.scope.0 as usize];
3591 state.pruned_memo_values += scope.declarations.len() as u32;
3592 self.traverse_pruned_scope(scope_block, state);
3593 }
3594 }
3595
3596 fn count_memo_blocks(func: &ReactiveFunction, env: &Environment) -> (u32, u32, u32, u32) {
3597 let visitor = CountMemoBlockVisitor { env };
3598 let mut state = CountMemoBlockState {
3599 memo_blocks: 0,
3600 memo_values: 0,
3601 pruned_memo_blocks: 0,
3602 pruned_memo_values: 0,
3603 };
3604 visit_reactive_function(func, &visitor, &mut state);
3605 (
3606 state.memo_blocks,
3607 state.memo_values,
3608 state.pruned_memo_blocks,
3609 state.pruned_memo_values,
3610 )
3611 }
3612
3613 // =============================================================================
3614 // Operator conversions
3615 // =============================================================================
3616
3617 fn convert_binary_operator(op: &react_compiler_hir::BinaryOperator) -> AstBinaryOperator {
3618 match op {
3619 react_compiler_hir::BinaryOperator::Equal => AstBinaryOperator::Eq,
3620 react_compiler_hir::BinaryOperator::NotEqual => AstBinaryOperator::Neq,
3621 react_compiler_hir::BinaryOperator::StrictEqual => AstBinaryOperator::StrictEq,
3622 react_compiler_hir::BinaryOperator::StrictNotEqual => AstBinaryOperator::StrictNeq,
3623 react_compiler_hir::BinaryOperator::LessThan => AstBinaryOperator::Lt,
3624 react_compiler_hir::BinaryOperator::LessEqual => AstBinaryOperator::Lte,
3625 react_compiler_hir::BinaryOperator::GreaterThan => AstBinaryOperator::Gt,
3626 react_compiler_hir::BinaryOperator::GreaterEqual => AstBinaryOperator::Gte,
3627 react_compiler_hir::BinaryOperator::ShiftLeft => AstBinaryOperator::Shl,
3628 react_compiler_hir::BinaryOperator::ShiftRight => AstBinaryOperator::Shr,
3629 react_compiler_hir::BinaryOperator::UnsignedShiftRight => AstBinaryOperator::UShr,
3630 react_compiler_hir::BinaryOperator::Add => AstBinaryOperator::Add,
3631 react_compiler_hir::BinaryOperator::Subtract => AstBinaryOperator::Sub,
3632 react_compiler_hir::BinaryOperator::Multiply => AstBinaryOperator::Mul,
3633 react_compiler_hir::BinaryOperator::Divide => AstBinaryOperator::Div,
3634 react_compiler_hir::BinaryOperator::Modulo => AstBinaryOperator::Rem,
3635 react_compiler_hir::BinaryOperator::Exponent => AstBinaryOperator::Exp,
3636 react_compiler_hir::BinaryOperator::BitwiseOr => AstBinaryOperator::BitOr,
3637 react_compiler_hir::BinaryOperator::BitwiseXor => AstBinaryOperator::BitXor,
3638 react_compiler_hir::BinaryOperator::BitwiseAnd => AstBinaryOperator::BitAnd,
3639 react_compiler_hir::BinaryOperator::In => AstBinaryOperator::In,
3640 react_compiler_hir::BinaryOperator::InstanceOf => AstBinaryOperator::Instanceof,
3641 }
3642 }
3643
3644 fn convert_unary_operator(op: &react_compiler_hir::UnaryOperator) -> AstUnaryOperator {
3645 match op {
3646 react_compiler_hir::UnaryOperator::Minus => AstUnaryOperator::Neg,
3647 react_compiler_hir::UnaryOperator::Plus => AstUnaryOperator::Plus,
3648 react_compiler_hir::UnaryOperator::Not => AstUnaryOperator::Not,
3649 react_compiler_hir::UnaryOperator::BitwiseNot => AstUnaryOperator::BitNot,
3650 react_compiler_hir::UnaryOperator::TypeOf => AstUnaryOperator::TypeOf,
3651 react_compiler_hir::UnaryOperator::Void => AstUnaryOperator::Void,
3652 }
3653 }
3654
3655 fn convert_logical_operator(op: &LogicalOperator) -> AstLogicalOperator {
3656 match op {
3657 LogicalOperator::And => AstLogicalOperator::And,
3658 LogicalOperator::Or => AstLogicalOperator::Or,
3659 LogicalOperator::NullishCoalescing => AstLogicalOperator::NullishCoalescing,
3660 }
3661 }
3662
3663 fn convert_update_operator(op: &react_compiler_hir::UpdateOperator) -> AstUpdateOperator {
3664 match op {
3665 react_compiler_hir::UpdateOperator::Increment => AstUpdateOperator::Increment,
3666 react_compiler_hir::UpdateOperator::Decrement => AstUpdateOperator::Decrement,
3667 }
3668 }
3669
3670 // =============================================================================
3671 // Helpers
3672 // =============================================================================
3673
3674 /// Create a BaseNode with the given type name and optional source location.
3675 /// Converts from the diagnostics SourceLocation (line, column) to the AST
3676 /// SourceLocation format. This is critical for Babel's `retainLines: true`
3677 /// option to insert blank lines at correct positions.
3678 fn base_node_with_loc(type_name: &str, loc: Option<DiagSourceLocation>) -> BaseNode {
3679 match loc {
3680 Some(loc) => BaseNode {
3681 node_type: Some(type_name.to_string()),
3682 loc: Some(AstSourceLocation {
3683 start: AstPosition {
3684 line: loc.start.line,
3685 column: loc.start.column,
3686 index: loc.start.index,
3687 },
3688 end: AstPosition {
3689 line: loc.end.line,
3690 column: loc.end.column,
3691 index: loc.end.index,
3692 },
3693 filename: None,
3694 identifier_name: None,
3695 }),
3696 ..Default::default()
3697 },
3698 None => BaseNode::typed(type_name),
3699 }
3700 }
3701
3702 fn make_identifier(name: &str) -> AstIdentifier {
3703 AstIdentifier {
3704 base: BaseNode::typed("Identifier"),
3705 name: name.to_string(),
3706 type_annotation: None,
3707 optional: None,
3708 decorators: None,
3709 }
3710 }
3711
3712 fn make_identifier_with_loc(name: &str, loc: Option<DiagSourceLocation>) -> AstIdentifier {
3713 AstIdentifier {
3714 base: base_node_with_loc("Identifier", loc),
3715 name: name.to_string(),
3716 type_annotation: None,
3717 optional: None,
3718 decorators: None,
3719 }
3720 }
3721
3722 fn make_var_declarator(id: PatternLike, init: Option<Expression>) -> VariableDeclarator {
3723 // Reconstruct VariableDeclarator.loc from id.loc.start and init.loc.end,
3724 // matching TS createVariableDeclarator behavior for retainLines support.
3725 let loc = get_pattern_loc(&id).and_then(|id_loc| {
3726 let end = match &init {
3727 Some(expr) => get_expression_loc(expr)
3728 .map(|l| l.end.clone())
3729 .unwrap_or_else(|| id_loc.end.clone()),
3730 None => id_loc.end.clone(),
3731 };
3732 Some(AstSourceLocation {
3733 start: id_loc.start.clone(),
3734 end,
3735 filename: id_loc.filename.clone(),
3736 identifier_name: None,
3737 })
3738 });
3739 VariableDeclarator {
3740 base: if let Some(loc) = loc {
3741 BaseNode {
3742 node_type: Some("VariableDeclarator".to_string()),
3743 loc: Some(loc),
3744 ..Default::default()
3745 }
3746 } else {
3747 BaseNode::typed("VariableDeclarator")
3748 },
3749 id,
3750 init: init.map(Box::new),
3751 definite: None,
3752 }
3753 }
3754
3755 /// Extract the loc from a PatternLike's base node.
3756 fn get_pattern_loc(pattern: &PatternLike) -> Option<&AstSourceLocation> {
3757 match pattern {
3758 PatternLike::Identifier(id) => id.base.loc.as_ref(),
3759 PatternLike::ObjectPattern(p) => p.base.loc.as_ref(),
3760 PatternLike::ArrayPattern(p) => p.base.loc.as_ref(),
3761 PatternLike::AssignmentPattern(p) => p.base.loc.as_ref(),
3762 PatternLike::RestElement(p) => p.base.loc.as_ref(),
3763 _ => None,
3764 }
3765 }
3766
3767 /// Extract the loc from an Expression's base node.
3768 fn get_expression_loc(expr: &Expression) -> Option<&AstSourceLocation> {
3769 match expr {
3770 Expression::Identifier(e) => e.base.loc.as_ref(),
3771 Expression::StringLiteral(e) => e.base.loc.as_ref(),
3772 Expression::NumericLiteral(e) => e.base.loc.as_ref(),
3773 Expression::BooleanLiteral(e) => e.base.loc.as_ref(),
3774 Expression::NullLiteral(e) => e.base.loc.as_ref(),
3775 Expression::CallExpression(e) => e.base.loc.as_ref(),
3776 Expression::MemberExpression(e) => e.base.loc.as_ref(),
3777 Expression::OptionalMemberExpression(e) => e.base.loc.as_ref(),
3778 Expression::ArrayExpression(e) => e.base.loc.as_ref(),
3779 Expression::ObjectExpression(e) => e.base.loc.as_ref(),
3780 Expression::ArrowFunctionExpression(e) => e.base.loc.as_ref(),
3781 Expression::FunctionExpression(e) => e.base.loc.as_ref(),
3782 Expression::BinaryExpression(e) => e.base.loc.as_ref(),
3783 Expression::UnaryExpression(e) => e.base.loc.as_ref(),
3784 Expression::UpdateExpression(e) => e.base.loc.as_ref(),
3785 Expression::LogicalExpression(e) => e.base.loc.as_ref(),
3786 Expression::ConditionalExpression(e) => e.base.loc.as_ref(),
3787 Expression::SequenceExpression(e) => e.base.loc.as_ref(),
3788 Expression::AssignmentExpression(e) => e.base.loc.as_ref(),
3789 Expression::TemplateLiteral(e) => e.base.loc.as_ref(),
3790 Expression::TaggedTemplateExpression(e) => e.base.loc.as_ref(),
3791 Expression::SpreadElement(e) => e.base.loc.as_ref(),
3792 Expression::RegExpLiteral(e) => e.base.loc.as_ref(),
3793 Expression::JSXElement(e) => e.base.loc.as_ref(),
3794 Expression::JSXFragment(e) => e.base.loc.as_ref(),
3795 Expression::NewExpression(e) => e.base.loc.as_ref(),
3796 Expression::OptionalCallExpression(e) => e.base.loc.as_ref(),
3797 _ => None,
3798 }
3799 }
3800
3801 /// Apply a source location to an ExpressionOrJsxText value, matching the TS behavior
3802 /// where `value.loc = instrValue.loc` is set at the end of codegenInstructionValue.
3803 fn apply_loc_to_value(value: &mut ExpressionOrJsxText, loc: DiagSourceLocation) {
3804 let ast_loc = AstSourceLocation {
3805 start: AstPosition {
3806 line: loc.start.line,
3807 column: loc.start.column,
3808 index: None,
3809 },
3810 end: AstPosition {
3811 line: loc.end.line,
3812 column: loc.end.column,
3813 index: None,
3814 },
3815 filename: None,
3816 identifier_name: None,
3817 };
3818 match value {
3819 ExpressionOrJsxText::Expression(expr) => {
3820 apply_loc_to_expression(expr, ast_loc);
3821 }
3822 ExpressionOrJsxText::JsxText(text) => {
3823 text.base.loc = Some(ast_loc);
3824 }
3825 }
3826 }
3827
3828 /// Apply a source location to an Expression's base node.
3829 fn apply_loc_to_expression(expr: &mut Expression, loc: AstSourceLocation) {
3830 let base = match expr {
3831 Expression::Identifier(e) => &mut e.base,
3832 Expression::StringLiteral(e) => &mut e.base,
3833 Expression::NumericLiteral(e) => &mut e.base,
3834 Expression::BooleanLiteral(e) => &mut e.base,
3835 Expression::NullLiteral(e) => &mut e.base,
3836 Expression::CallExpression(e) => &mut e.base,
3837 Expression::MemberExpression(e) => &mut e.base,
3838 Expression::OptionalMemberExpression(e) => &mut e.base,
3839 Expression::ArrayExpression(e) => &mut e.base,
3840 Expression::ObjectExpression(e) => &mut e.base,
3841 Expression::ArrowFunctionExpression(e) => &mut e.base,
3842 Expression::FunctionExpression(e) => &mut e.base,
3843 Expression::BinaryExpression(e) => &mut e.base,
3844 Expression::UnaryExpression(e) => &mut e.base,
3845 Expression::UpdateExpression(e) => &mut e.base,
3846 Expression::LogicalExpression(e) => &mut e.base,
3847 Expression::ConditionalExpression(e) => &mut e.base,
3848 Expression::SequenceExpression(e) => &mut e.base,
3849 Expression::AssignmentExpression(e) => &mut e.base,
3850 Expression::TemplateLiteral(e) => &mut e.base,
3851 Expression::TaggedTemplateExpression(e) => &mut e.base,
3852 Expression::SpreadElement(e) => &mut e.base,
3853 Expression::RegExpLiteral(e) => &mut e.base,
3854 Expression::JSXElement(e) => &mut e.base,
3855 Expression::JSXFragment(e) => &mut e.base,
3856 Expression::NewExpression(e) => &mut e.base,
3857 Expression::OptionalCallExpression(e) => &mut e.base,
3858 _ => return,
3859 };
3860 base.loc = Some(loc);
3861 }
3862
3863 fn codegen_label(id: BlockId) -> String {
3864 format!("bb{}", id.0)
3865 }
3866
3867 fn symbol_for(name: &str) -> Expression {
3868 Expression::CallExpression(ast_expr::CallExpression {
3869 base: BaseNode::typed("CallExpression"),
3870 callee: Box::new(Expression::MemberExpression(ast_expr::MemberExpression {
3871 base: BaseNode::typed("MemberExpression"),
3872 object: Box::new(Expression::Identifier(make_identifier("Symbol"))),
3873 property: Box::new(Expression::Identifier(make_identifier("for"))),
3874 computed: false,
3875 })),
3876 arguments: vec![Expression::StringLiteral(StringLiteral {
3877 base: BaseNode::typed("StringLiteral"),
3878 value: name.to_string().into(),
3879 })],
3880 type_parameters: None,
3881 type_arguments: None,
3882 optional: None,
3883 })
3884 }
3885
3886 fn codegen_primitive_value(value: &PrimitiveValue, loc: Option<DiagSourceLocation>) -> Expression {
3887 match value {
3888 PrimitiveValue::Number(n) => {
3889 let f = n.value();
3890 if f.is_nan() {
3891 Expression::Identifier(make_identifier("NaN"))
3892 } else if f.is_infinite() {
3893 if f > 0.0 {
3894 Expression::Identifier(make_identifier("Infinity"))
3895 } else {
3896 Expression::UnaryExpression(ast_expr::UnaryExpression {
3897 base: base_node_with_loc("UnaryExpression", loc),
3898 operator: AstUnaryOperator::Neg,
3899 prefix: true,
3900 argument: Box::new(Expression::Identifier(make_identifier("Infinity"))),
3901 })
3902 }
3903 } else if f < 0.0 {
3904 Expression::UnaryExpression(ast_expr::UnaryExpression {
3905 base: base_node_with_loc("UnaryExpression", loc),
3906 operator: AstUnaryOperator::Neg,
3907 prefix: true,
3908 argument: Box::new(Expression::NumericLiteral(NumericLiteral {
3909 base: base_node_with_loc("NumericLiteral", loc),
3910 value: -f,
3911 extra: None,
3912 })),
3913 })
3914 } else {
3915 Expression::NumericLiteral(NumericLiteral {
3916 base: base_node_with_loc("NumericLiteral", loc),
3917 value: f,
3918 extra: None,
3919 })
3920 }
3921 }
3922 PrimitiveValue::Boolean(b) => Expression::BooleanLiteral(BooleanLiteral {
3923 base: base_node_with_loc("BooleanLiteral", loc),
3924 value: *b,
3925 }),
3926 PrimitiveValue::String(s) => Expression::StringLiteral(StringLiteral {
3927 base: base_node_with_loc("StringLiteral", loc),
3928 value: s.clone(),
3929 }),
3930 PrimitiveValue::Null => Expression::NullLiteral(NullLiteral {
3931 base: base_node_with_loc("NullLiteral", loc),
3932 }),
3933 PrimitiveValue::Undefined => Expression::Identifier(make_identifier("undefined")),
3934 }
3935 }
3936
3937 fn property_literal_to_expression(prop: &PropertyLiteral) -> (Expression, bool) {
3938 match prop {
3939 PropertyLiteral::String(s) => (Expression::Identifier(make_identifier(s)), false),
3940 PropertyLiteral::Number(n) => (
3941 Expression::NumericLiteral(NumericLiteral {
3942 base: BaseNode::typed("NumericLiteral"),
3943 value: n.value(),
3944 extra: None,
3945 }),
3946 true,
3947 ),
3948 }
3949 }
3950
3951 fn convert_value_to_expression(value: ExpressionOrJsxText) -> Expression {
3952 match value {
3953 ExpressionOrJsxText::Expression(e) => e,
3954 ExpressionOrJsxText::JsxText(text) => Expression::StringLiteral(StringLiteral {
3955 base: BaseNode::typed("StringLiteral"),
3956 value: text.value.into(),
3957 }),
3958 }
3959 }
3960
3961 fn get_instruction_value(
3962 reactive_value: &ReactiveValue,
3963 ) -> Result<&InstructionValue, CompilerError> {
3964 match reactive_value {
3965 ReactiveValue::Instruction(iv) => Ok(iv),
3966 _ => Err(invariant_err("Expected base instruction value", None)),
3967 }
3968 }
3969
3970 fn invariant(
3971 condition: bool,
3972 reason: &str,
3973 loc: Option<DiagSourceLocation>,
3974 ) -> Result<(), CompilerError> {
3975 if !condition {
3976 Err(invariant_err(reason, loc))
3977 } else {
3978 Ok(())
3979 }
3980 }
3981
3982 fn invariant_err(reason: &str, loc: Option<DiagSourceLocation>) -> CompilerError {
3983 // Use CompilerDiagnostic (with details array) to match TS CompilerError.invariant()
3984 let mut err = CompilerError::new();
3985 err.push_diagnostic(
3986 CompilerDiagnostic::new(ErrorCategory::Invariant, reason, None::<String>).with_detail(
3987 CompilerDiagnosticDetail::Error {
3988 loc,
3989 message: Some(reason.to_string()),
3990 identifier_name: None,
3991 },
3992 ),
3993 );
3994 err
3995 }
3996
3997 fn invariant_err_with_detail_message(
3998 reason: &str,
3999 message: &str,
4000 loc: Option<DiagSourceLocation>,
4001 ) -> CompilerError {
4002 let mut err = CompilerError::new();
4003 let diagnostic = react_compiler_diagnostics::CompilerDiagnostic::new(
4004 ErrorCategory::Invariant,
4005 reason,
4006 None::<String>,
4007 )
4008 .with_detail(
4009 react_compiler_diagnostics::CompilerDiagnosticDetail::Error {
4010 loc,
4011 message: Some(message.to_string()),
4012 identifier_name: None,
4013 },
4014 );
4015 err.push_diagnostic(diagnostic);
4016 err
4017 }
4018
4019 fn get_statement_type_name(stmt: &Statement) -> &'static str {
4020 match stmt {
4021 Statement::ExpressionStatement(_) => "ExpressionStatement",
4022 Statement::BlockStatement(_) => "BlockStatement",
4023 Statement::VariableDeclaration(_) => "VariableDeclaration",
4024 Statement::ReturnStatement(_) => "ReturnStatement",
4025 Statement::IfStatement(_) => "IfStatement",
4026 Statement::SwitchStatement(_) => "SwitchStatement",
4027 Statement::ForStatement(_) => "ForStatement",
4028 Statement::ForInStatement(_) => "ForInStatement",
4029 Statement::ForOfStatement(_) => "ForOfStatement",
4030 Statement::WhileStatement(_) => "WhileStatement",
4031 Statement::DoWhileStatement(_) => "DoWhileStatement",
4032 Statement::LabeledStatement(_) => "LabeledStatement",
4033 Statement::ThrowStatement(_) => "ThrowStatement",
4034 Statement::TryStatement(_) => "TryStatement",
4035 Statement::BreakStatement(_) => "BreakStatement",
4036 Statement::ContinueStatement(_) => "ContinueStatement",
4037 Statement::FunctionDeclaration(_) => "FunctionDeclaration",
4038 Statement::DebuggerStatement(_) => "DebuggerStatement",
4039 Statement::EmptyStatement(_) => "EmptyStatement",
4040 _ => "Statement",
4041 }
4042 }
4043
4044 fn get_statement_loc(stmt: &Statement) -> Option<DiagSourceLocation> {
4045 let base = match stmt {
4046 Statement::ExpressionStatement(s) => &s.base,
4047 Statement::BlockStatement(s) => &s.base,
4048 Statement::VariableDeclaration(s) => &s.base,
4049 Statement::ReturnStatement(s) => &s.base,
4050 Statement::IfStatement(s) => &s.base,
4051 Statement::ForStatement(s) => &s.base,
4052 Statement::ForInStatement(s) => &s.base,
4053 Statement::ForOfStatement(s) => &s.base,
4054 Statement::WhileStatement(s) => &s.base,
4055 Statement::DoWhileStatement(s) => &s.base,
4056 Statement::LabeledStatement(s) => &s.base,
4057 Statement::ThrowStatement(s) => &s.base,
4058 Statement::TryStatement(s) => &s.base,
4059 Statement::SwitchStatement(s) => &s.base,
4060 Statement::BreakStatement(s) => &s.base,
4061 Statement::ContinueStatement(s) => &s.base,
4062 Statement::FunctionDeclaration(s) => &s.base,
4063 Statement::DebuggerStatement(s) => &s.base,
4064 Statement::EmptyStatement(s) => &s.base,
4065 _ => return None,
4066 };
4067 base.loc.as_ref().map(|loc| DiagSourceLocation {
4068 start: react_compiler_diagnostics::Position {
4069 line: loc.start.line,
4070 column: loc.start.column,
4071 index: loc.start.index,
4072 },
4073 end: react_compiler_diagnostics::Position {
4074 line: loc.end.line,
4075 column: loc.end.column,
4076 index: loc.end.index,
4077 },
4078 })
4079 }
4080
4081 fn compare_scope_dependency(
4082 a: &react_compiler_hir::ReactiveScopeDependency,
4083 b: &react_compiler_hir::ReactiveScopeDependency,
4084 env: &Environment,
4085 ) -> std::cmp::Ordering {
4086 let a_name = dep_to_sort_key(a, env);
4087 let b_name = dep_to_sort_key(b, env);
4088 a_name.cmp(&b_name)
4089 }
4090
4091 fn dep_to_sort_key(dep: &react_compiler_hir::ReactiveScopeDependency, env: &Environment) -> String {
4092 let ident = &env.identifiers[dep.identifier.0 as usize];
4093 let base = match &ident.name {
4094 Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(),
4095 Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(),
4096 None => format!("_t{}", dep.identifier.0),
4097 };
4098 let mut parts = vec![base];
4099 for entry in &dep.path {
4100 let prefix = if entry.optional { "?" } else { "" };
4101 let prop = match &entry.property {
4102 PropertyLiteral::String(s) => s.clone(),
4103 PropertyLiteral::Number(n) => format!("{}", n),
4104 };
4105 parts.push(format!("{prefix}{prop}"));
4106 }
4107 parts.join(".")
4108 }
4109
4110 fn compare_scope_declaration(
4111 a: &react_compiler_hir::ReactiveScopeDeclaration,
4112 b: &react_compiler_hir::ReactiveScopeDeclaration,
4113 env: &Environment,
4114 ) -> std::cmp::Ordering {
4115 let a_name = ident_sort_key(a.identifier, env);
4116 let b_name = ident_sort_key(b.identifier, env);
4117 a_name.cmp(&b_name)
4118 }
4119
4120 fn ident_sort_key(id: IdentifierId, env: &Environment) -> String {
4121 let ident = &env.identifiers[id.0 as usize];
4122 match &ident.name {
4123 Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(),
4124 Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(),
4125 None => format!("_t{}", id.0),
4126 }
4127 }
4128
4129 fn jsx_tag_loc(tag: &JsxTag) -> Option<DiagSourceLocation> {
4130 match tag {
4131 JsxTag::Place(p) => p.loc,
4132 JsxTag::Builtin(_) => None,
4133 }
4134 }
4135
4136 /// Conditionally wrap a call expression in a hook guard IIFE if enableEmitHookGuards
4137 /// is enabled and the callee is a hook.
4138 fn maybe_wrap_hook_call(
4139 cx: &Context<'_>,
4140 call_expr: Expression,
4141 callee_id: IdentifierId,
4142 ) -> Expression {
4143 if let Some(ref guard_name) = cx.env.hook_guard_name {
4144 if cx.env.output_mode == react_compiler_hir::environment::OutputMode::Client
4145 && is_hook_identifier(cx, callee_id)
4146 {
4147 return wrap_hook_call_with_guard(guard_name, call_expr, 2, 3);
4148 }
4149 }
4150 call_expr
4151 }
4152
4153 /// Check if a callee identifier refers to a hook function.
4154 fn is_hook_identifier(cx: &Context<'_>, identifier_id: IdentifierId) -> bool {
4155 let identifier = &cx.env.identifiers[identifier_id.0 as usize];
4156 let type_ = &cx.env.types[identifier.type_.0 as usize];
4157 cx.env
4158 .get_hook_kind_for_type(type_)
4159 .ok()
4160 .flatten()
4161 .is_some()
4162 }
4163
4164 /// Create the hook guard IIFE wrapper for a hook call expression.
4165 /// Wraps the call in: `(function() { try { $guard(before); return callExpr; } finally { $guard(after); } })()`
4166 fn wrap_hook_call_with_guard(
4167 guard_name: &str,
4168 call_expr: Expression,
4169 before: u32,
4170 after: u32,
4171 ) -> Expression {
4172 let guard_call = |kind: u32| -> Statement {
4173 Statement::ExpressionStatement(ExpressionStatement {
4174 base: BaseNode::typed("ExpressionStatement"),
4175 expression: Box::new(Expression::CallExpression(ast_expr::CallExpression {
4176 base: BaseNode::typed("CallExpression"),
4177 callee: Box::new(Expression::Identifier(make_identifier(guard_name))),
4178 arguments: vec![Expression::NumericLiteral(NumericLiteral {
4179 base: BaseNode::typed("NumericLiteral"),
4180 value: kind as f64,
4181 extra: None,
4182 })],
4183 type_parameters: None,
4184 type_arguments: None,
4185 optional: None,
4186 })),
4187 })
4188 };
4189
4190 let try_stmt = Statement::TryStatement(TryStatement {
4191 base: BaseNode::typed("TryStatement"),
4192 block: BlockStatement {
4193 base: BaseNode::typed("BlockStatement"),
4194 body: vec![
4195 guard_call(before),
4196 Statement::ReturnStatement(ReturnStatement {
4197 base: BaseNode::typed("ReturnStatement"),
4198 argument: Some(Box::new(call_expr)),
4199 }),
4200 ],
4201 directives: Vec::new(),
4202 },
4203 handler: None,
4204 finalizer: Some(BlockStatement {
4205 base: BaseNode::typed("BlockStatement"),
4206 body: vec![guard_call(after)],
4207 directives: Vec::new(),
4208 }),
4209 });
4210
4211 let iife = Expression::FunctionExpression(ast_expr::FunctionExpression {
4212 base: BaseNode::typed("FunctionExpression"),
4213 id: None,
4214 params: Vec::new(),
4215 body: BlockStatement {
4216 base: BaseNode::typed("BlockStatement"),
4217 body: vec![try_stmt],
4218 directives: Vec::new(),
4219 },
4220 generator: false,
4221 is_async: false,
4222 return_type: None,
4223 type_parameters: None,
4224 predicate: None,
4225 });
4226
4227 Expression::CallExpression(ast_expr::CallExpression {
4228 base: BaseNode::typed("CallExpression"),
4229 callee: Box::new(iife),
4230 arguments: vec![],
4231 type_parameters: None,
4232 type_arguments: None,
4233 optional: None,
4234 })
4235 }
4236
4237 /// Create a try/finally wrapping for the entire function body.
4238 /// `try { $guard(before); ...body...; } finally { $guard(after); }`
4239 fn create_function_body_hook_guard(
4240 guard_name: &str,
4241 body_stmts: Vec<Statement>,
4242 before: u32,
4243 after: u32,
4244 ) -> Statement {
4245 let guard_call = |kind: u32| -> Statement {
4246 Statement::ExpressionStatement(ExpressionStatement {
4247 base: BaseNode::typed("ExpressionStatement"),
4248 expression: Box::new(Expression::CallExpression(ast_expr::CallExpression {
4249 base: BaseNode::typed("CallExpression"),
4250 callee: Box::new(Expression::Identifier(make_identifier(guard_name))),
4251 arguments: vec![Expression::NumericLiteral(NumericLiteral {
4252 base: BaseNode::typed("NumericLiteral"),
4253 value: kind as f64,
4254 extra: None,
4255 })],
4256 type_parameters: None,
4257 type_arguments: None,
4258 optional: None,
4259 })),
4260 })
4261 };
4262
4263 let mut try_body = vec![guard_call(before)];
4264 try_body.extend(body_stmts);
4265
4266 Statement::TryStatement(TryStatement {
4267 base: BaseNode::typed("TryStatement"),
4268 block: BlockStatement {
4269 base: BaseNode::typed("BlockStatement"),
4270 body: try_body,
4271 directives: Vec::new(),
4272 },
4273 handler: None,
4274 finalizer: Some(BlockStatement {
4275 base: BaseNode::typed("BlockStatement"),
4276 body: vec![guard_call(after)],
4277 directives: Vec::new(),
4278 }),
4279 })
4280 }
4281
4282 fn apply_renames_to_json(
4283 value: &mut serde_json::Value,
4284 renames: &[react_compiler_hir::environment::BindingRename],
4285 reference_node_ids: &rustc_hash::FxHashSet<u32>,
4286 ) {
4287 apply_renames_to_json_inner(value, renames, reference_node_ids, false);
4288 }
4289
4290 fn apply_renames_to_json_inner(
4291 value: &mut serde_json::Value,
4292 renames: &[react_compiler_hir::environment::BindingRename],
4293 reference_node_ids: &rustc_hash::FxHashSet<u32>,
4294 is_property_key: bool,
4295 ) {
4296 if renames.is_empty() {
4297 return;
4298 }
4299 match value {
4300 serde_json::Value::Object(map) => {
4301 let node_type = map
4302 .get("type")
4303 .and_then(|v| v.as_str())
4304 .unwrap_or("")
4305 .to_string();
4306 // Rename Identifier nodes that are NOT object property keys.
4307 // Property keys in object type annotations (e.g., `id: string`)
4308 // use the original property name, not a variable binding name.
4309 if (node_type == "Identifier" || node_type == "GenericTypeAnnotation")
4310 && !is_property_key
4311 {
4312 let ident_node_id = map.get("_nodeId").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
4313 let ident_start = map.get("start").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
4314 // Only rename identifiers that are actual references to bindings
4315 // (identified by node_id). Type-level labels (e.g., ObjectTypeIndexer
4316 // params) are NOT in the reference set and keep their original names.
4317 let is_reference = ident_node_id > 0 && reference_node_ids.contains(&ident_node_id);
4318 let maybe_rename = if is_reference {
4319 map.get("name").and_then(|v| v.as_str()).and_then(|name| {
4320 renames
4321 .iter()
4322 .filter(|r| r.original == name && r.declaration_start <= ident_start)
4323 .max_by_key(|r| r.declaration_start)
4324 .map(|r| r.renamed.clone())
4325 })
4326 } else if ident_node_id == 0 {
4327 map.get("name").and_then(|v| v.as_str()).and_then(|name| {
4328 renames
4329 .iter()
4330 .find(|r| r.original == name)
4331 .map(|r| r.renamed.clone())
4332 })
4333 } else {
4334 None
4335 };
4336 if let Some(renamed) = maybe_rename {
4337 map.insert("name".to_string(), serde_json::Value::String(renamed));
4338 }
4339 if let Some(id) = map.get_mut("id") {
4340 apply_renames_to_json_inner(id, renames, reference_node_ids, false);
4341 }
4342 }
4343 let is_obj_type_prop =
4344 node_type == "ObjectTypeProperty" || node_type == "ObjectTypeIndexer";
4345 for (key, val) in map.iter_mut() {
4346 let child_is_key = is_obj_type_prop && key == "key";
4347 apply_renames_to_json_inner(val, renames, reference_node_ids, child_is_key);
4348 }
4349 }
4350 serde_json::Value::Array(arr) => {
4351 for item in arr {
4352 apply_renames_to_json_inner(item, renames, reference_node_ids, false);
4353 }
4354 }
4355 _ => {}
4356 }
4357 }
4358
4359 #[cfg(test)]
4360 mod tests {
4361 use react_compiler_ast::statements::Statement;
4362 use serde_json::json;
4363
4364 use super::{UnsupportedOriginalNode, codegen_unsupported_original_node};
4365
4366 /// The Fast Refresh source hash must match Node's
4367 /// `createHmac('sha256', code).digest('hex')` byte-for-byte, or hot-reload
4368 /// cache invalidation would diverge from the TS compiler. Reference values
4369 /// were computed with Node's `crypto` module.
4370 #[test]
4371 fn source_file_hash_matches_node_create_hmac() {
4372 use super::source_file_hash;
4373 assert_eq!(
4374 source_file_hash("hello world"),
4375 "0de8bee5d7f9c5d209f8c6fabed0ea84cb3fca1244e8ed38079a61b599a84c47"
4376 );
4377 assert_eq!(
4378 source_file_hash(""),
4379 "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"
4380 );
4381 assert_eq!(
4382 source_file_hash("function App(){}"),
4383 "d637acb4985c789d6622c70197db2b62dda282f16f3276aa810b598d6e6cab7b"
4384 );
4385 }
4386
4387 /// A modeled statement tag parses typed and is emitted directly.
4388 #[test]
4389 fn unsupported_original_node_modeled_statement_tag_emits_statement() {
4390 let node = json!({ "type": "DebuggerStatement", "start": 0, "end": 9 });
4391 match codegen_unsupported_original_node(&node).unwrap() {
4392 UnsupportedOriginalNode::Statement(Statement::DebuggerStatement(_)) => {}
4393 UnsupportedOriginalNode::Statement(other) => {
4394 panic!("expected typed DebuggerStatement, got {other:?}")
4395 }
4396 UnsupportedOriginalNode::ExpressionCodegen => {
4397 panic!("statement tag must not flow to expression codegen")
4398 }
4399 }
4400 }
4401
4402 /// A modeled statement tag with a malformed body is a serialize/
4403 /// deserialize asymmetry: error loudly, never degrade to `Unknown`.
4404 #[test]
4405 fn unsupported_original_node_malformed_statement_tag_errors() {
4406 let node = json!({ "type": "IfStatement", "consequent": { "type": "EmptyStatement" } });
4407 assert!(codegen_unsupported_original_node(&node).is_err());
4408 }
4409
4410 /// An expression tag flows to expression codegen, which binds the
4411 /// instruction's lvalue temporary. With the tolerant `Statement`
4412 /// deserializer, a plain try-parse-as-`Statement` would wrongly claim
4413 /// this node as `Statement::Unknown`.
4414 #[test]
4415 fn unsupported_original_node_expression_tag_flows_to_expression_codegen() {
4416 let node = json!({
4417 "type": "CallExpression",
4418 "callee": { "type": "Identifier", "name": "foo" },
4419 "arguments": []
4420 });
4421 assert!(matches!(
4422 codegen_unsupported_original_node(&node).unwrap(),
4423 UnsupportedOriginalNode::ExpressionCodegen
4424 ));
4425 }
4426
4427 /// A pattern tag (destructuring bailout target) also flows to expression
4428 /// codegen, preserving its placeholder fallback there.
4429 #[test]
4430 fn unsupported_original_node_pattern_tag_flows_to_expression_codegen() {
4431 let node = json!({ "type": "ObjectPattern", "properties": [] });
4432 assert!(matches!(
4433 codegen_unsupported_original_node(&node).unwrap(),
4434 UnsupportedOriginalNode::ExpressionCodegen
4435 ));
4436 }
4437
4438 /// A genuinely unmodeled tag is producible only by the unknown-statement
4439 /// lowering bailout, so it is preserved verbatim at statement position.
4440 #[test]
4441 fn unsupported_original_node_unknown_tag_becomes_unknown_statement() {
4442 let node = json!({
4443 "type": "TSImportEqualsDeclaration",
4444 "start": 0,
4445 "end": 39,
4446 "id": { "type": "Identifier", "name": "lib" }
4447 });
4448 match codegen_unsupported_original_node(&node).unwrap() {
4449 UnsupportedOriginalNode::Statement(Statement::Unknown(unknown)) => {
4450 assert_eq!(unknown.node_type(), "TSImportEqualsDeclaration");
4451 assert_eq!(unknown.raw().parse_value(), node);
4452 }
4453 UnsupportedOriginalNode::Statement(other) => {
4454 panic!("expected Statement::Unknown, got {other:?}")
4455 }
4456 UnsupportedOriginalNode::ExpressionCodegen => {
4457 panic!("unmodeled tag must not flow to expression codegen")
4458 }
4459 }
4460 }
4461 }