@samitouri / QOS-React / commits / 8810076bc6

Lookup hook declarations on environment

Changes from calling the global parseHookCall() function to looking up the hook declaration on the environment.

Joe Savona committed Feb 16, 2023 at 14:06 UTC 8810076bc63b2fcf3a750f0be815a84272cc1ba9
8 files changed +61 -36
compiler/forget/src/HIR/Environment.ts
+7 -2
@@ -1,4 +1,4 @@
1 -import { IdentifierId, makeIdentifierId } from "./HIR";
1 +import { Effect, IdentifierId, makeIdentifierId, ValueKind } from "./HIR";
2 import { BUILTIN_HOOKS, Hook } from "./Hooks";
3
4 const HOOK_PATTERN = /^_?use/;
@@ -35,6 +35,11 @@ export class Environment {
35 if (hook !== undefined) {
36 return hook;
37 }
38 - return null;
38 + return {
39 + kind: "Custom",
40 + name,
41 + effectKind: Effect.Mutate,
42 + valueKind: ValueKind.Mutable,
43 + };
44 }
45 }
compiler/forget/src/HIR/HIR.ts
+1
@@ -49,6 +49,7 @@ export type ReactiveFunction = {
49 generator: boolean;
50 async: boolean;
51 body: ReactiveBlock;
52 + env: Environment;
53 };
54
55 export type ReactiveScopeBlock = {
compiler/forget/src/HIR/Hooks.ts
+1 -18
@@ -5,7 +5,7 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import { Effect, Place, ValueKind } from "./HIR";
8 +import { Effect, ValueKind } from "./HIR";
9
10 export const BUILTIN_HOOKS: Map<string, Hook> = new Map([
11 [
@@ -62,20 +62,3 @@ export type Hook = {
62 effectKind: Effect;
63 valueKind: ValueKind;
64 };
65 -
66 -export function parseHookCall(place: Place): Hook | null {
67 - const name = place.identifier.name;
68 - if (name === null || !name.match(/^_?use/)) {
69 - return null;
70 - }
71 - const hook = BUILTIN_HOOKS.get(name);
72 - if (hook != null) {
73 - return hook;
74 - }
75 - return {
76 - kind: "Custom",
77 - name: place.identifier.name ?? "",
78 - effectKind: Effect.Mutate,
79 - valueKind: ValueKind.Mutable,
80 - };
81 -}
compiler/forget/src/Inference/InferReferenceEffects.ts
+12 -4
@@ -7,6 +7,7 @@
7
8 import invariant from "invariant";
9 import { CompilerError } from "../CompilerError";
10 +import { Environment } from "../HIR";
11 import {
12 BasicBlock,
13 BlockId,
@@ -19,7 +20,6 @@ import {
20 Place,
21 ValueKind,
22 } from "../HIR/HIR";
22 -import { parseHookCall } from "../HIR/Hooks";
23 import {
24 printMixedHIR,
25 printPlace,
@@ -151,7 +151,7 @@ export default function inferReferenceEffects(fn: HIRFunction) {
151
152 statesByBlock.set(blockId, incomingState);
153 const state = incomingState.clone();
154 - inferBlock(state, block);
154 + inferBlock(fn.env, state, block);
155
156 for (const nextBlockId of eachTerminalSuccessor(block.terminal)) {
157 queue(nextBlockId, state);
@@ -551,7 +551,12 @@ function mergeValues(a: ValueKind, b: ValueKind): ValueKind {
551 * Iterates over the given @param block, defining variables and
552 * recording references on the @param state according to JS semantics.
553 */
554 -function inferBlock(state: InferenceState, block: BasicBlock) {
554 +function inferBlock(
555 + env: Environment,
556 +
557 + state: InferenceState,
558 + block: BasicBlock
559 +) {
560 for (const phi of block.phis) {
561 state.inferPhi(phi);
562 }
@@ -583,7 +588,10 @@ function inferBlock(state: InferenceState, block: BasicBlock) {
588 case "CallExpression": {
589 valueKind = ValueKind.Mutable;
590 effectKind = Effect.Mutate;
586 - const hook = parseHookCall(instrValue.callee);
591 + const hook =
592 + instrValue.callee.identifier.name !== null
593 + ? env.getHookDeclaration(instrValue.callee.identifier.name)
594 + : null;
595 if (hook !== null) {
596 effectKind = hook.effectKind;
597 valueKind = hook.valueKind;
compiler/forget/src/ReactiveScopes/BuildReactiveFunction.ts
+1
@@ -48,6 +48,7 @@ export function buildReactiveFunction(fn: HIRFunction): ReactiveFunction {
48 generator: fn.generator,
49 async: fn.async,
50 body,
51 + env: fn.env,
52 };
53 }
54
compiler/forget/src/ReactiveScopes/FlattenScopesWithHooks.ts
+14 -4
@@ -6,13 +6,13 @@
6 */
7
8 import {
9 + Environment,
10 InstructionId,
11 ReactiveFunction,
12 ReactiveScopeBlock,
13 ReactiveStatement,
14 ReactiveValue,
15 } from "../HIR";
15 -import { parseHookCall } from "../HIR/Hooks";
16 import {
17 ReactiveFunctionTransform,
18 Transformed,
@@ -31,12 +31,19 @@ import {
31 * to ensure the hook call does not inadvertently become conditional.
32 */
33 export function flattenScopesWithHooks(fn: ReactiveFunction): void {
34 - visitReactiveFunction(fn, new Transform(), { hasHook: false });
34 + visitReactiveFunction(fn, new Transform(fn.env), { hasHook: false });
35 }
36
37 type State = { hasHook: boolean };
38
39 class Transform extends ReactiveFunctionTransform<State> {
40 + env: Environment;
41 +
42 + constructor(env: Environment) {
43 + super();
44 + this.env = env;
45 + }
46 +
47 override transformScope(
48 scope: ReactiveScopeBlock,
49 outerState: State
@@ -56,8 +63,11 @@ class Transform extends ReactiveFunctionTransform<State> {
63 value: ReactiveValue,
64 state: State
65 ): void {
59 - if (value.kind === "CallExpression") {
60 - const hook = parseHookCall(value.callee);
66 + if (
67 + value.kind === "CallExpression" &&
68 + value.callee.identifier.name !== null
69 + ) {
70 + const hook = this.env.getHookDeclaration(value.callee.identifier.name);
71 if (hook !== null) {
72 state.hasHook = true;
73 }
compiler/forget/src/ReactiveScopes/InferReactiveIdentifiers.ts
+18 -5
@@ -6,13 +6,13 @@
6 */
7
8 import { CompilerError } from "../CompilerError";
9 +import { Environment } from "../HIR";
10 import {
11 Effect,
12 IdentifierId,
13 ReactiveFunction,
14 ReactiveInstruction,
15 } from "../HIR/HIR";
15 -import { parseHookCall } from "../HIR/Hooks";
16 import { assertExhaustive } from "../Utils/utils";
17 import {
18 eachReactiveValueOperand,
@@ -21,7 +21,14 @@ import {
21 } from "./visitors";
22
23 type IdentifierReactivity = Map<IdentifierId, boolean>;
24 -class Environment extends ReactiveFunctionVisitor<IdentifierReactivity> {
24 +class Visitor extends ReactiveFunctionVisitor<IdentifierReactivity> {
25 + env: Environment;
26 +
27 + constructor(env: Environment) {
28 + super();
29 + this.env = env;
30 + }
31 +
32 override visitInstruction(
33 instr: ReactiveInstruction,
34 reactivityMap: IdentifierReactivity
@@ -42,13 +49,19 @@ class Environment extends ReactiveFunctionVisitor<IdentifierReactivity> {
49 break;
50 }
51 }
45 - if (!hasReactiveInput && instr.value.kind === "CallExpression") {
52 + if (
53 + !hasReactiveInput &&
54 + instr.value.kind === "CallExpression" &&
55 + instr.value.callee.identifier.name !== null
56 + ) {
57 // Hooks cannot be memoized. Even if they do not accept any reactive inputs,
58 // they are not guaranteed to memoize their return value, and their result
59 // must be assumed to be reactive.
60 // TODO: use types or an opt-in registry of custom hook information to
61 // allow treating safe hooks as non-reactive.
51 - const hook = parseHookCall(instr.value.callee);
62 + const hook = this.env.getHookDeclaration(
63 + instr.value.callee.identifier.name
64 + );
65 if (hook !== null) {
66 hasReactiveInput = true;
67 }
@@ -130,7 +143,7 @@ class Environment extends ReactiveFunctionVisitor<IdentifierReactivity> {
143 export function inferReactiveIdentifiers(
144 fn: ReactiveFunction
145 ): Set<IdentifierId> {
133 - const visitor = new Environment();
146 + const visitor = new Visitor(fn.env);
147 const reactivityMap: IdentifierReactivity = new Map();
148 for (const param of fn.params) {
149 reactivityMap.set(param.identifier.id, true);
compiler/forget/src/TypeInference/InferTypes.ts
+7 -3
@@ -1,5 +1,6 @@
1 import * as t from "@babel/types";
2 import invariant from "invariant";
3 +import { Environment } from "../HIR";
4 import {
5 HIRFunction,
6 Instruction,
@@ -8,7 +9,6 @@ import {
9 TypeId,
10 TypeVar,
11 } from "../HIR/HIR";
11 -import { parseHookCall } from "../HIR/Hooks";
12 import { eachInstructionOperand } from "../HIR/visitors";
13
14 function isPrimitiveBinaryOp(op: t.BinaryExpression["operator"]) {
@@ -83,12 +83,13 @@ function* generate(
83 }
84
85 for (const instr of block.instructions) {
86 - yield* generateInstructionTypes(instr);
86 + yield* generateInstructionTypes(func.env, instr);
87 }
88 }
89 }
90
91 function* generateInstructionTypes(
92 + env: Environment,
93 instr: Instruction
94 ): Generator<TypeEquation, void, undefined> {
95 const { lvalue, value } = instr;
@@ -121,7 +122,10 @@ function* generateInstructionTypes(
122 }
123
124 case "CallExpression": {
124 - const hook = parseHookCall(value.callee);
125 + const hook =
126 + value.callee.identifier.name !== null
127 + ? env.getHookDeclaration(value.callee.identifier.name)
128 + : null;
129 let type: Type;
130 if (hook !== null) {
131 type = { kind: "Hook", name: hook.name };