@samitouri / QOS-React-1 / commits / 12fbfc4fee

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 +};