diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index c5eea2ea2e..6d3ddb61c0 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -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; 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; const elements = lvalue.get("elements"); - let hasError = false; + const items: ArrayPattern["items"] = []; + const followups: Array<{ place: Place; path: NodePath }> = []; 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 }); // 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, - 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; - 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 }> = []; + 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 }); // 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({ diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index f88b5c23f1..dad13a8129 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -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; +}; + +export type ObjectPattern = { + kind: "ObjectPattern"; + properties: Array; +}; + +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; diff --git a/compiler/forget/src/HIR/PrintHIR.ts b/compiler/forget/src/HIR/PrintHIR.ts index b9890c67f8..e932fc1fd6 100644 --- a/compiler/forget/src/HIR/PrintHIR.ts +++ b/compiler/forget/src/HIR/PrintHIR.ts @@ -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, diff --git a/compiler/forget/src/HIR/visitors.ts b/compiler/forget/src/HIR/visitors.ts index 5c62a2f080..8aaeb8c996 100644 --- a/compiler/forget/src/HIR/visitors.ts +++ b/compiler/forget/src/HIR/visitors.ts @@ -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 { + 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. */ diff --git a/compiler/forget/src/Inference/InferAlias.ts b/compiler/forget/src/Inference/InferAlias.ts index 9aacaeb94a..b2d127a7e3 100644 --- a/compiler/forget/src/Inference/InferAlias.ts +++ b/compiler/forget/src/Inference/InferAlias.ts @@ -32,7 +32,10 @@ function inferInstr(instr: Instruction, aliases: DisjointSet) { break; } case "StoreLocal": { - // aliases.union([instrValue.place.identifier, instrValue.value.identifier]); + alias = instrValue.value; + break; + } + case "Destructure": { alias = instrValue.value; break; } diff --git a/compiler/forget/src/Inference/InferAliasForStores.ts b/compiler/forget/src/Inference/InferAliasForStores.ts index 6aa9e44f99..cb59236c37 100644 --- a/compiler/forget/src/Inference/InferAliasForStores.ts +++ b/compiler/forget/src/Inference/InferAliasForStores.ts @@ -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 ( diff --git a/compiler/forget/src/Inference/InferMutableLifetimes.ts b/compiler/forget/src/Inference/InferMutableLifetimes.ts index eb52f7d2b0..00e2d53e44 100644 --- a/compiler/forget/src/Inference/InferMutableLifetimes.ts +++ b/compiler/forget/src/Inference/InferMutableLifetimes.ts @@ -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); diff --git a/compiler/forget/src/Inference/InferReferenceEffects.ts b/compiler/forget/src/Inference/InferReferenceEffects.ts index f8bc60fcec..2543532e27 100644 --- a/compiler/forget/src/Inference/InferReferenceEffects.ts +++ b/compiler/forget/src/Inference/InferReferenceEffects.ts @@ -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: { diff --git a/compiler/forget/src/Optimization/DeadCodeElimination.ts b/compiler/forget/src/Optimization/DeadCodeElimination.ts index 79210ba6b0..ff0d6c3514 100644 --- a/compiler/forget/src/Optimization/DeadCodeElimination.ts +++ b/compiler/forget/src/Optimization/DeadCodeElimination.ts @@ -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": diff --git a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts index 150c56ff52..ad2b2752ab 100644 --- a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -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( diff --git a/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts index 68bc5d93c0..daa48d5ee1 100644 --- a/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -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": diff --git a/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts index 5505ce1487..fc00b44654 100644 --- a/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts +++ b/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts @@ -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); } diff --git a/compiler/forget/src/SSA/EnterSSA.ts b/compiler/forget/src/SSA/EnterSSA.ts index 8bd7b60667..f2c25eef87 100644 --- a/compiler/forget/src/SSA/EnterSSA.ts +++ b/compiler/forget/src/SSA/EnterSSA.ts @@ -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)); diff --git a/compiler/forget/src/SSA/LeaveSSA.ts b/compiler/forget/src/SSA/LeaveSSA.ts index f6e1736d08..d75a8d6ac7 100644 --- a/compiler/forget/src/SSA/LeaveSSA.ts +++ b/compiler/forget/src/SSA/LeaveSSA.ts @@ -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 = 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, - declarations: Map + declarations: Map ): void { const prevIdentifier = place.identifier; const nextIdentifier = rewrites.get(prevIdentifier); diff --git a/compiler/forget/src/__tests__/fixtures/hir/bug_object-pattern.expect.md b/compiler/forget/src/__tests__/fixtures/hir/bug_object-pattern.expect.md index 649298ccc0..31b19bf7d3 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/bug_object-pattern.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/bug_object-pattern.expect.md @@ -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) { diff --git a/compiler/forget/src/__tests__/fixtures/hir/capturing-function-member-expr-call.expect.md b/compiler/forget/src/__tests__/fixtures/hir/capturing-function-member-expr-call.expect.md index cd11bc3c6e..2dc57b8057 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/capturing-function-member-expr-call.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/capturing-function-member-expr-call.expect.md @@ -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) { diff --git a/compiler/forget/src/__tests__/fixtures/hir/concise-arrow-expr.expect.md b/compiler/forget/src/__tests__/fixtures/hir/concise-arrow-expr.expect.md index e43709ed22..517503efd5 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/concise-arrow-expr.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/concise-arrow-expr.expect.md @@ -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) { diff --git a/compiler/forget/src/__tests__/fixtures/hir/controlled-input.expect.md b/compiler/forget/src/__tests__/fixtures/hir/controlled-input.expect.md index 1e18384a2c..417dcef789 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/controlled-input.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/controlled-input.expect.md @@ -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) { diff --git a/compiler/forget/src/__tests__/fixtures/hir/destructure-direct-reassignment.expect.md b/compiler/forget/src/__tests__/fixtures/hir/destructure-direct-reassignment.expect.md new file mode 100644 index 0000000000..7dc266f451 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/destructure-direct-reassignment.expect.md @@ -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; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/destructure-direct-reassignment.js b/compiler/forget/src/__tests__/fixtures/hir/destructure-direct-reassignment.js new file mode 100644 index 0000000000..27fb91ec50 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/destructure-direct-reassignment.js @@ -0,0 +1,6 @@ +function foo(props) { + let x, y; + ({ x, y } = { x: props.a, y: props.b }); + x = props.c; + return x + y; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.expect.md b/compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.expect.md index 556b6b1465..f6ceeaa99b 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.expect.md @@ -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; diff --git a/compiler/forget/src/__tests__/fixtures/hir/destructuring.expect.md b/compiler/forget/src/__tests__/fixtures/hir/destructuring.expect.md index 5548d4a434..f40bb9601b 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/destructuring.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/destructuring.expect.md @@ -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; } diff --git a/compiler/forget/src/__tests__/fixtures/hir/destructuring.js b/compiler/forget/src/__tests__/fixtures/hir/destructuring.js index d75719b816..1e226e3e72 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/destructuring.js +++ b/compiler/forget/src/__tests__/fixtures/hir/destructuring.js @@ -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]; } diff --git a/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md b/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md index c004bd6556..70dd995935 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md @@ -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(); - ; ; ; ; @@ -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 | ; + 15 | const { z, aa = "aa" } = useCustom(); + 16 | + 17 | ; [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 | ; - 20 | ; - -[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 | ; - 20 | ; + 16 | + 17 | ; + 18 | ; [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle JSXNamespacedName attribute names in JSXElement - 18 | - 19 | ; -> 20 | ; + 15 | const { z, aa = "aa" } = useCustom(); + 16 | +> 17 | ; | ^^^^^^^^^^ - 21 | ; - 22 | ; - 23 | ; + 18 | ; + 19 | ; + 20 | ; [ReactForget] TodoError: (BuildHIR::lowerJsxElement) Handle JSXEmptyExpression expressions - 20 | ; - 21 | ; -> 22 | ; + 17 | ; + 18 | ; +> 19 | ; | ^^^^^^^^^^^^ - 23 | ; - 24 | - 25 | const j = function bar([quz, qux], ...args) {}; + 20 | ; + 21 | + 22 | const j = function bar([quz, qux], ...args) {}; [ReactForget] TodoError: (BuildHIR::lowerJsxElementName) Handle JSXMemberExpression tags - 21 | ; - 22 | ; -> 23 | ; + 18 | ; + 19 | ; +> 20 | ; | ^^^^^^^^^^^^^^^^^^^ - 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 | ; - 24 | -> 25 | const j = function bar([quz, qux], ...args) {}; + 20 | ; + 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 | ; - 24 | -> 25 | const j = function bar([quz, qux], ...args) {}; + 20 | ; + 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; ``` \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.js b/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.js index 3c94e92198..5f79d9c548 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.js +++ b/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.js @@ -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(); - ; ; ; ; diff --git a/compiler/forget/src/__tests__/fixtures/hir/inadvertent-mutability-readonly-lambda.expect.md b/compiler/forget/src/__tests__/fixtures/hir/inadvertent-mutability-readonly-lambda.expect.md index b84ed09282..b4de9d23f9 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/inadvertent-mutability-readonly-lambda.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/inadvertent-mutability-readonly-lambda.expect.md @@ -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); diff --git a/compiler/forget/src/__tests__/fixtures/hir/object-pattern-params.expect.md b/compiler/forget/src/__tests__/fixtures/hir/object-pattern-params.expect.md index 09d63d71ff..8ad14aa253 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/object-pattern-params.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/object-pattern-params.expect.md @@ -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) { diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring-with-mutation.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring-with-mutation.expect.md new file mode 100644 index 0000000000..741838448e --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring-with-mutation.expect.md @@ -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; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring-with-mutation.js b/compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring-with-mutation.js new file mode 100644 index 0000000000..bb6e80749f --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring-with-mutation.js @@ -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; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring.expect.md new file mode 100644 index 0000000000..23c6be2ab4 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring.expect.md @@ -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; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring.js b/compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring.js new file mode 100644 index 0000000000..05fe3bbdf9 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-renaming-via-destructuring.js @@ -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; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/use-callback-simple.expect.md b/compiler/forget/src/__tests__/fixtures/hir/use-callback-simple.expect.md index da5aab321d..27e04507db 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/use-callback-simple.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/use-callback-simple.expect.md @@ -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;