Enable hooks validation on playground, make deguggable
Enables hooks validation in playground. Also adds a tab to show the output of validation (in case it passes) with the inferred post dominator tree. We can use this to debug the dominator in case of false negatives. <img width="1724" alt="Screenshot 2023-05-11 at 11 07 08 AM" src="https://github.com/facebook/react-forget/assets/6425824/8f7ae472-8415-4899-aedf-c8f26094ebfe">
Joe Savona committed
May 11, 2023 at 13:39 UTC
f5bdf462b2385add21a6d042c13a9b10919c2c9e
5 files changed
+52
-14
compiler/forget/packages/playground/components/Editor/Output.tsx
+12
-6
@@ -7,6 +7,11 @@
7
8
import generate from "@babel/generator";
9
import * as t from "@babel/types";
10
+import {
11
+ CodeIcon,
12
+ DocumentAddIcon,
13
+ InformationCircleIcon,
14
+} from "@heroicons/react/outline";
15
import MonacoEditor, { DiffEditor } from "@monaco-editor/react";
16
import { type CompilerError } from "babel-plugin-react-forget";
17
import prettier from "prettier";
@@ -15,11 +20,6 @@ import { memo, useMemo, useState } from "react";
20
import { type Store } from "../../lib/stores";
21
import TabbedWindow from "../TabbedWindow";
22
import { monacoOptions } from "./monacoOptions";
18
-import {
19
- CodeIcon,
20
- DocumentAddIcon,
21
- InformationCircleIcon,
22
-} from "@heroicons/react/outline";
23
24
const MemoizedOutput = memo(Output);
25
@@ -38,7 +38,8 @@ export type PrintedCompilerPipelineValue =
38
fnName: string | null;
39
value: string;
40
}
41
- | { kind: "reactive"; name: string; fnName: string | null; value: string };
41
+ | { kind: "reactive"; name: string; fnName: string | null; value: string }
42
+ | { kind: "debug"; name: string; fnName: string | null; value: string };
43
44
export type CompilerOutput =
45
| { kind: "ok"; results: Map<string, PrintedCompilerPipelineValue[]> }
@@ -86,7 +87,12 @@ function tabify(source: string, compilerOutput: CompilerOutput) {
87
case "ast":
88
topLevelFnDecls.push(result.value);
89
break;
90
+ case "debug": {
91
+ concattedResults.set(passName, result.value);
92
+ break;
93
+ }
94
default: {
95
+ const _: never = result;
96
throw new Error("Unexpected result kind");
97
}
98
}
compiler/forget/packages/playground/components/Editor/index.tsx
+11
@@ -132,6 +132,7 @@ function compile(source: string): CompilerOutput {
132
for (const fn of parseFunctions(source)) {
133
for (const result of run(fn, {
134
customHooks: new Map([...COMMON_HOOKS]),
135
+ validateHooksUsage: true,
136
})) {
137
const fnName = fn.node.id?.name ?? null;
138
switch (result.kind) {
@@ -162,7 +163,17 @@ function compile(source: string): CompilerOutput {
163
});
164
break;
165
}
166
+ case "debug": {
167
+ upsert({
168
+ kind: "debug",
169
+ fnName,
170
+ name: result.name,
171
+ value: result.value,
172
+ });
173
+ break;
174
+ }
175
default: {
176
+ const _: never = result;
177
throw new Error(`Unhandled result ${result}`);
178
}
179
}
compiler/forget/src/CompilerPipeline.ts
+11
-2
@@ -53,7 +53,8 @@ import { assertExhaustive } from "./Utils/utils";
53
export type CompilerPipelineValue =
54
| { kind: "ast"; name: string; value: t.FunctionDeclaration }
55
| { kind: "hir"; name: string; value: HIRFunction }
56
- | { kind: "reactive"; name: string; value: ReactiveFunction };
56
+ | { kind: "reactive"; name: string; value: ReactiveFunction }
57
+ | { kind: "debug"; name: string; value: string };
58
59
export function* run(
60
func: NodePath<t.FunctionDeclaration>,
@@ -88,7 +89,12 @@ export function* run(
89
90
if (env.validateHooksUsage) {
91
validateHooksUsage(hir);
91
- validateUnconditionalHooks(hir);
92
+ const conditionalHooksResult = validateUnconditionalHooks(hir).unwrap();
93
+ yield log({
94
+ kind: "debug",
95
+ name: "ValidateUnconditionalHooks",
96
+ value: conditionalHooksResult.debug(),
97
+ });
98
}
99
100
dropMemoCalls(hir);
@@ -252,6 +258,9 @@ export function log(value: CompilerPipelineValue): CompilerPipelineValue {
258
logReactiveFunction(value.name, value.value);
259
break;
260
}
261
+ case "debug": {
262
+ break;
263
+ }
264
default: {
265
assertExhaustive(value, "Unexpected compilation kind");
266
}
compiler/forget/src/HIR/Dominator.ts
+10
-3
@@ -66,7 +66,7 @@ type Graph<T> = {
66
/**
67
* A dominator tree that stores the immediate dominator for each block in function.
68
*/
69
-class Dominator<T> {
69
+export class Dominator<T> {
70
#entry: T;
71
#nodes: Map<T, T>;
72
@@ -97,7 +97,7 @@ class Dominator<T> {
97
}
98
}
99
100
-class PostDominator<T> {
100
+export class PostDominator<T> {
101
#exit: T;
102
#nodes: Map<T, T>;
103
@@ -124,7 +124,14 @@ class PostDominator<T> {
124
}
125
126
debug(): string {
127
- return prettyFormat(this.#nodes);
127
+ const postDominators = new Map();
128
+ for (const [key, value] of this.#nodes) {
129
+ postDominators.set(`bb${key}`, `bb${value}`);
130
+ }
131
+ return prettyFormat({
132
+ exit: `bb${this.exit}`,
133
+ postDominators,
134
+ });
135
}
136
}
137
compiler/forget/src/HIR/ValidateUnconditionalHooks.ts
+8
-3
@@ -11,7 +11,8 @@ import {
11
ErrorSeverity,
12
} from "../CompilerError";
13
import { findBlocksWithBackEdges } from "../Optimization/DeadCodeElimination";
14
-import { computePostDominatorTree } from "./Dominator";
14
+import { Err, Ok, Result } from "../Utils/Result";
15
+import { PostDominator, computePostDominatorTree } from "./Dominator";
16
import { BlockId, HIRFunction, isHookType } from "./HIR";
17
18
/**
@@ -54,7 +55,9 @@ import { BlockId, HIRFunction, isHookType } from "./HIR";
55
* Now only the exit node would post dominate the entry node: there is no other node which is
56
* guaranteed to be reachable. In this graph is is only safe to call hooks in bb0.
57
*/
57
-export function validateUnconditionalHooks(fn: HIRFunction): void {
58
+export function validateUnconditionalHooks(
59
+ fn: HIRFunction
60
+): Result<PostDominator<BlockId>, CompilerError> {
61
// Construct the set of blocks that is always reachable from the entry block.
62
const unconditionalBlocks = new Set<BlockId>();
63
const blocksWithBackEdges = findBlocksWithBackEdges(fn);
@@ -102,6 +105,8 @@ export function validateUnconditionalHooks(fn: HIRFunction): void {
105
}
106
}
107
if (errors.hasErrors()) {
105
- throw errors;
108
+ return Err(errors);
109
+ } else {
110
+ return Ok(dominators);
111
}
112
}