[typer] Add type inference for Phis
Sathya Gunasekaran committed
Jan 11, 2023 at 11:30 UTC
bbba752e98738c259e862ce0088dc8dda36cbec0
3 files changed
+97
-4
compiler/forget/src/TypeInference/InferTypes.ts
+37
-4
@@ -3,7 +3,6 @@ import invariant from "invariant";
3
import {
4
HIRFunction,
5
Instruction,
6
- Place,
6
Type,
7
typeEquals,
8
TypeId,
@@ -46,6 +45,9 @@ export default function (func: HIRFunction) {
45
46
function apply(func: HIRFunction, unifier: Unifier) {
47
for (const [_, block] of func.body.blocks) {
48
+ for (const phi of block.phis) {
49
+ phi.type = unifier.get(phi.type);
50
+ }
51
for (const instr of block.instructions) {
52
for (const place of eachInstructionOperand(instr)) {
53
place.identifier.type = unifier.get(place.identifier.type);
@@ -61,8 +63,20 @@ type TypeEquation = {
63
right: Type;
64
};
65
64
-function* generate(func: HIRFunction) {
66
+function* generate(
67
+ func: HIRFunction
68
+): Generator<TypeEquation, void, undefined> {
69
for (const [_, block] of func.body.blocks) {
70
+ for (const phi of block.phis) {
71
+ yield {
72
+ left: phi.type,
73
+ right: {
74
+ kind: "Phi",
75
+ operands: [...phi.operands.values()].map((id) => id.type),
76
+ },
77
+ };
78
+ }
79
+
80
for (const instr of block.instructions) {
81
yield* generateTypeEquation(instr);
82
}
@@ -148,6 +162,19 @@ class Unifier {
162
return;
163
}
164
165
+ if (type.kind === "Phi") {
166
+ const operands = new Set(type.operands.map((i) => this.get(i).kind));
167
+
168
+ invariant(operands.size > 0, "there should be at least one operand");
169
+ const kind = operands.values().next().value;
170
+
171
+ // there's only one unique type and it's not a type var
172
+ if (operands.size === 1 && kind !== "Type") {
173
+ this.unify(v, type.operands[0]);
174
+ return;
175
+ }
176
+ }
177
+
178
if (this.occursCheck(v, type)) {
179
throw new Error("cycle detected");
180
}
@@ -162,6 +189,10 @@ class Unifier {
189
return this.occursCheck(v, this.substitutions.get(type.id)!);
190
}
191
192
+ if (type.kind === "Phi") {
193
+ return type.operands.some((o) => this.occursCheck(v, o));
194
+ }
195
+
196
return false;
197
}
198
@@ -169,11 +200,13 @@ class Unifier {
200
if (type.kind === "Type") {
201
if (this.substitutions.has(type.id)) {
202
return this.get(this.substitutions.get(type.id)!);
172
- } else {
173
- return type;
203
}
204
}
205
206
+ if (type.kind === "Phi") {
207
+ return { kind: "Phi", operands: type.operands.map((o) => this.get(o)) };
208
+ }
209
+
210
return type;
211
}
212
}
compiler/forget/src/__tests__/fixtures/hir/infer-phi-primitive.expect.md
new
+49
@@ -0,0 +1,49 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function foo(a, b) {
6
+ let x;
7
+ if (a) {
8
+ x = 1;
9
+ } else {
10
+ x = 2;
11
+ }
12
+
13
+ let y = x;
14
+ return y;
15
+}
16
+
17
+```
18
+
19
+## Code
20
+
21
+```javascript
22
+function foo(a, b) {
23
+ const $ = React.useMemoCache();
24
+ const x = undefined;
25
+ const c_0 = $[0] !== a;
26
+ let x$0;
27
+ if (c_0) {
28
+ x$0 = undefined;
29
+
30
+ if (a) {
31
+ const x$1 = 1;
32
+ x$0 = x$1;
33
+ } else {
34
+ const x$2 = 2;
35
+ x$0 = x$2;
36
+ }
37
+
38
+ $[0] = a;
39
+ $[1] = x$0;
40
+ } else {
41
+ x$0 = $[1];
42
+ }
43
+
44
+ const y = x$0;
45
+ return y;
46
+}
47
+
48
+```
49
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/infer-phi-primitive.js
new
+11
@@ -0,0 +1,11 @@
1
+function foo(a, b) {
2
+ let x;
3
+ if (a) {
4
+ x = 1;
5
+ } else {
6
+ x = 2;
7
+ }
8
+
9
+ let y = x;
10
+ return y;
11
+}