Replace NodePath with SourceLocation

Removes all the `path: NodePath` values from various IR node types, replacing 
them with `loc: SourceLocation`. This type is an alias for babel's source 
location type plus a "generated" variant. The "OtherStatement" kind also used 
the `path` to print back the original AST (since we don't look into these); 
instead, we now capture the underlying `node` and emit that as-is during 
codegen. 

While I was doing this, i also fixed up the places where we had passed a null 
path; the vast majority have a clear place we can pull a location from. For 
example, `a ?? b` syntax creates some places/instructions for the `a != null`, 
but those can all point back to the `a` location.
This commit is contained in:
Joseph Savona
2022-11-04 08:55:18 -07:00
parent a9419099b4
commit 5f81b7bc30
6 changed files with 127 additions and 76 deletions
+74 -40
View File
@@ -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<t.Expression>
): InstructionValue {
const exprNode = exprPath.node;
const exprLoc = exprNode.loc ?? GeneratedSource;
switch (exprNode.type) {
case "Identifier": {
const expr = exprPath as NodePath<t.Identifier>;
@@ -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<t.LVal>): Place {
const exprNode = exprPath.node;
const exprLoc = exprNode.loc ?? GeneratedSource;
switch (exprNode.type) {
case "Identifier": {
// const expr = exprPath as NodePath<t.Identifier>;
@@ -1133,7 +1167,7 @@ function lowerLVal(builder: HIRBuilder, exprPath: NodePath<t.LVal>): Place {
identifier: identifier,
memberPath: null,
effect: Effect.Unknown,
path: exprPath,
loc: exprLoc,
};
return place;
}
@@ -1152,7 +1186,7 @@ function lowerLVal(builder: HIRBuilder, exprPath: NodePath<t.LVal>): Place {
identifier: object.identifier,
memberPath: [...(object.memberPath ?? []), propertyPath.node.name],
effect: Effect.Unknown,
path: exprPath,
loc: exprLoc,
};
return place;
}
+8 -15
View File
@@ -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<t.Statement>) {
}
function writeInstr(cx: Context, instr: Instruction, body: Array<t.Statement>) {
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<t.Statement>) {
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);
+24 -8
View File
@@ -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<t.Function>;
loc: SourceLocation;
id: Identifier | null;
params: Array<Place>;
returnScope: ScopeId;
@@ -59,10 +68,12 @@ export type ReactiveScope = {
* A function declaration including its path
*/
export type HIRFunction = {
path: NodePath<t.Function>;
loc: SourceLocation;
id: Identifier | null;
params: Array<Place>;
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<string> | 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<t.Node | null | undefined>;
loc: SourceLocation;
};
/**
@@ -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]));
}
+11 -3
View File
@@ -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}`;
}
}
+2 -2
View File
@@ -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<ScopeId, ReactiveScope> = new Map();
return {
path: fn.path,
loc: fn.loc,
id: fn.id,
params: fn.params,
returnScope: returnScopeId,