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.
This commit is contained in:
Joe Savona
2023-03-03 17:09:25 -08:00
parent d24f3fdad9
commit ca9d49090a
32 changed files with 925 additions and 400 deletions
+143 -95
View File
@@ -14,6 +14,7 @@ import { Err, Ok, Result } from "../Utils/Result";
import { assertExhaustive } from "../Utils/utils";
import { Environment, EnvironmentOptions } from "./Environment";
import {
ArrayPattern,
BlockId,
BranchTerminal,
Case,
@@ -27,6 +28,7 @@ import {
InstructionValue,
JsxAttribute,
makeInstructionId,
ObjectPattern,
Place,
ReturnTerminal,
SourceLocation,
@@ -1967,6 +1969,11 @@ function lowerAssignment(
return { kind: "LoadLocal", place, loc: temporary.loc };
}
case "MemberExpression": {
// This can only occur because of a coding error, parsers enforce this condition
invariant(
kind === InstructionKind.Reassign,
"MemberExpression may only appear in an assignment expression"
);
const lvalue = lvaluePath as NodePath<t.MemberExpression>;
const property = lvalue.get("property");
const object = lowerExpressionToTemporary(builder, lvalue.get("object"));
@@ -2009,115 +2016,156 @@ function lowerAssignment(
case "ArrayPattern": {
const lvalue = lvaluePath as NodePath<t.ArrayPattern>;
const elements = lvalue.get("elements");
let hasError = false;
const items: ArrayPattern["items"] = [];
const followups: Array<{ place: Place; path: NodePath<t.LVal> }> = [];
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
if (element.node == null) {
continue;
}
if (element.node.type === "RestElement") {
builder.errors.push({
reason: `(BuildHIR::lowerAssignment) Handle ${element.type} in ArrayPattern`,
severity: ErrorSeverity.Todo,
nodePath: element,
if (element.isRestElement()) {
const argument = element.get("argument");
if (!argument.isIdentifier()) {
builder.errors.push({
reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ArrayPattern`,
severity: ErrorSeverity.Todo,
nodePath: element,
});
continue;
}
const identifier = lowerIdentifier(builder, argument);
items.push({
kind: "Spread",
place: identifier,
});
hasError = true;
continue;
} else if (element.isIdentifier()) {
const identifier = lowerIdentifier(builder, element);
items.push(identifier);
} else {
const temp = buildTemporaryPlace(
builder,
element.node.loc ?? GeneratedSource
);
items.push({ ...temp });
followups.push({ place: temp, path: element as NodePath<t.LVal> }); // TODO remove type cast
}
const property = buildTemporaryPlace(
builder,
element.node.loc ?? GeneratedSource
);
builder.push({
id: makeInstructionId(0),
lvalue: { ...property },
value: {
kind: "Primitive",
value: i,
loc: element.node.loc ?? GeneratedSource,
},
loc: element.node.loc ?? GeneratedSource,
});
const propertyPlace = buildTemporaryPlace(builder, property.loc);
builder.push({
id: makeInstructionId(0),
lvalue: { ...propertyPlace },
value: {
kind: "ComputedLoad",
loc,
object: { ...value },
property,
},
loc,
});
lowerAssignment(
builder,
loc,
kind,
element as NodePath<t.LVal>,
propertyPlace
);
}
return hasError
? { kind: "UnsupportedNode", node: lvalueNode, loc }
: { kind: "LoadLocal", place: value, loc: value.loc };
const temp = buildTemporaryPlace(builder, loc);
builder.push({
id: makeInstructionId(0),
lvalue: { ...temp },
value: {
kind: "Destructure",
lvalue: {
kind,
pattern: {
kind: "ArrayPattern",
items,
},
},
value,
loc,
},
loc,
});
for (const { place, path } of followups) {
lowerAssignment(builder, path.node.loc ?? loc, kind, path, place);
}
return { kind: "LoadLocal", place: value, loc: value.loc };
}
case "ObjectPattern": {
const lvalue = lvaluePath as NodePath<t.ObjectPattern>;
const properties = lvalue.get("properties");
let hasError = false;
for (let i = 0; i < properties.length; i++) {
const property = properties[i];
if (!property.isObjectProperty()) {
builder.errors.push({
reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in ObjectPattern`,
severity: ErrorSeverity.Todo,
nodePath: property,
const propertiesPaths = lvalue.get("properties");
const properties: ObjectPattern["properties"] = [];
const followups: Array<{ place: Place; path: NodePath<t.LVal> }> = [];
for (let i = 0; i < propertiesPaths.length; i++) {
const property = propertiesPaths[i];
if (property.isRestElement()) {
const argument = property.get("argument");
if (!argument.isIdentifier()) {
builder.errors.push({
reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ArrayPattern`,
severity: ErrorSeverity.Todo,
nodePath: argument,
});
continue;
}
const identifier = lowerIdentifier(builder, argument);
properties.push({
kind: "Spread",
place: identifier,
});
hasError = true;
continue;
} else {
// TODO: this should always be true given the if/else
if (!property.isObjectProperty()) {
builder.errors.push({
reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in ObjectPattern`,
severity: ErrorSeverity.Todo,
nodePath: property,
});
continue;
}
const key = property.get("key");
if (!key.isIdentifier()) {
builder.errors.push({
reason: `(BuildHIR::lowerAssignment) Handle ${key.type} keys in ObjectPattern`,
severity: ErrorSeverity.Todo,
nodePath: key,
});
continue;
}
const element = property.get("value");
if (!element.isLVal()) {
builder.errors.push({
reason: `(BuildHIR::lowerAssignment) Expected object property value to be an LVal, got: ${element.type}`,
severity: ErrorSeverity.InvalidInput,
nodePath: element,
});
continue;
}
if (element.isIdentifier()) {
const identifier = lowerIdentifier(builder, element);
properties.push({
kind: "ObjectProperty",
name: key.node.name,
place: identifier,
});
} else {
const temp = buildTemporaryPlace(
builder,
element.node.loc ?? GeneratedSource
);
properties.push({
kind: "ObjectProperty",
name: key.node.name,
place: { ...temp },
});
followups.push({ place: temp, path: element as NodePath<t.LVal> }); // TODO remove type cast
}
}
const key = property.get("key");
if (!key.isIdentifier()) {
builder.errors.push({
reason: `(BuildHIR::lowerAssignment) Handle ${key.type} keys in ObjectPattern`,
severity: ErrorSeverity.Todo,
nodePath: key,
});
hasError = true;
continue;
}
const element = property.get("value");
if (!element.isLVal()) {
builder.errors.push({
reason: `(BuildHIR::lowerAssignment) Expected object property value to be an LVal, got: ${element.type}`,
severity: ErrorSeverity.InvalidInput,
nodePath: element,
});
hasError = true;
continue;
}
const propertyPlace = buildTemporaryPlace(
builder,
property.node.loc ?? GeneratedSource
);
builder.push({
id: makeInstructionId(0),
lvalue: { ...propertyPlace },
value: {
kind: "PropertyLoad",
loc,
object: { ...value },
property: key.node.name,
optional: false, // Key of ObjectPattern (evaluation of LVal) cannot be optional.
},
loc,
});
lowerAssignment(builder, loc, kind, element, propertyPlace);
}
return hasError
? { kind: "UnsupportedNode", node: lvalueNode, loc }
: { kind: "LoadLocal", place: value, loc: value.loc };
const temp = buildTemporaryPlace(builder, loc);
builder.push({
id: makeInstructionId(0),
lvalue: { ...temp },
value: {
kind: "Destructure",
lvalue: {
kind,
pattern: {
kind: "ObjectPattern",
properties,
},
},
value,
loc,
},
loc,
});
for (const { place, path } of followups) {
lowerAssignment(builder, path.node.loc ?? loc, kind, path, place);
}
return { kind: "LoadLocal", place: value, loc: value.loc };
}
default: {
builder.errors.push({
+34
View File
@@ -375,6 +375,34 @@ export type LValue = {
kind: InstructionKind;
};
export type LValuePattern = {
pattern: Pattern;
kind: InstructionKind;
};
export type Pattern = ArrayPattern | ObjectPattern;
export type SpreadPattern = {
kind: "Spread";
place: Place;
};
export type ArrayPattern = {
kind: "ArrayPattern";
items: Array<Place | SpreadPattern>;
};
export type ObjectPattern = {
kind: "ObjectPattern";
properties: Array<ObjectProperty | SpreadPattern>;
};
export type ObjectProperty = {
kind: "ObjectProperty";
name: string; // TODO: make a Place
place: Place;
};
export enum InstructionKind {
/**
* const declaration
@@ -424,6 +452,12 @@ export type InstructionValue =
value: Place;
loc: SourceLocation;
}
| {
kind: "Destructure";
lvalue: LValuePattern;
value: Place;
loc: SourceLocation;
}
| {
kind: "Primitive";
value: number | boolean | string | null | undefined;
+51
View File
@@ -19,12 +19,14 @@ import {
InstructionValue,
LValue,
MutableRange,
Pattern,
Phi,
Place,
ReactiveInstruction,
ReactiveScope,
ReactiveValue,
SourceLocation,
SpreadPattern,
Terminal,
Type,
} from "./HIR";
@@ -321,6 +323,12 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
)} = ${printPlace(instrValue.value)}`;
break;
}
case "Destructure": {
value = `Destructure ${instrValue.lvalue.kind} ${printPattern(
instrValue.lvalue.pattern
)} = ${printPlace(instrValue.value)}`;
break;
}
case "PropertyLoad": {
value = `PropertyLoad ${printPlace(instrValue.object)}.${
instrValue.property
@@ -458,6 +466,49 @@ export function printLValue(lval: LValue): string {
}
}
export function printPattern(pattern: Pattern | Place | SpreadPattern): string {
switch (pattern.kind) {
case "ArrayPattern": {
return (
"[ " + pattern.items.map((item) => printPattern(item)).join(", ") + " ]"
);
}
case "ObjectPattern": {
return (
"{ " +
pattern.properties
.map((item) => {
switch (item.kind) {
case "ObjectProperty": {
return `${item.name}: ${printPattern(item.place)}`;
}
case "Spread": {
return printPattern(item);
}
default: {
assertExhaustive(item, "Unexpected object property kind");
}
}
})
.join(", ") +
" }"
);
}
case "Spread": {
return `...${printPlace(pattern.place)}`;
}
case "Identifier": {
return printPlace(pattern);
}
default: {
assertExhaustive(
pattern,
`Unexpected pattern kind '${(pattern as any).kind}'`
);
}
}
}
export function printPlace(place: Place): string {
const items = [
place.effect,
+85
View File
@@ -13,6 +13,7 @@ import {
Instruction,
InstructionValue,
makeInstructionId,
Pattern,
Place,
ReactiveScope,
ScopeId,
@@ -57,6 +58,11 @@ export function* eachInstructionValueOperand(
yield instrValue.value;
break;
}
case "Destructure": {
yield* eachPatternOperand(instrValue.lvalue.pattern);
yield instrValue.value;
break;
}
case "PropertyLoad": {
yield instrValue.object;
break;
@@ -151,6 +157,49 @@ export function* eachInstructionValueOperand(
}
}
export function* eachPatternOperand(pattern: Pattern): Iterable<Place> {
switch (pattern.kind) {
case "ArrayPattern": {
for (const item of pattern.items) {
if (item.kind === "Identifier") {
yield item;
} else if (item.kind === "Spread") {
yield item.place;
} else {
assertExhaustive(
item,
`Unexpected item kind '${(item as any).kind}'`
);
}
}
break;
}
case "ObjectPattern": {
for (const property of pattern.properties) {
if (property.kind === "ObjectProperty") {
if (property.place.kind === "Identifier") {
yield property.place;
}
} else if (property.kind === "Spread") {
yield property.place;
} else {
assertExhaustive(
property,
`Unexpected item kind '${(property as any).kind}'`
);
}
}
break;
}
default: {
assertExhaustive(
pattern,
`Unexpected pattern kind '${(pattern as any).kind}'`
);
}
}
}
export function mapInstructionOperands(
instr: Instruction,
fn: (place: Place) => Place
@@ -191,6 +240,11 @@ export function mapInstructionOperands(
instrValue.value = fn(instrValue.value);
break;
}
case "Destructure": {
mapPatternOperands(instrValue.lvalue.pattern, fn);
instrValue.value = fn(instrValue.value);
break;
}
case "NewExpression":
case "CallExpression": {
instrValue.callee = fn(instrValue.callee);
@@ -282,6 +336,37 @@ export function mapInstructionOperands(
}
}
export function mapPatternOperands(
pattern: Pattern,
fn: (place: Place) => Place
): void {
switch (pattern.kind) {
case "ArrayPattern": {
pattern.items = pattern.items.map((item) => {
if (item.kind === "Identifier") {
return fn(item);
} else {
item.place = fn(item.place);
return item;
}
});
break;
}
case "ObjectPattern": {
for (const property of pattern.properties) {
property.place = fn(property.place);
}
break;
}
default: {
assertExhaustive(
pattern,
`Unexpected pattern kind '${(pattern as any).kind}'`
);
}
}
}
/**
* Maps a terminal node's block assignments using the provided function.
*/
+4 -1
View File
@@ -32,7 +32,10 @@ function inferInstr(instr: Instruction, aliases: DisjointSet<Identifier>) {
break;
}
case "StoreLocal": {
// aliases.union([instrValue.place.identifier, instrValue.value.identifier]);
alias = instrValue.value;
break;
}
case "Destructure": {
alias = instrValue.value;
break;
}
@@ -11,7 +11,10 @@ import {
InstructionId,
Place,
} from "../HIR/HIR";
import { eachInstructionValueOperand } from "../HIR/visitors";
import {
eachInstructionValueOperand,
eachPatternOperand,
} from "../HIR/visitors";
import DisjointSet from "../Utils/DisjointSet";
export function inferAliasForStores(
@@ -26,6 +29,10 @@ export function inferAliasForStores(
}
if (value.kind === "StoreLocal") {
maybeAlias(aliases, value.lvalue.place, value.value, instr.id);
} else if (value.kind === "Destructure") {
for (const place of eachPatternOperand(value.lvalue.pattern)) {
maybeAlias(aliases, place, value.value, instr.id);
}
}
for (const operand of eachInstructionValueOperand(value)) {
if (
@@ -14,7 +14,7 @@ import {
Place,
} from "../HIR/HIR";
import { printInstruction, printPlace } from "../HIR/PrintHIR";
import { eachInstructionOperand } from "../HIR/visitors";
import { eachInstructionOperand, eachPatternOperand } from "../HIR/visitors";
import { assertExhaustive } from "../Utils/utils";
/**
@@ -123,6 +123,12 @@ export function inferMutableLifetimes(
instr.value.lvalue.place.identifier.mutableRange.start = instr.id;
instr.value.lvalue.place.identifier.mutableRange.end =
makeInstructionId(instr.id + 1);
} else if (instr.value.kind === "Destructure") {
inferPlace(instr.value.value, instr, inferMutableRangeForStores);
for (const place of eachPatternOperand(instr.value.lvalue.pattern)) {
place.identifier.mutableRange.start = instr.id;
place.identifier.mutableRange.end = makeInstructionId(instr.id + 1);
}
} else {
for (const input of eachInstructionOperand(instr)) {
inferPlace(input, instr, inferMutableRangeForStores);
@@ -28,6 +28,7 @@ import {
import {
eachInstructionOperand,
eachInstructionValueOperand,
eachPatternOperand,
eachTerminalOperand,
eachTerminalSuccessor,
} from "../HIR/visitors";
@@ -806,7 +807,29 @@ function inferBlock(
state.alias(lvalue, instrValue.value);
lvalue.effect = Effect.Store;
state.alias(instrValue.lvalue.place, instrValue.value);
instrValue.lvalue.place.effect = Effect.Store;
state.reference(instrValue.lvalue.place, Effect.Store);
continue;
}
case "Destructure": {
let effect: Effect = Effect.Capture;
for (const place of eachPatternOperand(instrValue.lvalue.pattern)) {
if (
state.isDefined(place) &&
state.kind(place) === ValueKind.Context
) {
effect = Effect.Mutate;
break;
}
}
state.reference(instrValue.value, effect);
const lvalue = instr.lvalue;
state.alias(lvalue, instrValue.value);
lvalue.effect = Effect.Store;
for (const place of eachPatternOperand(instrValue.lvalue.pattern)) {
state.alias(place, instrValue.value);
state.reference(place, Effect.Store);
}
continue;
}
default: {
@@ -8,6 +8,7 @@
import { BlockId, HIRFunction, Identifier, InstructionValue } from "../HIR";
import {
eachInstructionValueOperand,
eachPatternOperand,
eachTerminalOperand,
} from "../HIR/visitors";
import { assertExhaustive, retainWhere } from "../Utils/utils";
@@ -85,6 +86,16 @@ function pruneableValue(
// Stores are pruneable only if the identifier being stored to is never read later
return !used.has(value.lvalue.place.identifier);
}
case "Destructure": {
// Destructure is pruneable only if none of the identifiers are read from later
// TODO: as an optimization, prune unused properties where safe
for (const place of eachPatternOperand(value.lvalue.pattern)) {
if (used.has(place.identifier)) {
return false;
}
}
return true;
}
case "CallExpression":
case "ComputedCall":
case "ComputedStore":
@@ -14,7 +14,7 @@ import {
Identifier,
IdentifierId,
InstructionKind,
LValue,
Pattern,
Place,
ReactiveBlock,
ReactiveFunction,
@@ -24,7 +24,9 @@ import {
ReactiveTerminal,
ReactiveValue,
SourceLocation,
SpreadPattern,
} from "../HIR/HIR";
import { eachPatternOperand } from "../HIR/visitors";
import { Err, Ok, Result } from "../Utils/Result";
import { assertExhaustive } from "../Utils/utils";
@@ -358,36 +360,39 @@ function codegenInstructionNullable(
instr: ReactiveInstruction
): t.Statement | null {
let statement;
if (instr.value.kind === "StoreLocal") {
const kind = cx.hasDeclared(instr.value.lvalue.place.identifier)
? InstructionKind.Reassign
: instr.value.lvalue.kind;
if (instr.value.kind === "StoreLocal" || instr.value.kind === "Destructure") {
let kind: InstructionKind = instr.value.lvalue.kind;
let lvalue;
if (instr.value.kind === "StoreLocal") {
kind = cx.hasDeclared(instr.value.lvalue.place.identifier)
? InstructionKind.Reassign
: kind;
lvalue = instr.value.lvalue.place;
} else {
lvalue = instr.value.lvalue.pattern;
for (const place of eachPatternOperand(lvalue)) {
if (cx.hasDeclared(place.identifier)) {
kind = InstructionKind.Reassign;
break;
}
}
}
const value = codegenPlace(cx, instr.value.value);
switch (kind) {
case InstructionKind.Const: {
return createVariableDeclaration(instr.loc, "const", [
t.variableDeclarator(
convertIdentifier(instr.value.lvalue.place.identifier),
value
),
t.variableDeclarator(codegenLValue(lvalue), value),
]);
}
case InstructionKind.Let: {
return createVariableDeclaration(instr.loc, "let", [
t.variableDeclarator(
convertIdentifier(instr.value.lvalue.place.identifier),
value
),
t.variableDeclarator(codegenLValue(lvalue), value),
]);
}
case InstructionKind.Reassign: {
return createExpressionStatement(
instr.loc,
t.assignmentExpression(
"=",
convertIdentifier(instr.value.lvalue.place.identifier),
value
)
t.assignmentExpression("=", codegenLValue(lvalue), value)
);
}
default: {
@@ -717,12 +722,6 @@ function codegenInstructionValue(
value = codegenPlace(cx, instrValue.place);
break;
}
case "StoreLocal": {
CompilerError.invariant(
`Unexpected StoreLocal in codegenInstructionValue`,
instrValue.loc
);
}
case "FunctionExpression": {
value = instrValue.expr;
break;
@@ -814,6 +813,13 @@ function codegenInstructionValue(
value = t.identifier(instrValue.name);
break;
}
case "Destructure":
case "StoreLocal": {
CompilerError.invariant(
`Unexpected StoreLocal in codegenInstructionValue`,
instrValue.loc
);
}
default: {
assertExhaustive(
instrValue,
@@ -844,8 +850,44 @@ function codegenJsxElement(
}
}
function codegenLVal(lval: LValue): t.LVal {
return convertIdentifier(lval.place.identifier);
function codegenLValue(
pattern: Pattern | Place | SpreadPattern
): t.ArrayPattern | t.ObjectPattern | t.RestElement | t.Identifier {
switch (pattern.kind) {
case "ArrayPattern": {
return t.arrayPattern(pattern.items.map((item) => codegenLValue(item)));
}
case "ObjectPattern": {
return t.objectPattern(
pattern.properties.map((property) => {
if (property.kind === "ObjectProperty") {
const key = t.identifier(property.name);
const value = codegenLValue(property.place);
return t.objectProperty(
key,
value,
false,
value.type === "Identifier" && value.name === key.name
);
} else {
return t.restElement(codegenLValue(property.place));
}
})
);
}
case "Spread": {
return t.restElement(codegenLValue(pattern.place));
}
case "Identifier": {
return convertIdentifier(pattern.identifier);
}
default: {
assertExhaustive(
pattern,
`Unexpected pattern kind '${(pattern as any).kind}'`
);
}
}
}
function codegenValue(
@@ -15,7 +15,7 @@ import {
Place,
ReactiveScope,
} from "../HIR/HIR";
import { eachInstructionOperand } from "../HIR/visitors";
import { eachInstructionOperand, eachPatternOperand } from "../HIR/visitors";
import DisjointSet from "../Utils/DisjointSet";
import { assertExhaustive } from "../Utils/utils";
@@ -115,6 +115,21 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
) {
operands.push(instr.value.value.identifier);
}
} else if (instr.value.kind === "Destructure") {
for (const place of eachPatternOperand(instr.value.lvalue.pattern)) {
if (
place.identifier.mutableRange.end >
place.identifier.mutableRange.start + 1
) {
operands.push(place.identifier);
}
}
if (
isMutable(instr, instr.value.value) &&
instr.value.value.identifier.mutableRange.start > 0
) {
operands.push(instr.value.value.identifier);
}
} else {
for (const operand of eachInstructionOperand(instr)) {
if (
@@ -183,6 +198,7 @@ function isMutable({ id }: Instruction, place: Place): boolean {
function mayAllocate(value: InstructionValue): boolean {
switch (value.kind) {
case "Destructure":
case "StoreLocal":
case "LoadGlobal":
case "TypeCastExpression":
@@ -20,7 +20,10 @@ import {
ReactiveScopeDependency,
ReactiveValue,
} from "../HIR/HIR";
import { eachInstructionValueOperand } from "../HIR/visitors";
import {
eachInstructionValueOperand,
eachPatternOperand,
} from "../HIR/visitors";
import { assertExhaustive } from "../Utils/utils";
/**
@@ -707,6 +710,17 @@ function visitInstructionValue(
id,
scope: context.currentScope,
});
} else if (value.kind === "Destructure") {
context.visitOperand(value.value);
for (const place of eachPatternOperand(value.lvalue.pattern)) {
if (value.lvalue.kind === InstructionKind.Reassign) {
context.visitReassignment(place);
}
context.declare(place.identifier, {
id,
scope: context.currentScope,
});
}
} else {
visitReactiveValue(context, id, value);
}
+6
View File
@@ -16,6 +16,7 @@ import { printIdentifier } from "../HIR/PrintHIR";
import {
eachTerminalSuccessor,
mapInstructionOperands,
mapPatternOperands,
mapTerminalOperands,
} from "../HIR/visitors";
@@ -228,6 +229,11 @@ export default function enterSSA(func: HIRFunction): void {
const newPlace = builder.definePlace(oldPlace);
instr.value.lvalue.place = newPlace;
instr.value.value = builder.getPlace(instr.value.value);
} else if (instr.value.kind === "Destructure") {
mapPatternOperands(instr.value.lvalue.pattern, (place) =>
builder.definePlace(place)
);
instr.value.value = builder.getPlace(instr.value.value);
} else {
mapInstructionOperands(instr, (place) => builder.getPlace(place));
+78 -27
View File
@@ -6,6 +6,7 @@
*/
import invariant from "invariant";
import { CompilerError } from "../CompilerError";
import {
BasicBlock,
Effect,
@@ -16,12 +17,15 @@ import {
InstructionId,
InstructionKind,
LValue,
LValuePattern,
makeInstructionId,
Phi,
Place,
} from "../HIR/HIR";
import { printPlace } from "../HIR/PrintHIR";
import {
eachInstructionValueOperand,
eachPatternOperand,
eachTerminalOperand,
} from "../HIR/visitors";
@@ -89,7 +93,10 @@ import {
*/
export function leaveSSA(fn: HIRFunction): void {
// Maps identifier names to their original declaration.
const declarations: Map<string, LValue> = new Map();
const declarations: Map<
string,
{ lvalue: LValue | LValuePattern; place: Place }
> = new Map();
// For non-memoizable phis, this maps original identifiers to the identifier they should be
// *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 {
// Iterate the instructions and perform any rewrites as well as promoting SSA variables to
// `let` or `reassign` where possible.
const { lvalue, value } = instr;
if (
value.kind === "StoreLocal" &&
value.lvalue.place.identifier.name != null
) {
const originalLVal = declarations.get(
value.lvalue.place.identifier.name
);
if (originalLVal === undefined) {
declarations.set(value.lvalue.place.identifier.name, value.lvalue);
value.lvalue.kind = InstructionKind.Const;
} else {
// This is an instance of the original id, so we need to promote the original declaration
// to a `let` and the current lval to a `reassign`
originalLVal.kind = InstructionKind.Let;
if (value.kind === "StoreLocal") {
if (value.lvalue.place.identifier.name != null) {
const originalLVal = declarations.get(
value.lvalue.place.identifier.name
);
if (originalLVal === undefined) {
declarations.set(value.lvalue.place.identifier.name, {
lvalue: value.lvalue,
place: value.lvalue.place,
});
value.lvalue.kind = InstructionKind.Const;
} else {
// This is an instance of the original id, so we need to promote the original declaration
// to a `let` and the current lval to a `reassign`
originalLVal.lvalue.kind = InstructionKind.Let;
}
} else if (rewrites.has(value.lvalue.place.identifier)) {
value.lvalue.kind =
rewrites.get(value.lvalue.place.identifier) ===
value.lvalue.place.identifier
? InstructionKind.Let
: InstructionKind.Reassign;
}
} else if (
value.kind === "StoreLocal" &&
rewrites.has(value.lvalue.place.identifier)
) {
value.lvalue.kind =
rewrites.get(value.lvalue.place.identifier) ===
value.lvalue.place.identifier
? InstructionKind.Let
: InstructionKind.Reassign;
} else if (value.kind === "Destructure") {
let kind: InstructionKind | null = null;
for (const place of eachPatternOperand(value.lvalue.pattern)) {
if (place.identifier.name == null) {
if (kind !== null && kind !== InstructionKind.Const) {
CompilerError.invariant(
`Expected consistent kind for destructuring, other places were '${kind}' but '${printPlace(
place
)}' is const`,
place.loc
);
}
kind = InstructionKind.Const;
} else {
const originalLVal = declarations.get(place.identifier.name);
if (originalLVal === undefined) {
declarations.set(place.identifier.name, {
lvalue: value.lvalue,
place,
});
if (kind !== null && kind !== InstructionKind.Const) {
CompilerError.invariant(
`Expected consistent kind for destructuring, other places were '${kind}' but '${printPlace(
place
)}' is const`,
place.loc
);
}
kind = InstructionKind.Const;
} else {
if (kind !== null && kind !== InstructionKind.Reassign) {
CompilerError.invariant(
`Expected consistent kind for destructuring, other places were '${kind}' but '${printPlace(
place
)}' is reassigned`,
place.loc
);
}
kind = InstructionKind.Reassign;
originalLVal.lvalue.kind = InstructionKind.Let;
}
}
}
invariant(kind !== null, "Expected at least one operand");
value.lvalue.kind = kind;
}
rewritePlace(lvalue, rewrites, declarations);
for (const operand of eachInstructionValueOperand(instr.value)) {
@@ -304,7 +355,7 @@ export function leaveSSA(fn: HIRFunction): void {
loc: GeneratedSource,
};
block.instructions.push(instr);
declarations.set(phi.id.name, lvalue);
declarations.set(phi.id.name, { lvalue, place: lvalue.place });
phi.id.mutableRange.start = terminal.id;
if (!isPhiMutatedAfterCreation) {
phi.id.mutableRange.end = makeInstructionId(terminal.id + 1);
@@ -343,7 +394,7 @@ export function leaveSSA(fn: HIRFunction): void {
if (canonicalId.name !== null) {
const declaration = declarations.get(canonicalId.name);
if (declaration !== undefined) {
declaration.kind = InstructionKind.Let;
declaration.lvalue.kind = InstructionKind.Let;
}
}
}
@@ -370,7 +421,7 @@ export function leaveSSA(fn: HIRFunction): void {
function rewritePlace(
place: Place,
rewrites: Map<Identifier, Identifier>,
declarations: Map<string, LValue>
declarations: Map<string, { lvalue: LValue | LValuePattern; place: Place }>
): void {
const prevIdentifier = place.identifier;
const nextIdentifier = rewrites.get(prevIdentifier);
@@ -15,7 +15,7 @@ function component(t) {
```javascript
function component(t) {
const $ = React.unstable_useMemoCache(2);
const a = t.a;
const { a } = t;
const c_0 = $[0] !== a;
let t0;
if (c_0) {
@@ -19,9 +19,9 @@ function component({ mutator }) {
## Code
```javascript
function component(t28) {
function component(t27) {
const $ = React.unstable_useMemoCache(7);
const mutator = t28.mutator;
const { mutator } = t27;
const c_0 = $[0] !== mutator;
let t0;
if (c_0) {
@@ -15,7 +15,7 @@ function component() {
```javascript
function component() {
const $ = React.unstable_useMemoCache(4);
const setX = useState(0)[1];
const [x, setX] = useState(0);
const c_0 = $[0] !== setX;
let t0;
if (c_0) {
@@ -15,9 +15,7 @@ function component() {
```javascript
function component() {
const $ = React.unstable_useMemoCache(5);
const t2 = useState(0);
const x = t2[0];
const setX = t2[1];
const [x, setX] = useState(0);
const c_0 = $[0] !== setX;
let t0;
if (c_0) {
@@ -0,0 +1,36 @@
## Input
```javascript
function foo(props) {
let x, y;
({ x, y } = { x: props.a, y: props.b });
x = props.c;
return x + y;
}
```
## Code
```javascript
function foo(props) {
const $ = React.unstable_useMemoCache(3);
const c_0 = $[0] !== props.a;
const c_1 = $[1] !== props.b;
let t0;
if (c_0 || c_1) {
t0 = { x: props.a, y: props.b };
$[0] = props.a;
$[1] = props.b;
$[2] = t0;
} else {
t0 = $[2];
}
let { x, y } = t0;
x = props.c;
return x + y;
}
```
@@ -0,0 +1,6 @@
function foo(props) {
let x, y;
({ x, y } = { x: props.a, y: props.b });
x = props.c;
return x + y;
}
@@ -29,11 +29,16 @@ function foo(a, b, c) {
function foo(a, b, c) {
const $ = React.unstable_useMemoCache(5);
const d = a[0];
const g = a[1][0].e.f;
const [d, t54] = a;
const n = b.l.m[0][0];
const o = b.o;
const [t56] = t54;
const { e: t58 } = t56;
const { f: g } = t58;
const { l: t63, o } = b;
const { m: t66 } = t63;
const [t68] = t66;
const [n] = t68;
const c_0 = $[0] !== d;
const c_1 = $[1] !== g;
const c_2 = $[2] !== n;
@@ -8,16 +8,18 @@ function foo(a, b, c) {
[
{
e: { f },
...g
},
],
...h
] = a;
const {
l: {
m: [[n]],
m: [[n], ...o],
},
o,
p,
} = b;
return [d, f, n, o];
return [d, f, g, h, n, o, p];
}
```
@@ -26,26 +28,37 @@ function foo(a, b, c) {
```javascript
function foo(a, b, c) {
const $ = React.unstable_useMemoCache(5);
const d = a[0];
const f = a[1][0].e.f;
const $ = React.unstable_useMemoCache(8);
const [d, t40, ...h] = a;
const n = b.l.m[0][0];
const o = b.o;
const [t43] = t40;
const { e: t45, ...g } = t43;
const { f } = t45;
const { l: t51, p } = b;
const { m: t54 } = t51;
const [t56, ...o] = t54;
const [n] = t56;
const c_0 = $[0] !== d;
const c_1 = $[1] !== f;
const c_2 = $[2] !== n;
const c_3 = $[3] !== o;
const c_2 = $[2] !== g;
const c_3 = $[3] !== h;
const c_4 = $[4] !== n;
const c_5 = $[5] !== o;
const c_6 = $[6] !== p;
let t0;
if (c_0 || c_1 || c_2 || c_3) {
t0 = [d, f, n, o];
if (c_0 || c_1 || c_2 || c_3 || c_4 || c_5 || c_6) {
t0 = [d, f, g, h, n, o, p];
$[0] = d;
$[1] = f;
$[2] = n;
$[3] = o;
$[4] = t0;
$[2] = g;
$[3] = h;
$[4] = n;
$[5] = o;
$[6] = p;
$[7] = t0;
} else {
t0 = $[4];
t0 = $[7];
}
return t0;
}
@@ -4,14 +4,16 @@ function foo(a, b, c) {
[
{
e: { f },
...g
},
],
...h
] = a;
const {
l: {
m: [[n]],
m: [[n], ...o],
},
o,
p,
} = b;
return [d, f, n, o];
return [d, f, g, h, n, o, p];
}
@@ -13,14 +13,11 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
}
}
const g = { ...a, b() {}, c: () => {} };
const h = [...b];
const g = { b() {}, c: () => {} };
new c(...args);
c(...args);
const [y, ...yy] = useState(0);
const { z, aa = "aa", ...zz } = useCustom();
const { z, aa = "aa" } = useCustom();
<Button {...args}></Button>;
<Button xlink:href="localhost:3000"></Button>;
<Button haha={1}></Button>;
<Button>{/** empty */}</Button>;
@@ -126,293 +123,257 @@ let moduleLocal = false;
7 | constructor() {
8 | console.log(this.#secretSauce);
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle SpreadElement properties in ObjectExpression
10 | }
11 |
> 12 | const g = { ...a, b() {}, c: () => {} };
| ^^^^
13 | const h = [...b];
14 | new c(...args);
15 | c(...args);
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle ObjectMethod properties in ObjectExpression
10 | }
11 |
> 12 | const g = { ...a, b() {}, c: () => {} };
| ^^^^^^
13 | const h = [...b];
14 | new c(...args);
15 | c(...args);
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle SpreadElement elements in ArrayExpression
11 |
12 | const g = { ...a, b() {}, c: () => {} };
> 13 | const h = [...b];
| ^^^^
14 | new c(...args);
15 | c(...args);
16 | const [y, ...yy] = useState(0);
> 12 | const g = { b() {}, c: () => {} };
| ^^^^^^
13 | new c(...args);
14 | c(...args);
15 | const { z, aa = "aa" } = useCustom();
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle SpreadElement arguments in NewExpression
12 | const g = { ...a, b() {}, c: () => {} };
13 | const h = [...b];
> 14 | new c(...args);
11 |
12 | const g = { b() {}, c: () => {} };
> 13 | new c(...args);
| ^^^^^^^
15 | c(...args);
16 | const [y, ...yy] = useState(0);
17 | const { z, aa = "aa", ...zz } = useCustom();
14 | c(...args);
15 | const { z, aa = "aa" } = useCustom();
16 |
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle SpreadElement arguments in CallExpression
13 | const h = [...b];
14 | new c(...args);
> 15 | c(...args);
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();
18 |
[ReactForget] TodoError: (BuildHIR::lowerAssignment) Handle RestElement in ArrayPattern
14 | new c(...args);
15 | c(...args);
> 16 | const [y, ...yy] = useState(0);
| ^^^^^
17 | const { z, aa = "aa", ...zz } = useCustom();
18 |
19 | <Button {...args}></Button>;
15 | const { z, aa = "aa" } = useCustom();
16 |
17 | <Button xlink:href="localhost:3000"></Button>;
[ReactForget] TodoError: (BuildHIR::lowerAssignment) Handle AssignmentPattern assignments
15 | c(...args);
16 | const [y, ...yy] = useState(0);
> 17 | const { z, aa = "aa", ...zz } = useCustom();
13 | new c(...args);
14 | c(...args);
> 15 | const { z, aa = "aa" } = useCustom();
| ^^^^^^^^^
18 |
19 | <Button {...args}></Button>;
20 | <Button xlink:href="localhost:3000"></Button>;
[ReactForget] TodoError: (BuildHIR::lowerAssignment) Handle RestElement properties in ObjectPattern
15 | c(...args);
16 | const [y, ...yy] = useState(0);
> 17 | const { z, aa = "aa", ...zz } = useCustom();
| ^^^^^
18 |
19 | <Button {...args}></Button>;
20 | <Button xlink:href="localhost:3000"></Button>;
16 |
17 | <Button xlink:href="localhost:3000"></Button>;
18 | <Button haha={1}></Button>;
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle JSXNamespacedName attribute names in JSXElement
18 |
19 | <Button {...args}></Button>;
> 20 | <Button xlink:href="localhost:3000"></Button>;
15 | const { z, aa = "aa" } = useCustom();
16 |
> 17 | <Button xlink:href="localhost:3000"></Button>;
| ^^^^^^^^^^
21 | <Button haha={1}></Button>;
22 | <Button>{/** empty */}</Button>;
23 | <DesignSystem.Button />;
18 | <Button haha={1}></Button>;
19 | <Button>{/** empty */}</Button>;
20 | <DesignSystem.Button />;
[ReactForget] TodoError: (BuildHIR::lowerJsxElement) Handle JSXEmptyExpression expressions
20 | <Button xlink:href="localhost:3000"></Button>;
21 | <Button haha={1}></Button>;
> 22 | <Button>{/** empty */}</Button>;
17 | <Button xlink:href="localhost:3000"></Button>;
18 | <Button haha={1}></Button>;
> 19 | <Button>{/** empty */}</Button>;
| ^^^^^^^^^^^^
23 | <DesignSystem.Button />;
24 |
25 | const j = function bar([quz, qux], ...args) {};
20 | <DesignSystem.Button />;
21 |
22 | const j = function bar([quz, qux], ...args) {};
[ReactForget] TodoError: (BuildHIR::lowerJsxElementName) Handle JSXMemberExpression tags
21 | <Button haha={1}></Button>;
22 | <Button>{/** empty */}</Button>;
> 23 | <DesignSystem.Button />;
18 | <Button haha={1}></Button>;
19 | <Button>{/** empty */}</Button>;
> 20 | <DesignSystem.Button />;
| ^^^^^^^^^^^^^^^^^^^
24 |
25 | const j = function bar([quz, qux], ...args) {};
26 |
21 |
22 | const j = function bar([quz, qux], ...args) {};
23 |
[ReactForget] TodoError: (BuildHIR::lower) Handle ArrayPattern params
23 | <DesignSystem.Button />;
24 |
> 25 | const j = function bar([quz, qux], ...args) {};
20 | <DesignSystem.Button />;
21 |
> 22 | const j = function bar([quz, qux], ...args) {};
| ^^^^^^^^^^
26 |
27 | for (; i < 3; i += 1) {
28 | x.push(i);
23 |
24 | for (; i < 3; i += 1) {
25 | x.push(i);
[ReactForget] TodoError: (BuildHIR::lower) Handle RestElement params
23 | <DesignSystem.Button />;
24 |
> 25 | const j = function bar([quz, qux], ...args) {};
20 | <DesignSystem.Button />;
21 |
> 22 | const j = function bar([quz, qux], ...args) {};
| ^^^^^^^
26 |
27 | for (; i < 3; i += 1) {
28 | x.push(i);
23 |
24 | for (; i < 3; i += 1) {
25 | x.push(i);
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
25 | const j = function bar([quz, qux], ...args) {};
26 |
> 27 | for (; i < 3; i += 1) {
22 | const j = function bar([quz, qux], ...args) {};
23 |
> 24 | for (; i < 3; i += 1) {
| ^
28 | x.push(i);
29 | }
30 | for (; i < 3; ) {
25 | x.push(i);
26 | }
27 | for (; i < 3; ) {
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
28 | x.push(i);
29 | }
> 30 | for (; i < 3; ) {
25 | x.push(i);
26 | }
> 27 | for (; i < 3; ) {
| ^
31 | break;
32 | }
33 | for (;;) {
28 | break;
29 | }
30 | for (;;) {
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty update in ForStatement
28 | x.push(i);
29 | }
> 30 | for (; i < 3; ) {
25 | x.push(i);
26 | }
> 27 | for (; i < 3; ) {
| ^
31 | break;
32 | }
33 | for (;;) {
28 | break;
29 | }
30 | for (;;) {
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
28 | break;
29 | }
> 30 | for (;;) {
| ^
31 | break;
32 | }
> 33 | for (;;) {
| ^
34 | break;
35 | }
36 |
33 |
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty update in ForStatement
28 | break;
29 | }
> 30 | for (;;) {
| ^
31 | break;
32 | }
> 33 | for (;;) {
| ^
34 | break;
35 | }
36 |
33 |
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty test in ForStatement
28 | break;
29 | }
> 30 | for (;;) {
| ^
31 | break;
32 | }
> 33 | for (;;) {
| ^
34 | break;
35 | }
36 |
33 |
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle tagged template with interpolations
35 | }
36 |
> 37 | graphql`
32 | }
33 |
> 34 | graphql`
| ^
38 | ${g}
39 | `;
40 |
35 | ${g}
36 | `;
37 |
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value
39 | `;
40 |
> 41 | graphql`\\t\n`;
36 | `;
37 |
> 38 | graphql`\\t\n`;
| ^^^^^^^^^^^^^^
42 |
43 | for (const c of [1, 2]) {
44 | }
39 |
40 | for (const c of [1, 2]) {
41 | }
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ForOfStatement statements
41 | graphql`\\t\n`;
42 |
> 43 | for (const c of [1, 2]) {
38 | graphql`\\t\n`;
39 |
> 40 | for (const c of [1, 2]) {
| ^
44 | }
45 |
46 | for (let x in { a: 1 }) {
41 | }
42 |
43 | for (let x in { a: 1 }) {
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ForInStatement statements
41 | }
42 |
> 43 | for (let x in { a: 1 }) {
| ^
44 | }
45 |
> 46 | for (let x in { a: 1 }) {
| ^
47 | }
48 |
49 | let updateIdentifier = 0;
46 | let updateIdentifier = 0;
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle prefix UpdateExpression
48 |
49 | let updateIdentifier = 0;
> 50 | --updateIdentifier;
45 |
46 | let updateIdentifier = 0;
> 47 | --updateIdentifier;
| ^^^^^^^^^^^^^^^^^^
51 | ++updateIdentifier;
52 | updateIdentifier.y++;
53 | updateIdentifier.y--;
48 | ++updateIdentifier;
49 | updateIdentifier.y++;
50 | updateIdentifier.y--;
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle prefix UpdateExpression
49 | let updateIdentifier = 0;
50 | --updateIdentifier;
> 51 | ++updateIdentifier;
46 | let updateIdentifier = 0;
47 | --updateIdentifier;
> 48 | ++updateIdentifier;
| ^^^^^^^^^^^^^^^^^^
52 | updateIdentifier.y++;
53 | updateIdentifier.y--;
54 |
49 | updateIdentifier.y++;
50 | updateIdentifier.y--;
51 |
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument
50 | --updateIdentifier;
51 | ++updateIdentifier;
> 52 | updateIdentifier.y++;
47 | --updateIdentifier;
48 | ++updateIdentifier;
> 49 | updateIdentifier.y++;
| ^^^^^^^^^^^^^^^^^^^^
53 | updateIdentifier.y--;
54 |
55 | switch (i) {
50 | updateIdentifier.y--;
51 |
52 | switch (i) {
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument
51 | ++updateIdentifier;
52 | updateIdentifier.y++;
> 53 | updateIdentifier.y--;
48 | ++updateIdentifier;
49 | updateIdentifier.y++;
> 50 | updateIdentifier.y--;
| ^^^^^^^^^^^^^^^^^^^^
54 |
55 | switch (i) {
56 | case 1 + 1: {
51 |
52 | switch (i) {
53 | case 1 + 1: {
[ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
58 | case foo(): {
59 | }
> 60 | case x.y: {
55 | case foo(): {
56 | }
> 57 | case x.y: {
| ^^^
61 | }
62 | default: {
63 | }
58 | }
59 | default: {
60 | }
[ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
56 | case 1 + 1: {
57 | }
> 58 | case foo(): {
53 | case 1 + 1: {
54 | }
> 55 | case foo(): {
| ^^^^^
59 | }
60 | case x.y: {
61 | }
56 | }
57 | case x.y: {
58 | }
[ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
54 |
55 | switch (i) {
> 56 | case 1 + 1: {
51 |
52 | switch (i) {
> 53 | case 1 + 1: {
| ^^^^^
57 | }
58 | case foo(): {
59 | }
54 | }
55 | case foo(): {
56 | }
[ReactForget] InvalidInputError: (BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported.
65 |
66 | // Cannot assign to globals
> 67 | someUnknownGlobal = true;
62 |
63 | // Cannot assign to globals
> 64 | someUnknownGlobal = true;
| ^^^^^^^^^^^^^^^^^
68 | moduleLocal = true;
69 | }
70 |
65 | moduleLocal = true;
66 | }
67 |
[ReactForget] InvalidInputError: (BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported.
66 | // Cannot assign to globals
67 | someUnknownGlobal = true;
> 68 | moduleLocal = true;
63 | // Cannot assign to globals
64 | someUnknownGlobal = true;
> 65 | moduleLocal = true;
| ^^^^^^^^^^^
69 | }
70 |
71 | let moduleLocal = false;
66 | }
67 |
68 | let moduleLocal = false;
```
@@ -9,14 +9,11 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
}
}
const g = { ...a, b() {}, c: () => {} };
const h = [...b];
const g = { b() {}, c: () => {} };
new c(...args);
c(...args);
const [y, ...yy] = useState(0);
const { z, aa = "aa", ...zz } = useCustom();
const { z, aa = "aa" } = useCustom();
<Button {...args}></Button>;
<Button xlink:href="localhost:3000"></Button>;
<Button haha={1}></Button>;
<Button>{/** empty */}</Button>;
@@ -22,7 +22,7 @@ function Component(props) {
```javascript
function Component(props) {
const setValue = useState(null)[1];
const [value, setValue] = useState(null);
const onChange = (e) => setValue((value) => value + e.target.value);
@@ -13,10 +13,9 @@ function component({ a, b }) {
## Code
```javascript
function component(t19) {
function component(t16) {
const $ = React.unstable_useMemoCache(7);
const a = t19.a;
const b = t19.b;
const { a, b } = t16;
const c_0 = $[0] !== a;
let t0;
if (c_0) {
@@ -0,0 +1,44 @@
## Input
```javascript
function foo(props) {
let { x } = { x: [] };
x.push(props.bar);
if (props.cond) {
({ x } = { x: {} });
({ x } = { x: [] });
x.push(props.foo);
}
mut(x);
return x;
}
```
## Code
```javascript
function foo(props) {
const $ = React.unstable_useMemoCache(2);
const c_0 = $[0] !== props;
let x;
if (c_0) {
({ x } = { x: [] });
x.push(props.bar);
if (props.cond) {
({ x } = { x: [] });
x.push(props.foo);
}
mut(x);
$[0] = props;
$[1] = x;
} else {
x = $[1];
}
return x;
}
```
@@ -0,0 +1,11 @@
function foo(props) {
let { x } = { x: [] };
x.push(props.bar);
if (props.cond) {
({ x } = { x: {} });
({ x } = { x: [] });
x.push(props.foo);
}
mut(x);
return x;
}
@@ -0,0 +1,48 @@
## Input
```javascript
function foo(props) {
let { x } = { x: [] };
x.push(props.bar);
if (props.cond) {
({ x } = { x: {} });
({ x } = { x: [] });
x.push(props.foo);
}
return x;
}
```
## Code
```javascript
function foo(props) {
const $ = React.unstable_useMemoCache(4);
const c_0 = $[0] !== props.bar;
let x;
if (c_0) {
({ x } = { x: [] });
x.push(props.bar);
$[0] = props.bar;
$[1] = x;
} else {
x = $[1];
}
if (props.cond) {
const c_2 = $[2] !== props.foo;
if (c_2) {
({ x } = { x: [] });
x.push(props.foo);
$[2] = props.foo;
$[3] = x;
} else {
x = $[3];
}
}
return x;
}
```
@@ -0,0 +1,10 @@
function foo(props) {
let { x } = { x: [] };
x.push(props.bar);
if (props.cond) {
({ x } = { x: {} });
({ x } = { x: [] });
x.push(props.foo);
}
return x;
}
@@ -16,9 +16,7 @@ function component() {
```javascript
function component() {
const $ = React.unstable_useMemoCache(5);
const t2 = useState(0);
const count = t2[0];
const setCount = t2[1];
const [count, setCount] = useState(0);
const c_0 = $[0] !== setCount;
const c_1 = $[1] !== count;
let t0;