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

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 }