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