Use variable type annotations to drive inference
Builds on the utilities added previously to infer types from type annotations on variable declarations. This is a limited form, where currently we only infer for local identifiers (not function parameters) and only infer a type for the variable initializer and not subsequent reassignments.
Joe Savona committed
Dec 11, 2023 at 11:34 UTC
12fbfc4fee2eba6d36250a04a0a7dd9a4eb3822a
13 files changed
+336
-10
compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+46
-9
@@ -1591,6 +1591,7 @@ function lowerExpression(
1591
kind: "StoreLocal",
1592
lvalue: { kind: InstructionKind.Const, place: { ...place } },
1593
value: last,
1594
+ type: makeType(),
1595
loc: exprLoc,
1596
});
1597
}
@@ -1632,6 +1633,7 @@ function lowerExpression(
1633
kind: "StoreLocal",
1634
lvalue: { kind: InstructionKind.Const, place: { ...place } },
1635
value: consequent,
1636
+ type: makeType(),
1637
loc: exprLoc,
1638
});
1639
return {
@@ -1650,6 +1652,7 @@ function lowerExpression(
1652
kind: "StoreLocal",
1653
lvalue: { kind: InstructionKind.Const, place: { ...place } },
1654
value: alternate,
1655
+ type: makeType(),
1656
loc: exprLoc,
1657
});
1658
return {
@@ -1700,6 +1703,7 @@ function lowerExpression(
1703
kind: "StoreLocal",
1704
lvalue: { kind: InstructionKind.Const, place: { ...place } },
1705
value: { ...leftPlace },
1706
+ type: makeType(),
1707
loc: leftPlace.loc,
1708
});
1709
return {
@@ -1716,6 +1720,7 @@ function lowerExpression(
1720
kind: "StoreLocal",
1721
lvalue: { kind: InstructionKind.Const, place: { ...place } },
1722
value: { ...right },
1723
+ type: makeType(),
1724
loc: right.loc,
1725
});
1726
return {
@@ -1815,15 +1820,29 @@ function lowerExpression(
1820
right,
1821
loc: exprLoc,
1822
});
1818
- lowerValueToTemporary(builder, {
1819
- kind: getStoreKind(builder, leftExpr),
1820
- lvalue: {
1821
- place: { ...identifier },
1822
- kind: InstructionKind.Reassign,
1823
- },
1824
- value: { ...binaryPlace },
1825
- loc: exprLoc,
1826
- });
1823
+ const kind = getStoreKind(builder, leftExpr);
1824
+ if (kind === "StoreLocal") {
1825
+ lowerValueToTemporary(builder, {
1826
+ kind: "StoreLocal",
1827
+ lvalue: {
1828
+ place: { ...identifier },
1829
+ kind: InstructionKind.Reassign,
1830
+ },
1831
+ value: { ...binaryPlace },
1832
+ type: makeType(),
1833
+ loc: exprLoc,
1834
+ });
1835
+ } else {
1836
+ lowerValueToTemporary(builder, {
1837
+ kind: "StoreContext",
1838
+ lvalue: {
1839
+ place: { ...identifier },
1840
+ kind: InstructionKind.Reassign,
1841
+ },
1842
+ value: { ...binaryPlace },
1843
+ loc: exprLoc,
1844
+ });
1845
+ }
1846
return { kind: "LoadLocal", place: identifier, loc: exprLoc };
1847
}
1848
case "MemberExpression": {
@@ -2272,6 +2291,7 @@ function lowerOptionalMemberExpression(
2291
kind: "StoreLocal",
2292
lvalue: { kind: InstructionKind.Const, place: { ...place } },
2293
value: { ...temp },
2294
+ type: makeType(),
2295
loc,
2296
});
2297
return {
@@ -2326,6 +2346,7 @@ function lowerOptionalMemberExpression(
2346
kind: "StoreLocal",
2347
lvalue: { kind: InstructionKind.Const, place: { ...place } },
2348
value: { ...temp },
2349
+ type: makeType(),
2350
loc,
2351
});
2352
return {
@@ -2382,6 +2403,7 @@ function lowerOptionalCallExpression(
2403
kind: "StoreLocal",
2404
lvalue: { kind: InstructionKind.Const, place: { ...place } },
2405
value: { ...temp },
2406
+ type: makeType(),
2407
loc,
2408
});
2409
return {
@@ -2483,6 +2505,7 @@ function lowerOptionalCallExpression(
2505
kind: "StoreLocal",
2506
lvalue: { kind: InstructionKind.Const, place: { ...place } },
2507
value: { ...temp },
2508
+ type: makeType(),
2509
loc,
2510
});
2511
return {
@@ -3215,10 +3238,22 @@ function lowerAssignment(
3238
loc,
3239
});
3240
} else {
3241
+ const typeAnnotation = lvalue.get("typeAnnotation");
3242
+ let type: Type;
3243
+ if (typeAnnotation.isTSTypeAnnotation()) {
3244
+ const typePath = typeAnnotation.get("typeAnnotation");
3245
+ type = lowerType(builder, typePath);
3246
+ } else if (typeAnnotation.isTypeAnnotation()) {
3247
+ const typePath = typeAnnotation.get("typeAnnotation");
3248
+ type = lowerType(builder, typePath);
3249
+ } else {
3250
+ type = makeType();
3251
+ }
3252
temporary = lowerValueToTemporary(builder, {
3253
kind: "StoreLocal",
3254
lvalue: { place: { ...place }, kind },
3255
value,
3256
+ type,
3257
loc,
3258
});
3259
}
@@ -3525,6 +3560,7 @@ function lowerAssignment(
3560
kind: "StoreLocal",
3561
lvalue: { kind: InstructionKind.Const, place: { ...temp } },
3562
value: { ...defaultValue },
3563
+ type: makeType(),
3564
loc,
3565
});
3566
return {
@@ -3541,6 +3577,7 @@ function lowerAssignment(
3577
kind: "StoreLocal",
3578
lvalue: { kind: InstructionKind.Const, place: { ...temp } },
3579
value: { ...value },
3580
+ type: makeType(),
3581
loc,
3582
});
3583
return {
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+1
@@ -676,6 +676,7 @@ export type InstructionValue =
676
kind: "StoreLocal";
677
lvalue: LValue;
678
value: Place;
679
+ type: Type;
680
loc: SourceLocation;
681
}
682
| {
compiler/packages/babel-plugin-react-forget/src/HIR/Types.ts
+44
@@ -90,6 +90,50 @@ export function makeType(): TypeVar {
90
};
91
}
92
93
+/**
94
+ * Duplicates the given type, copying types that are exact while creating fresh
95
+ * type identifiers for any abstract types.
96
+ */
97
+export function duplicateType(type: Type): Type {
98
+ switch (type.kind) {
99
+ case "Function": {
100
+ return {
101
+ kind: "Function",
102
+ return: duplicateType(type.return),
103
+ shapeId: type.shapeId,
104
+ };
105
+ }
106
+ case "Object": {
107
+ return { kind: "Object", shapeId: type.shapeId };
108
+ }
109
+ case "ObjectMethod": {
110
+ return { kind: "ObjectMethod" };
111
+ }
112
+ case "Phi": {
113
+ return {
114
+ kind: "Phi",
115
+ operands: type.operands.map((operand) => duplicateType(operand)),
116
+ };
117
+ }
118
+ case "Poly": {
119
+ return { kind: "Poly" };
120
+ }
121
+ case "Primitive": {
122
+ return { kind: "Primitive" };
123
+ }
124
+ case "Property": {
125
+ return {
126
+ kind: "Property",
127
+ object: duplicateType(type.object),
128
+ propertyName: type.propertyName,
129
+ };
130
+ }
131
+ case "Type": {
132
+ return makeType();
133
+ }
134
+ }
135
+}
136
+
137
export function typeEquals(tA: Type, tB: Type): boolean {
138
if (tA.kind !== tB.kind) return false;
139
return (
compiler/packages/babel-plugin-react-forget/src/Inference/InlineImmediatelyInvokedFunctionExpressions.ts
+1
@@ -245,6 +245,7 @@ function rewriteBlock(
245
kind: "StoreLocal",
246
lvalue: { kind: InstructionKind.Reassign, place: { ...returnValue } },
247
value: terminal.value,
248
+ type: makeType(),
249
loc: terminal.loc,
250
},
251
});
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/ExtractScopeDeclarationsFromDestructuring.ts
+2
@@ -15,6 +15,7 @@ import {
15
ReactiveFunction,
16
ReactiveInstruction,
17
ReactiveScopeBlock,
18
+ makeType,
19
} from "../HIR";
20
import { eachPatternOperand, mapPatternOperands } from "../HIR/visitors";
21
import { ReactiveFunctionTransform, visitReactiveFunction } from "./visitors";
@@ -177,6 +178,7 @@ function transformDestructuring(
178
place: original,
179
},
180
value: temporary,
181
+ type: makeType(),
182
loc: destructure.loc,
183
},
184
loc: instr.loc,
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneHoistedContexts.ts
+2
@@ -11,6 +11,7 @@ import {
11
ReactiveFunction,
12
ReactiveInstruction,
13
ReactiveStatement,
14
+ makeType,
15
} from "../HIR";
16
import {
17
ReactiveFunctionTransform,
@@ -59,6 +60,7 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
60
...instruction.value.lvalue,
61
kind: InstructionKind.Const,
62
},
63
+ type: makeType(),
64
kind: "StoreLocal",
65
},
66
},
compiler/packages/babel-plugin-react-forget/src/TypeInference/InferTypes.ts
+2
-1
@@ -137,11 +137,12 @@ function* generateInstructionTypes(
137
}
138
139
case "StoreLocal": {
140
- yield equation(left, value.value.identifier.type);
140
yield equation(
141
value.lvalue.place.identifier.type,
142
value.value.identifier.type
143
);
144
+ yield equation(value.type, value.lvalue.place.identifier.type);
145
+ yield equation(left, value.type);
146
break;
147
}
148
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/type-annotations/todo_type-annotations-props.expect.md
new
+51
@@ -0,0 +1,51 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function useArray(items: Array<number>) {
6
+ // With type information we know that the callback cannot escape
7
+ // and does not need to be memoized, only the result needs to be
8
+ // memoized:
9
+ return items.filter((x) => x !== 0);
10
+}
11
+
12
+export const FIXTURE_ENTRYPOINT = {
13
+ fn: useArray,
14
+ params: [[1, 0, 2, 0, 3, 0, 42]],
15
+};
16
+
17
+```
18
+
19
+## Code
20
+
21
+```javascript
22
+import { unstable_useMemoCache as useMemoCache } from "react";
23
+function useArray(items) {
24
+ const $ = useMemoCache(3);
25
+ let t1;
26
+ if ($[0] !== items) {
27
+ let t0;
28
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
29
+ t0 = (x) => x !== 0;
30
+ $[2] = t0;
31
+ } else {
32
+ t0 = $[2];
33
+ }
34
+ t1 = items.filter(t0);
35
+ $[0] = items;
36
+ $[1] = t1;
37
+ } else {
38
+ t1 = $[1];
39
+ }
40
+ return t1;
41
+}
42
+
43
+export const FIXTURE_ENTRYPOINT = {
44
+ fn: useArray,
45
+ params: [[1, 0, 2, 0, 3, 0, 42]],
46
+};
47
+
48
+```
49
+
50
+### Eval output
51
+(kind: ok) [1,2,3,42]
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/type-annotations/todo_type-annotations-props.ts
new
+11
@@ -0,0 +1,11 @@
1
+function useArray(items: Array<number>) {
2
+ // With type information we know that the callback cannot escape
3
+ // and does not need to be memoized, only the result needs to be
4
+ // memoized:
5
+ return items.filter((x) => x !== 0);
6
+}
7
+
8
+export const FIXTURE_ENTRYPOINT = {
9
+ fn: useArray,
10
+ params: [[1, 0, 2, 0, 3, 0, 42]],
11
+};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/type-annotations/type-annotation-var-array.expect.md
new
+70
@@ -0,0 +1,70 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props: { id: number }) {
6
+ const x: number[] = makeArray(props.id);
7
+ const y = x.at(0);
8
+ return y;
9
+}
10
+
11
+function makeArray<T>(x: T): Array<T> {
12
+ return [x];
13
+}
14
+
15
+export const FIXTURE_ENTRYPOINT = {
16
+ fn: Component,
17
+ params: [{ id: 42 }],
18
+};
19
+
20
+```
21
+
22
+## Code
23
+
24
+```javascript
25
+import { unstable_useMemoCache as useMemoCache } from "react";
26
+function Component(props) {
27
+ const $ = useMemoCache(4);
28
+ let t0;
29
+ if ($[0] !== props.id) {
30
+ t0 = makeArray(props.id);
31
+ $[0] = props.id;
32
+ $[1] = t0;
33
+ } else {
34
+ t0 = $[1];
35
+ }
36
+ const x = t0;
37
+ let t1;
38
+ if ($[2] !== x) {
39
+ t1 = x.at(0);
40
+ $[2] = x;
41
+ $[3] = t1;
42
+ } else {
43
+ t1 = $[3];
44
+ }
45
+ const y = t1;
46
+ return y;
47
+}
48
+
49
+function makeArray(x) {
50
+ const $ = useMemoCache(2);
51
+ let t0;
52
+ if ($[0] !== x) {
53
+ t0 = [x];
54
+ $[0] = x;
55
+ $[1] = t0;
56
+ } else {
57
+ t0 = $[1];
58
+ }
59
+ return t0;
60
+}
61
+
62
+export const FIXTURE_ENTRYPOINT = {
63
+ fn: Component,
64
+ params: [{ id: 42 }],
65
+};
66
+
67
+```
68
+
69
+### Eval output
70
+(kind: ok) 42
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/type-annotations/type-annotation-var-array.ts
new
+14
@@ -0,0 +1,14 @@
1
+function Component(props: { id: number }) {
2
+ const x: number[] = makeArray(props.id);
3
+ const y = x.at(0);
4
+ return y;
5
+}
6
+
7
+function makeArray<T>(x: T): Array<T> {
8
+ return [x];
9
+}
10
+
11
+export const FIXTURE_ENTRYPOINT = {
12
+ fn: Component,
13
+ params: [{ id: 42 }],
14
+};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/type-annotations/type-annotation-var-array_.flow.expect.md
new
+75
@@ -0,0 +1,75 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @flow
6
+import { identity } from "shared-runtime";
7
+
8
+function Component(props: { id: number }) {
9
+ const x: Array<number> = makeArray(props.id);
10
+ const y = x.at(0);
11
+ return y;
12
+}
13
+
14
+function makeArray<T>(x: T): Array<T> {
15
+ return [x];
16
+}
17
+
18
+export const FIXTURE_ENTRYPOINT = {
19
+ fn: Component,
20
+ params: [{ id: 42 }],
21
+};
22
+
23
+```
24
+
25
+## Code
26
+
27
+```javascript
28
+import { unstable_useMemoCache as useMemoCache } from "react";
29
+import { identity } from "shared-runtime";
30
+
31
+function Component(props) {
32
+ const $ = useMemoCache(4);
33
+ let t0;
34
+ if ($[0] !== props.id) {
35
+ t0 = makeArray(props.id);
36
+ $[0] = props.id;
37
+ $[1] = t0;
38
+ } else {
39
+ t0 = $[1];
40
+ }
41
+ const x = t0;
42
+ let t1;
43
+ if ($[2] !== x) {
44
+ t1 = x.at(0);
45
+ $[2] = x;
46
+ $[3] = t1;
47
+ } else {
48
+ t1 = $[3];
49
+ }
50
+ const y = t1;
51
+ return y;
52
+}
53
+
54
+function makeArray(x) {
55
+ const $ = useMemoCache(2);
56
+ let t0;
57
+ if ($[0] !== x) {
58
+ t0 = [x];
59
+ $[0] = x;
60
+ $[1] = t0;
61
+ } else {
62
+ t0 = $[1];
63
+ }
64
+ return t0;
65
+}
66
+
67
+export const FIXTURE_ENTRYPOINT = {
68
+ fn: Component,
69
+ params: [{ id: 42 }],
70
+};
71
+
72
+```
73
+
74
+### Eval output
75
+(kind: ok) 42
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/type-annotations/type-annotation-var-array_.flow.js
new
+17
@@ -0,0 +1,17 @@
1
+// @flow
2
+import { identity } from "shared-runtime";
3
+
4
+function Component(props: { id: number }) {
5
+ const x: Array<number> = makeArray(props.id);
6
+ const y = x.at(0);
7
+ return y;
8
+}
9
+
10
+function makeArray<T>(x: T): Array<T> {
11
+ return [x];
12
+}
13
+
14
+export const FIXTURE_ENTRYPOINT = {
15
+ fn: Component,
16
+ params: [{ id: 42 }],
17
+};