Skip to main content

react_compiler/
debug_print.rs

1use react_compiler_diagnostics::CompilerError;
2use react_compiler_hir::environment::Environment;
3use react_compiler_hir::print::{self, PrintFormatter};
4use react_compiler_hir::{
5    BasicBlock, BlockId, HirFunction, Instruction, ParamPattern, Place, Terminal,
6};
7
8// =============================================================================
9// DebugPrinter struct — thin wrapper around PrintFormatter for HIR-specific logic
10// =============================================================================
11
12struct DebugPrinter<'a> {
13    fmt: PrintFormatter<'a>,
14}
15
16impl<'a> DebugPrinter<'a> {
17    fn new(env: &'a Environment) -> Self {
18        Self {
19            fmt: PrintFormatter::new(env),
20        }
21    }
22
23    // =========================================================================
24    // Function
25    // =========================================================================
26
27    fn format_function(&mut self, func: &HirFunction) {
28        self.fmt.indent();
29        self.fmt.line(&format!(
30            "id: {}",
31            match &func.id {
32                Some(id) => format!("\"{}\"", id),
33                None => "null".to_string(),
34            }
35        ));
36        self.fmt.line(&format!(
37            "name_hint: {}",
38            match &func.name_hint {
39                Some(h) => format!("\"{}\"", h),
40                None => "null".to_string(),
41            }
42        ));
43        self.fmt.line(&format!("fn_type: {:?}", func.fn_type));
44        self.fmt.line(&format!("generator: {}", func.generator));
45        self.fmt.line(&format!("is_async: {}", func.is_async));
46        self.fmt.line(&format!("loc: {}", print::format_loc(&func.loc)));
47
48        // params
49        self.fmt.line("params:");
50        self.fmt.indent();
51        for (i, param) in func.params.iter().enumerate() {
52            match param {
53                ParamPattern::Place(place) => {
54                    self.fmt.format_place_field(&format!("[{}]", i), place);
55                }
56                ParamPattern::Spread(spread) => {
57                    self.fmt.line(&format!("[{}] Spread:", i));
58                    self.fmt.indent();
59                    self.fmt.format_place_field("place", &spread.place);
60                    self.fmt.dedent();
61                }
62            }
63        }
64        self.fmt.dedent();
65
66        // returns
67        self.fmt.line("returns:");
68        self.fmt.indent();
69        self.fmt.format_place_field("value", &func.returns);
70        self.fmt.dedent();
71
72        // context
73        self.fmt.line("context:");
74        self.fmt.indent();
75        for (i, place) in func.context.iter().enumerate() {
76            self.fmt.format_place_field(&format!("[{}]", i), place);
77        }
78        self.fmt.dedent();
79
80        // aliasing_effects
81        match &func.aliasing_effects {
82            Some(effects) => {
83                self.fmt.line("aliasingEffects:");
84                self.fmt.indent();
85                for (i, eff) in effects.iter().enumerate() {
86                    self.fmt.line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
87                }
88                self.fmt.dedent();
89            }
90            None => self.fmt.line("aliasingEffects: null"),
91        }
92
93        // directives
94        self.fmt.line("directives:");
95        self.fmt.indent();
96        for (i, d) in func.directives.iter().enumerate() {
97            self.fmt.line(&format!("[{}] \"{}\"", i, d));
98        }
99        self.fmt.dedent();
100
101        // return_type_annotation
102        self.fmt.line(&format!(
103            "returnTypeAnnotation: {}",
104            match &func.return_type_annotation {
105                Some(ann) => ann.clone(),
106                None => "null".to_string(),
107            }
108        ));
109
110        self.fmt.line("");
111        self.fmt.line("Blocks:");
112        self.fmt.indent();
113        for (block_id, block) in &func.body.blocks {
114            self.format_block(block_id, block, &func.instructions);
115        }
116        self.fmt.dedent();
117        self.fmt.dedent();
118    }
119
120    // =========================================================================
121    // Block
122    // =========================================================================
123
124    fn format_block(
125        &mut self,
126        block_id: &BlockId,
127        block: &BasicBlock,
128        instructions: &[Instruction],
129    ) {
130        self.fmt.line(&format!("bb{} ({}):", block_id.0, block.kind));
131        self.fmt.indent();
132
133        // preds
134        let preds: Vec<String> = block.preds.iter().map(|p| format!("bb{}", p.0)).collect();
135        self.fmt.line(&format!("preds: [{}]", preds.join(", ")));
136
137        // phis
138        self.fmt.line("phis:");
139        self.fmt.indent();
140        for phi in &block.phis {
141            self.format_phi(phi);
142        }
143        self.fmt.dedent();
144
145        // instructions
146        self.fmt.line("instructions:");
147        self.fmt.indent();
148        for (index, instr_id) in block.instructions.iter().enumerate() {
149            let instr = &instructions[instr_id.0 as usize];
150            self.format_instruction(instr, index);
151        }
152        self.fmt.dedent();
153
154        // terminal
155        self.fmt.line("terminal:");
156        self.fmt.indent();
157        self.format_terminal(&block.terminal);
158        self.fmt.dedent();
159
160        self.fmt.dedent();
161    }
162
163    // =========================================================================
164    // Phi
165    // =========================================================================
166
167    fn format_phi(&mut self, phi: &react_compiler_hir::Phi) {
168        self.fmt.line("Phi {");
169        self.fmt.indent();
170        self.fmt.format_place_field("place", &phi.place);
171        self.fmt.line("operands:");
172        self.fmt.indent();
173        for (block_id, place) in &phi.operands {
174            self.fmt.line(&format!("bb{}:", block_id.0));
175            self.fmt.indent();
176            self.fmt.format_place_field("value", place);
177            self.fmt.dedent();
178        }
179        self.fmt.dedent();
180        self.fmt.dedent();
181        self.fmt.line("}");
182    }
183
184    // =========================================================================
185    // Instruction
186    // =========================================================================
187
188    fn format_instruction(&mut self, instr: &Instruction, index: usize) {
189        self.fmt.line(&format!("[{}] Instruction {{", index));
190        self.fmt.indent();
191        self.fmt.line(&format!("id: {}", instr.id.0));
192        self.fmt.format_place_field("lvalue", &instr.lvalue);
193        self.fmt.line("value:");
194        self.fmt.indent();
195        // For the HIR printer, inner functions are formatted via format_function
196        self.fmt.format_instruction_value(
197            &instr.value,
198            Some(&|fmt: &mut PrintFormatter, func: &HirFunction| {
199                // We need to recursively format the inner function
200                // Use a temporary DebugPrinter that shares the formatter state
201                let mut inner = DebugPrinter {
202                    fmt: PrintFormatter {
203                        env: fmt.env,
204                        seen_identifiers: std::mem::take(&mut fmt.seen_identifiers),
205                        seen_scopes: std::mem::take(&mut fmt.seen_scopes),
206                        output: Vec::new(),
207                        indent_level: fmt.indent_level,
208                    },
209                };
210                inner.format_function(func);
211                // Write the output lines into the parent formatter
212                for line in &inner.fmt.output {
213                    fmt.line_raw(line);
214                }
215                // Copy back the seen state
216                fmt.seen_identifiers = inner.fmt.seen_identifiers;
217                fmt.seen_scopes = inner.fmt.seen_scopes;
218            }),
219        );
220        self.fmt.dedent();
221        match &instr.effects {
222            Some(effects) => {
223                self.fmt.line("effects:");
224                self.fmt.indent();
225                for (i, eff) in effects.iter().enumerate() {
226                    self.fmt.line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
227                }
228                self.fmt.dedent();
229            }
230            None => self.fmt.line("effects: null"),
231        }
232        self.fmt.line(&format!("loc: {}", print::format_loc(&instr.loc)));
233        self.fmt.dedent();
234        self.fmt.line("}");
235    }
236
237    // =========================================================================
238    // Terminal
239    // =========================================================================
240
241    fn format_terminal(&mut self, terminal: &Terminal) {
242        match terminal {
243            Terminal::If {
244                test,
245                consequent,
246                alternate,
247                fallthrough,
248                id,
249                loc,
250            } => {
251                self.fmt.line("If {");
252                self.fmt.indent();
253                self.fmt.line(&format!("id: {}", id.0));
254                self.fmt.format_place_field("test", test);
255                self.fmt.line(&format!("consequent: bb{}", consequent.0));
256                self.fmt.line(&format!("alternate: bb{}", alternate.0));
257                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
258                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
259                self.fmt.dedent();
260                self.fmt.line("}");
261            }
262            Terminal::Branch {
263                test,
264                consequent,
265                alternate,
266                fallthrough,
267                id,
268                loc,
269            } => {
270                self.fmt.line("Branch {");
271                self.fmt.indent();
272                self.fmt.line(&format!("id: {}", id.0));
273                self.fmt.format_place_field("test", test);
274                self.fmt.line(&format!("consequent: bb{}", consequent.0));
275                self.fmt.line(&format!("alternate: bb{}", alternate.0));
276                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
277                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
278                self.fmt.dedent();
279                self.fmt.line("}");
280            }
281            Terminal::Logical {
282                operator,
283                test,
284                fallthrough,
285                id,
286                loc,
287            } => {
288                self.fmt.line("Logical {");
289                self.fmt.indent();
290                self.fmt.line(&format!("id: {}", id.0));
291                self.fmt.line(&format!("operator: \"{}\"", operator));
292                self.fmt.line(&format!("test: bb{}", test.0));
293                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
294                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
295                self.fmt.dedent();
296                self.fmt.line("}");
297            }
298            Terminal::Ternary {
299                test,
300                fallthrough,
301                id,
302                loc,
303            } => {
304                self.fmt.line("Ternary {");
305                self.fmt.indent();
306                self.fmt.line(&format!("id: {}", id.0));
307                self.fmt.line(&format!("test: bb{}", test.0));
308                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
309                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
310                self.fmt.dedent();
311                self.fmt.line("}");
312            }
313            Terminal::Optional {
314                optional,
315                test,
316                fallthrough,
317                id,
318                loc,
319            } => {
320                self.fmt.line("Optional {");
321                self.fmt.indent();
322                self.fmt.line(&format!("id: {}", id.0));
323                self.fmt.line(&format!("optional: {}", optional));
324                self.fmt.line(&format!("test: bb{}", test.0));
325                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
326                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
327                self.fmt.dedent();
328                self.fmt.line("}");
329            }
330            Terminal::Throw { value, id, loc } => {
331                self.fmt.line("Throw {");
332                self.fmt.indent();
333                self.fmt.line(&format!("id: {}", id.0));
334                self.fmt.format_place_field("value", value);
335                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
336                self.fmt.dedent();
337                self.fmt.line("}");
338            }
339            Terminal::Return {
340                value,
341                return_variant,
342                id,
343                loc,
344                effects,
345            } => {
346                self.fmt.line("Return {");
347                self.fmt.indent();
348                self.fmt.line(&format!("id: {}", id.0));
349                self.fmt.line(&format!("returnVariant: {:?}", return_variant));
350                self.fmt.format_place_field("value", value);
351                match effects {
352                    Some(e) => {
353                        self.fmt.line("effects:");
354                        self.fmt.indent();
355                        for (i, eff) in e.iter().enumerate() {
356                            self.fmt.line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
357                        }
358                        self.fmt.dedent();
359                    }
360                    None => self.fmt.line("effects: null"),
361                }
362                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
363                self.fmt.dedent();
364                self.fmt.line("}");
365            }
366            Terminal::Goto {
367                block,
368                variant,
369                id,
370                loc,
371            } => {
372                self.fmt.line("Goto {");
373                self.fmt.indent();
374                self.fmt.line(&format!("id: {}", id.0));
375                self.fmt.line(&format!("block: bb{}", block.0));
376                self.fmt.line(&format!("variant: {:?}", variant));
377                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
378                self.fmt.dedent();
379                self.fmt.line("}");
380            }
381            Terminal::Switch {
382                test,
383                cases,
384                fallthrough,
385                id,
386                loc,
387            } => {
388                self.fmt.line("Switch {");
389                self.fmt.indent();
390                self.fmt.line(&format!("id: {}", id.0));
391                self.fmt.format_place_field("test", test);
392                self.fmt.line("cases:");
393                self.fmt.indent();
394                for (i, case) in cases.iter().enumerate() {
395                    match &case.test {
396                        Some(p) => {
397                            self.fmt.line(&format!("[{}] Case {{", i));
398                            self.fmt.indent();
399                            self.fmt.format_place_field("test", p);
400                            self.fmt.line(&format!("block: bb{}", case.block.0));
401                            self.fmt.dedent();
402                            self.fmt.line("}");
403                        }
404                        None => {
405                            self.fmt.line(&format!(
406                                "[{}] Default {{ block: bb{} }}",
407                                i, case.block.0
408                            ));
409                        }
410                    }
411                }
412                self.fmt.dedent();
413                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
414                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
415                self.fmt.dedent();
416                self.fmt.line("}");
417            }
418            Terminal::DoWhile {
419                loop_block,
420                test,
421                fallthrough,
422                id,
423                loc,
424            } => {
425                self.fmt.line("DoWhile {");
426                self.fmt.indent();
427                self.fmt.line(&format!("id: {}", id.0));
428                self.fmt.line(&format!("loop: bb{}", loop_block.0));
429                self.fmt.line(&format!("test: bb{}", test.0));
430                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
431                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
432                self.fmt.dedent();
433                self.fmt.line("}");
434            }
435            Terminal::While {
436                test,
437                loop_block,
438                fallthrough,
439                id,
440                loc,
441            } => {
442                self.fmt.line("While {");
443                self.fmt.indent();
444                self.fmt.line(&format!("id: {}", id.0));
445                self.fmt.line(&format!("test: bb{}", test.0));
446                self.fmt.line(&format!("loop: bb{}", loop_block.0));
447                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
448                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
449                self.fmt.dedent();
450                self.fmt.line("}");
451            }
452            Terminal::For {
453                init,
454                test,
455                update,
456                loop_block,
457                fallthrough,
458                id,
459                loc,
460            } => {
461                self.fmt.line("For {");
462                self.fmt.indent();
463                self.fmt.line(&format!("id: {}", id.0));
464                self.fmt.line(&format!("init: bb{}", init.0));
465                self.fmt.line(&format!("test: bb{}", test.0));
466                self.fmt.line(&format!(
467                    "update: {}",
468                    match update {
469                        Some(u) => format!("bb{}", u.0),
470                        None => "null".to_string(),
471                    }
472                ));
473                self.fmt.line(&format!("loop: bb{}", loop_block.0));
474                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
475                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
476                self.fmt.dedent();
477                self.fmt.line("}");
478            }
479            Terminal::ForOf {
480                init,
481                test,
482                loop_block,
483                fallthrough,
484                id,
485                loc,
486            } => {
487                self.fmt.line("ForOf {");
488                self.fmt.indent();
489                self.fmt.line(&format!("id: {}", id.0));
490                self.fmt.line(&format!("init: bb{}", init.0));
491                self.fmt.line(&format!("test: bb{}", test.0));
492                self.fmt.line(&format!("loop: bb{}", loop_block.0));
493                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
494                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
495                self.fmt.dedent();
496                self.fmt.line("}");
497            }
498            Terminal::ForIn {
499                init,
500                loop_block,
501                fallthrough,
502                id,
503                loc,
504            } => {
505                self.fmt.line("ForIn {");
506                self.fmt.indent();
507                self.fmt.line(&format!("id: {}", id.0));
508                self.fmt.line(&format!("init: bb{}", init.0));
509                self.fmt.line(&format!("loop: bb{}", loop_block.0));
510                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
511                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
512                self.fmt.dedent();
513                self.fmt.line("}");
514            }
515            Terminal::Label {
516                block,
517                fallthrough,
518                id,
519                loc,
520            } => {
521                self.fmt.line("Label {");
522                self.fmt.indent();
523                self.fmt.line(&format!("id: {}", id.0));
524                self.fmt.line(&format!("block: bb{}", block.0));
525                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
526                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
527                self.fmt.dedent();
528                self.fmt.line("}");
529            }
530            Terminal::Sequence {
531                block,
532                fallthrough,
533                id,
534                loc,
535            } => {
536                self.fmt.line("Sequence {");
537                self.fmt.indent();
538                self.fmt.line(&format!("id: {}", id.0));
539                self.fmt.line(&format!("block: bb{}", block.0));
540                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
541                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
542                self.fmt.dedent();
543                self.fmt.line("}");
544            }
545            Terminal::Unreachable { id, loc } => {
546                self.fmt.line(&format!(
547                    "Unreachable {{ id: {}, loc: {} }}",
548                    id.0,
549                    print::format_loc(loc)
550                ));
551            }
552            Terminal::Unsupported { id, loc } => {
553                self.fmt.line(&format!(
554                    "Unsupported {{ id: {}, loc: {} }}",
555                    id.0,
556                    print::format_loc(loc)
557                ));
558            }
559            Terminal::MaybeThrow {
560                continuation,
561                handler,
562                id,
563                loc,
564                effects,
565            } => {
566                self.fmt.line("MaybeThrow {");
567                self.fmt.indent();
568                self.fmt.line(&format!("id: {}", id.0));
569                self.fmt.line(&format!("continuation: bb{}", continuation.0));
570                self.fmt.line(&format!(
571                    "handler: {}",
572                    match handler {
573                        Some(h) => format!("bb{}", h.0),
574                        None => "null".to_string(),
575                    }
576                ));
577                match effects {
578                    Some(e) => {
579                        self.fmt.line("effects:");
580                        self.fmt.indent();
581                        for (i, eff) in e.iter().enumerate() {
582                            self.fmt.line(&format!("[{}] {}", i, self.fmt.format_effect(eff)));
583                        }
584                        self.fmt.dedent();
585                    }
586                    None => self.fmt.line("effects: null"),
587                }
588                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
589                self.fmt.dedent();
590                self.fmt.line("}");
591            }
592            Terminal::Scope {
593                fallthrough,
594                block,
595                scope,
596                id,
597                loc,
598            } => {
599                self.fmt.line("Scope {");
600                self.fmt.indent();
601                self.fmt.line(&format!("id: {}", id.0));
602                self.fmt.format_scope_field("scope", *scope);
603                self.fmt.line(&format!("block: bb{}", block.0));
604                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
605                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
606                self.fmt.dedent();
607                self.fmt.line("}");
608            }
609            Terminal::PrunedScope {
610                fallthrough,
611                block,
612                scope,
613                id,
614                loc,
615            } => {
616                self.fmt.line("PrunedScope {");
617                self.fmt.indent();
618                self.fmt.line(&format!("id: {}", id.0));
619                self.fmt.format_scope_field("scope", *scope);
620                self.fmt.line(&format!("block: bb{}", block.0));
621                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
622                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
623                self.fmt.dedent();
624                self.fmt.line("}");
625            }
626            Terminal::Try {
627                block,
628                handler_binding,
629                handler,
630                fallthrough,
631                id,
632                loc,
633            } => {
634                self.fmt.line("Try {");
635                self.fmt.indent();
636                self.fmt.line(&format!("id: {}", id.0));
637                self.fmt.line(&format!("block: bb{}", block.0));
638                self.fmt.line(&format!("handler: bb{}", handler.0));
639                match handler_binding {
640                    Some(p) => self.fmt.format_place_field("handlerBinding", p),
641                    None => self.fmt.line("handlerBinding: null"),
642                }
643                self.fmt.line(&format!("fallthrough: bb{}", fallthrough.0));
644                self.fmt.line(&format!("loc: {}", print::format_loc(loc)));
645                self.fmt.dedent();
646                self.fmt.line("}");
647            }
648        }
649    }
650}
651
652// =============================================================================
653// Entry point
654// =============================================================================
655
656pub fn debug_hir(hir: &HirFunction, env: &Environment) -> String {
657    let mut printer = DebugPrinter::new(env);
658    printer.format_function(hir);
659
660    // Print outlined functions (matches TS DebugPrintHIR.ts: printDebugHIR)
661    for outlined in env.get_outlined_functions() {
662        printer.fmt.line("");
663        printer.format_function(&outlined.func);
664    }
665
666    printer.fmt.line("");
667    printer.fmt.line("Environment:");
668    printer.fmt.indent();
669    printer.fmt.format_errors(&env.errors);
670    printer.fmt.dedent();
671
672    printer.fmt.to_string_output()
673}
674
675// =============================================================================
676// Error formatting (kept for backward compatibility)
677// =============================================================================
678
679pub fn format_errors(error: &CompilerError) -> String {
680    let env = Environment::new();
681    let mut fmt = PrintFormatter::new(&env);
682    fmt.format_errors(error);
683    fmt.to_string_output()
684}
685
686/// Format an HIR function into a reactive PrintFormatter.
687/// This bridges the two debug printers so inner functions in FunctionExpression/ObjectMethod
688/// can be printed within the reactive function output.
689pub fn format_hir_function_into(
690    reactive_fmt: &mut PrintFormatter,
691    func: &HirFunction,
692) {
693    // Create a temporary DebugPrinter that shares the same environment
694    let mut printer = DebugPrinter {
695        fmt: PrintFormatter {
696            env: reactive_fmt.env,
697            seen_identifiers: std::mem::take(&mut reactive_fmt.seen_identifiers),
698            seen_scopes: std::mem::take(&mut reactive_fmt.seen_scopes),
699            output: Vec::new(),
700            indent_level: reactive_fmt.indent_level,
701        },
702    };
703    printer.format_function(func);
704
705    // Write the output lines into the reactive formatter
706    for line in &printer.fmt.output {
707        reactive_fmt.line_raw(line);
708    }
709    // Copy back the seen state
710    reactive_fmt.seen_identifiers = printer.fmt.seen_identifiers;
711    reactive_fmt.seen_scopes = printer.fmt.seen_scopes;
712}
713
714// =============================================================================
715// Helpers for effect formatting (kept for backward compatibility)
716// =============================================================================
717
718#[allow(dead_code)]
719fn format_place_short(place: &Place, env: &Environment) -> String {
720    let ident = &env.identifiers[place.identifier.0 as usize];
721    let name = match &ident.name {
722        Some(name) => name.value().to_string(),
723        None => String::new(),
724    };
725    let scope = match ident.scope {
726        Some(scope_id) => format!(":{}", scope_id.0),
727        None => String::new(),
728    };
729    format!("{}${}{}", name, place.identifier.0, scope)
730}