The library for web and native user interfaces.
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
+};