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

New destructuring representation modeled on StoreLocal

Changes to explicitly model destructuring (array and object patterns), expanding support to include rest elements and preserving destructuring through the output. The new "Destructure" instruction is similar to "StoreLocal" but has a pattern instead of a place. For now each level of nested array/object patterns creates a separate destructure instruction, which ensures we have a temporary Place to talk about the intermediate array/object and its type/effects etc. Example: ``` // INPUT const [x, {y}, ...z] = a; // yay rest elements work now! // HIR [1] <unknown> $2 = LoadLocal a$1 [2] <unknown> $6 = Destructure Const [ <unknown> x$3, <unknown> $4, ...<unknown> z$5 ] = <unknown> $2 [3] <unknown> $8 = Destructure Const { y: <unknown> y$8 } = <unknown> $4 // OUTPUT const [x, t0, ...z] = a; const {y} = t0; ``` Note that we can still collapse to a single destructure statement during codegen, independently of whether we have separate instructions internally. For now i'm going w the simple approach of emitting multiple statements in codegen (the code will very likely get further rewritten by downstream babel passes anyway). Also, I don't love the "if StoreLocal/Destructure else ..." pattern that the StoreLocal created and that this PR entrenches. As discussed w @gsathya offline, the long-term direction will be to add a separate visitor, roughly `eachLValue()` and `eachOperand()` so that we can treat all instructions the same. Existing Instruction.lvalue will go away and become a property of the other types of instructions.

