[hir] implement nullish coalescing operator
This currently basically lowers the code into the equivalent of ``` const vLeft = <left>; const vNull = null; const vCond = vLeft != vNull; vCond ? vLeft : <right> ``` I created a temporary `Place` to hold the `null` constant value because the binary operator in HIR accepts only `Place`s. Not sure if this is the preferred approach. Alternatives I could think of: - Allow constants as an alternative to Place? - A `NotNull` operator for `<x> != null` - Some other extension to the HIR?
Jan Kassens committed
Nov 3, 2022 at 10:28 UTC
189b22171f1ffb93fa2570d1dac01c99731bba40
4 files changed
+218
-5
compiler/forget/src/HIR/BuildHIR.ts
+49
-2
@@ -811,8 +811,55 @@ function lowerExpression(
811
);
812
}
813
case "??": {
814
- // test should be roughly the equivalent of `<left> != null`
815
- todo("Handle logical ??");
814
+ // generate the equivalent of
815
+ // const tmp = <left>;
816
+ // tmp != null ? tmp : <right>
817
+ const left = lowerExpressionToPlace(builder, leftPath);
818
+
819
+ const nullPlace: Place = {
820
+ kind: "Identifier",
821
+ identifier: builder.makeTemporary(),
822
+ memberPath: null,
823
+ effect: Effect.Unknown,
824
+ path: null as any,
825
+ };
826
+ builder.push({
827
+ value: {
828
+ kind: "Primitive",
829
+ value: null,
830
+ path: null as any,
831
+ },
832
+ path: exprPath,
833
+ lvalue: { place: { ...nullPlace }, kind: InstructionKind.Const },
834
+ });
835
+
836
+ const condPlace: Place = {
837
+ kind: "Identifier",
838
+ identifier: builder.makeTemporary(),
839
+ memberPath: null,
840
+ effect: Effect.Unknown,
841
+ path: null as any,
842
+ };
843
+ builder.push({
844
+ lvalue: {
845
+ place: { ...condPlace },
846
+ kind: InstructionKind.Const,
847
+ },
848
+ value: {
849
+ kind: "BinaryExpression",
850
+ operator: "!=",
851
+ left,
852
+ right: nullPlace,
853
+ path: null as any,
854
+ },
855
+ path: null as any,
856
+ });
857
+ return lowerConditional(
858
+ builder,
859
+ condPlace,
860
+ () => left,
861
+ () => lowerExpression(builder, expr.get("right"))
862
+ );
863
}
864
default: {
865
assertExhaustive(
compiler/forget/src/HIR/InferReferenceEffects.ts
+2
-3
@@ -210,9 +210,8 @@ class Environment {
210
const values = this.#variables.get(value.identifier.id);
211
invariant(
212
values != null,
213
- `Expected value to be populated at '${String(value.path)}' in '${String(
214
- value.path.parentPath
215
- )}'`
213
+ "Expected value for identifier `%s` to be initialized.",
214
+ value.identifier.id
215
);
216
this.#variables.set(place.identifier.id, new Set(values));
217
}
compiler/forget/src/__tests__/fixtures/hir/logical-expression.expect.md
new
+153
@@ -0,0 +1,153 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function And() {
6
+ return f() && g();
7
+}
8
+
9
+function Or() {
10
+ return f() || g();
11
+}
12
+
13
+function QuestionQuestion(props) {
14
+ return f() ?? g();
15
+}
16
+
17
+function f() {}
18
+function g() {}
19
+
20
+```
21
+
22
+## HIR
23
+
24
+```
25
+bb0:
26
+ Const mutate $5 = Call mutate f$1()
27
+ If (read $5) then:bb2 else:bb3
28
+bb2:
29
+ predecessor blocks: bb0
30
+ Const mutate $6 = Call mutate g$4()
31
+ Goto bb1
32
+bb3:
33
+ predecessor blocks: bb0
34
+ Const mutate $7 = read $5
35
+ Goto bb1
36
+bb1:
37
+ predecessor blocks: bb3 bb2
38
+ $8: phi(bb3: $7, bb2: $6)
39
+ Return freeze $8
40
+```
41
+
42
+## Code
43
+
44
+```javascript
45
+function And$0() {
46
+ if (f$1()) {
47
+ ("<<TODO: handle complex control flow in codegen>>");
48
+ } else {
49
+ ("<<TODO: handle complex control flow in codegen>>");
50
+ }
51
+ return t8;
52
+}
53
+
54
+```
55
+## HIR
56
+
57
+```
58
+bb0:
59
+ Const mutate $5 = Call mutate f$1()
60
+ If (read $5) then:bb2 else:bb3
61
+bb2:
62
+ predecessor blocks: bb0
63
+ Const mutate $6 = read $5
64
+ Goto bb1
65
+bb3:
66
+ predecessor blocks: bb0
67
+ Const mutate $7 = Call mutate g$4()
68
+ Goto bb1
69
+bb1:
70
+ predecessor blocks: bb3 bb2
71
+ $8: phi(bb3: $7, bb2: $6)
72
+ Return freeze $8
73
+```
74
+
75
+## Code
76
+
77
+```javascript
78
+function Or$0() {
79
+ if (f$1()) {
80
+ ("<<TODO: handle complex control flow in codegen>>");
81
+ } else {
82
+ ("<<TODO: handle complex control flow in codegen>>");
83
+ }
84
+ return t8;
85
+}
86
+
87
+```
88
+## HIR
89
+
90
+```
91
+bb0:
92
+ Const mutate $9 = Call mutate f$2()
93
+ Const mutate $10 = null
94
+ Const mutate $11 = Binary read $9 != read $10
95
+ If (read $11) then:bb2 else:bb3
96
+bb2:
97
+ predecessor blocks: bb0
98
+ Const mutate $12 = read $9
99
+ Goto bb1
100
+bb3:
101
+ predecessor blocks: bb0
102
+ Const mutate $13 = Call mutate g$7()
103
+ Goto bb1
104
+bb1:
105
+ predecessor blocks: bb3 bb2
106
+ $14: phi(bb3: $13, bb2: $12)
107
+ Return freeze $14
108
+```
109
+
110
+## Code
111
+
112
+```javascript
113
+function QuestionQuestion$0(props$8) {
114
+ if (f$2() != null) {
115
+ ("<<TODO: handle complex control flow in codegen>>");
116
+ } else {
117
+ ("<<TODO: handle complex control flow in codegen>>");
118
+ }
119
+ return t14;
120
+}
121
+
122
+```
123
+## HIR
124
+
125
+```
126
+bb0:
127
+ Return
128
+```
129
+
130
+## Code
131
+
132
+```javascript
133
+function f$0() {
134
+ return;
135
+}
136
+
137
+```
138
+## HIR
139
+
140
+```
141
+bb0:
142
+ Return
143
+```
144
+
145
+## Code
146
+
147
+```javascript
148
+function g$0() {
149
+ return;
150
+}
151
+
152
+```
153
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/logical-expression.js
new
+14
@@ -0,0 +1,14 @@
1
+function And() {
2
+ return f() && g();
3
+}
4
+
5
+function Or() {
6
+ return f() || g();
7
+}
8
+
9
+function QuestionQuestion(props) {
10
+ return f() ?? g();
11
+}
12
+
13
+function f() {}
14
+function g() {}