@samitouri / QOS-React-2 / commits / 08e51d3fd7

[rust] Pretty-printer for HIR

Implements a pretty-printer for the HIR and switches the fixture tests to use this instead of the debug format. It's much more readable now! Note that not all types are properly printed, I only implemented the instructions and terminals used in the example. For others we fall back to the Debug impl so we at least print something.

Joe Savona committed Jul 6, 2023 at 09:24 UTC 08e51d3fd72db5f11d9a1dd35480faa5d2305f65
7 files changed +192 -557
compiler/forget/crates/fixtures/tests/fixtures/simple.js
+1 -1
@@ -1,4 +1,4 @@
1 function test() {
2 - [true, false, null, 1, 3.14, "hello world!"];
2 + [true, false, null, 1, 3.14, ...["hello world!"]];
3 return 2;
4 }
compiler/forget/crates/fixtures/tests/fixtures_test.rs
+9 -6
@@ -2,7 +2,7 @@ use build_hir::build;
2 use bumpalo::Bump;
3 use estree::{ModuleItem, Statement};
4 use estree_swc::parse;
5 -use hir::{Environment, Registry};
5 +use hir::{Environment, Print, Registry};
6 use insta::{assert_snapshot, glob};
7
8 #[test]
@@ -11,9 +11,9 @@ fn fixtures() {
11 let input = std::fs::read_to_string(path).unwrap();
12 let ast = parse(&input, path.to_str().unwrap()).unwrap();
13
14 - let mut output = Vec::new();
14 + let mut output = String::new();
15
16 - for item in ast.body {
16 + for (ix, item) in ast.body.into_iter().enumerate() {
17 if let ModuleItem::Statement(stmt) = item {
18 if let Statement::FunctionDeclaration(fun) = *stmt {
19 let allocator = Bump::new();
@@ -25,12 +25,15 @@ fn fixtures() {
25 Registry,
26 ));
27 let hir = build(&environment, *fun).unwrap();
28 - output.push(format!("{hir:#?}"));
28 +
29 + if ix != 0 {
30 + output.push_str("\n\n");
31 + }
32 + hir.print(&mut output).unwrap();
33 }
34 }
35 }
36
33 - let joined = output.join("\n\n");
34 - assert_snapshot!(format!("Input:\n{input}\n\nOutput:\n{joined}"));
37 + assert_snapshot!(format!("Input:\n{input}\n\nOutput:\n{output}"));
38 });
39 }
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures.snap
+15 -549
@@ -1,560 +1,26 @@
1 ---
2 source: crates/fixtures/tests/fixtures_test.rs
3 -expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{joined}\")"
3 +expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
4 input_file: crates/fixtures/tests/fixtures/simple.js
5 ---
6 Input:
7 function test() {
8 - [true, false, null, 1, 3.14, "hello world!"];
8 + [true, false, null, 1, 3.14, ...["hello world!"]];
9 return 2;
10 }
11
12
13 Output:
14 -Function {
15 - body: HIR {
16 - entry: BlockId(
17 - 0,
18 - ),
19 - blocks: {
20 - BlockId(
21 - 0,
22 - ): BasicBlock {
23 - id: BlockId(
24 - 0,
25 - ),
26 - kind: Block,
27 - instructions: [
28 - Instruction {
29 - id: InstructionId(
30 - 0,
31 - ),
32 - lvalue: Place {
33 - identifier: Identifier {
34 - id: IdentifierId(
35 - 0,
36 - ),
37 - name: None,
38 - data: RefCell {
39 - value: IdentifierData {
40 - mutable_range: MutableRange {
41 - start: InstructionId(
42 - 0,
43 - ),
44 - end: InstructionId(
45 - 0,
46 - ),
47 - },
48 - scope: None,
49 - type_: Var(
50 - TypeVarId(
51 - 0,
52 - ),
53 - ),
54 - },
55 - },
56 - },
57 - effect: None,
58 - },
59 - value: Primitive(
60 - Primitive {
61 - value: Boolean(
62 - true,
63 - ),
64 - },
65 - ),
66 - },
67 - Instruction {
68 - id: InstructionId(
69 - 1,
70 - ),
71 - lvalue: Place {
72 - identifier: Identifier {
73 - id: IdentifierId(
74 - 1,
75 - ),
76 - name: None,
77 - data: RefCell {
78 - value: IdentifierData {
79 - mutable_range: MutableRange {
80 - start: InstructionId(
81 - 0,
82 - ),
83 - end: InstructionId(
84 - 0,
85 - ),
86 - },
87 - scope: None,
88 - type_: Var(
89 - TypeVarId(
90 - 1,
91 - ),
92 - ),
93 - },
94 - },
95 - },
96 - effect: None,
97 - },
98 - value: Primitive(
99 - Primitive {
100 - value: Boolean(
101 - false,
102 - ),
103 - },
104 - ),
105 - },
106 - Instruction {
107 - id: InstructionId(
108 - 2,
109 - ),
110 - lvalue: Place {
111 - identifier: Identifier {
112 - id: IdentifierId(
113 - 2,
114 - ),
115 - name: None,
116 - data: RefCell {
117 - value: IdentifierData {
118 - mutable_range: MutableRange {
119 - start: InstructionId(
120 - 0,
121 - ),
122 - end: InstructionId(
123 - 0,
124 - ),
125 - },
126 - scope: None,
127 - type_: Var(
128 - TypeVarId(
129 - 2,
130 - ),
131 - ),
132 - },
133 - },
134 - },
135 - effect: None,
136 - },
137 - value: Primitive(
138 - Primitive {
139 - value: Null,
140 - },
141 - ),
142 - },
143 - Instruction {
144 - id: InstructionId(
145 - 3,
146 - ),
147 - lvalue: Place {
148 - identifier: Identifier {
149 - id: IdentifierId(
150 - 3,
151 - ),
152 - name: None,
153 - data: RefCell {
154 - value: IdentifierData {
155 - mutable_range: MutableRange {
156 - start: InstructionId(
157 - 0,
158 - ),
159 - end: InstructionId(
160 - 0,
161 - ),
162 - },
163 - scope: None,
164 - type_: Var(
165 - TypeVarId(
166 - 3,
167 - ),
168 - ),
169 - },
170 - },
171 - },
172 - effect: None,
173 - },
174 - value: Primitive(
175 - Primitive {
176 - value: Number(
177 - Number(
178 - 4607182418800017408,
179 - ),
180 - ),
181 - },
182 - ),
183 - },
184 - Instruction {
185 - id: InstructionId(
186 - 4,
187 - ),
188 - lvalue: Place {
189 - identifier: Identifier {
190 - id: IdentifierId(
191 - 4,
192 - ),
193 - name: None,
194 - data: RefCell {
195 - value: IdentifierData {
196 - mutable_range: MutableRange {
197 - start: InstructionId(
198 - 0,
199 - ),
200 - end: InstructionId(
201 - 0,
202 - ),
203 - },
204 - scope: None,
205 - type_: Var(
206 - TypeVarId(
207 - 4,
208 - ),
209 - ),
210 - },
211 - },
212 - },
213 - effect: None,
214 - },
215 - value: Primitive(
216 - Primitive {
217 - value: Number(
218 - Number(
219 - 4614253070214989087,
220 - ),
221 - ),
222 - },
223 - ),
224 - },
225 - Instruction {
226 - id: InstructionId(
227 - 5,
228 - ),
229 - lvalue: Place {
230 - identifier: Identifier {
231 - id: IdentifierId(
232 - 5,
233 - ),
234 - name: None,
235 - data: RefCell {
236 - value: IdentifierData {
237 - mutable_range: MutableRange {
238 - start: InstructionId(
239 - 0,
240 - ),
241 - end: InstructionId(
242 - 0,
243 - ),
244 - },
245 - scope: None,
246 - type_: Var(
247 - TypeVarId(
248 - 5,
249 - ),
250 - ),
251 - },
252 - },
253 - },
254 - effect: None,
255 - },
256 - value: Primitive(
257 - Primitive {
258 - value: String(
259 - "hello world!",
260 - ),
261 - },
262 - ),
263 - },
264 - Instruction {
265 - id: InstructionId(
266 - 6,
267 - ),
268 - lvalue: Place {
269 - identifier: Identifier {
270 - id: IdentifierId(
271 - 6,
272 - ),
273 - name: None,
274 - data: RefCell {
275 - value: IdentifierData {
276 - mutable_range: MutableRange {
277 - start: InstructionId(
278 - 0,
279 - ),
280 - end: InstructionId(
281 - 0,
282 - ),
283 - },
284 - scope: None,
285 - type_: Var(
286 - TypeVarId(
287 - 6,
288 - ),
289 - ),
290 - },
291 - },
292 - },
293 - effect: None,
294 - },
295 - value: Array(
296 - Array {
297 - elements: [
298 - Place(
299 - Place {
300 - identifier: Identifier {
301 - id: IdentifierId(
302 - 0,
303 - ),
304 - name: None,
305 - data: RefCell {
306 - value: IdentifierData {
307 - mutable_range: MutableRange {
308 - start: InstructionId(
309 - 0,
310 - ),
311 - end: InstructionId(
312 - 0,
313 - ),
314 - },
315 - scope: None,
316 - type_: Var(
317 - TypeVarId(
318 - 0,
319 - ),
320 - ),
321 - },
322 - },
323 - },
324 - effect: None,
325 - },
326 - ),
327 - Place(
328 - Place {
329 - identifier: Identifier {
330 - id: IdentifierId(
331 - 1,
332 - ),
333 - name: None,
334 - data: RefCell {
335 - value: IdentifierData {
336 - mutable_range: MutableRange {
337 - start: InstructionId(
338 - 0,
339 - ),
340 - end: InstructionId(
341 - 0,
342 - ),
343 - },
344 - scope: None,
345 - type_: Var(
346 - TypeVarId(
347 - 1,
348 - ),
349 - ),
350 - },
351 - },
352 - },
353 - effect: None,
354 - },
355 - ),
356 - Place(
357 - Place {
358 - identifier: Identifier {
359 - id: IdentifierId(
360 - 2,
361 - ),
362 - name: None,
363 - data: RefCell {
364 - value: IdentifierData {
365 - mutable_range: MutableRange {
366 - start: InstructionId(
367 - 0,
368 - ),
369 - end: InstructionId(
370 - 0,
371 - ),
372 - },
373 - scope: None,
374 - type_: Var(
375 - TypeVarId(
376 - 2,
377 - ),
378 - ),
379 - },
380 - },
381 - },
382 - effect: None,
383 - },
384 - ),
385 - Place(
386 - Place {
387 - identifier: Identifier {
388 - id: IdentifierId(
389 - 3,
390 - ),
391 - name: None,
392 - data: RefCell {
393 - value: IdentifierData {
394 - mutable_range: MutableRange {
395 - start: InstructionId(
396 - 0,
397 - ),
398 - end: InstructionId(
399 - 0,
400 - ),
401 - },
402 - scope: None,
403 - type_: Var(
404 - TypeVarId(
405 - 3,
406 - ),
407 - ),
408 - },
409 - },
410 - },
411 - effect: None,
412 - },
413 - ),
414 - Place(
415 - Place {
416 - identifier: Identifier {
417 - id: IdentifierId(
418 - 4,
419 - ),
420 - name: None,
421 - data: RefCell {
422 - value: IdentifierData {
423 - mutable_range: MutableRange {
424 - start: InstructionId(
425 - 0,
426 - ),
427 - end: InstructionId(
428 - 0,
429 - ),
430 - },
431 - scope: None,
432 - type_: Var(
433 - TypeVarId(
434 - 4,
435 - ),
436 - ),
437 - },
438 - },
439 - },
440 - effect: None,
441 - },
442 - ),
443 - Place(
444 - Place {
445 - identifier: Identifier {
446 - id: IdentifierId(
447 - 5,
448 - ),
449 - name: None,
450 - data: RefCell {
451 - value: IdentifierData {
452 - mutable_range: MutableRange {
453 - start: InstructionId(
454 - 0,
455 - ),
456 - end: InstructionId(
457 - 0,
458 - ),
459 - },
460 - scope: None,
461 - type_: Var(
462 - TypeVarId(
463 - 5,
464 - ),
465 - ),
466 - },
467 - },
468 - },
469 - effect: None,
470 - },
471 - ),
472 - ],
473 - },
474 - ),
475 - },
476 - Instruction {
477 - id: InstructionId(
478 - 7,
479 - ),
480 - lvalue: Place {
481 - identifier: Identifier {
482 - id: IdentifierId(
483 - 7,
484 - ),
485 - name: None,
486 - data: RefCell {
487 - value: IdentifierData {
488 - mutable_range: MutableRange {
489 - start: InstructionId(
490 - 0,
491 - ),
492 - end: InstructionId(
493 - 0,
494 - ),
495 - },
496 - scope: None,
497 - type_: Var(
498 - TypeVarId(
499 - 7,
500 - ),
501 - ),
502 - },
503 - },
504 - },
505 - effect: None,
506 - },
507 - value: Primitive(
508 - Primitive {
509 - value: Number(
510 - Number(
511 - 4611686018427387904,
512 - ),
513 - ),
514 - },
515 - ),
516 - },
517 - ],
518 - terminal: Terminal {
519 - id: InstructionId(
520 - 8,
521 - ),
522 - value: ReturnTerminal(
523 - ReturnTerminal {
524 - value: Place {
525 - identifier: Identifier {
526 - id: IdentifierId(
527 - 7,
528 - ),
529 - name: None,
530 - data: RefCell {
531 - value: IdentifierData {
532 - mutable_range: MutableRange {
533 - start: InstructionId(
534 - 0,
535 - ),
536 - end: InstructionId(
537 - 0,
538 - ),
539 - },
540 - scope: None,
541 - type_: Var(
542 - TypeVarId(
543 - 7,
544 - ),
545 - ),
546 - },
547 - },
548 - },
549 - effect: None,
550 - },
551 - },
552 - ),
553 - },
554 - predecessors: {},
555 - },
556 - },
557 - },
558 - is_async: false,
559 - is_generator: false,
560 -}
14 +entry bb0
15 +bb0
16 + [0] true
17 + [1] false
18 + [2] null
19 + [3] 1
20 + [4] 3.14
21 + [5] "hello world!"
22 + [6] Array [unknown $5]
23 + [7] Array [unknown $0, unknown $1, unknown $2, unknown $3, unknown $4, ...unknown $6]
24 + [8] 2
25 + [9] Return unknown $8
26 +
compiler/forget/crates/hir/src/id_types.rs
+20
@@ -1,3 +1,5 @@
1 +use std::fmt::Display;
2 +
3 /// Unique identifier for a basic block. Values are unique only with respect to
4 /// a single top-level function, and may be reused across different top-level
5 /// functions. Notably, ids *are* unique across the basic blocks of a function
@@ -12,6 +14,12 @@ impl BlockId {
14 }
15 }
16
17 +impl Display for BlockId {
18 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 + f.write_fmt(format_args!("bb{}", self.0))
20 + }
21 +}
22 +
23 /// Unique identifier for a variable within a program. This is used to distinguish
24 /// different instances of a variable with the same name in different scopes, or
25 /// even the same named identifier across reassignments (when in SSA form).
@@ -24,6 +32,12 @@ impl IdentifierId {
32 }
33 }
34
35 +impl Display for IdentifierId {
36 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 + f.write_fmt(format_args!("${}", self.0))
38 + }
39 +}
40 +
41 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
42 pub struct TypeVarId(pub(crate) u32);
43
@@ -42,6 +56,12 @@ impl TypeVarId {
56 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
57 pub struct InstructionId(pub(crate) u32);
58
59 +impl Display for InstructionId {
60 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 + f.write_fmt(format_args!("[{}]", self.0))
62 + }
63 +}
64 +
65 pub struct InstructionIdGenerator(u32);
66
67 impl InstructionIdGenerator {
compiler/forget/crates/hir/src/instruction.rs
+14 -1
@@ -1,4 +1,4 @@
1 -use std::{cell::RefCell, rc::Rc};
1 +use std::{cell::RefCell, fmt::Display, rc::Rc};
2
3 use bumpalo::collections::{String, Vec};
4
@@ -176,6 +176,19 @@ impl Effect {
176 }
177 }
178
179 +impl Display for Effect {
180 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 + f.write_str(match self {
182 + Effect::Capture => "capture",
183 + Effect::ConditionallyMutate => "mutate?",
184 + Effect::Freeze => "freeze",
185 + Effect::Mutate => "mutate",
186 + Effect::Read => "read",
187 + Effect::Store => "store",
188 + })
189 + }
190 +}
191 +
192 #[derive(Clone, Debug)]
193 pub struct Identifier<'a> {
194 /// Uniquely identifiers this identifier
compiler/forget/crates/hir/src/lib.rs
+2
@@ -4,6 +4,7 @@ mod features;
4 mod function;
5 mod id_types;
6 mod instruction;
7 +mod print;
8 mod registry;
9 mod terminal;
10 mod types;
@@ -14,6 +15,7 @@ pub use features::*;
15 pub use function::*;
16 pub use id_types::*;
17 pub use instruction::*;
18 +pub use print::Print;
19 pub use registry::Registry;
20 pub use terminal::*;
21 pub use types::*;
compiler/forget/crates/hir/src/print.rs new
+131
@@ -0,0 +1,131 @@
1 +use std::fmt::{Result, Write};
2 +
3 +use crate::{
4 + ArrayElement, BasicBlock, Function, Instruction, InstructionValue, Place, PrimitiveValue,
5 + Terminal, TerminalValue,
6 +};
7 +
8 +/// Trait for HIR types to describe how they print themselves.
9 +/// Eventually we should add a higher-level abstraction for printing to
10 +/// handle things like indentation and maybe wrapping long lines. The
11 +/// `pretty` crate seems to have a lot of usage but the type signatures
12 +/// are pretty tedious, we can make something much simpler.
13 +pub trait Print {
14 + fn print(&self, out: &mut impl Write) -> Result;
15 +}
16 +
17 +impl<'a> Print for Function<'a> {
18 + fn print(&self, out: &mut impl Write) -> Result {
19 + writeln!(out, "entry {}", self.body.entry)?;
20 + for block in self.body.blocks.values() {
21 + block.print(out)?;
22 + }
23 + Ok(())
24 + }
25 +}
26 +
27 +impl<'a> Print for BasicBlock<'a> {
28 + fn print(&self, out: &mut impl Write) -> Result {
29 + writeln!(out, "{}", self.id)?;
30 + for instr in &self.instructions {
31 + instr.print(out)?;
32 + }
33 + self.terminal.print(out)?;
34 + Ok(())
35 + }
36 +}
37 +
38 +impl<'a> Print for Instruction<'a> {
39 + fn print(&self, out: &mut impl Write) -> Result {
40 + write!(out, " {} ", self.id)?;
41 + self.value.print(out)?;
42 + writeln!(out, "")?;
43 + Ok(())
44 + }
45 +}
46 +
47 +impl<'a> Print for InstructionValue<'a> {
48 + fn print(&self, out: &mut impl Write) -> Result {
49 + match self {
50 + InstructionValue::Array(value) => {
51 + write!(out, "Array [")?;
52 + for (ix, item) in value.elements.iter().enumerate() {
53 + if ix != 0 {
54 + write!(out, ", ")?;
55 + }
56 + item.print(out)?;
57 + }
58 + write!(out, "]")?;
59 + }
60 + InstructionValue::Primitive(value) => {
61 + // Unlike other variants we don't print the variant name ("Primitive") since it's
62 + // obvious
63 + match &value.value {
64 + PrimitiveValue::Boolean(value) => write!(out, "{}", value)?,
65 + PrimitiveValue::Null => write!(out, "null")?,
66 + PrimitiveValue::Number(value) => write!(out, "{}", f64::from(*value))?,
67 +
68 + // TODO: quote the string itself (JS version uses JSON.stringify())
69 + PrimitiveValue::String(value) => write!(out, "\"{}\"", value.as_str())?,
70 +
71 + PrimitiveValue::Undefined => write!(out, "<undefined>")?,
72 + };
73 + }
74 + _ => write!(out, "{:?}", self)?,
75 + }
76 + Ok(())
77 + }
78 +}
79 +
80 +impl<'a> Print for ArrayElement<'a> {
81 + fn print(&self, out: &mut impl Write) -> Result {
82 + match self {
83 + ArrayElement::Place(place) => place.print(out),
84 + ArrayElement::Spread(place) => {
85 + write!(out, "...")?;
86 + place.print(out)?;
87 + Ok(())
88 + }
89 + }
90 + }
91 +}
92 +
93 +impl<'a> Print for Place<'a> {
94 + fn print(&self, out: &mut impl Write) -> Result {
95 + write!(
96 + out,
97 + "{} {}{}",
98 + match self.effect {
99 + Some(effect) => format!("{}", effect),
100 + None => "unknown".to_string(),
101 + },
102 + match &self.identifier.name {
103 + Some(name) => name.to_string(),
104 + None => "".to_string(),
105 + },
106 + self.identifier.id
107 + )
108 + }
109 +}
110 +
111 +impl<'a> Print for Terminal<'a> {
112 + fn print(&self, out: &mut impl Write) -> Result {
113 + write!(out, " {} ", self.id)?;
114 + self.value.print(out)?;
115 + writeln!(out, "")?;
116 + Ok(())
117 + }
118 +}
119 +
120 +impl<'a> Print for TerminalValue<'a> {
121 + fn print(&self, out: &mut impl Write) -> Result {
122 + match self {
123 + TerminalValue::ReturnTerminal(terminal) => {
124 + write!(out, "Return ")?;
125 + terminal.value.print(out)?;
126 + }
127 + _ => write!(out, "{:?}", self)?,
128 + }
129 + Ok(())
130 + }
131 +}