Joe Savona committed Mar 3, 2023 at 17:09 UTC ca9d49090a78e22393d0f7fb4dbbabfc99d07aed
32 files changed +921 -396
compiler/forget/src/HIR/BuildHIR.ts
+139 -91
@@ -14,6 +14,7 @@ import { Err, Ok, Result } from "../Utils/Result";
14 import { assertExhaustive } from "../Utils/utils";
15 import { Environment, EnvironmentOptions } from "./Environment";
16 import {
17 + ArrayPattern,
18 BlockId,
19 BranchTerminal,
20 Case,
@@ -27,6 +28,7 @@ import {
28 InstructionValue,
29 JsxAttribute,
30 makeInstructionId,
31 + ObjectPattern,
32 Place,
33 ReturnTerminal,
34 SourceLocation,
@@ -1967,6 +1969,11 @@ function lowerAssignment(
1969 return { kind: "LoadLocal", place, loc: temporary.loc };
1970 }
1971 case "MemberExpression": {
1972 + // This can only occur because of a coding error, parsers enforce this condition
1973 + invariant(
1974 + kind === InstructionKind.Reassign,
1975 + "MemberExpression may only appear in an assignment expression"
1976 + );
1977 const lvalue = lvaluePath as NodePath<t.MemberExpression>;
1978 const property = lvalue.get("property");
1979 const object = lowerExpressionToTemporary(builder, lvalue.get("object"));
@@ -2009,115 +2016,156 @@ function lowerAssignment(
2016 case "ArrayPattern": {
2017 const lvalue = lvaluePath as NodePath<t.ArrayPattern>;
2018 const elements = lvalue.get("elements");
2012 - let hasError = false;
2019 + const items: ArrayPattern["items"] = [];
2020 + const followups: Array<{ place: Place; path: NodePath<t.LVal> }> = [];
2021 for (let i = 0; i < elements.length; i++) {
2022 const element = elements[i];
2023 if (element.node == null) {
2024 continue;
2025 }
2018 - if (element.node.type === "RestElement") {
2019 - builder.errors.push({
2020 - reason: `(BuildHIR::lowerAssignment) Handle ${element.type} in ArrayPattern`,
2021 - severity: ErrorSeverity.Todo,
2022 - nodePath: element,
2026 + if (element.isRestElement()) {
2027 + const argument = element.get("argument");
2028 + if (!argument.isIdentifier()) {
2029 + builder.errors.push({
2030 + reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ArrayPattern`,
2031 + severity: ErrorSeverity.Todo,
2032 + nodePath: element,
2033 + });
2034 + continue;
2035 + }
2036 + const identifier = lowerIdentifier(builder, argument);
2037 + items.push({
2038 + kind: "Spread",
2039 + place: identifier,
2040 });
2024 - hasError = true;
2025 - continue;
2041 + } else if (element.isIdentifier()) {
2042 + const identifier = lowerIdentifier(builder, element);
2043 + items.push(identifier);
2044 + } else {
2045 + const temp = buildTemporaryPlace(
2046 + builder,
2047 + element.node.loc ?? GeneratedSource
2048 + );
2049 + items.push({ ...temp });
2050 + followups.push({ place: temp, path: element as NodePath<t.LVal> }); // TODO remove type cast
2051 }
2027 - const property = buildTemporaryPlace(
2028 - builder,
2029 - element.node.loc ?? GeneratedSource
2030 - );
2031 - builder.push({
2032 - id: makeInstructionId(0),
2033 - lvalue: { ...property },
2034 - value: {
2035 - kind: "Primitive",
2036 - value: i,
2037 - loc: element.node.loc ?? GeneratedSource,
2038 - },
2039 - loc: element.node.loc ?? GeneratedSource,
2040 - });
2041 - const propertyPlace = buildTemporaryPlace(builder, property.loc);
2042 - builder.push({
2043 - id: makeInstructionId(0),
2044 - lvalue: { ...propertyPlace },
2045 - value: {
2046 - kind: "ComputedLoad",
2047 - loc,
2048 - object: { ...value },
2049 - property,
2052 + }
2053 + const temp = buildTemporaryPlace(builder, loc);
2054 + builder.push({
2055 + id: makeInstructionId(0),
2056 + lvalue: { ...temp },
2057 + value: {
2058 + kind: "Destructure",
2059 + lvalue: {
2060 + kind,
2061 + pattern: {
2062 + kind: "ArrayPattern",
2063 + items,
2064 + },
2065 },
2066 + value,
2067 loc,
2052 - });
2053 - lowerAssignment(
2054 - builder,
2055 - loc,
2056 - kind,
2057 - element as NodePath<t.LVal>,
2058 - propertyPlace
2059 - );
2068 + },
2069 + loc,
2070 + });
2071 + for (const { place, path } of followups) {
2072 + lowerAssignment(builder, path.node.loc ?? loc, kind, path, place);
2073 }
2061 - return hasError
2062 - ? { kind: "UnsupportedNode", node: lvalueNode, loc }
2063 - : { kind: "LoadLocal", place: value, loc: value.loc };
2074 + return { kind: "LoadLocal", place: value, loc: value.loc };
2075 }
2076 case "ObjectPattern": {
2077 const lvalue = lvaluePath as NodePath<t.ObjectPattern>;
2067 - const properties = lvalue.get("properties");
2068 - let hasError = false;
2069 - for (let i = 0; i < properties.length; i++) {
2070 - const property = properties[i];
2071 - if (!property.isObjectProperty()) {
2072 - builder.errors.push({
2073 - reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in ObjectPattern`,
2074 - severity: ErrorSeverity.Todo,
2075 - nodePath: property,
2076 - });
2077 - hasError = true;
2078 - continue;
2079 - }
2080 - const key = property.get("key");
2081 - if (!key.isIdentifier()) {
2082 - builder.errors.push({
2083 - reason: `(BuildHIR::lowerAssignment) Handle ${key.type} keys in ObjectPattern`,
2084 - severity: ErrorSeverity.Todo,
2085 - nodePath: key,
2086 - });
2087 - hasError = true;
2088 - continue;
2089 - }
2090 - const element = property.get("value");
2091 - if (!element.isLVal()) {
2092 - builder.errors.push({
2093 - reason: `(BuildHIR::lowerAssignment) Expected object property value to be an LVal, got: ${element.type}`,
2094 - severity: ErrorSeverity.InvalidInput,
2095 - nodePath: element,
2078 + const propertiesPaths = lvalue.get("properties");
2079 + const properties: ObjectPattern["properties"] = [];
2080 + const followups: Array<{ place: Place; path: NodePath<t.LVal> }> = [];
2081 + for (let i = 0; i < propertiesPaths.length; i++) {
2082 + const property = propertiesPaths[i];
2083 + if (property.isRestElement()) {
2084 + const argument = property.get("argument");
2085 + if (!argument.isIdentifier()) {
2086 + builder.errors.push({
2087 + reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ArrayPattern`,
2088 + severity: ErrorSeverity.Todo,
2089 + nodePath: argument,
2090 + });
2091 + continue;
2092 + }
2093 + const identifier = lowerIdentifier(builder, argument);
2094 + properties.push({
2095 + kind: "Spread",
2096 + place: identifier,
2097 });
2097 - hasError = true;
2098 - continue;
2098 + } else {
2099 + // TODO: this should always be true given the if/else
2100 + if (!property.isObjectProperty()) {
2101 + builder.errors.push({
2102 + reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in ObjectPattern`,
2103 + severity: ErrorSeverity.Todo,
2104 + nodePath: property,
2105 + });
2106 + continue;
2107 + }
2108 + const key = property.get("key");
2109 + if (!key.isIdentifier()) {
2110 + builder.errors.push({
2111 + reason: `(BuildHIR::lowerAssignment) Handle ${key.type} keys in ObjectPattern`,
2112 + severity: ErrorSeverity.Todo,
2113 + nodePath: key,
2114 + });
2115 + continue;
2116 + }
2117 + const element = property.get("value");
2118 + if (!element.isLVal()) {
2119 + builder.errors.push({
2120 + reason: `(BuildHIR::lowerAssignment) Expected object property value to be an LVal, got: ${element.type}`,
2121 + severity: ErrorSeverity.InvalidInput,
2122 + nodePath: element,
2123 + });
2124 + continue;
2125 + }
2126 + if (element.isIdentifier()) {
2127 + const identifier = lowerIdentifier(builder, element);
2128 + properties.push({
2129 + kind: "ObjectProperty",
2130 + name: key.node.name,
2131 + place: identifier,
2132 + });
2133 + } else {
2134 + const temp = buildTemporaryPlace(
2135 + builder,
2136 + element.node.loc ?? GeneratedSource
2137 + );
2138 + properties.push({
2139 + kind: "ObjectProperty",
2140 + name: key.node.name,
2141 + place: { ...temp },
2142 + });
2143 + followups.push({ place: temp, path: element as NodePath<t.LVal> }); // TODO remove type cast
2144 + }
2145 }
2100 - const propertyPlace = buildTemporaryPlace(
2101 - builder,
2102 - property.node.loc ?? GeneratedSource
2103 - );
2104 - builder.push({
2105 - id: makeInstructionId(0),
2106 - lvalue: { ...propertyPlace },
2107 - value: {
2108 - kind: "PropertyLoad",
2109 - loc,
2110 - object: { ...value },
2111 - property: key.node.name,
2112 - optional: false, // Key of ObjectPattern (evaluation of LVal) cannot be optional.
2146 + }
2147 + const temp = buildTemporaryPlace(builder, loc);
2148 + builder.push({
2149 + id: makeInstructionId(0),
2150 + lvalue: { ...temp },
2151 + value: {
2152 + kind: "Destructure",
2153 + lvalue: {
2154 + kind,
2155 + pattern: {
2156 + kind: "ObjectPattern",
2157 + properties,
2158 + },
2159 },
2160 + value,
2161 loc,
2115 - });
2116 - lowerAssignment(builder, loc, kind, element, propertyPlace);
2162 + },
2163 + loc,
2164 + });
2165 + for (const { place, path } of followups) {
2166 + lowerAssignment(builder, path.node.loc ?? loc, kind, path, place);
2167 }
2118 - return hasError
2119 - ? { kind: "UnsupportedNode", node: lvalueNode, loc }
2120 - : { kind: "LoadLocal", place: value, loc: value.loc };
2168 + return { kind: "LoadLocal", place: value, loc: value.loc };
2169 }
2170 default: {
2171 builder.errors.push({
compiler/forget/src/HIR/HIR.ts
+34
@@ -375,6 +375,34 @@ export type LValue = {
375 kind: InstructionKind;
376 };
377
378 +export type LValuePattern = {
379 + pattern: Pattern;
380 + kind: InstructionKind;
381 +};
382 +
383 +export type Pattern = ArrayPattern | ObjectPattern;
384 +
385 +export type SpreadPattern = {
386 + kind: "Spread";
387 + place: Place;
388 +};
389 +
390 +export type ArrayPattern = {
391 + kind: "ArrayPattern";
392 + items: Array<Place | SpreadPattern>;
393 +};
394 +
395 +export type ObjectPattern = {
396 + kind: "ObjectPattern";
397 + properties: Array<ObjectProperty | SpreadPattern>;
398 +};
399 +
400 +export type ObjectProperty = {
401 + kind: "ObjectProperty";
402 + name: string; // TODO: make a Place
403 + place: Place;
404 +};
405 +
406 export enum InstructionKind {
407 /**
408 * const declaration
@@ -424,6 +452,12 @@ export type InstructionValue =
452 value: Place;
453 loc: SourceLocation;
454 }
455 + | {
456 + kind: "Destructure";
457 + lvalue: LValuePattern;
458 + value: Place;
459 + loc: SourceLocation;
460 + }
461 | {
462 kind: "Primitive";
463 value: number | boolean | string | null | undefined;
compiler/forget/src/HIR/PrintHIR.ts
+51
@@ -19,12 +19,14 @@ import {
19 InstructionValue,
20 LValue,
21 MutableRange,
22 + Pattern,
23 Phi,
24 Place,
25 ReactiveInstruction,
26 ReactiveScope,
27 ReactiveValue,
28 SourceLocation,
29 + SpreadPattern,
30 Terminal,
31 Type,
32 } from "./HIR";
@@ -321,6 +323,12 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
323 )} = ${printPlace(instrValue.value)}`;
324 break;
325 }
326 + case "Destructure": {
327 + value = `Destructure ${instrValue.lvalue.kind} ${printPattern(
328 + instrValue.lvalue.pattern
329 + )} = ${printPlace(instrValue.value)}`;
330 + break;
331 + }
332 case "PropertyLoad": {
333 value = `PropertyLoad ${printPlace(instrValue.object)}.${
334 instrValue.property
@@ -458,6 +466,49 @@ export function printLValue(lval: LValue): string {
466 }
467 }
468
469 +export function printPattern(pattern: Pattern | Place | SpreadPattern): string {
470 + switch (pattern.kind) {
471 + case "ArrayPattern": {
472 + return (
473 + "[ " + pattern.items.map((item) => printPattern(item)).join(", ") + " ]"
474 + );
475 + }
476 + case "ObjectPattern": {
477 + return (
478 + "{ " +
479 + pattern.properties
480 + .map((item) => {
481 + switch (item.kind) {
482 + case "ObjectProperty": {
483 + return `${item.name}: ${printPattern(item.place)}`;
484 + }
485 + case "Spread": {
486 + return printPattern(item);
487 + }
488 + default: {
489 + assertExhaustive(item, "Unexpected object property kind");
490 + }
491 + }
492 + })
493 + .join(", ") +
494 + " }"
495 + );
496 + }
497 + case "Spread": {
498 + return `...${printPlace(pattern.place)}`;
499 + }
500 + case "Identifier": {
501 + return printPlace(pattern);
502 + }
503 + default: {
504 + assertExhaustive(
505 + pattern,
506 + `Unexpected pattern kind '${(pattern as any).kind}'`
507 + );
508 + }
509 + }
510 +}
511 +
512 export function printPlace(place: Place): string {
513 const items = [
514 place.effect,
compiler/forget/src/HIR/visitors.ts
+85
@@ -13,6 +13,7 @@ import {
13 Instruction,
14 InstructionValue,
15 makeInstructionId,
16 + Pattern,
17 Place,
18 ReactiveScope,
19 ScopeId,
@@ -57,6 +58,11 @@ export function* eachInstructionValueOperand(
58 yield instrValue.value;
59 break;
60 }
61 + case "Destructure": {
62 + yield* eachPatternOperand(instrValue.lvalue.pattern);
63 + yield instrValue.value;
64 + break;
65 + }
66 case "PropertyLoad": {
67 yield instrValue.object;
68 break;
@@ -151,6 +157,49 @@ export function* eachInstructionValueOperand(
157 }
158 }
159
160 +export function* eachPatternOperand(pattern: Pattern): Iterable<Place> {
161 + switch (pattern.kind) {
162 + case "ArrayPattern": {
163 + for (const item of pattern.items) {
164 + if (item.kind === "Identifier") {
165 + yield item;
166 + } else if (item.kind === "Spread") {
167 + yield item.place;
168 + } else {
169 + assertExhaustive(
170 + item,
171 + `Unexpected item kind '${(item as any).kind}'`
172 + );
173 + }
174 + }
175 + break;
176 + }
177 + case "ObjectPattern": {
178 + for (const property of pattern.properties) {
179 + if (property.kind === "ObjectProperty") {
180 + if (property.place.kind === "Identifier") {
181 + yield property.place;
182 + }
183 + } else if (property.kind === "Spread") {
184 + yield property.place;
185 + } else {
186 + assertExhaustive(
187 + property,
188 + `Unexpected item kind '${(property as any).kind}'`
189 + );
190 + }
191 + }
192 + break;
193 + }
194 + default: {
195 + assertExhaustive(
196 + pattern,
197 + `Unexpected pattern kind '${(pattern as any).kind}'`
198 + );
199 + }
200 + }
201 +}
202 +
203 export function mapInstructionOperands(
204 instr: Instruction,
205 fn: (place: Place) => Place
@@ -191,6 +240,11 @@ export function mapInstructionOperands(
240 instrValue.value = fn(instrValue.value);
241 break;
242 }
243 + case "Destructure": {
244 + mapPatternOperands(instrValue.lvalue.pattern, fn);
245 + instrValue.value = fn(instrValue.value);
246 + break;
247 + }
248 case "NewExpression":
249 case "CallExpression": {
250 instrValue.callee = fn(instrValue.callee);
@@ -282,6 +336,37 @@ export function mapInstructionOperands(
336 }
337 }
338
339 +export function mapPatternOperands(
340 + pattern: Pattern,
341 + fn: (place: Place) => Place
342 +): void {
343 + switch (pattern.kind) {
344 + case "ArrayPattern": {
345 + pattern.items = pattern.items.map((item) => {
346 + if (item.kind === "Identifier") {
347 + return fn(item);
348 + } else {
349 + item.place = fn(item.place);
350 + return item;
351 + }
352 + });
353 + break;
354 + }
355 + case "ObjectPattern": {
356 + for (const property of pattern.properties) {
357 + property.place = fn(property.place);
358 + }
359 + break;
360 + }
361 + default: {
362 + assertExhaustive(
363 + pattern,
364 + `Unexpected pattern kind '${(pattern as any).kind}'`
365 + );
366 + }
367 + }
368 +}
369 +
370 /**
371 * Maps a terminal node's block assignments using the provided function.
372 */
compiler/forget/src/Inference/InferAlias.ts
+4 -1
@@ -32,7 +32,10 @@ function inferInstr(instr: Instruction, aliases: DisjointSet<Identifier>) {
32 break;
33 }
34 case "StoreLocal": {
35 - // aliases.union([instrValue.place.identifier, instrValue.value.identifier]);
35 + alias = instrValue.value;
36 + break;
37 + }
38 + case "Destructure": {
39 alias = instrValue.value;
40 break;
41 }
compiler/forget/src/Inference/InferAliasForStores.ts
+8 -1
@@ -11,7 +11,10 @@ import {
11 InstructionId,
12 Place,
13 } from "../HIR/HIR";
14 -import { eachInstructionValueOperand } from "../HIR/visitors";
14 +import {
15 + eachInstructionValueOperand,
16 + eachPatternOperand,
17 +} from "../HIR/visitors";
18 import DisjointSet from "../Utils/DisjointSet";
19
20 export function inferAliasForStores(
@@ -26,6 +29,10 @@ export function inferAliasForStores(
29 }
30 if (value.kind === "StoreLocal") {
31 maybeAlias(aliases, value.lvalue.place, value.value, instr.id);
32 + } else if (value.kind === "Destructure") {
33 + for (const place of eachPatternOperand(value.lvalue.pattern)) {
34 + maybeAlias(aliases, place, value.value, instr.id);
35 + }
36 }
37 for (const operand of eachInstructionValueOperand(value)) {
38 if (
compiler/forget/src/Inference/InferMutableLifetimes.ts
+7 -1
@@ -14,7 +14,7 @@ import {
14 Place,
15 } from "../HIR/HIR";
16 import { printInstruction, printPlace } from "../HIR/PrintHIR";
17 -import { eachInstructionOperand } from "../HIR/visitors";
17 +import { eachInstructionOperand, eachPatternOperand } from "../HIR/visitors";
18 import { assertExhaustive } from "../Utils/utils";
19
20 /**
@@ -123,6 +123,12 @@ export function inferMutableLifetimes(
123 instr.value.lvalue.place.identifier.mutableRange.start = instr.id;
124 instr.value.lvalue.place.identifier.mutableRange.end =
125 makeInstructionId(instr.id + 1);
126 + } else if (instr.value.kind === "Destructure") {
127 + inferPlace(instr.value.value, instr, inferMutableRangeForStores);
128 + for (const place of eachPatternOperand(instr.value.lvalue.pattern)) {
129 + place.identifier.mutableRange.start = instr.id;
130 + place.identifier.mutableRange.end = makeInstructionId(instr.id + 1);
131 + }
132 } else {
133 for (const input of eachInstructionOperand(instr)) {
134 inferPlace(input, instr, inferMutableRangeForStores);
compiler/forget/src/Inference/InferReferenceEffects.ts
+24 -1
@@ -28,6 +28,7 @@ import {
28 import {
29 eachInstructionOperand,
30 eachInstructionValueOperand,
31 + eachPatternOperand,
32 eachTerminalOperand,
33 eachTerminalSuccessor,
34 } from "../HIR/visitors";
@@ -806,7 +807,29 @@ function inferBlock(
807 state.alias(lvalue, instrValue.value);
808 lvalue.effect = Effect.Store;
809 state.alias(instrValue.lvalue.place, instrValue.value);
809 - instrValue.lvalue.place.effect = Effect.Store;
810 + state.reference(instrValue.lvalue.place, Effect.Store);
811 + continue;
812 + }
813 + case "Destructure": {
814 + let effect: Effect = Effect.Capture;
815 + for (const place of eachPatternOperand(instrValue.lvalue.pattern)) {
816 + if (
817 + state.isDefined(place) &&
818 + state.kind(place) === ValueKind.Context
819 + ) {
820 + effect = Effect.Mutate;
821 + break;
822 + }
823 + }
824 + state.reference(instrValue.value, effect);
825 +
826 + const lvalue = instr.lvalue;
827 + state.alias(lvalue, instrValue.value);
828 + lvalue.effect = Effect.Store;
829 + for (const place of eachPatternOperand(instrValue.lvalue.pattern)) {
830 + state.alias(place, instrValue.value);
831 + state.reference(place, Effect.Store);
832 + }
833 continue;
834 }
835 default: {
compiler/forget/src/Optimization/DeadCodeElimination.ts
+11
@@ -8,6 +8,7 @@
8 import { BlockId, HIRFunction, Identifier, InstructionValue } from "../HIR";
9 import {
10 eachInstructionValueOperand,
11 + eachPatternOperand,
12 eachTerminalOperand,
13 } from "../HIR/visitors";
14 import { assertExhaustive, retainWhere } from "../Utils/utils";
@@ -85,6 +86,16 @@ function pruneableValue(
86 // Stores are pruneable only if the identifier being stored to is never read later
87 return !used.has(value.lvalue.place.identifier);
88 }
89 + case "Destructure": {
90 + // Destructure is pruneable only if none of the identifiers are read from later
91 + // TODO: as an optimization, prune unused properties where safe
92 + for (const place of eachPatternOperand(value.lvalue.pattern)) {
93 + if (used.has(place.identifier)) {
94 + return false;
95 + }
96 + }
97 + return true;
98 + }
99 case "CallExpression":
100 case "ComputedCall":
101 case "ComputedStore":
compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+68 -26
@@ -14,7 +14,7 @@ import {
14 Identifier,
15 IdentifierId,
16 InstructionKind,
17 - LValue,
17 + Pattern,
18 Place,
19 ReactiveBlock,
20 ReactiveFunction,
@@ -24,7 +24,9 @@ import {
24 ReactiveTerminal,
25 ReactiveValue,
26 SourceLocation,
27 + SpreadPattern,
28 } from "../HIR/HIR";
29 +import { eachPatternOperand } from "../HIR/visitors";
30 import { Err, Ok, Result } from "../Utils/Result";
31 import { assertExhaustive } from "../Utils/utils";
32
@@ -358,36 +360,39 @@ function codegenInstructionNullable(
360 instr: ReactiveInstruction
361 ): t.Statement | null {
362 let statement;
361 - if (instr.value.kind === "StoreLocal") {
362 - const kind = cx.hasDeclared(instr.value.lvalue.place.identifier)
363 - ? InstructionKind.Reassign
364 - : instr.value.lvalue.kind;
363 + if (instr.value.kind === "StoreLocal" || instr.value.kind === "Destructure") {
364 + let kind: InstructionKind = instr.value.lvalue.kind;
365 + let lvalue;
366 + if (instr.value.kind === "StoreLocal") {
367 + kind = cx.hasDeclared(instr.value.lvalue.place.identifier)
368 + ? InstructionKind.Reassign
369 + : kind;
370 + lvalue = instr.value.lvalue.place;
371 + } else {
372 + lvalue = instr.value.lvalue.pattern;
373 + for (const place of eachPatternOperand(lvalue)) {
374 + if (cx.hasDeclared(place.identifier)) {
375 + kind = InstructionKind.Reassign;
376 + break;
377 + }
378 + }
379 + }
380 const value = codegenPlace(cx, instr.value.value);
381 switch (kind) {
382 case InstructionKind.Const: {
383 return createVariableDeclaration(instr.loc, "const", [
369 - t.variableDeclarator(
370 - convertIdentifier(instr.value.lvalue.place.identifier),
371 - value
372 - ),
384 + t.variableDeclarator(codegenLValue(lvalue), value),
385 ]);
386 }
387 case InstructionKind.Let: {
388 return createVariableDeclaration(instr.loc, "let", [
377 - t.variableDeclarator(
378 - convertIdentifier(instr.value.lvalue.place.identifier),
379 - value
380 - ),
389 + t.variableDeclarator(codegenLValue(lvalue), value),
390 ]);
391 }
392 case InstructionKind.Reassign: {
393 return createExpressionStatement(
394 instr.loc,
386 - t.assignmentExpression(
387 - "=",
388 - convertIdentifier(instr.value.lvalue.place.identifier),
389 - value
390 - )
395 + t.assignmentExpression("=", codegenLValue(lvalue), value)
396 );
397 }
398 default: {
@@ -717,12 +722,6 @@ function codegenInstructionValue(
722 value = codegenPlace(cx, instrValue.place);
723 break;
724 }
720 - case "StoreLocal": {
721 - CompilerError.invariant(
722 - `Unexpected StoreLocal in codegenInstructionValue`,
723 - instrValue.loc
724 - );
725 - }
725 case "FunctionExpression": {
726 value = instrValue.expr;
727 break;
@@ -814,6 +813,13 @@ function codegenInstructionValue(
813 value = t.identifier(instrValue.name);
814 break;
815 }
816 + case "Destructure":
817 + case "StoreLocal": {
818 + CompilerError.invariant(
819 + `Unexpected StoreLocal in codegenInstructionValue`,
820 + instrValue.loc
821 + );
822 + }
823 default: {
824 assertExhaustive(
825 instrValue,
@@ -844,8 +850,44 @@ function codegenJsxElement(
850 }
851 }
852
847 -function codegenLVal(lval: LValue): t.LVal {
848 - return convertIdentifier(lval.place.identifier);
853 +function codegenLValue(
854 + pattern: Pattern | Place | SpreadPattern
855 +): t.ArrayPattern | t.ObjectPattern | t.RestElement | t.Identifier {
856 + switch (pattern.kind) {
857 + case "ArrayPattern": {
858 + return t.arrayPattern(pattern.items.map((item) => codegenLValue(item)));
859 + }
860 + case "ObjectPattern": {
861 + return t.objectPattern(
862 + pattern.properties.map((property) => {
863 + if (property.kind === "ObjectProperty") {
864 + const key = t.identifier(property.name);
865 + const value = codegenLValue(property.place);
866 + return t.objectProperty(
867 + key,
868 + value,
869 + false,
870 + value.type === "Identifier" && value.name === key.name
871 + );
872 + } else {
873 + return t.restElement(codegenLValue(property.place));
874 + }
875 + })
876 + );
877 + }
878 + case "Spread": {
879 + return t.restElement(codegenLValue(pattern.place));
880 + }
881 + case "Identifier": {
882 + return convertIdentifier(pattern.identifier);
883 + }
884 + default: {
885 + assertExhaustive(
886 + pattern,
887 + `Unexpected pattern kind '${(pattern as any).kind}'`
888 + );
889 + }
890 + }
891 }
892
893 function codegenValue(
compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts
+17 -1
@@ -15,7 +15,7 @@ import {
15 Place,
16 ReactiveScope,
17 } from "../HIR/HIR";
18 -import { eachInstructionOperand } from "../HIR/visitors";
18 +import { eachInstructionOperand, eachPatternOperand } from "../HIR/visitors";
19 import DisjointSet from "../Utils/DisjointSet";
20 import { assertExhaustive } from "../Utils/utils";
21
@@ -115,6 +115,21 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
115 ) {
116 operands.push(instr.value.value.identifier);
117 }
118 + } else if (instr.value.kind === "Destructure") {
119 + for (const place of eachPatternOperand(instr.value.lvalue.pattern)) {
120 + if (
121 + place.identifier.mutableRange.end >
122 + place.identifier.mutableRange.start + 1
123 + ) {
124 + operands.push(place.identifier);
125 + }
126 + }
127 + if (
128 + isMutable(instr, instr.value.value) &&
129 + instr.value.value.identifier.mutableRange.start > 0
130 + ) {
131 + operands.push(instr.value.value.identifier);
132 + }
133 } else {
134 for (const operand of eachInstructionOperand(instr)) {
135 if (
@@ -183,6 +198,7 @@ function isMutable({ id }: Instruction, place: Place): boolean {
198
199 function mayAllocate(value: InstructionValue): boolean {
200 switch (value.kind) {
201 + case "Destructure":
202 case "StoreLocal":
203 case "LoadGlobal":
204 case "TypeCastExpression":
compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+15 -1
@@ -20,7 +20,10 @@ import {
20 ReactiveScopeDependency,
21 ReactiveValue,
22 } from "../HIR/HIR";
23 -import { eachInstructionValueOperand } from "../HIR/visitors";
23 +import {
24 + eachInstructionValueOperand,
25 + eachPatternOperand,
26 +} from "../HIR/visitors";
27 import { assertExhaustive } from "../Utils/utils";
28
29 /**
@@ -707,6 +710,17 @@ function visitInstructionValue(
710 id,
711 scope: context.currentScope,
712 });
713 + } else if (value.kind === "Destructure") {
714 + context.visitOperand(value.value);
715 + for (const place of eachPatternOperand(value.lvalue.pattern)) {
716 + if (value.lvalue.kind === InstructionKind.Reassign) {
717 + context.visitReassignment(place);
718 + }
719 + context.declare(place.identifier, {
720 + id,
721 + scope: context.currentScope,
722 + });
723 + }
724 } else {
725 visitReactiveValue(context, id, value);
726 }
compiler/forget/src/SSA/EnterSSA.ts
+6
@@ -16,6 +16,7 @@ import { printIdentifier } from "../HIR/PrintHIR";
16 import {
17 eachTerminalSuccessor,
18 mapInstructionOperands,
19 + mapPatternOperands,
20 mapTerminalOperands,
21 } from "../HIR/visitors";
22
@@ -228,6 +229,11 @@ export default function enterSSA(func: HIRFunction): void {
229 const newPlace = builder.definePlace(oldPlace);
230 instr.value.lvalue.place = newPlace;
231
232 + instr.value.value = builder.getPlace(instr.value.value);
233 + } else if (instr.value.kind === "Destructure") {
234 + mapPatternOperands(instr.value.lvalue.pattern, (place) =>
235 + builder.definePlace(place)
236 + );
237 instr.value.value = builder.getPlace(instr.value.value);
238 } else {
239 mapInstructionOperands(instr, (place) => builder.getPlace(place));
compiler/forget/src/SSA/LeaveSSA.ts
+78 -27
@@ -6,6 +6,7 @@
6 */
7
8 import invariant from "invariant";
9 +import { CompilerError } from "../CompilerError";
10 import {
11 BasicBlock,
12 Effect,
@@ -16,12 +17,15 @@ import {
17 InstructionId,
18 InstructionKind,
19 LValue,
20 + LValuePattern,
21 makeInstructionId,
22 Phi,
23 Place,
24 } from "../HIR/HIR";
25 +import { printPlace } from "../HIR/PrintHIR";
26 import {
27 eachInstructionValueOperand,
28 + eachPatternOperand,
29 eachTerminalOperand,
30 } from "../HIR/visitors";
31
@@ -89,7 +93,10 @@ import {
93 */
94 export function leaveSSA(fn: HIRFunction): void {
95 // Maps identifier names to their original declaration.
92 - const declarations: Map<string, LValue> = new Map();
96 + const declarations: Map<
97 + string,
98 + { lvalue: LValue | LValuePattern; place: Place }
99 + > = new Map();
100
101 // For non-memoizable phis, this maps original identifiers to the identifier they should be
102 // *rewritten* to. The keys are the original identifiers, and the value will be _either_ the
@@ -111,30 +118,74 @@ export function leaveSSA(fn: HIRFunction): void {
118 // Iterate the instructions and perform any rewrites as well as promoting SSA variables to
119 // `let` or `reassign` where possible.
120 const { lvalue, value } = instr;
114 - if (
115 - value.kind === "StoreLocal" &&
116 - value.lvalue.place.identifier.name != null
117 - ) {
118 - const originalLVal = declarations.get(
119 - value.lvalue.place.identifier.name
120 - );
121 - if (originalLVal === undefined) {
122 - declarations.set(value.lvalue.place.identifier.name, value.lvalue);
123 - value.lvalue.kind = InstructionKind.Const;
124 - } else {
125 - // This is an instance of the original id, so we need to promote the original declaration
126 - // to a `let` and the current lval to a `reassign`
127 - originalLVal.kind = InstructionKind.Let;
121 + if (value.kind === "StoreLocal") {
122 + if (value.lvalue.place.identifier.name != null) {
123 + const originalLVal = declarations.get(
124 + value.lvalue.place.identifier.name
125 + );
126 + if (originalLVal === undefined) {
127 + declarations.set(value.lvalue.place.identifier.name, {
128 + lvalue: value.lvalue,
129 + place: value.lvalue.place,
130 + });
131 + value.lvalue.kind = InstructionKind.Const;
132 + } else {
133 + // This is an instance of the original id, so we need to promote the original declaration
134 + // to a `let` and the current lval to a `reassign`
135 + originalLVal.lvalue.kind = InstructionKind.Let;
136 + }
137 + } else if (rewrites.has(value.lvalue.place.identifier)) {
138 + value.lvalue.kind =
139 + rewrites.get(value.lvalue.place.identifier) ===
140 + value.lvalue.place.identifier
141 + ? InstructionKind.Let
142 + : InstructionKind.Reassign;
143 + }
144 + } else if (value.kind === "Destructure") {
145 + let kind: InstructionKind | null = null;
146 + for (const place of eachPatternOperand(value.lvalue.pattern)) {
147 + if (place.identifier.name == null) {
148 + if (kind !== null && kind !== InstructionKind.Const) {
149 + CompilerError.invariant(
150 + `Expected consistent kind for destructuring, other places were '${kind}' but '${printPlace(
151 + place
152 + )}' is const`,
153 + place.loc
154 + );
155 + }
156 + kind = InstructionKind.Const;
157 + } else {
158 + const originalLVal = declarations.get(place.identifier.name);
159 + if (originalLVal === undefined) {
160 + declarations.set(place.identifier.name, {
161 + lvalue: value.lvalue,
162 + place,
163 + });
164 + if (kind !== null && kind !== InstructionKind.Const) {
165 + CompilerError.invariant(
166 + `Expected consistent kind for destructuring, other places were '${kind}' but '${printPlace(
167 + place
168 + )}' is const`,
169 + place.loc
170 + );
171 + }
172 + kind = InstructionKind.Const;
173 + } else {
174 + if (kind !== null && kind !== InstructionKind.Reassign) {
175 + CompilerError.invariant(
176 + `Expected consistent kind for destructuring, other places were '${kind}' but '${printPlace(
177 + place
178 + )}' is reassigned`,
179 + place.loc
180 + );
181 + }
182 + kind = InstructionKind.Reassign;
183 + originalLVal.lvalue.kind = InstructionKind.Let;
184 + }
185 + }
186 }
129 - } else if (
130 - value.kind === "StoreLocal" &&
131 - rewrites.has(value.lvalue.place.identifier)
132 - ) {
133 - value.lvalue.kind =
134 - rewrites.get(value.lvalue.place.identifier) ===
135 - value.lvalue.place.identifier
136 - ? InstructionKind.Let
137 - : InstructionKind.Reassign;
187 + invariant(kind !== null, "Expected at least one operand");
188 + value.lvalue.kind = kind;
189 }
190 rewritePlace(lvalue, rewrites, declarations);
191 for (const operand of eachInstructionValueOperand(instr.value)) {
@@ -304,7 +355,7 @@ export function leaveSSA(fn: HIRFunction): void {
355 loc: GeneratedSource,
356 };
357 block.instructions.push(instr);
307 - declarations.set(phi.id.name, lvalue);
358 + declarations.set(phi.id.name, { lvalue, place: lvalue.place });
359 phi.id.mutableRange.start = terminal.id;
360 if (!isPhiMutatedAfterCreation) {
361 phi.id.mutableRange.end = makeInstructionId(terminal.id + 1);
@@ -343,7 +394,7 @@ export function leaveSSA(fn: HIRFunction): void {
394 if (canonicalId.name !== null) {
395 const declaration = declarations.get(canonicalId.name);
396 if (declaration !== undefined) {
346 - declaration.kind = InstructionKind.Let;
397 + declaration.lvalue.kind = InstructionKind.Let;
398 }
399 }
400 }
@@ -370,7 +421,7 @@ export function leaveSSA(fn: HIRFunction): void {
421 function rewritePlace(
422 place: Place,
423 rewrites: Map<Identifier, Identifier>,
373 - declarations: Map<string, LValue>
424 + declarations: Map<string, { lvalue: LValue | LValuePattern; place: Place }>
425 ): void {
426 const prevIdentifier = place.identifier;
427 const nextIdentifier = rewrites.get(prevIdentifier);
compiler/forget/src/__tests__/fixtures/hir/bug_object-pattern.expect.md
+1 -1
@@ -15,7 +15,7 @@ function component(t) {
15 ```javascript
16 function component(t) {
17 const $ = React.unstable_useMemoCache(2);
18 - const a = t.a;
18 + const { a } = t;
19 const c_0 = $[0] !== a;
20 let t0;
21 if (c_0) {
compiler/forget/src/__tests__/fixtures/hir/capturing-function-member-expr-call.expect.md
+2 -2
@@ -19,9 +19,9 @@ function component({ mutator }) {
19 ## Code
20
21 ```javascript
22 -function component(t28) {
22 +function component(t27) {
23 const $ = React.unstable_useMemoCache(7);
24 - const mutator = t28.mutator;
24 + const { mutator } = t27;
25 const c_0 = $[0] !== mutator;
26 let t0;
27 if (c_0) {
compiler/forget/src/__tests__/fixtures/hir/concise-arrow-expr.expect.md
+1 -1
@@ -15,7 +15,7 @@ function component() {
15 ```javascript
16 function component() {
17 const $ = React.unstable_useMemoCache(4);
18 - const setX = useState(0)[1];
18 + const [x, setX] = useState(0);
19 const c_0 = $[0] !== setX;
20 let t0;
21 if (c_0) {
compiler/forget/src/__tests__/fixtures/hir/controlled-input.expect.md
+1 -3
@@ -15,9 +15,7 @@ function component() {
15 ```javascript
16 function component() {
17 const $ = React.unstable_useMemoCache(5);
18 - const t2 = useState(0);
19 - const x = t2[0];
20 - const setX = t2[1];
18 + const [x, setX] = useState(0);
19 const c_0 = $[0] !== setX;
20 let t0;
21 if (c_0) {
compiler/forget/src/__tests__/fixtures/hir/destructure-direct-reassignment.expect.md new
+36
@@ -0,0 +1,36 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function foo(props) {
6 + let x, y;
7 + ({ x, y } = { x: props.a, y: props.b });
8 + x = props.c;
9 + return x + y;
10 +}
11 +
12 +```
13 +
14 +## Code
15 +
16 +```javascript
17 +function foo(props) {
18 + const $ = React.unstable_useMemoCache(3);
19 + const c_0 = $[0] !== props.a;
20 + const c_1 = $[1] !== props.b;
21 + let t0;
22 + if (c_0 || c_1) {
23 + t0 = { x: props.a, y: props.b };
24 + $[0] = props.a;
25 + $[1] = props.b;
26 + $[2] = t0;
27 + } else {
28 + t0 = $[2];
29 + }
30 + let { x, y } = t0;
31 + x = props.c;
32 + return x + y;
33 +}
34 +
35 +```
36 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/destructure-direct-reassignment.js new
+6
@@ -0,0 +1,6 @@
1 +function foo(props) {
2 + let x, y;
3 + ({ x, y } = { x: props.a, y: props.b });
4 + x = props.c;
5 + return x + y;
6 +}
compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.expect.md
+9 -4
@@ -29,11 +29,16 @@ function foo(a, b, c) {
29 function foo(a, b, c) {
30 const $ = React.unstable_useMemoCache(5);
31
32 - const d = a[0];
33 - const g = a[1][0].e.f;
32 + const [d, t54] = a;
33
35 - const n = b.l.m[0][0];
36 - const o = b.o;
34 + const [t56] = t54;
35 + const { e: t58 } = t56;
36 + const { f: g } = t58;
37 +
38 + const { l: t63, o } = b;
39 + const { m: t66 } = t63;
40 + const [t68] = t66;
41 + const [n] = t68;
42 const c_0 = $[0] !== d;
43 const c_1 = $[1] !== g;
44 const c_2 = $[2] !== n;
compiler/forget/src/__tests__/fixtures/hir/destructuring.expect.md
+29 -16
@@ -8,16 +8,18 @@ function foo(a, b, c) {
8 [
9 {
10 e: { f },
11 + ...g
12 },
13 ],
14 + ...h
15 ] = a;
16 const {
17 l: {
16 - m: [[n]],
18 + m: [[n], ...o],
19 },
18 - o,
20 + p,
21 } = b;
20 - return [d, f, n, o];
22 + return [d, f, g, h, n, o, p];
23 }
24
25 ```
@@ -26,26 +28,37 @@ function foo(a, b, c) {
28
29 ```javascript
30 function foo(a, b, c) {
29 - const $ = React.unstable_useMemoCache(5);
30 - const d = a[0];
31 - const f = a[1][0].e.f;
31 + const $ = React.unstable_useMemoCache(8);
32 + const [d, t40, ...h] = a;
33
33 - const n = b.l.m[0][0];
34 - const o = b.o;
34 + const [t43] = t40;
35 + const { e: t45, ...g } = t43;
36 + const { f } = t45;
37 +
38 + const { l: t51, p } = b;
39 + const { m: t54 } = t51;
40 + const [t56, ...o] = t54;
41 + const [n] = t56;
42 const c_0 = $[0] !== d;
43 const c_1 = $[1] !== f;
37 - const c_2 = $[2] !== n;
38 - const c_3 = $[3] !== o;
44 + const c_2 = $[2] !== g;
45 + const c_3 = $[3] !== h;
46 + const c_4 = $[4] !== n;
47 + const c_5 = $[5] !== o;
48 + const c_6 = $[6] !== p;
49 let t0;
40 - if (c_0 || c_1 || c_2 || c_3) {
41 - t0 = [d, f, n, o];
50 + if (c_0 || c_1 || c_2 || c_3 || c_4 || c_5 || c_6) {
51 + t0 = [d, f, g, h, n, o, p];
52 $[0] = d;
53 $[1] = f;
44 - $[2] = n;
45 - $[3] = o;
46 - $[4] = t0;
54 + $[2] = g;
55 + $[3] = h;
56 + $[4] = n;
57 + $[5] = o;
58 + $[6] = p;
59 + $[7] = t0;
60 } else {
48 - t0 = $[4];
61 + t0 = $[7];
62 }
63 return t0;
64 }
compiler/forget/src/__tests__/fixtures/hir/destructuring.js
+5 -3
@@ -4,14 +4,16 @@ function foo(a, b, c) {
4 [
5 {
6 e: { f },
7 + ...g
8 },
9 ],
10 + ...h
11 ] = a;
12 const {
13 l: {
12 - m: [[n]],
14 + m: [[n], ...o],
15 },
14 - o,
16 + p,
17 } = b;
16 - return [d, f, n, o];
18 + return [d, f, g, h, n, o, p];
19 }
compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md
+165 -204
@@ -13,14 +13,11 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
13 }
14 }
15
16 - const g = { ...a, b() {}, c: () => {} };
17 - const h = [...b];
16 + const g = { b() {}, c: () => {} };
17 new c(...args);
18 c(...args);
20 - const [y, ...yy] = useState(0);
21 - const { z, aa = "aa", ...zz } = useCustom();
19 + const { z, aa = "aa" } = useCustom();
20
23 - <Button {...args}></Button>;
21 <Button xlink:href="localhost:3000"></Button>;
22 <Button haha={1}></Button>;
23 <Button>{/** empty */}</Button>;
@@ -126,293 +123,257 @@ let moduleLocal = false;
123 7 | constructor() {
124 8 | console.log(this.#secretSauce);
125
129 -[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle SpreadElement properties in ObjectExpression
130 - 10 | }
131 - 11 |
132 -> 12 | const g = { ...a, b() {}, c: () => {} };
133 - | ^^^^
134 - 13 | const h = [...b];
135 - 14 | new c(...args);
136 - 15 | c(...args);
137 -
126 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle ObjectMethod properties in ObjectExpression
127 10 | }
128 11 |
141 -> 12 | const g = { ...a, b() {}, c: () => {} };
142 - | ^^^^^^
143 - 13 | const h = [...b];
144 - 14 | new c(...args);
145 - 15 | c(...args);
146 -
147 -[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle SpreadElement elements in ArrayExpression
148 - 11 |
149 - 12 | const g = { ...a, b() {}, c: () => {} };
150 -> 13 | const h = [...b];
151 - | ^^^^
152 - 14 | new c(...args);
153 - 15 | c(...args);
154 - 16 | const [y, ...yy] = useState(0);
129 +> 12 | const g = { b() {}, c: () => {} };
130 + | ^^^^^^
131 + 13 | new c(...args);
132 + 14 | c(...args);
133 + 15 | const { z, aa = "aa" } = useCustom();
134
135 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle SpreadElement arguments in NewExpression
157 - 12 | const g = { ...a, b() {}, c: () => {} };
158 - 13 | const h = [...b];
159 -> 14 | new c(...args);
136 + 11 |
137 + 12 | const g = { b() {}, c: () => {} };
138 +> 13 | new c(...args);
139 | ^^^^^^^
161 - 15 | c(...args);
162 - 16 | const [y, ...yy] = useState(0);
163 - 17 | const { z, aa = "aa", ...zz } = useCustom();
140 + 14 | c(...args);
141 + 15 | const { z, aa = "aa" } = useCustom();
142 + 16 |
143
144 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle SpreadElement arguments in CallExpression
166 - 13 | const h = [...b];
167 - 14 | new c(...args);
168 -> 15 | c(...args);
145 + 12 | const g = { b() {}, c: () => {} };
146 + 13 | new c(...args);
147 +> 14 | c(...args);
148 | ^^^^^^^
170 - 16 | const [y, ...yy] = useState(0);
171 - 17 | const { z, aa = "aa", ...zz } = useCustom();
172 - 18 |
173 -
174 -[ReactForget] TodoError: (BuildHIR::lowerAssignment) Handle RestElement in ArrayPattern
175 - 14 | new c(...args);
176 - 15 | c(...args);
177 -> 16 | const [y, ...yy] = useState(0);
178 - | ^^^^^
179 - 17 | const { z, aa = "aa", ...zz } = useCustom();
180 - 18 |
181 - 19 | <Button {...args}></Button>;
149 + 15 | const { z, aa = "aa" } = useCustom();
150 + 16 |
151 + 17 | <Button xlink:href="localhost:3000"></Button>;
152
153 [ReactForget] TodoError: (BuildHIR::lowerAssignment) Handle AssignmentPattern assignments
184 - 15 | c(...args);
185 - 16 | const [y, ...yy] = useState(0);
186 -> 17 | const { z, aa = "aa", ...zz } = useCustom();
154 + 13 | new c(...args);
155 + 14 | c(...args);
156 +> 15 | const { z, aa = "aa" } = useCustom();
157 | ^^^^^^^^^
188 - 18 |
189 - 19 | <Button {...args}></Button>;
190 - 20 | <Button xlink:href="localhost:3000"></Button>;
191 -
192 -[ReactForget] TodoError: (BuildHIR::lowerAssignment) Handle RestElement properties in ObjectPattern
193 - 15 | c(...args);
194 - 16 | const [y, ...yy] = useState(0);
195 -> 17 | const { z, aa = "aa", ...zz } = useCustom();
196 - | ^^^^^
197 - 18 |
198 - 19 | <Button {...args}></Button>;
199 - 20 | <Button xlink:href="localhost:3000"></Button>;
158 + 16 |
159 + 17 | <Button xlink:href="localhost:3000"></Button>;
160 + 18 | <Button haha={1}></Button>;
161
162 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle JSXNamespacedName attribute names in JSXElement
202 - 18 |
203 - 19 | <Button {...args}></Button>;
204 -> 20 | <Button xlink:href="localhost:3000"></Button>;
163 + 15 | const { z, aa = "aa" } = useCustom();
164 + 16 |
165 +> 17 | <Button xlink:href="localhost:3000"></Button>;
166 | ^^^^^^^^^^
206 - 21 | <Button haha={1}></Button>;
207 - 22 | <Button>{/** empty */}</Button>;
208 - 23 | <DesignSystem.Button />;
167 + 18 | <Button haha={1}></Button>;
168 + 19 | <Button>{/** empty */}</Button>;
169 + 20 | <DesignSystem.Button />;
170
171 [ReactForget] TodoError: (BuildHIR::lowerJsxElement) Handle JSXEmptyExpression expressions
211 - 20 | <Button xlink:href="localhost:3000"></Button>;
212 - 21 | <Button haha={1}></Button>;
213 -> 22 | <Button>{/** empty */}</Button>;
172 + 17 | <Button xlink:href="localhost:3000"></Button>;
173 + 18 | <Button haha={1}></Button>;
174 +> 19 | <Button>{/** empty */}</Button>;
175 | ^^^^^^^^^^^^
215 - 23 | <DesignSystem.Button />;
216 - 24 |
217 - 25 | const j = function bar([quz, qux], ...args) {};
176 + 20 | <DesignSystem.Button />;
177 + 21 |
178 + 22 | const j = function bar([quz, qux], ...args) {};
179
180 [ReactForget] TodoError: (BuildHIR::lowerJsxElementName) Handle JSXMemberExpression tags
220 - 21 | <Button haha={1}></Button>;
221 - 22 | <Button>{/** empty */}</Button>;
222 -> 23 | <DesignSystem.Button />;
181 + 18 | <Button haha={1}></Button>;
182 + 19 | <Button>{/** empty */}</Button>;
183 +> 20 | <DesignSystem.Button />;
184 | ^^^^^^^^^^^^^^^^^^^
224 - 24 |
225 - 25 | const j = function bar([quz, qux], ...args) {};
226 - 26 |
185 + 21 |
186 + 22 | const j = function bar([quz, qux], ...args) {};
187 + 23 |
188
189 [ReactForget] TodoError: (BuildHIR::lower) Handle ArrayPattern params
229 - 23 | <DesignSystem.Button />;
230 - 24 |
231 -> 25 | const j = function bar([quz, qux], ...args) {};
190 + 20 | <DesignSystem.Button />;
191 + 21 |
192 +> 22 | const j = function bar([quz, qux], ...args) {};
193 | ^^^^^^^^^^
233 - 26 |
234 - 27 | for (; i < 3; i += 1) {
235 - 28 | x.push(i);
194 + 23 |
195 + 24 | for (; i < 3; i += 1) {
196 + 25 | x.push(i);
197
198 [ReactForget] TodoError: (BuildHIR::lower) Handle RestElement params
238 - 23 | <DesignSystem.Button />;
239 - 24 |
240 -> 25 | const j = function bar([quz, qux], ...args) {};
199 + 20 | <DesignSystem.Button />;
200 + 21 |
201 +> 22 | const j = function bar([quz, qux], ...args) {};
202 | ^^^^^^^
242 - 26 |
243 - 27 | for (; i < 3; i += 1) {
244 - 28 | x.push(i);
203 + 23 |
204 + 24 | for (; i < 3; i += 1) {
205 + 25 | x.push(i);
206
207 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
247 - 25 | const j = function bar([quz, qux], ...args) {};
248 - 26 |
249 -> 27 | for (; i < 3; i += 1) {
208 + 22 | const j = function bar([quz, qux], ...args) {};
209 + 23 |
210 +> 24 | for (; i < 3; i += 1) {
211 | ^
251 - 28 | x.push(i);
252 - 29 | }
253 - 30 | for (; i < 3; ) {
212 + 25 | x.push(i);
213 + 26 | }
214 + 27 | for (; i < 3; ) {
215
216 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
256 - 28 | x.push(i);
257 - 29 | }
258 -> 30 | for (; i < 3; ) {
217 + 25 | x.push(i);
218 + 26 | }
219 +> 27 | for (; i < 3; ) {
220 | ^
260 - 31 | break;
261 - 32 | }
262 - 33 | for (;;) {
221 + 28 | break;
222 + 29 | }
223 + 30 | for (;;) {
224
225 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty update in ForStatement
265 - 28 | x.push(i);
266 - 29 | }
267 -> 30 | for (; i < 3; ) {
226 + 25 | x.push(i);
227 + 26 | }
228 +> 27 | for (; i < 3; ) {
229 | ^
269 - 31 | break;
270 - 32 | }
271 - 33 | for (;;) {
230 + 28 | break;
231 + 29 | }
232 + 30 | for (;;) {
233
234 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
235 + 28 | break;
236 + 29 | }
237 +> 30 | for (;;) {
238 + | ^
239 31 | break;
240 32 | }
276 -> 33 | for (;;) {
277 - | ^
278 - 34 | break;
279 - 35 | }
280 - 36 |
241 + 33 |
242
243 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty update in ForStatement
244 + 28 | break;
245 + 29 | }
246 +> 30 | for (;;) {
247 + | ^
248 31 | break;
249 32 | }
285 -> 33 | for (;;) {
286 - | ^
287 - 34 | break;
288 - 35 | }
289 - 36 |
250 + 33 |
251
252 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty test in ForStatement
253 + 28 | break;
254 + 29 | }
255 +> 30 | for (;;) {
256 + | ^
257 31 | break;
258 32 | }
294 -> 33 | for (;;) {
295 - | ^
296 - 34 | break;
297 - 35 | }
298 - 36 |
259 + 33 |
260
261 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle tagged template with interpolations
301 - 35 | }
302 - 36 |
303 -> 37 | graphql`
262 + 32 | }
263 + 33 |
264 +> 34 | graphql`
265 | ^
305 - 38 | ${g}
306 - 39 | `;
307 - 40 |
266 + 35 | ${g}
267 + 36 | `;
268 + 37 |
269
270 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value
310 - 39 | `;
311 - 40 |
312 -> 41 | graphql`\\t\n`;
271 + 36 | `;
272 + 37 |
273 +> 38 | graphql`\\t\n`;
274 | ^^^^^^^^^^^^^^
314 - 42 |
315 - 43 | for (const c of [1, 2]) {
316 - 44 | }
275 + 39 |
276 + 40 | for (const c of [1, 2]) {
277 + 41 | }
278
279 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ForOfStatement statements
319 - 41 | graphql`\\t\n`;
320 - 42 |
321 -> 43 | for (const c of [1, 2]) {
280 + 38 | graphql`\\t\n`;
281 + 39 |
282 +> 40 | for (const c of [1, 2]) {
283 | ^
323 - 44 | }
324 - 45 |
325 - 46 | for (let x in { a: 1 }) {
284 + 41 | }
285 + 42 |
286 + 43 | for (let x in { a: 1 }) {
287
288 [ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ForInStatement statements
289 + 41 | }
290 + 42 |
291 +> 43 | for (let x in { a: 1 }) {
292 + | ^
293 44 | }
294 45 |
330 -> 46 | for (let x in { a: 1 }) {
331 - | ^
332 - 47 | }
333 - 48 |
334 - 49 | let updateIdentifier = 0;
295 + 46 | let updateIdentifier = 0;
296
297 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle prefix UpdateExpression
337 - 48 |
338 - 49 | let updateIdentifier = 0;
339 -> 50 | --updateIdentifier;
298 + 45 |
299 + 46 | let updateIdentifier = 0;
300 +> 47 | --updateIdentifier;
301 | ^^^^^^^^^^^^^^^^^^
341 - 51 | ++updateIdentifier;
342 - 52 | updateIdentifier.y++;
343 - 53 | updateIdentifier.y--;
302 + 48 | ++updateIdentifier;
303 + 49 | updateIdentifier.y++;
304 + 50 | updateIdentifier.y--;
305
306 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle prefix UpdateExpression
346 - 49 | let updateIdentifier = 0;
347 - 50 | --updateIdentifier;
348 -> 51 | ++updateIdentifier;
307 + 46 | let updateIdentifier = 0;
308 + 47 | --updateIdentifier;
309 +> 48 | ++updateIdentifier;
310 | ^^^^^^^^^^^^^^^^^^
350 - 52 | updateIdentifier.y++;
351 - 53 | updateIdentifier.y--;
352 - 54 |
311 + 49 | updateIdentifier.y++;
312 + 50 | updateIdentifier.y--;
313 + 51 |
314
315 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument
355 - 50 | --updateIdentifier;
356 - 51 | ++updateIdentifier;
357 -> 52 | updateIdentifier.y++;
316 + 47 | --updateIdentifier;
317 + 48 | ++updateIdentifier;
318 +> 49 | updateIdentifier.y++;
319 | ^^^^^^^^^^^^^^^^^^^^
359 - 53 | updateIdentifier.y--;
360 - 54 |
361 - 55 | switch (i) {
320 + 50 | updateIdentifier.y--;
321 + 51 |
322 + 52 | switch (i) {
323
324 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument
364 - 51 | ++updateIdentifier;
365 - 52 | updateIdentifier.y++;
366 -> 53 | updateIdentifier.y--;
325 + 48 | ++updateIdentifier;
326 + 49 | updateIdentifier.y++;
327 +> 50 | updateIdentifier.y--;
328 | ^^^^^^^^^^^^^^^^^^^^
368 - 54 |
369 - 55 | switch (i) {
370 - 56 | case 1 + 1: {
329 + 51 |
330 + 52 | switch (i) {
331 + 53 | case 1 + 1: {
332
333 [ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
373 - 58 | case foo(): {
374 - 59 | }
375 -> 60 | case x.y: {
334 + 55 | case foo(): {
335 + 56 | }
336 +> 57 | case x.y: {
337 | ^^^
377 - 61 | }
378 - 62 | default: {
379 - 63 | }
338 + 58 | }
339 + 59 | default: {
340 + 60 | }
341
342 [ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
382 - 56 | case 1 + 1: {
383 - 57 | }
384 -> 58 | case foo(): {
343 + 53 | case 1 + 1: {
344 + 54 | }
345 +> 55 | case foo(): {
346 | ^^^^^
386 - 59 | }
387 - 60 | case x.y: {
388 - 61 | }
347 + 56 | }
348 + 57 | case x.y: {
349 + 58 | }
350
351 [ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
391 - 54 |
392 - 55 | switch (i) {
393 -> 56 | case 1 + 1: {
352 + 51 |
353 + 52 | switch (i) {
354 +> 53 | case 1 + 1: {
355 | ^^^^^
395 - 57 | }
396 - 58 | case foo(): {
397 - 59 | }
356 + 54 | }
357 + 55 | case foo(): {
358 + 56 | }
359
360 [ReactForget] InvalidInputError: (BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported.
400 - 65 |
401 - 66 | // Cannot assign to globals
402 -> 67 | someUnknownGlobal = true;
361 + 62 |
362 + 63 | // Cannot assign to globals
363 +> 64 | someUnknownGlobal = true;
364 | ^^^^^^^^^^^^^^^^^
404 - 68 | moduleLocal = true;
405 - 69 | }
406 - 70 |
365 + 65 | moduleLocal = true;
366 + 66 | }
367 + 67 |
368
369 [ReactForget] InvalidInputError: (BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported.
409 - 66 | // Cannot assign to globals
410 - 67 | someUnknownGlobal = true;
411 -> 68 | moduleLocal = true;
370 + 63 | // Cannot assign to globals
371 + 64 | someUnknownGlobal = true;
372 +> 65 | moduleLocal = true;
373 | ^^^^^^^^^^^
413 - 69 | }
414 - 70 |
415 - 71 | let moduleLocal = false;
374 + 66 | }
375 + 67 |
376 + 68 | let moduleLocal = false;
377 ```
378
379
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.js
+2 -5
@@ -9,14 +9,11 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
9 }
10 }
11
12 - const g = { ...a, b() {}, c: () => {} };
13 - const h = [...b];
12 + const g = { b() {}, c: () => {} };
13 new c(...args);
14 c(...args);
16 - const [y, ...yy] = useState(0);
17 - const { z, aa = "aa", ...zz } = useCustom();
15 + const { z, aa = "aa" } = useCustom();
16
19 - <Button {...args}></Button>;
17 <Button xlink:href="localhost:3000"></Button>;
18 <Button haha={1}></Button>;
19 <Button>{/** empty */}</Button>;
compiler/forget/src/__tests__/fixtures/hir/inadvertent-mutability-readonly-lambda.expect.md
+1 -1
@@ -22,7 +22,7 @@ function Component(props) {
22
23 ```javascript
24 function Component(props) {
25 - const setValue = useState(null)[1];
25 + const [value, setValue] = useState(null);
26
27 const onChange = (e) => setValue((value) => value + e.target.value);
28
compiler/forget/src/__tests__/fixtures/hir/object-pattern-params.expect.md
+2 -3
@@ -13,10 +13,9 @@ function component({ a, b }) {
13 ## Code
14
15 ```javascript
16 -function component(t19) {
16 +function component(t16) {
17 const $ = React.unstable_useMemoCache(7);
18 - const a = t19.a;
19 - const b = t19.b;
18 + const { a, b } = t16;
19 const c_0 = $[0] !== a;
20 let t0;
21 if (c_0) {
compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring-with-mutation.expect.md new
+44
@@ -0,0 +1,44 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function foo(props) {
6 + let { x } = { x: [] };
7 + x.push(props.bar);
8 + if (props.cond) {
9 + ({ x } = { x: {} });
10 + ({ x } = { x: [] });
11 + x.push(props.foo);
12 + }
13 + mut(x);
14 + return x;
15 +}
16 +
17 +```
18 +
19 +## Code
20 +
21 +```javascript
22 +function foo(props) {
23 + const $ = React.unstable_useMemoCache(2);
24 + const c_0 = $[0] !== props;
25 + let x;
26 + if (c_0) {
27 + ({ x } = { x: [] });
28 + x.push(props.bar);
29 + if (props.cond) {
30 + ({ x } = { x: [] });
31 + x.push(props.foo);
32 + }
33 +
34 + mut(x);
35 + $[0] = props;
36 + $[1] = x;
37 + } else {
38 + x = $[1];
39 + }
40 + return x;
41 +}
42 +
43 +```
44 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring-with-mutation.js new
+11
@@ -0,0 +1,11 @@
1 +function foo(props) {
2 + let { x } = { x: [] };
3 + x.push(props.bar);
4 + if (props.cond) {
5 + ({ x } = { x: {} });
6 + ({ x } = { x: [] });
7 + x.push(props.foo);
8 + }
9 + mut(x);
10 + return x;
11 +}
compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring.expect.md new
+48
@@ -0,0 +1,48 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function foo(props) {
6 + let { x } = { x: [] };
7 + x.push(props.bar);
8 + if (props.cond) {
9 + ({ x } = { x: {} });
10 + ({ x } = { x: [] });
11 + x.push(props.foo);
12 + }
13 + return x;
14 +}
15 +
16 +```
17 +
18 +## Code
19 +
20 +```javascript
21 +function foo(props) {
22 + const $ = React.unstable_useMemoCache(4);
23 + const c_0 = $[0] !== props.bar;
24 + let x;
25 + if (c_0) {
26 + ({ x } = { x: [] });
27 + x.push(props.bar);
28 + $[0] = props.bar;
29 + $[1] = x;
30 + } else {
31 + x = $[1];
32 + }
33 + if (props.cond) {
34 + const c_2 = $[2] !== props.foo;
35 + if (c_2) {
36 + ({ x } = { x: [] });
37 + x.push(props.foo);
38 + $[2] = props.foo;
39 + $[3] = x;
40 + } else {
41 + x = $[3];
42 + }
43 + }
44 + return x;
45 +}
46 +
47 +```
48 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring.js new
+10
@@ -0,0 +1,10 @@
1 +function foo(props) {
2 + let { x } = { x: [] };
3 + x.push(props.bar);
4 + if (props.cond) {
5 + ({ x } = { x: {} });
6 + ({ x } = { x: [] });
7 + x.push(props.foo);
8 + }
9 + return x;
10 +}
compiler/forget/src/__tests__/fixtures/hir/use-callback-simple.expect.md
+1 -3
@@ -16,9 +16,7 @@ function component() {
16 ```javascript
17 function component() {
18 const $ = React.unstable_useMemoCache(5);
19 - const t2 = useState(0);
20 - const count = t2[0];
21 - const setCount = t2[1];
19 + const [count, setCount] = useState(0);
20 const c_0 = $[0] !== setCount;
21 const c_1 = $[1] !== count;
22 let t0;