@samitouri / QOS-React-2 / commits / e43f91edc3

[env] Type inference for global object

- Add `DEFAULT_SHAPES` ShapeRegistry, which holds builtins and all `ObjectShapes` used in `DEFAULT_GLOBALS`. - Add a few typed objects / functions into `DEFAULT_GLOBALS` (used for tests) - Add type inference and `infer-global-object` test

Mofei Zhang committed Mar 30, 2023 at 15:03 UTC e43f91edc392728e6e78aeb68b1707911b760b6f
5 files changed +170 -13
compiler/forget/src/HIR/Environment.ts
+8 -7
@@ -1,6 +1,11 @@
1 import invariant from "invariant";
2 import { log } from "../Utils/logger";
3 -import { DEFAULT_GLOBALS, Global, GlobalRegistry } from "./Globals";
3 +import {
4 + DEFAULT_GLOBALS,
5 + DEFAULT_SHAPES,
6 + Global,
7 + GlobalRegistry,
8 +} from "./Globals";
9 import {
10 BuiltInType,
11 Effect,
@@ -12,11 +17,7 @@ import {
17 ValueKind,
18 } from "./HIR";
19 import { Hook } from "./Hooks";
15 -import {
16 - BUILTIN_SHAPES,
17 - FunctionSignature,
18 - ShapeRegistry,
19 -} from "./ObjectShape";
20 +import { FunctionSignature, ShapeRegistry } from "./ObjectShape";
21
22 const HOOK_PATTERN = /^_?use/;
23
@@ -38,7 +39,7 @@ export class Environment {
39 #nextIdentifer: number = 0;
40
41 constructor(config: EnvironmentConfig | null) {
41 - this.#shapes = BUILTIN_SHAPES;
42 + this.#shapes = DEFAULT_SHAPES;
43
44 if (config?.customHooks) {
45 this.#globals = new Map(DEFAULT_GLOBALS);
compiler/forget/src/HIR/Globals.ts
+81 -2
@@ -7,6 +7,13 @@
7
8 import { Effect, ValueKind } from "./HIR";
9 import { Hook } from "./Hooks";
10 +import {
11 + addFunction,
12 + addObject,
13 + BuiltInArrayId,
14 + BUILTIN_SHAPES,
15 + ShapeRegistry,
16 +} from "./ObjectShape";
17 import { BuiltInType, HookType, PolyType } from "./Types";
18
19 /**
@@ -17,16 +24,19 @@ import { BuiltInType, HookType, PolyType } from "./Types";
24 * (i.e. read vs mutate) in source programs.
25 */
26
27 +/**
28 + * ShapeRegistry with default definitions for builtins and global objects.
29 + */
30 +export const DEFAULT_SHAPES: ShapeRegistry = new Map(BUILTIN_SHAPES);
31 +
32 // Hack until we add ObjectShapes for all globals
33 const UNTYPED_GLOBALS: Set<string> = new Set([
34 "String",
35 "Object",
36 "Function",
25 - "Array",
37 "Number",
38 "RegExp",
39 "Date",
29 - "Math",
40 "Error",
41 "Function",
42 "TypeError",
@@ -63,6 +73,65 @@ const UNTYPED_GLOBALS: Set<string> = new Set([
73 "decodeURIComponent",
74 ]);
75
76 +const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
77 + [
78 + "Array",
79 + addObject(DEFAULT_SHAPES, "Array", [
80 + [
81 + "isArray",
82 + // Array.isArray(value)
83 + addFunction(DEFAULT_SHAPES, [], {
84 + positionalParams: [Effect.Read],
85 + restParam: null,
86 + returnType: { kind: "Primitive" },
87 + calleeEffect: Effect.Read,
88 + }),
89 + ],
90 + [
91 + "from",
92 + // Array.from(arrayLike, optionalFn, optionalThis)
93 + addFunction(DEFAULT_SHAPES, [], {
94 + positionalParams: [Effect.Mutate],
95 + restParam: Effect.Read,
96 + returnType: { kind: "Object", shapeId: BuiltInArrayId },
97 + calleeEffect: Effect.Read,
98 + }),
99 + ],
100 + [
101 + "of",
102 + // Array.of(element0, ..., elementN)
103 + addFunction(DEFAULT_SHAPES, [], {
104 + positionalParams: [],
105 + restParam: Effect.Read,
106 + returnType: { kind: "Object", shapeId: BuiltInArrayId },
107 + calleeEffect: Effect.Read,
108 + }),
109 + ],
110 + ]),
111 + ],
112 + [
113 + "Math",
114 + addObject(DEFAULT_SHAPES, "Math", [
115 + // Static properties (TODO)
116 + ["PI", { kind: "Primitive" }],
117 + // Static methods (TODO)
118 + [
119 + "max",
120 + // Math.max(value0, ..., valueN)
121 + addFunction(DEFAULT_SHAPES, [], {
122 + positionalParams: [],
123 + restParam: Effect.Read,
124 + returnType: { kind: "Primitive" },
125 + calleeEffect: Effect.Read,
126 + }),
127 + ],
128 + ]),
129 + ],
130 + ["Infinity", { kind: "Primitive" }],
131 + ["NaN", { kind: "Primitive" }],
132 + // TODO: rest of Global objects
133 +];
134 +
135 const BUILTIN_HOOKS: Array<[string, Hook]> = [
136 [
137 "useState",
@@ -122,3 +191,13 @@ for (const name of UNTYPED_GLOBALS) {
191 kind: "Poly",
192 });
193 }
194 +
195 +for (const [name, type_] of TYPED_GLOBALS) {
196 + DEFAULT_GLOBALS.set(name, type_);
197 +}
198 +
199 +// Recursive global type
200 +DEFAULT_GLOBALS.set(
201 + "globalThis",
202 + addObject(DEFAULT_SHAPES, "globalThis", TYPED_GLOBALS)
203 +);
compiler/forget/src/TypeInference/InferTypes.ts
+1 -4
@@ -137,10 +137,7 @@ function* generateInstructionTypes(
137 case "LoadGlobal": {
138 const globalType = env.getGlobalDeclaration(value.name);
139 if (globalType) {
140 - if (globalType.kind === "Hook") {
141 - yield equation(left, globalType);
142 - }
143 - // TODO(mofeiZ): add type inference for other globals
140 + yield equation(left, globalType);
141 }
142 break;
143 }
compiler/forget/src/__tests__/fixtures/compiler/infer-global-object.expect.md new
+67
@@ -0,0 +1,67 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// Check that we correctly resolve type and effect lookups on the javascript
6 +// global object.
7 +function Component(props) {
8 + let neverAliasedOrMutated = foo(props.b);
9 + let primitiveVal1 = Math.max(props.a, neverAliasedOrMutated);
10 + let primitiveVal2 = Infinity;
11 + let primitiveVal3 = globaThis.globalThis.NaN;
12 +
13 + // Even though we don't know the function signature of foo,
14 + // we should be able to infer that it does not mutate its inputs.
15 + foo(primitiveVal1, primitiveVal2, primitiveVal3);
16 + return { primitiveVal1, primitiveVal2, primitiveVal3 };
17 +}
18 +
19 +```
20 +
21 +## Code
22 +
23 +```javascript
24 +// Check that we correctly resolve type and effect lookups on the javascript
25 +// global object.
26 +function Component(props) {
27 + const $ = React.unstable_useMemoCache(7);
28 + const c_0 = $[0] !== props.b;
29 + let t0;
30 + if (c_0) {
31 + t0 = foo(props.b);
32 + $[0] = props.b;
33 + $[1] = t0;
34 + } else {
35 + t0 = $[1];
36 + }
37 + const neverAliasedOrMutated = t0;
38 + const c_2 = $[2] !== props.a;
39 + const c_3 = $[3] !== neverAliasedOrMutated;
40 + let t1;
41 + if (c_2 || c_3) {
42 + t1 = Math.max(props.a, neverAliasedOrMutated);
43 + $[2] = props.a;
44 + $[3] = neverAliasedOrMutated;
45 + $[4] = t1;
46 + } else {
47 + t1 = $[4];
48 + }
49 + const primitiveVal1 = t1;
50 + const primitiveVal2 = Infinity;
51 + const primitiveVal3 = globaThis.globalThis.NaN;
52 +
53 + foo(primitiveVal1, primitiveVal2, primitiveVal3);
54 + const c_5 = $[5] !== primitiveVal1;
55 + let t2;
56 + if (c_5) {
57 + t2 = { primitiveVal1, primitiveVal2, primitiveVal3 };
58 + $[5] = primitiveVal1;
59 + $[6] = t2;
60 + } else {
61 + t2 = $[6];
62 + }
63 + return t2;
64 +}
65 +
66 +```
67 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/infer-global-object.js new
+13
@@ -0,0 +1,13 @@
1 +// Check that we correctly resolve type and effect lookups on the javascript
2 +// global object.
3 +function Component(props) {
4 + let neverAliasedOrMutated = foo(props.b);
5 + let primitiveVal1 = Math.max(props.a, neverAliasedOrMutated);
6 + let primitiveVal2 = Infinity;
7 + let primitiveVal3 = globaThis.globalThis.NaN;
8 +
9 + // Even though we don't know the function signature of foo,
10 + // we should be able to infer that it does not mutate its inputs.
11 + foo(primitiveVal1, primitiveVal2, primitiveVal3);
12 + return { primitiveVal1, primitiveVal2, primitiveVal3 };
13 +}