diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index ce3debfab4..71d6fa9bd9 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -11,12 +11,14 @@ import { assertExhaustive } from "../Common/utils"; import { invariant } from "../CompilerError"; import { Effect, + GeneratedSource, HIRFunction, IfTerminal, InstructionKind, InstructionValue, Place, ReturnTerminal, + SourceLocation, Terminal, ThrowTerminal, } from "./HIR"; @@ -66,7 +68,7 @@ export function lower( identifier, memberPath: null, effect: Effect.Unknown, - path: null as any, + loc: param.loc ?? GeneratedSource, }; params.push(place); }); @@ -81,10 +83,12 @@ export function lower( } return { - path: func, id, params, body: builder.build(), + generator: func.node.generator === true, + async: func.node.async === true, + loc: func.node.loc ?? GeneratedSource, }; } @@ -574,13 +578,13 @@ function lowerStatement( value = { kind: "Primitive", value: undefined, - path: init as any, // TODO + loc: id.loc, }; } builder.push({ lvalue: { place: id, kind }, value, - path: stmt, + loc: declaration.node.loc ?? GeneratedSource, }); } return; @@ -596,7 +600,7 @@ function lowerStatement( builder.push({ lvalue: null, value, - path: stmt, + loc: stmt.node.loc ?? GeneratedSource, }); return; } @@ -632,9 +636,13 @@ function lowerStatement( case "TSTypeAliasDeclaration": case "WithStatement": { builder.push({ - path: stmtPath, lvalue: null, - value: { kind: "OtherStatement", path: stmtPath }, + loc: stmtPath.node.loc ?? GeneratedSource, + value: { + kind: "OtherStatement", + loc: stmtPath.node.loc ?? GeneratedSource, + node: stmtPath.node, + }, }); return; } @@ -654,6 +662,7 @@ function lowerExpression( exprPath: NodePath ): InstructionValue { const exprNode = exprPath.node; + const exprLoc = exprNode.loc ?? GeneratedSource; switch (exprNode.type) { case "Identifier": { const expr = exprPath as NodePath; @@ -663,7 +672,7 @@ function lowerExpression( return { kind: "Primitive", value: null, - path: exprPath, + loc: exprLoc, }; } case "BooleanLiteral": @@ -676,7 +685,7 @@ function lowerExpression( return { kind: "Primitive", value, - path: exprPath, + loc: exprLoc, }; } case "ObjectExpression": { @@ -701,7 +710,7 @@ function lowerExpression( return { kind: "ObjectExpression", properties, - path: exprPath, + loc: exprLoc, }; } case "ArrayExpression": { @@ -716,7 +725,7 @@ function lowerExpression( return { kind: "ArrayExpression", elements, - path: exprPath, + loc: exprLoc, }; } case "NewExpression": { @@ -739,7 +748,7 @@ function lowerExpression( kind: "NewExpression", callee, args, - path: exprPath, + loc: exprLoc, }; } case "CallExpression": { @@ -762,7 +771,7 @@ function lowerExpression( kind: "CallExpression", callee, args, - path: exprPath, + loc: exprLoc, }; } case "BinaryExpression": { @@ -780,7 +789,7 @@ function lowerExpression( operator, left, right, - path: exprPath, + loc: exprLoc, }; } case "LogicalExpression": { @@ -797,6 +806,7 @@ function lowerExpression( return lowerConditional( builder, left, + exprLoc, () => left, () => lowerExpression(builder, expr.get("right")) ); @@ -806,6 +816,7 @@ function lowerExpression( return lowerConditional( builder, left, + exprLoc, () => lowerExpression(builder, expr.get("right")), () => left ); @@ -821,15 +832,15 @@ function lowerExpression( identifier: builder.makeTemporary(), memberPath: null, effect: Effect.Unknown, - path: null as any, + loc: left.loc, }; builder.push({ value: { kind: "Primitive", value: null, - path: null as any, + loc: GeneratedSource, }, - path: exprPath, + loc: left.loc, lvalue: { place: { ...nullPlace }, kind: InstructionKind.Const }, }); @@ -838,7 +849,7 @@ function lowerExpression( identifier: builder.makeTemporary(), memberPath: null, effect: Effect.Unknown, - path: null as any, + loc: left.loc, }; builder.push({ lvalue: { @@ -850,13 +861,14 @@ function lowerExpression( operator: "!=", left, right: nullPlace, - path: null as any, + loc: left.loc, }, - path: null as any, + loc: left.loc, }); return lowerConditional( builder, condPlace, + exprLoc, () => left, () => lowerExpression(builder, expr.get("right")) ); @@ -877,8 +889,8 @@ function lowerExpression( todoInvariant(operator === "=", "todo: support non-simple assignment"); builder.push({ lvalue: { place: left, kind: InstructionKind.Reassign }, - path: exprPath, value: right, + loc: exprLoc, }); return left; } @@ -896,7 +908,7 @@ function lowerExpression( identifier: object.identifier, memberPath: [...(object.memberPath ?? []), property.node.name], effect: Effect.Unknown, - path: exprPath, + loc: exprLoc, }; return place; } @@ -933,10 +945,10 @@ function lowerExpression( }); return { kind: "JsxExpression", - path: exprPath, tag, props, children, + loc: exprLoc, }; } default: { @@ -952,6 +964,7 @@ function lowerExpression( function lowerConditional( builder: HIRBuilder, test: Place, + loc: SourceLocation, consequent: () => InstructionValue, alternate: () => InstructionValue ): Place { @@ -960,7 +973,7 @@ function lowerConditional( identifier: builder.makeTemporary(), memberPath: null, effect: Effect.Read, - path: null as any, // TODO + loc, }; // Block for code following the if const continuationBlock = builder.reserve(); @@ -970,7 +983,7 @@ function lowerConditional( builder.push({ value, lvalue: { place: { ...place }, kind: InstructionKind.Const }, - path: value.path, + loc: value.loc, }); return { kind: "goto", @@ -983,7 +996,7 @@ function lowerConditional( builder.push({ value, lvalue: { place: { ...place }, kind: InstructionKind.Const }, - path: value.path, + loc: value.loc, }); return { kind: "goto", @@ -1008,6 +1021,7 @@ function lowerJsxElementName( > ): Place { todoInvariant(exprPath.isJSXIdentifier(), "handle non-identifier tags"); + const exprLoc = exprPath.node.loc ?? GeneratedSource; const tag: string = exprPath.node.name; if (tag.match(/^[A-Z]/)) { const binding = @@ -1023,7 +1037,7 @@ function lowerJsxElementName( identifier: identifier, memberPath: null, effect: Effect.Unknown, - path: exprPath, + loc: exprLoc, }; return place; } else { @@ -1032,11 +1046,15 @@ function lowerJsxElementName( identifier: builder.makeTemporary(), memberPath: null, effect: Effect.Unknown, - path: exprPath, + loc: exprLoc, }; builder.push({ - value: { kind: "Primitive", value: tag, path: exprPath }, - path: exprPath, + value: { + kind: "Primitive", + value: tag, + loc: exprLoc, + }, + loc: exprLoc, lvalue: { place, kind: InstructionKind.Const }, }); return { ...place }; @@ -1053,6 +1071,8 @@ function lowerJsxElement( | t.JSXFragment > ): Place { + const exprNode = exprPath.node; + const exprLoc = exprNode.loc ?? GeneratedSource; if (exprPath.isJSXElement()) { return lowerExpressionToPlace(builder, exprPath); } else if (exprPath.isJSXExpressionContainer()) { @@ -1065,25 +1085,37 @@ function lowerJsxElement( identifier: builder.makeTemporary(), memberPath: null, effect: Effect.Unknown, - path: exprPath, + loc: exprLoc, }; builder.push({ - value: { kind: "JSXText", value: exprPath.node.value, path: exprPath }, - path: exprPath, + value: { + kind: "JSXText", + value: exprPath.node.value, + loc: exprLoc, + }, + loc: exprLoc, lvalue: { place: { ...place }, kind: InstructionKind.Const }, }); return place; } else { + invariant( + t.isJSXFragment(exprNode) || t.isJSXSpreadChild(exprNode), + "Expected refinement to work" + ); const place: Place = { kind: "Identifier", identifier: builder.makeTemporary(), memberPath: null, effect: Effect.Unknown, - path: exprPath, + loc: exprLoc, }; builder.push({ - value: { kind: "OtherStatement", path: exprPath }, - path: exprPath, + value: { + kind: "OtherStatement", + node: exprNode, + loc: exprLoc, + }, + loc: exprLoc, lvalue: { place: { ...place }, kind: InstructionKind.Const }, }); return place; @@ -1098,16 +1130,17 @@ function lowerExpressionToPlace( if (instr.kind === "Identifier") { return instr; } + const exprLoc = exprPath.node.loc ?? GeneratedSource; const place: Place = { kind: "Identifier", identifier: builder.makeTemporary(), memberPath: null, effect: Effect.Unknown, - path: exprPath, + loc: exprLoc, }; builder.push({ value: instr, - path: exprPath, + loc: exprLoc, lvalue: { place: { ...place }, kind: InstructionKind.Const }, }); return place; @@ -1115,6 +1148,7 @@ function lowerExpressionToPlace( function lowerLVal(builder: HIRBuilder, exprPath: NodePath): Place { const exprNode = exprPath.node; + const exprLoc = exprNode.loc ?? GeneratedSource; switch (exprNode.type) { case "Identifier": { // const expr = exprPath as NodePath; @@ -1133,7 +1167,7 @@ function lowerLVal(builder: HIRBuilder, exprPath: NodePath): Place { identifier: identifier, memberPath: null, effect: Effect.Unknown, - path: exprPath, + loc: exprLoc, }; return place; } @@ -1152,7 +1186,7 @@ function lowerLVal(builder: HIRBuilder, exprPath: NodePath): Place { identifier: object.identifier, memberPath: [...(object.memberPath ?? []), propertyPath.node.name], effect: Effect.Unknown, - path: exprPath, + loc: exprLoc, }; return place; } diff --git a/compiler/forget/src/HIR/Codegen.ts b/compiler/forget/src/HIR/Codegen.ts index d1a4592681..a0ec126edd 100644 --- a/compiler/forget/src/HIR/Codegen.ts +++ b/compiler/forget/src/HIR/Codegen.ts @@ -45,18 +45,13 @@ export default function codegen(fn: HIRFunction): t.Function { const entry = fn.body.blocks.get(fn.body.entry)!; const cx: Context = { ir: fn.body, temp: new Map() }; const body = codegenBlock(cx, entry); - const node = fn.path.node; - todoInvariant( - t.isFunctionDeclaration(node), - "todo: handle other than function declaration" - ); const params = fn.params.map((param) => convertIdentifier(param.identifier)); return t.functionDeclaration( fn.id !== null ? convertIdentifier(fn.id) : null, params, body, - node.generator, - node.async + fn.generator, + fn.async ); } @@ -158,7 +153,7 @@ function writeBlock(cx: Context, block: BasicBlock, body: Array) { } function writeInstr(cx: Context, instr: Instruction, body: Array) { - let value; + let value: t.Expression; const instrValue = instr.value; switch (instrValue.kind) { case "ArrayExpression": { @@ -253,15 +248,13 @@ function writeInstr(cx: Context, instr: Instruction, body: Array) { break; } case "OtherStatement": { - const node = instrValue.path.node; - if (node != null) { - invariant( - t.isStatement(node), - "Expected node to be a statement if present" - ); + const node = instrValue.node; + if (t.isStatement(node)) { body.push(node); + return; } - return; + value = node as any; // TODO(josephsavona) complete handling of JSX fragment/spreadchild elements + break; } case "Identifier": { value = codegenPlace(cx, instrValue); diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index 4ff496894c..4ff5422e32 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import { NodePath } from "@babel/core"; import * as t from "@babel/types"; import { invariant } from "../CompilerError"; @@ -22,6 +21,16 @@ import { invariant } from "../CompilerError"; // AST -> (lowering) -> HIR -> (dep analysis) -> Reactive Scopes -> (scheduling?) -> HIR -> (codegen) -> AST +/** + * A location in a source file, intended to be used for providing diagnostic information and + * transforming code while preserving source information (ie to emit source maps). + * + * `GeneratedSource` indicates that there is no single source location from which the code derives. + * + */ +export const GeneratedSource = Symbol(); +export type SourceLocation = t.SourceLocation | typeof GeneratedSource; + /** * A React function defines a computation that takes some set of reactive * inputs (eg props, hook arguments) and returns a result (JSX, hook return @@ -37,7 +46,7 @@ import { invariant } from "../CompilerError"; * may depend upon (transitively). */ export type ReactFunction = { - path: NodePath; + loc: SourceLocation; id: Identifier | null; params: Array; returnScope: ScopeId; @@ -59,10 +68,12 @@ export type ReactiveScope = { * A function declaration including its path */ export type HIRFunction = { - path: NodePath; + loc: SourceLocation; id: Identifier | null; params: Array; body: HIR; + generator: boolean; + async: boolean; }; /** @@ -140,7 +151,7 @@ export type SwitchTerminal = { export type Instruction = { lvalue: LValue | null; value: InstructionValue; - path: NodePath; + loc: SourceLocation; }; export type LValue = { @@ -162,7 +173,9 @@ export enum InstructionKind { * * Values are therefore only a Place or a primitive value. */ -export type InstructionValue = (InstructionData & { path: NodePath }) | Place; +export type InstructionValue = + | (InstructionData & { loc: SourceLocation }) + | Place; export type Phi = { kind: "Phi"; @@ -199,7 +212,10 @@ export type InstructionData = * which are not directly represented, but included for completeness and to allow * passing through in codegen. */ - | { kind: "OtherStatement" }; + | { + kind: "OtherStatement"; + node: t.Statement | t.JSXSpreadChild | t.JSXFragment; + }; /** * A place where data may be read from / written to: @@ -211,7 +227,7 @@ export type Place = { identifier: Identifier; memberPath: Array | null; effect: Effect; - path: NodePath; + loc: SourceLocation; }; /** @@ -220,7 +236,7 @@ export type Place = { export type Primitive = { kind: "Primitive"; value: number | boolean | string | null | undefined; - path: NodePath; + loc: SourceLocation; }; /** diff --git a/compiler/forget/src/HIR/InferReferenceEffects.ts b/compiler/forget/src/HIR/InferReferenceEffects.ts index e93bf4e4d0..7b1dd27983 100644 --- a/compiler/forget/src/HIR/InferReferenceEffects.ts +++ b/compiler/forget/src/HIR/InferReferenceEffects.ts @@ -20,7 +20,7 @@ import { ValueKind, } from "./HIR"; import { mapTerminalSuccessors } from "./HIRBuilder"; -import { printMixedHIR, printPlace } from "./PrintHIR"; +import { printMixedHIR, printPlace, printSourceLocation } from "./PrintHIR"; /** * For every usage of a value in the given function, infers the effect or action @@ -72,12 +72,12 @@ export default function inferReferenceEffects(fn: HIRFunction) { kind: "Identifier", memberPath: null, identifier: fn.id as any, - path: null as any, // TODO + loc: fn.loc, effect: Effect.Freeze, }; const value: InstructionValue = { kind: "Primitive", - path: null as any, // TODO + loc: fn.loc, value: undefined, }; initialEnvironment.initialize(value, ValueKind.Frozen); @@ -86,7 +86,7 @@ export default function inferReferenceEffects(fn: HIRFunction) { for (const param of fn.params) { const value: InstructionValue = { kind: "Primitive", - path: null as any, // TODO + loc: param.loc, value: undefined, }; initialEnvironment.initialize(value, ValueKind.Frozen); @@ -189,7 +189,9 @@ class Environment { const values = this.#variables.get(place.identifier.id); invariant( values != null, - `Expected value kind to be initialized at '${String(place.path)}'` + `Expected value kind to be initialized at '${printSourceLocation( + place.loc + )}'` ); let mergedKind: ValueKind | null = null; for (const value of values) { @@ -226,9 +228,7 @@ class Environment { ); invariant( this.#values.has(value), - `Expected value to be initialized at '${String(value.path)}' in '${String( - value.path?.parentPath - )}'` + `Expected value to be initialized at '${printSourceLocation(value.loc)}'` ); this.#variables.set(place.identifier.id, new Set([value])); } diff --git a/compiler/forget/src/HIR/PrintHIR.ts b/compiler/forget/src/HIR/PrintHIR.ts index 3a8e693a3b..27e5b9a7fa 100644 --- a/compiler/forget/src/HIR/PrintHIR.ts +++ b/compiler/forget/src/HIR/PrintHIR.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import generate from "@babel/generator"; import { assertExhaustive } from "../Common/utils"; import { HIR, @@ -15,6 +16,7 @@ import { LValue, Phi, Place, + SourceLocation, Terminal, } from "./HIR"; @@ -217,9 +219,7 @@ function printInstructionValue(instrValue: InstructionValue): string { break; } case "OtherStatement": { - value = `Other(${instrValue.path?.node?.type}): \`${String( - instrValue.path - )}\``; + value = `OtherStatement(${generate(instrValue.node).code})`; break; } case "Identifier": { @@ -270,3 +270,11 @@ export function printPlace(place: Place): string { export function printIdentifier(id: Identifier): string { return `${id.name ?? ""}\$${id.id}`; } + +export function printSourceLocation(loc: SourceLocation): string { + if (typeof loc === "symbol") { + return "generated"; + } else { + return `${loc.start.line}:${loc.start.column}:${loc.end.line}:${loc.end.column}`; + } +} diff --git a/compiler/forget/src/HIR/ScopeAnalysis.ts b/compiler/forget/src/HIR/ScopeAnalysis.ts index ba752bcea1..cb8c582416 100644 --- a/compiler/forget/src/HIR/ScopeAnalysis.ts +++ b/compiler/forget/src/HIR/ScopeAnalysis.ts @@ -65,7 +65,7 @@ export default function analyzeScopes(fn: HIRFunction): ReactFunction { instructions: fn.body, }); return { - path: fn.path, + loc: fn.loc, id: fn.id, params: fn.params, returnScope: returnScopeId, @@ -177,7 +177,7 @@ function analyze(fn: HIRFunction): ReactFunction { const scopes: Map = new Map(); return { - path: fn.path, + loc: fn.loc, id: fn.id, params: fn.params, returnScope: returnScopeId,