@samitouri / QOS-React / commits / 12fbfc4fee
Fork 0

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