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

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