[types] Consolidate Hook and Function types: Hooks are functions
Mofei Zhang committed
May 23, 2023 at 13:55 UTC
b76ee1ab32201143b73d8792ad1fd6d1e1207bc3
16 files changed
+238
-165
compiler/forget/packages/playground/components/Editor/index.tsx
-10
@@ -74,8 +74,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
74
[
75
"useFragment",
76
{
77
- name: "useFragment",
78
- kind: "Custom",
77
valueKind: ValueKind.Frozen,
78
effectKind: Effect.Freeze,
79
},
@@ -83,8 +81,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
81
[
82
"usePaginationFragment",
83
{
86
- name: "usePaginationFragment",
87
- kind: "Custom",
84
valueKind: ValueKind.Frozen,
85
effectKind: Effect.Freeze,
86
},
@@ -92,8 +88,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
88
[
89
"useRefetchableFragment",
90
{
95
- name: "useRefetchableFragment",
96
- kind: "Custom",
91
valueKind: ValueKind.Frozen,
92
effectKind: Effect.Freeze,
93
},
@@ -101,8 +95,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
95
[
96
"useLazyLoadQuery",
97
{
104
- name: "useLazyLoadQuery",
105
- kind: "Custom",
98
valueKind: ValueKind.Frozen,
99
effectKind: Effect.Freeze,
100
},
@@ -110,8 +102,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
102
[
103
"usePreloadedQuery",
104
{
113
- name: "usePreloadedQuery",
114
- kind: "Custom",
105
valueKind: ValueKind.Frozen,
106
effectKind: Effect.Freeze,
107
},
compiler/forget/packages/snap/src/compiler-worker.ts
-2
@@ -134,8 +134,6 @@ export async function compile(
134
[
135
"useFreeze",
136
{
137
- name: "useFreeze",
138
- kind: "Custom",
137
valueKind: "frozen",
138
effectKind: "freeze",
139
},
compiler/forget/src/HIR/Environment.ts
+31
-11
@@ -17,15 +17,27 @@ import {
17
import {
18
BlockId,
19
BuiltInType,
20
+ Effect,
21
FunctionType,
22
IdentifierId,
23
ObjectType,
24
PolyType,
25
+ ValueKind,
26
makeBlockId,
27
makeIdentifierId,
28
} from "./HIR";
27
-import { Hook } from "./Hooks";
28
-import { FunctionSignature, ShapeRegistry } from "./ObjectShape";
29
+import {
30
+ DefaultMutatingHook,
31
+ DefaultNonmutatingHook,
32
+ FunctionSignature,
33
+ ShapeRegistry,
34
+ addHook,
35
+} from "./ObjectShape";
36
+
37
+export type Hook = {
38
+ effectKind: Effect;
39
+ valueKind: ValueKind;
40
+};
41
42
// TODO(mofeiZ): User defined global types (with corresponding shapes).
43
// User defined global types should have inline ObjectShapes instead of directly
@@ -107,7 +119,7 @@ export class Environment {
119
config: EnvironmentConfig | null,
120
contextIdentifiers: Set<t.Identifier>
121
) {
110
- this.#shapes = DEFAULT_SHAPES;
122
+ this.#shapes = new Map(DEFAULT_SHAPES);
123
124
if (config?.customHooks) {
125
this.#globals = new Map(DEFAULT_GLOBALS);
@@ -116,10 +128,17 @@ export class Environment {
128
!this.#globals.has(hookName),
129
`[Globals] Found existing definition in global registry for custom hook ${hookName}`
130
);
119
- this.#globals.set(hookName, {
120
- kind: "Hook",
121
- definition: hook,
122
- });
131
+ this.#globals.set(
132
+ hookName,
133
+ addHook(this.#shapes, [], {
134
+ positionalParams: [],
135
+ restParam: hook.effectKind,
136
+ returnType: { kind: "Poly" },
137
+ returnValueKind: hook.valueKind,
138
+ calleeEffect: Effect.Read,
139
+ hookKind: "Custom",
140
+ })
141
+ );
142
}
143
} else {
144
this.#globals = DEFAULT_GLOBALS;
@@ -150,10 +169,11 @@ export class Environment {
169
if (resolvedGlobal === null) {
170
// Hack, since we don't track module level declarations and imports
171
if (isHookName(name)) {
153
- return {
154
- kind: "Hook",
155
- definition: null,
156
- };
172
+ if (this.enableAssumeHooksFollowRulesOfReact) {
173
+ return DefaultNonmutatingHook;
174
+ } else {
175
+ return DefaultMutatingHook;
176
+ }
177
} else {
178
log(() => `Undefined global '${name}'`);
179
}
compiler/forget/src/HIR/Globals.ts
+63
-57
@@ -6,15 +6,15 @@
6
*/
7
8
import { Effect, ValueKind } from "./HIR";
9
-import { Hook } from "./Hooks";
9
import {
10
BUILTIN_SHAPES,
11
BuiltInArrayId,
12
ShapeRegistry,
13
addFunction,
14
+ addHook,
15
addObject,
16
} from "./ObjectShape";
17
-import { BuiltInType, HookType, PolyType } from "./Types";
17
+import { BuiltInType, FunctionType, PolyType } from "./Types";
18
19
/**
20
* This file exports types and defaults for JavaScript global objects.
@@ -229,85 +229,91 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
229
// TODO: rest of Global objects
230
];
231
232
-const BUILTIN_HOOKS: Array<[string, Hook]> = [
232
+// TODO(mofeiZ): We currently only store rest param effects for hooks
233
+// until FeatureFlag `enableTreatHooksAsFunctions` is removed
234
+const BUILTIN_HOOKS: Array<[string, FunctionType]> = [
235
[
236
"useContext",
235
- {
236
- kind: "State",
237
- name: "useContext",
238
- effectKind: Effect.Read,
239
- valueKind: ValueKind.Mutable,
240
- },
237
+ addHook(DEFAULT_SHAPES, [], {
238
+ positionalParams: [],
239
+ restParam: Effect.Read,
240
+ returnType: { kind: "Poly" },
241
+ calleeEffect: Effect.Read,
242
+ hookKind: "useContext",
243
+ returnValueKind: ValueKind.Mutable,
244
+ }),
245
],
246
[
247
"useState",
244
- {
245
- kind: "State",
246
- name: "useState",
247
- effectKind: Effect.Freeze,
248
- valueKind: ValueKind.Frozen,
249
- },
248
+ addHook(DEFAULT_SHAPES, [], {
249
+ positionalParams: [],
250
+ restParam: Effect.Freeze,
251
+ returnType: { kind: "Poly" },
252
+ calleeEffect: Effect.Read,
253
+ hookKind: "useState",
254
+ returnValueKind: ValueKind.Frozen,
255
+ }),
256
],
257
[
258
"useRef",
253
- {
254
- kind: "Ref",
255
- name: "useRef",
256
- effectKind: Effect.Capture,
257
- valueKind: ValueKind.Mutable,
258
- },
259
+ addHook(DEFAULT_SHAPES, [], {
260
+ positionalParams: [],
261
+ restParam: Effect.Capture,
262
+ returnType: { kind: "Poly" },
263
+ calleeEffect: Effect.Read,
264
+ hookKind: "useRef",
265
+ returnValueKind: ValueKind.Mutable,
266
+ }),
267
],
268
[
269
"useMemo",
262
- {
263
- kind: "Memo",
264
- name: "useMemo",
265
- effectKind: Effect.Freeze,
266
- valueKind: ValueKind.Frozen,
267
- },
270
+ addHook(DEFAULT_SHAPES, [], {
271
+ positionalParams: [],
272
+ restParam: Effect.Freeze,
273
+ returnType: { kind: "Poly" },
274
+ calleeEffect: Effect.Read,
275
+ hookKind: "useMemo",
276
+ returnValueKind: ValueKind.Frozen,
277
+ }),
278
],
279
[
280
"useCallback",
271
- {
272
- kind: "Memo",
273
- name: "useCallback",
274
- effectKind: Effect.Freeze,
275
- valueKind: ValueKind.Frozen,
276
- },
281
+ addHook(DEFAULT_SHAPES, [], {
282
+ positionalParams: [],
283
+ restParam: Effect.Freeze,
284
+ returnType: { kind: "Poly" },
285
+ calleeEffect: Effect.Read,
286
+ hookKind: "useCallback",
287
+ returnValueKind: ValueKind.Frozen,
288
+ }),
289
],
290
[
291
"useEffect",
280
- {
281
- kind: "Memo",
282
- name: "useEffect",
283
- effectKind: Effect.Freeze,
284
- valueKind: ValueKind.Frozen,
285
- },
292
+ addHook(DEFAULT_SHAPES, [], {
293
+ positionalParams: [],
294
+ restParam: Effect.Freeze,
295
+ returnType: { kind: "Poly" },
296
+ calleeEffect: Effect.Read,
297
+ hookKind: "useEffect",
298
+ returnValueKind: ValueKind.Frozen,
299
+ }),
300
],
301
[
302
"useLayoutEffect",
289
- {
290
- kind: "Memo",
291
- name: "useLayoutEffect",
292
- effectKind: Effect.Freeze,
293
- valueKind: ValueKind.Frozen,
294
- },
303
+ addHook(DEFAULT_SHAPES, [], {
304
+ positionalParams: [],
305
+ restParam: Effect.Freeze,
306
+ returnType: { kind: "Poly" },
307
+ calleeEffect: Effect.Read,
308
+ hookKind: "useLayoutEffect",
309
+ returnValueKind: ValueKind.Frozen,
310
+ }),
311
],
312
];
313
298
-export type Global = BuiltInType | HookType | PolyType;
314
+export type Global = BuiltInType | PolyType;
315
export type GlobalRegistry = Map<string, Global>;
300
-export const DEFAULT_GLOBALS: GlobalRegistry = new Map(
301
- BUILTIN_HOOKS.map(([hookName, hook]) => {
302
- return [
303
- hookName,
304
- {
305
- kind: "Hook",
306
- definition: hook,
307
- },
308
- ];
309
- })
310
-);
316
+export const DEFAULT_GLOBALS: GlobalRegistry = new Map(BUILTIN_HOOKS);
317
318
// Hack until we add ObjectShapes for all globals
319
for (const name of UNTYPED_GLOBALS) {
compiler/forget/src/HIR/HIR.ts
+8
-2
@@ -8,6 +8,7 @@
8
import * as t from "@babel/types";
9
import invariant from "invariant";
10
import { Environment } from "./Environment";
11
+import { HookKind } from "./ObjectShape";
12
import { Type } from "./Types";
13
14
// *******************************************************************************************
@@ -951,8 +952,13 @@ export function isPrimitiveType(id: Identifier): boolean {
952
return id.type.kind === "Primitive";
953
}
954
954
-export function isHookType(id: Identifier): boolean {
955
- return id.type.kind === "Hook";
955
+export function getHookKind(env: Environment, id: Identifier): HookKind | null {
956
+ const idType = id.type;
957
+ if (idType.kind === "Function") {
958
+ const signature = env.getFunctionSignature(idType);
959
+ return signature?.hookKind ?? null;
960
+ }
961
+ return null;
962
}
963
964
export * from "./Types";
compiler/forget/src/HIR/Hooks.ts
deleted
-16
@@ -1,16 +0,0 @@
1
-/**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- */
7
-
8
-import { Effect, ValueKind } from "./HIR";
9
-
10
-export type HookKind = "State" | "Ref" | "Custom" | "Memo";
11
-export type Hook = {
12
- kind: HookKind;
13
- name: string;
14
- effectKind: Effect;
15
- valueKind: ValueKind;
16
-};
compiler/forget/src/HIR/ObjectShape.ts
+53
-2
@@ -33,14 +33,36 @@ function createAnonId(): string {
33
}
34
35
/**
36
- * Add a function to an existing ShapeRegistry.
36
+ * Add a non-hook function to an existing ShapeRegistry.
37
*
38
* @returns a {@link FunctionType} representing the added function.
39
*/
40
export function addFunction(
41
registry: ShapeRegistry,
42
properties: Iterable<[string, BuiltInType | PolyType]>,
43
- fn: FunctionSignature
43
+ fn: Omit<FunctionSignature, "hookKind">
44
+): FunctionType {
45
+ const shapeId = createAnonId();
46
+ addShape(registry, shapeId, properties, {
47
+ ...fn,
48
+ hookKind: null,
49
+ });
50
+ return {
51
+ kind: "Function",
52
+ return: fn.returnType,
53
+ shapeId,
54
+ };
55
+}
56
+
57
+/**
58
+ * Add a hook to an existing ShapeRegistry.
59
+ *
60
+ * @returns a {@link FunctionType} representing the added hook function.
61
+ */
62
+export function addHook(
63
+ registry: ShapeRegistry,
64
+ properties: Iterable<[string, BuiltInType | PolyType]>,
65
+ fn: FunctionSignature & { hookKind: HookKind }
66
): FunctionType {
67
const shapeId = createAnonId();
68
addShape(registry, shapeId, properties, fn);
@@ -88,6 +110,16 @@ function addShape(
110
return shape;
111
}
112
113
+export type HookKind =
114
+ | "useContext"
115
+ | "useState"
116
+ | "useRef"
117
+ | "useEffect"
118
+ | "useLayoutEffect"
119
+ | "useMemo"
120
+ | "useCallback"
121
+ | "Custom";
122
+
123
/**
124
* Call signature of a function, used for type and effect inference.
125
*
@@ -102,6 +134,7 @@ export type FunctionSignature = {
134
returnType: BuiltInType | PolyType;
135
returnValueKind: ValueKind;
136
calleeEffect: Effect;
137
+ hookKind: HookKind | null;
138
};
139
140
/**
@@ -184,3 +217,21 @@ addObject(BUILTIN_SHAPES, BuiltInObjectId, [
217
// TODO:
218
// hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString, valueOf
219
]);
220
+
221
+export const DefaultMutatingHook = addHook(BUILTIN_SHAPES, [], {
222
+ positionalParams: [],
223
+ restParam: Effect.Mutate,
224
+ returnType: { kind: "Poly" },
225
+ calleeEffect: Effect.Read,
226
+ hookKind: "Custom",
227
+ returnValueKind: ValueKind.Mutable,
228
+});
229
+
230
+export const DefaultNonmutatingHook = addHook(BUILTIN_SHAPES, [], {
231
+ positionalParams: [],
232
+ restParam: Effect.Freeze,
233
+ returnType: { kind: "Poly" },
234
+ calleeEffect: Effect.Read,
235
+ hookKind: "Custom",
236
+ returnValueKind: ValueKind.Frozen,
237
+});
compiler/forget/src/HIR/Types.ts
+1
-19
@@ -6,22 +6,11 @@
6
*/
7
8
import invariant from "invariant";
9
-import { Hook } from "./Hooks";
9
10
export type BuiltInType = PrimitiveType | FunctionType | ObjectType;
11
13
-export type Type =
14
- | BuiltInType
15
- | HookType
16
- | PhiType
17
- | TypeVar
18
- | PolyType
19
- | PropType;
12
+export type Type = BuiltInType | PhiType | TypeVar | PolyType | PropType;
13
export type PrimitiveType = { kind: "Primitive" };
21
-export type HookType = {
22
- kind: "Hook";
23
- definition: Hook | null;
24
-};
14
15
/**
16
* An {@link FunctionType} or {@link ObjectType} (also a JS object) may be associated with an
@@ -93,7 +82,6 @@ export function typeEquals(tA: Type, tB: Type): boolean {
82
return (
83
typeVarEquals(tA, tB) ||
84
funcTypeEquals(tA, tB) ||
96
- hookTypeEquals(tA, tB) ||
85
objectTypeEquals(tA, tB) ||
86
primitiveTypeEquals(tA, tB) ||
87
polyTypeEquals(tA, tB) ||
@@ -131,12 +119,6 @@ function funcTypeEquals(tA: Type, tB: Type): boolean {
119
return typeEquals(tA.return, tB.return);
120
}
121
134
-function hookTypeEquals(tA: Type, tB: Type): boolean {
135
- return (
136
- tA.kind === "Hook" && tB.kind === "Hook" && tA.definition === tB.definition
137
- );
138
-}
139
-
122
function phiTypeEquals(tA: Type, tB: Type): boolean {
123
if (tA.kind === "Phi" && tB.kind === "Phi") {
124
if (tA.operands.length !== tB.operands.length) {
compiler/forget/src/HIR/ValidateHooksUsage.ts
+2
-2
@@ -11,7 +11,7 @@ import {
11
ErrorSeverity,
12
} from "../CompilerError";
13
import { hasBackEdge } from "../Optimization/DeadCodeElimination";
14
-import { HIRFunction, IdentifierId, Place, isHookType } from "./HIR";
14
+import { HIRFunction, IdentifierId, Place, getHookKind } from "./HIR";
15
import { eachInstructionValueOperand, eachTerminalOperand } from "./visitors";
16
17
/**
@@ -56,7 +56,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
56
for (const instr of block.instructions) {
57
if (
58
instr.value.kind === "LoadGlobal" &&
59
- isHookType(instr.lvalue.identifier)
59
+ getHookKind(fn.env, instr.lvalue.identifier) != null
60
) {
61
hooks.add(instr.lvalue.identifier.id);
62
} else if (instr.value.kind === "CallExpression") {
compiler/forget/src/HIR/ValidateUnconditionalHooks.ts
+2
-2
@@ -13,7 +13,7 @@ import {
13
import { findBlocksWithBackEdges } from "../Optimization/DeadCodeElimination";
14
import { Err, Ok, Result } from "../Utils/Result";
15
import { PostDominator, computePostDominatorTree } from "./Dominator";
16
-import { BlockId, HIRFunction, isHookType } from "./HIR";
16
+import { BlockId, HIRFunction, getHookKind } from "./HIR";
17
18
/**
19
* Validates that the function honors the [Rules of Hooks](https://react.dev/warnings/invalid-hook-call-warning)
@@ -85,7 +85,7 @@ export function validateUnconditionalHooks(
85
for (const instr of block.instructions) {
86
if (
87
instr.value.kind === "CallExpression" &&
88
- isHookType(instr.value.callee.identifier)
88
+ getHookKind(fn.env, instr.value.callee.identifier) != null
89
) {
90
const loc = instr.loc;
91
// TODO: the current ESLint rule has different error messages for code that is called conditionally, in a loop, etc.
compiler/forget/src/HIR/index.ts
+1
-2
@@ -7,7 +7,7 @@
7
8
export { lower } from "./BuildHIR";
9
export { computeDominatorTree, computePostDominatorTree } from "./Dominator";
10
-export { Environment } from "./Environment";
10
+export { Environment, Hook } from "./Environment";
11
export * from "./HIR";
12
export {
13
markInstructionIds,
@@ -15,7 +15,6 @@ export {
15
removeUnreachableFallthroughs,
16
reversePostorderBlocks,
17
} from "./HIRBuilder";
18
-export { Hook } from "./Hooks";
18
export { mergeConsecutiveBlocks } from "./MergeConsecutiveBlocks";
19
export { printFunction, printHIR } from "./PrintHIR";
20
export { validateConsistentIdentifiers } from "./ValidateConsistentIdentifiers";
compiler/forget/src/Inference/DropMemoCalls.ts
+5
-6
@@ -5,17 +5,16 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import { Effect, HIRFunction, HookType, isHookType } from "../HIR";
8
+import { Effect, HIRFunction, getHookKind } from "../HIR";
9
10
export default function (func: HIRFunction): void {
11
for (const [_, block] of func.body.blocks) {
12
for (const instr of block.instructions) {
13
switch (instr.value.kind) {
14
case "CallExpression": {
15
- if (isHookType(instr.value.callee.identifier)) {
16
- const name = (instr.value.callee.identifier.type as HookType)
17
- .definition?.name;
18
- if (name === "useMemo") {
15
+ const hookKind = getHookKind(func.env, instr.value.callee.identifier);
16
+ if (hookKind != null) {
17
+ if (hookKind === "useMemo") {
18
const [fn] = instr.value.args;
19
20
// TODO(gsn): Consider inlining the function passed to useMemo,
@@ -38,7 +37,7 @@ export default function (func: HIRFunction): void {
37
loc: instr.value.loc,
38
};
39
}
41
- } else if (name === "useCallback") {
40
+ } else if (hookKind === "useCallback") {
41
const [fn] = instr.value.args;
42
43
// Instead of a Call, just alias the callback directly.
compiler/forget/src/Inference/InferReferenceEffects.ts
+36
-20
@@ -24,7 +24,11 @@ import {
24
Type,
25
ValueKind,
26
} from "../HIR/HIR";
27
-import { FunctionSignature } from "../HIR/ObjectShape";
27
+import {
28
+ DefaultMutatingHook,
29
+ DefaultNonmutatingHook,
30
+ FunctionSignature,
31
+} from "../HIR/ObjectShape";
32
import {
33
printIdentifier,
34
printMixedHIR,
@@ -708,25 +712,33 @@ function inferBlock(
712
continue;
713
}
714
case "CallExpression": {
711
- if (instrValue.callee.identifier.type.kind === "Hook") {
712
- const definition = instrValue.callee.identifier.type.definition;
713
- if (definition !== null) {
714
- effectKind = definition.effectKind;
715
- valueKind = definition.valueKind;
716
- } else if (env.enableAssumeHooksFollowRulesOfReact) {
717
- effectKind = Effect.Freeze;
718
- valueKind = ValueKind.Frozen;
719
- } else {
720
- effectKind = Effect.Mutate;
721
- valueKind = ValueKind.Mutable;
722
- }
715
+ let signature = getFunctionCallSignature(
716
+ env,
717
+ instrValue.callee.identifier.type
718
+ );
719
+ signature =
720
+ env.enableFunctionCallSignatureOptimizations ||
721
+ signature?.hookKind != null
722
+ ? signature
723
+ : null;
724
+
725
+ if (
726
+ signature &&
727
+ signature.hookKind != null &&
728
+ !env.enableTreatHooksAsFunctions
729
+ ) {
730
+ effectKind = signature.restParam;
731
+ valueKind = signature.returnValueKind;
732
break;
733
}
734
726
- const signature = env.enableFunctionCallSignatureOptimizations
727
- ? getFunctionCallSignature(env, instrValue.callee.identifier.type)
728
- : null;
729
-
735
+ // We currently always check reference effects of typed functions
736
+ // (i.e. call `referenceAndCheckError`). However, default custom hooks
737
+ // should not assert reference effects, since their signatures are only
738
+ // assumptions / defaults.
739
+ const isDefaultCustomHook =
740
+ instrValue.callee.identifier.type === DefaultMutatingHook ||
741
+ instrValue.callee.identifier.type === DefaultNonmutatingHook;
742
const effects =
743
signature !== null ? getFunctionEffects(instrValue, signature) : null;
744
const returnValueKind =
@@ -735,9 +747,13 @@ function inferBlock(
747
const arg = instrValue.args[i];
748
const place = arg.kind === "Identifier" ? arg : arg.place;
749
if (effects !== null) {
738
- // If effects are inferred for an argument, we should fail invalid
739
- // mutating effects
740
- state.referenceAndCheckError(place, effects[i]);
750
+ if (isDefaultCustomHook) {
751
+ state.reference(place, effects[i]);
752
+ } else {
753
+ // If effects are inferred for an argument, we should fail invalid
754
+ // mutating effects
755
+ state.referenceAndCheckError(place, effects[i]);
756
+ }
757
} else {
758
state.reference(place, Effect.Mutate);
759
}
compiler/forget/src/ReactiveScopes/FlattenScopesWithHooks.ts
+15
-5
@@ -6,12 +6,13 @@
6
*/
7
8
import {
9
+ Environment,
10
InstructionId,
10
- isHookType,
11
ReactiveFunction,
12
ReactiveScopeBlock,
13
ReactiveStatement,
14
ReactiveValue,
15
+ getHookKind,
16
} from "../HIR";
17
import {
18
ReactiveFunctionTransform,
@@ -31,17 +32,26 @@ import {
32
* to ensure the hook call does not inadvertently become conditional.
33
*/
34
export function flattenScopesWithHooks(fn: ReactiveFunction): void {
34
- visitReactiveFunction(fn, new Transform(), { hasHook: false });
35
+ visitReactiveFunction(fn, new Transform(), {
36
+ env: fn.env,
37
+ hasHook: false,
38
+ });
39
}
40
37
-type State = { hasHook: boolean };
41
+type State = {
42
+ env: Environment;
43
+ hasHook: boolean;
44
+};
45
46
class Transform extends ReactiveFunctionTransform<State> {
47
override transformScope(
48
scope: ReactiveScopeBlock,
49
outerState: State
50
): Transformed<ReactiveStatement> {
44
- const innerState: State = { hasHook: false };
51
+ const innerState: State = {
52
+ env: outerState.env,
53
+ hasHook: false,
54
+ };
55
this.visitScope(scope, innerState);
56
outerState.hasHook ||= innerState.hasHook;
57
if (innerState.hasHook) {
@@ -59,7 +69,7 @@ class Transform extends ReactiveFunctionTransform<State> {
69
this.traverseValue(id, value, state);
70
if (
71
value.kind === "CallExpression" &&
62
- isHookType(value.callee.identifier)
72
+ getHookKind(state.env, value.callee.identifier) != null
73
) {
74
state.hasHook = true;
75
}
compiler/forget/src/ReactiveScopes/InferReactiveIdentifiers.ts
+10
-4
@@ -6,26 +6,32 @@
6
*/
7
8
import { CompilerError } from "../CompilerError";
9
+import { Environment } from "../HIR";
10
import {
11
Effect,
12
IdentifierId,
12
- isHookType,
13
ReactiveFunction,
14
ReactiveInstruction,
15
+ getHookKind,
16
} from "../HIR/HIR";
17
import { eachInstructionLValue } from "../HIR/visitors";
18
import { assertExhaustive } from "../Utils/utils";
19
import {
19
- eachReactiveValueOperand,
20
ReactiveFunctionVisitor,
21
+ eachReactiveValueOperand,
22
visitReactiveFunction,
23
} from "./visitors";
24
25
type IdentifierReactivity = Map<IdentifierId, boolean>;
26
27
class State {
28
+ env: Environment;
29
reactivityMap: IdentifierReactivity = new Map();
30
temporaries: Map<IdentifierId, IdentifierId> = new Map();
31
+
32
+ constructor(env: Environment) {
33
+ this.env = env;
34
+ }
35
}
36
37
class Visitor extends ReactiveFunctionVisitor<State> {
@@ -65,7 +71,7 @@ class Visitor extends ReactiveFunctionVisitor<State> {
71
if (
72
!hasReactiveInput &&
73
instr.value.kind === "CallExpression" &&
68
- isHookType(instr.value.callee.identifier)
74
+ getHookKind(state.env, instr.value.callee.identifier) != null
75
) {
76
// Hooks cannot be memoized. Even if they do not accept any reactive inputs,
77
// they are not guaranteed to memoize their return value, and their result
@@ -169,7 +175,7 @@ export function inferReactiveIdentifiers(
175
fn: ReactiveFunction
176
): Set<IdentifierId> {
177
const visitor = new Visitor();
172
- const state = new State();
178
+ const state = new State(fn.env);
179
for (const param of fn.params) {
180
state.reactivityMap.set(param.identifier.id, true);
181
}
compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts
+11
-5
@@ -9,10 +9,9 @@ import invariant from "invariant";
9
import prettyFormat from "pretty-format";
10
import { CompilerError } from "../CompilerError";
11
import {
12
+ Environment,
13
IdentifierId,
14
InstructionId,
14
- isHookType,
15
- isMutableEffect,
15
Pattern,
16
Place,
17
ReactiveFunction,
@@ -23,6 +22,8 @@ import {
22
ReactiveTerminalStatement,
23
ReactiveValue,
24
ScopeId,
25
+ getHookKind,
26
+ isMutableEffect,
27
} from "../HIR";
28
import { eachInstructionValueOperand } from "../HIR/visitors";
29
import { log } from "../Utils/logger";
@@ -30,10 +31,10 @@ import { assertExhaustive } from "../Utils/utils";
31
import { getPlaceScope } from "./BuildReactiveBlocks";
32
import { printReactiveFunction } from "./PrintReactiveFunction";
33
import {
33
- eachReactiveValueOperand,
34
ReactiveFunctionTransform,
35
ReactiveFunctionVisitor,
36
Transformed,
37
+ eachReactiveValueOperand,
38
visitReactiveFunction,
39
} from "./visitors";
40
@@ -116,7 +117,7 @@ export function pruneNonEscapingScopes(
117
): void {
118
// First build up a map of which instructions are involved in creating which values,
119
// and which values are returned.
119
- const state = new State();
120
+ const state = new State(fn.env);
121
if (fn.id !== null) {
122
state.declare(fn.id.id);
123
}
@@ -200,6 +201,7 @@ type ScopeNode = {
201
202
// Stores the identifier and scope graphs, set of returned identifiers, etc
203
class State {
204
+ env: Environment;
205
// Maps lvalues for LoadLocal to the identifier being loaded, to resolve indirections
206
// in subsequent lvalues/rvalues
207
definitions: Map<IdentifierId, IdentifierId> = new Map();
@@ -208,6 +210,10 @@ class State {
210
scopes: Map<ScopeId, ScopeNode> = new Map();
211
escapingValues: Set<IdentifierId> = new Set();
212
213
+ constructor(env: Environment) {
214
+ this.env = env;
215
+ }
216
+
217
/**
218
* Declare a new identifier, used for function id and params
219
*/
@@ -715,7 +721,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<State> {
721
);
722
} else if (instruction.value.kind === "CallExpression") {
723
const callee = instruction.value.callee;
718
- if (isHookType(callee.identifier)) {
724
+ if (getHookKind(state.env, callee.identifier)) {
725
for (const operand of eachInstructionValueOperand(instruction.value)) {
726
state.escapingValues.add(operand.identifier.id);
727
}