mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Compiler: unfork prettier config (#30205)
Updates the prettier config to format all `.ts` and `.tsx` files in the repo using the existing defaults and removing overrides. The first commit in this PR contains the config changes, the second is just the result of running `yarn prettier-all`.
This commit is contained in:
+8
-8
@@ -10,8 +10,8 @@ import {
|
||||
IdentifierId,
|
||||
ReactiveScope,
|
||||
makeInstructionId,
|
||||
} from "../HIR";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
} from '../HIR';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
|
||||
/**
|
||||
* Ensures that method call instructions have scopes such that either:
|
||||
@@ -24,8 +24,8 @@ export function alignMethodCallScopes(fn: HIRFunction): void {
|
||||
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
const { lvalue, value } = instr;
|
||||
if (value.kind === "MethodCall") {
|
||||
const {lvalue, value} = instr;
|
||||
if (value.kind === 'MethodCall') {
|
||||
const lvalueScope = lvalue.identifier.scope;
|
||||
const propertyScope = value.property.identifier.scope;
|
||||
if (lvalueScope !== null) {
|
||||
@@ -44,8 +44,8 @@ export function alignMethodCallScopes(fn: HIRFunction): void {
|
||||
scopeMapping.set(value.property.identifier.id, null);
|
||||
}
|
||||
} else if (
|
||||
value.kind === "FunctionExpression" ||
|
||||
value.kind === "ObjectMethod"
|
||||
value.kind === 'FunctionExpression' ||
|
||||
value.kind === 'ObjectMethod'
|
||||
) {
|
||||
alignMethodCallScopes(value.loweredFunc.func);
|
||||
}
|
||||
@@ -57,10 +57,10 @@ export function alignMethodCallScopes(fn: HIRFunction): void {
|
||||
return;
|
||||
}
|
||||
root.range.start = makeInstructionId(
|
||||
Math.min(scope.range.start, root.range.start)
|
||||
Math.min(scope.range.start, root.range.start),
|
||||
);
|
||||
root.range.end = makeInstructionId(
|
||||
Math.max(scope.range.end, root.range.end)
|
||||
Math.max(scope.range.end, root.range.end),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
+15
-15
@@ -5,16 +5,16 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "..";
|
||||
import {CompilerError} from '..';
|
||||
import {
|
||||
GeneratedSource,
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
ReactiveScope,
|
||||
makeInstructionId,
|
||||
} from "../HIR";
|
||||
import { eachInstructionValueOperand } from "../HIR/visitors";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
} from '../HIR';
|
||||
import {eachInstructionValueOperand} from '../HIR/visitors';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
|
||||
/**
|
||||
* Align scopes of object method values to that of their enclosing object expressions.
|
||||
@@ -27,10 +27,10 @@ function findScopesToMerge(fn: HIRFunction): DisjointSet<ReactiveScope> {
|
||||
const mergeScopesBuilder = new DisjointSet<ReactiveScope>();
|
||||
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
for (const { lvalue, value } of block.instructions) {
|
||||
if (value.kind === "ObjectMethod") {
|
||||
for (const {lvalue, value} of block.instructions) {
|
||||
if (value.kind === 'ObjectMethod') {
|
||||
objectMethodDecls.add(lvalue.identifier);
|
||||
} else if (value.kind === "ObjectExpression") {
|
||||
} else if (value.kind === 'ObjectExpression') {
|
||||
for (const operand of eachInstructionValueOperand(value)) {
|
||||
if (objectMethodDecls.has(operand.identifier)) {
|
||||
const operandScope = operand.identifier.scope;
|
||||
@@ -40,10 +40,10 @@ function findScopesToMerge(fn: HIRFunction): DisjointSet<ReactiveScope> {
|
||||
operandScope != null && lvalueScope != null,
|
||||
{
|
||||
reason:
|
||||
"Internal error: Expected all ObjectExpressions and ObjectMethods to have non-null scope.",
|
||||
'Internal error: Expected all ObjectExpressions and ObjectMethods to have non-null scope.',
|
||||
suggestions: null,
|
||||
loc: GeneratedSource,
|
||||
}
|
||||
},
|
||||
);
|
||||
mergeScopesBuilder.union([operandScope, lvalueScope]);
|
||||
}
|
||||
@@ -57,10 +57,10 @@ function findScopesToMerge(fn: HIRFunction): DisjointSet<ReactiveScope> {
|
||||
export function alignObjectMethodScopes(fn: HIRFunction): void {
|
||||
// Handle inner functions: we assume that Scopes are disjoint across functions
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
for (const { value } of block.instructions) {
|
||||
for (const {value} of block.instructions) {
|
||||
if (
|
||||
value.kind === "ObjectMethod" ||
|
||||
value.kind === "FunctionExpression"
|
||||
value.kind === 'ObjectMethod' ||
|
||||
value.kind === 'FunctionExpression'
|
||||
) {
|
||||
alignObjectMethodScopes(value.loweredFunc.func);
|
||||
}
|
||||
@@ -74,10 +74,10 @@ export function alignObjectMethodScopes(fn: HIRFunction): void {
|
||||
for (const [scope, root] of scopeGroupsMap) {
|
||||
if (scope !== root) {
|
||||
root.range.start = makeInstructionId(
|
||||
Math.min(scope.range.start, root.range.start)
|
||||
Math.min(scope.range.start, root.range.start),
|
||||
);
|
||||
root.range.end = makeInstructionId(
|
||||
Math.max(scope.range.end, root.range.end)
|
||||
Math.max(scope.range.end, root.range.end),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export function alignObjectMethodScopes(fn: HIRFunction): void {
|
||||
*/
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
for (const {
|
||||
lvalue: { identifier },
|
||||
lvalue: {identifier},
|
||||
} of block.instructions) {
|
||||
if (identifier.scope != null) {
|
||||
const root = scopeGroupsMap.get(identifier.scope);
|
||||
|
||||
+9
-9
@@ -14,9 +14,9 @@ import {
|
||||
ReactiveScope,
|
||||
ScopeId,
|
||||
makeInstructionId,
|
||||
} from "../HIR/HIR";
|
||||
import { getPlaceScope } from "./BuildReactiveBlocks";
|
||||
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
} from '../HIR/HIR';
|
||||
import {getPlaceScope} from './BuildReactiveBlocks';
|
||||
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
|
||||
/*
|
||||
* Note: this is the 2nd of 4 passes that determine how to break a function into discrete
|
||||
@@ -84,10 +84,10 @@ class Visitor extends ReactiveFunctionVisitor<Context> {
|
||||
|
||||
override visitInstruction(instr: ReactiveInstruction, state: Context): void {
|
||||
switch (instr.value.kind) {
|
||||
case "OptionalExpression":
|
||||
case "SequenceExpression":
|
||||
case "ConditionalExpression":
|
||||
case "LogicalExpression": {
|
||||
case 'OptionalExpression':
|
||||
case 'SequenceExpression':
|
||||
case 'ConditionalExpression':
|
||||
case 'LogicalExpression': {
|
||||
const prevScopeCount = state.currentScopes().length;
|
||||
this.traverseInstruction(instr, state);
|
||||
|
||||
@@ -105,7 +105,7 @@ class Visitor extends ReactiveFunctionVisitor<Context> {
|
||||
for (let i = prevScopeCount; i < scopes.length; i++) {
|
||||
const scope = scopes[i];
|
||||
scope.scope.range.start = makeInstructionId(
|
||||
Math.min(instr.id, scope.scope.range.start)
|
||||
Math.min(instr.id, scope.scope.range.start),
|
||||
);
|
||||
}
|
||||
break;
|
||||
@@ -123,7 +123,7 @@ class Visitor extends ReactiveFunctionVisitor<Context> {
|
||||
}
|
||||
}
|
||||
|
||||
type PendingReactiveScope = { active: boolean; scope: ReactiveScope };
|
||||
type PendingReactiveScope = {active: boolean; scope: ReactiveScope};
|
||||
|
||||
class Context {
|
||||
/*
|
||||
|
||||
+27
-27
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "..";
|
||||
import {CompilerError} from '..';
|
||||
import {
|
||||
BlockId,
|
||||
HIRFunction,
|
||||
@@ -14,16 +14,16 @@ import {
|
||||
Place,
|
||||
ReactiveScope,
|
||||
makeInstructionId,
|
||||
} from "../HIR/HIR";
|
||||
} from '../HIR/HIR';
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionValueOperand,
|
||||
eachTerminalOperand,
|
||||
mapTerminalSuccessors,
|
||||
terminalFallthrough,
|
||||
} from "../HIR/visitors";
|
||||
import { retainWhere_Set } from "../Utils/utils";
|
||||
import { getPlaceScope } from "./BuildReactiveBlocks";
|
||||
} from '../HIR/visitors';
|
||||
import {retainWhere_Set} from '../Utils/utils';
|
||||
import {getPlaceScope} from './BuildReactiveBlocks';
|
||||
|
||||
type InstructionRange = MutableRange;
|
||||
/*
|
||||
@@ -80,7 +80,7 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
function recordPlace(
|
||||
id: InstructionId,
|
||||
place: Place,
|
||||
node: ValueBlockNode | null
|
||||
node: ValueBlockNode | null,
|
||||
): void {
|
||||
if (place.identifier.scope !== null) {
|
||||
placeScopes.set(place, place.identifier.scope);
|
||||
@@ -91,7 +91,7 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
return;
|
||||
}
|
||||
activeScopes.add(scope);
|
||||
node?.children.push({ kind: "scope", scope, id });
|
||||
node?.children.push({kind: 'scope', scope, id});
|
||||
|
||||
if (seen.has(scope)) {
|
||||
return;
|
||||
@@ -99,17 +99,17 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
seen.add(scope);
|
||||
if (node != null && node.valueRange !== null) {
|
||||
scope.range.start = makeInstructionId(
|
||||
Math.min(node.valueRange.start, scope.range.start)
|
||||
Math.min(node.valueRange.start, scope.range.start),
|
||||
);
|
||||
scope.range.end = makeInstructionId(
|
||||
Math.max(node.valueRange.end, scope.range.end)
|
||||
Math.max(node.valueRange.end, scope.range.end),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
const startingId = block.instructions[0]?.id ?? block.terminal.id;
|
||||
retainWhere_Set(activeScopes, (scope) => scope.range.end > startingId);
|
||||
retainWhere_Set(activeScopes, scope => scope.range.end > startingId);
|
||||
const top = activeBlockFallthroughRanges.at(-1);
|
||||
if (top?.fallthrough === block.id) {
|
||||
activeBlockFallthroughRanges.pop();
|
||||
@@ -120,12 +120,12 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
*/
|
||||
for (const scope of activeScopes) {
|
||||
scope.range.start = makeInstructionId(
|
||||
Math.min(scope.range.start, top.range.start)
|
||||
Math.min(scope.range.start, top.range.start),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { instructions, terminal } = block;
|
||||
const {instructions, terminal} = block;
|
||||
const node = valueBlockNodes.get(block.id) ?? null;
|
||||
for (const instr of instructions) {
|
||||
for (const lvalue of eachInstructionLValue(instr)) {
|
||||
@@ -152,7 +152,7 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
for (const scope of activeScopes) {
|
||||
if (scope.range.end > terminal.id) {
|
||||
scope.range.end = makeInstructionId(
|
||||
Math.max(scope.range.end, nextId)
|
||||
Math.max(scope.range.end, nextId),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -169,7 +169,7 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
});
|
||||
|
||||
CompilerError.invariant(!valueBlockNodes.has(fallthrough), {
|
||||
reason: "Expect hir blocks to have unique fallthroughs",
|
||||
reason: 'Expect hir blocks to have unique fallthroughs',
|
||||
loc: terminal.loc,
|
||||
});
|
||||
if (node != null) {
|
||||
@@ -185,22 +185,22 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
* TODO: add a variant of eachTerminalSuccessor() that visits _all_ successors, not
|
||||
* just those that are direct successors for normal control-flow ordering.
|
||||
*/
|
||||
mapTerminalSuccessors(terminal, (successor) => {
|
||||
mapTerminalSuccessors(terminal, successor => {
|
||||
if (valueBlockNodes.has(successor)) {
|
||||
return successor;
|
||||
}
|
||||
|
||||
const successorBlock = fn.body.blocks.get(successor)!;
|
||||
if (successorBlock.kind === "block" || successorBlock.kind === "catch") {
|
||||
if (successorBlock.kind === 'block' || successorBlock.kind === 'catch') {
|
||||
/*
|
||||
* we need the block kind check here because the do..while terminal's
|
||||
* successor is a block, and try's successor is a catch block
|
||||
*/
|
||||
} else if (
|
||||
node == null ||
|
||||
terminal.kind === "ternary" ||
|
||||
terminal.kind === "logical" ||
|
||||
terminal.kind === "optional"
|
||||
terminal.kind === 'ternary' ||
|
||||
terminal.kind === 'logical' ||
|
||||
terminal.kind === 'optional'
|
||||
) {
|
||||
/**
|
||||
* Create a new node whenever we transition from non-value -> value block.
|
||||
@@ -232,7 +232,7 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
valueRange = node.valueRange;
|
||||
}
|
||||
const childNode: ValueBlockNode = {
|
||||
kind: "node",
|
||||
kind: 'node',
|
||||
id: terminal.id,
|
||||
children: [],
|
||||
valueRange,
|
||||
@@ -249,13 +249,13 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
}
|
||||
|
||||
type ValueBlockNode = {
|
||||
kind: "node";
|
||||
kind: 'node';
|
||||
id: InstructionId;
|
||||
valueRange: MutableRange;
|
||||
children: Array<ValueBlockNode | ReactiveScopeNode>;
|
||||
};
|
||||
type ReactiveScopeNode = {
|
||||
kind: "scope";
|
||||
kind: 'scope';
|
||||
id: InstructionId;
|
||||
scope: ReactiveScope;
|
||||
};
|
||||
@@ -263,17 +263,17 @@ type ReactiveScopeNode = {
|
||||
function _debug(node: ValueBlockNode): string {
|
||||
const buf: Array<string> = [];
|
||||
_printNode(node, buf, 0);
|
||||
return buf.join("\n");
|
||||
return buf.join('\n');
|
||||
}
|
||||
function _printNode(
|
||||
node: ValueBlockNode | ReactiveScopeNode,
|
||||
out: Array<string>,
|
||||
depth: number = 0
|
||||
depth: number = 0,
|
||||
): void {
|
||||
let prefix = " ".repeat(depth);
|
||||
if (node.kind === "scope") {
|
||||
let prefix = ' '.repeat(depth);
|
||||
if (node.kind === 'scope') {
|
||||
out.push(
|
||||
`${prefix}[${node.id}] @${node.scope.id} [${node.scope.range.start}:${node.scope.range.end}]`
|
||||
`${prefix}[${node.id}] @${node.scope.id} [${node.scope.range.start}:${node.scope.range.end}]`,
|
||||
);
|
||||
} else {
|
||||
let range = ` (range=[${node.valueRange.start}:${node.valueRange.end}])`;
|
||||
|
||||
+9
-9
@@ -5,17 +5,17 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { visitReactiveFunction } from ".";
|
||||
import { CompilerError } from "..";
|
||||
import {visitReactiveFunction} from '.';
|
||||
import {CompilerError} from '..';
|
||||
import {
|
||||
InstructionId,
|
||||
Place,
|
||||
ReactiveFunction,
|
||||
ReactiveScopeBlock,
|
||||
ScopeId,
|
||||
} from "../HIR";
|
||||
import { getPlaceScope } from "./BuildReactiveBlocks";
|
||||
import { ReactiveFunctionVisitor } from "./visitors";
|
||||
} from '../HIR';
|
||||
import {getPlaceScope} from './BuildReactiveBlocks';
|
||||
import {ReactiveFunctionVisitor} from './visitors';
|
||||
|
||||
/*
|
||||
* Internal validation pass that checks all the instructions involved in creating
|
||||
@@ -41,14 +41,14 @@ import { ReactiveFunctionVisitor } from "./visitors";
|
||||
* against compiler coding mistakes in earlier passes.
|
||||
*/
|
||||
export function assertScopeInstructionsWithinScopes(
|
||||
fn: ReactiveFunction
|
||||
fn: ReactiveFunction,
|
||||
): void {
|
||||
const existingScopes = new Set<ScopeId>();
|
||||
visitReactiveFunction(fn, new FindAllScopesVisitor(), existingScopes);
|
||||
visitReactiveFunction(
|
||||
fn,
|
||||
new CheckInstructionsAgainstScopesVisitor(),
|
||||
existingScopes
|
||||
existingScopes,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ class CheckInstructionsAgainstScopesVisitor extends ReactiveFunctionVisitor<
|
||||
override visitPlace(
|
||||
id: InstructionId,
|
||||
place: Place,
|
||||
state: Set<ScopeId>
|
||||
state: Set<ScopeId>,
|
||||
): void {
|
||||
const scope = getPlaceScope(id, place);
|
||||
if (
|
||||
@@ -84,7 +84,7 @@ class CheckInstructionsAgainstScopesVisitor extends ReactiveFunctionVisitor<
|
||||
description: `Instruction [${id}] is part of scope @${scope.id}, but that scope has already completed.`,
|
||||
loc: place.loc,
|
||||
reason:
|
||||
"Encountered an instruction that should be part of a scope, but where that scope has already completed",
|
||||
'Encountered an instruction that should be part of a scope, but where that scope has already completed',
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
+6
-6
@@ -5,9 +5,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "..";
|
||||
import { BlockId, ReactiveFunction, ReactiveTerminalStatement } from "../HIR";
|
||||
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
import {CompilerError} from '..';
|
||||
import {BlockId, ReactiveFunction, ReactiveTerminalStatement} from '../HIR';
|
||||
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
|
||||
/**
|
||||
* Assert that all break/continue targets reference existent labels.
|
||||
@@ -19,15 +19,15 @@ export function assertWellFormedBreakTargets(fn: ReactiveFunction): void {
|
||||
class Visitor extends ReactiveFunctionVisitor<Set<BlockId>> {
|
||||
override visitTerminal(
|
||||
stmt: ReactiveTerminalStatement,
|
||||
seenLabels: Set<BlockId>
|
||||
seenLabels: Set<BlockId>,
|
||||
): void {
|
||||
if (stmt.label != null) {
|
||||
seenLabels.add(stmt.label.id);
|
||||
}
|
||||
const terminal = stmt.terminal;
|
||||
if (terminal.kind === "break" || terminal.kind === "continue") {
|
||||
if (terminal.kind === 'break' || terminal.kind === 'continue') {
|
||||
CompilerError.invariant(seenLabels.has(terminal.target), {
|
||||
reason: "Unexpected break to invalid label",
|
||||
reason: 'Unexpected break to invalid label',
|
||||
loc: stmt.terminal.loc,
|
||||
});
|
||||
}
|
||||
|
||||
+30
-30
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {
|
||||
BlockId,
|
||||
InstructionId,
|
||||
@@ -17,10 +17,10 @@ import {
|
||||
ReactiveScopeBlock,
|
||||
ReactiveStatement,
|
||||
ScopeId,
|
||||
} from "../HIR";
|
||||
import { eachInstructionLValue } from "../HIR/visitors";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
import { eachReactiveValueOperand, mapTerminalBlocks } from "./visitors";
|
||||
} from '../HIR';
|
||||
import {eachInstructionLValue} from '../HIR/visitors';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {eachReactiveValueOperand, mapTerminalBlocks} from './visitors';
|
||||
|
||||
/*
|
||||
* Note: this is the 4th of 4 passes that determine how to break a function into discrete
|
||||
@@ -63,7 +63,7 @@ class Context {
|
||||
|
||||
append(
|
||||
stmt: ReactiveStatement,
|
||||
label: { id: BlockId; implicit: boolean } | null
|
||||
label: {id: BlockId; implicit: boolean} | null,
|
||||
): void {
|
||||
this.#builders.at(-1)!.append(stmt, label);
|
||||
}
|
||||
@@ -74,7 +74,7 @@ class Context {
|
||||
fn();
|
||||
const popped = this.#builders.pop();
|
||||
CompilerError.invariant(popped === builder, {
|
||||
reason: "Expected push/pop to be called 1:1",
|
||||
reason: 'Expected push/pop to be called 1:1',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -86,23 +86,23 @@ class Context {
|
||||
class Builder {
|
||||
#instructions: ReactiveBlock;
|
||||
#stack: Array<
|
||||
| { kind: "scope"; block: ReactiveScopeBlock }
|
||||
| { kind: "block"; block: ReactiveBlock }
|
||||
| {kind: 'scope'; block: ReactiveScopeBlock}
|
||||
| {kind: 'block'; block: ReactiveBlock}
|
||||
>;
|
||||
|
||||
constructor() {
|
||||
const block: ReactiveBlock = [];
|
||||
this.#instructions = block;
|
||||
this.#stack = [{ kind: "block", block }];
|
||||
this.#stack = [{kind: 'block', block}];
|
||||
}
|
||||
|
||||
append(
|
||||
item: ReactiveStatement,
|
||||
label: { id: BlockId; implicit: boolean } | null
|
||||
label: {id: BlockId; implicit: boolean} | null,
|
||||
): void {
|
||||
if (label !== null) {
|
||||
CompilerError.invariant(item.kind === "terminal", {
|
||||
reason: "Only terminals may have a label",
|
||||
CompilerError.invariant(item.kind === 'terminal', {
|
||||
reason: 'Only terminals may have a label',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -114,25 +114,25 @@ class Builder {
|
||||
|
||||
startScope(scope: ReactiveScope): void {
|
||||
const block: ReactiveScopeBlock = {
|
||||
kind: "scope",
|
||||
kind: 'scope',
|
||||
scope,
|
||||
instructions: [],
|
||||
};
|
||||
this.append(block, null);
|
||||
this.#instructions = block.instructions;
|
||||
this.#stack.push({ kind: "scope", block });
|
||||
this.#stack.push({kind: 'scope', block});
|
||||
}
|
||||
|
||||
visitId(id: InstructionId): void {
|
||||
for (let i = 0; i < this.#stack.length; i++) {
|
||||
const entry = this.#stack[i]!;
|
||||
if (entry.kind === "scope" && id >= entry.block.scope.range.end) {
|
||||
if (entry.kind === 'scope' && id >= entry.block.scope.range.end) {
|
||||
this.#stack.length = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const last = this.#stack[this.#stack.length - 1]!;
|
||||
if (last.kind === "block") {
|
||||
if (last.kind === 'block') {
|
||||
this.#instructions = last.block;
|
||||
} else {
|
||||
this.#instructions = last.block.instructions;
|
||||
@@ -148,8 +148,8 @@ class Builder {
|
||||
* );
|
||||
*/
|
||||
const first = this.#stack[0]!;
|
||||
CompilerError.invariant(first.kind === "block", {
|
||||
reason: "Expected first stack item to be a basic block",
|
||||
CompilerError.invariant(first.kind === 'block', {
|
||||
reason: 'Expected first stack item to be a basic block',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -161,7 +161,7 @@ class Builder {
|
||||
function visitBlock(context: Context, block: ReactiveBlock): void {
|
||||
for (const stmt of block) {
|
||||
switch (stmt.kind) {
|
||||
case "instruction": {
|
||||
case 'instruction': {
|
||||
context.visitId(stmt.instruction.id);
|
||||
const scope = getInstructionScope(stmt.instruction);
|
||||
if (scope !== null) {
|
||||
@@ -170,12 +170,12 @@ function visitBlock(context: Context, block: ReactiveBlock): void {
|
||||
context.append(stmt, null);
|
||||
break;
|
||||
}
|
||||
case "terminal": {
|
||||
case 'terminal': {
|
||||
const id = stmt.terminal.id;
|
||||
if (id !== null) {
|
||||
context.visitId(id);
|
||||
}
|
||||
mapTerminalBlocks(stmt.terminal, (block) => {
|
||||
mapTerminalBlocks(stmt.terminal, block => {
|
||||
return context.enter(() => {
|
||||
visitBlock(context, block);
|
||||
});
|
||||
@@ -183,10 +183,10 @@ function visitBlock(context: Context, block: ReactiveBlock): void {
|
||||
context.append(stmt, stmt.label);
|
||||
break;
|
||||
}
|
||||
case "pruned-scope":
|
||||
case "scope": {
|
||||
case 'pruned-scope':
|
||||
case 'scope': {
|
||||
CompilerError.invariant(false, {
|
||||
reason: "Expected the function to not have scopes already assigned",
|
||||
reason: 'Expected the function to not have scopes already assigned',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -195,7 +195,7 @@ function visitBlock(context: Context, block: ReactiveBlock): void {
|
||||
default: {
|
||||
assertExhaustive(
|
||||
stmt,
|
||||
`Unexpected statement kind \`${(stmt as any).kind}\``
|
||||
`Unexpected statement kind \`${(stmt as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -203,12 +203,12 @@ function visitBlock(context: Context, block: ReactiveBlock): void {
|
||||
}
|
||||
|
||||
export function getInstructionScope(
|
||||
instr: ReactiveInstruction
|
||||
instr: ReactiveInstruction,
|
||||
): ReactiveScope | null {
|
||||
CompilerError.invariant(instr.lvalue !== null, {
|
||||
reason:
|
||||
"Expected lvalues to not be null when assigning scopes. " +
|
||||
"Pruning lvalues too early can result in missing scope information.",
|
||||
'Expected lvalues to not be null when assigning scopes. ' +
|
||||
'Pruning lvalues too early can result in missing scope information.',
|
||||
description: null,
|
||||
loc: instr.loc,
|
||||
suggestions: null,
|
||||
@@ -230,7 +230,7 @@ export function getInstructionScope(
|
||||
|
||||
export function getPlaceScope(
|
||||
id: InstructionId,
|
||||
place: Place
|
||||
place: Place,
|
||||
): ReactiveScope | null {
|
||||
const scope = place.identifier.scope;
|
||||
if (scope !== null && isScopeActive(scope, id)) {
|
||||
|
||||
+175
-185
File diff suppressed because it is too large
Load Diff
+499
-498
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -12,8 +12,8 @@ import {
|
||||
PrunedReactiveScopeBlock,
|
||||
ReactiveFunction,
|
||||
isPrimitiveType,
|
||||
} from "../HIR/HIR";
|
||||
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
} from '../HIR/HIR';
|
||||
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
|
||||
class Visitor extends ReactiveFunctionVisitor<Set<IdentifierId>> {
|
||||
/*
|
||||
@@ -23,7 +23,7 @@ class Visitor extends ReactiveFunctionVisitor<Set<IdentifierId>> {
|
||||
override visitLValue(
|
||||
id: InstructionId,
|
||||
lvalue: Place,
|
||||
state: Set<IdentifierId>
|
||||
state: Set<IdentifierId>,
|
||||
): void {
|
||||
this.visitPlace(id, lvalue, state);
|
||||
}
|
||||
@@ -36,7 +36,7 @@ class Visitor extends ReactiveFunctionVisitor<Set<IdentifierId>> {
|
||||
override visitPlace(
|
||||
_id: InstructionId,
|
||||
place: Place,
|
||||
state: Set<IdentifierId>
|
||||
state: Set<IdentifierId>,
|
||||
): void {
|
||||
if (place.reactive) {
|
||||
state.add(place.identifier.id);
|
||||
@@ -45,7 +45,7 @@ class Visitor extends ReactiveFunctionVisitor<Set<IdentifierId>> {
|
||||
|
||||
override visitPrunedScope(
|
||||
scopeBlock: PrunedReactiveScopeBlock,
|
||||
state: Set<IdentifierId>
|
||||
state: Set<IdentifierId>,
|
||||
): void {
|
||||
this.traversePrunedScope(scopeBlock, state);
|
||||
|
||||
@@ -62,7 +62,7 @@ class Visitor extends ReactiveFunctionVisitor<Set<IdentifierId>> {
|
||||
* in `InferReactivePlaces`.
|
||||
*/
|
||||
export function collectReactiveIdentifiers(
|
||||
fn: ReactiveFunction
|
||||
fn: ReactiveFunction,
|
||||
): Set<IdentifierId> {
|
||||
const visitor = new Visitor();
|
||||
const state = new Set<IdentifierId>();
|
||||
|
||||
+7
-7
@@ -5,9 +5,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { visitReactiveFunction } from ".";
|
||||
import { InstructionId, Place, ReactiveFunction, ReactiveValue } from "../HIR";
|
||||
import { ReactiveFunctionVisitor } from "./visitors";
|
||||
import {visitReactiveFunction} from '.';
|
||||
import {InstructionId, Place, ReactiveFunction, ReactiveValue} from '../HIR';
|
||||
import {ReactiveFunctionVisitor} from './visitors';
|
||||
|
||||
/**
|
||||
* Returns a set of unique globals (by name) that are referenced transitively within the function.
|
||||
@@ -22,12 +22,12 @@ class Visitor extends ReactiveFunctionVisitor<Set<string>> {
|
||||
override visitValue(
|
||||
id: InstructionId,
|
||||
value: ReactiveValue,
|
||||
state: Set<string>
|
||||
state: Set<string>,
|
||||
): void {
|
||||
this.traverseValue(id, value, state);
|
||||
if (value.kind === "FunctionExpression" || value.kind === "ObjectMethod") {
|
||||
if (value.kind === 'FunctionExpression' || value.kind === 'ObjectMethod') {
|
||||
this.visitHirFunction(value.loweredFunc.func, state);
|
||||
} else if (value.kind === "LoadGlobal") {
|
||||
} else if (value.kind === 'LoadGlobal') {
|
||||
state.add(value.binding.name);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ class Visitor extends ReactiveFunctionVisitor<Set<string>> {
|
||||
_id: InstructionId,
|
||||
_dependencies: Array<Place>,
|
||||
fn: ReactiveFunction,
|
||||
state: Set<string>
|
||||
state: Set<string>,
|
||||
): void {
|
||||
visitReactiveFunction(fn, this, state);
|
||||
}
|
||||
|
||||
+42
-44
@@ -5,10 +5,10 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import { Identifier, ReactiveScopeDependency } from "../HIR";
|
||||
import { printIdentifier } from "../HIR/PrintHIR";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {Identifier, ReactiveScopeDependency} from '../HIR';
|
||||
import {printIdentifier} from '../HIR/PrintHIR';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
/*
|
||||
* We need to understand optional member expressions only when determining
|
||||
@@ -69,7 +69,7 @@ export class ReactiveScopeDependencyTree {
|
||||
}
|
||||
|
||||
add(dep: ReactiveScopePropertyDependency, inConditional: boolean): void {
|
||||
const { path, optionalPath } = dep;
|
||||
const {path, optionalPath} = dep;
|
||||
let currNode = this.#getOrCreateRoot(dep.identifier);
|
||||
|
||||
const accessType = inConditional
|
||||
@@ -111,7 +111,7 @@ export class ReactiveScopeDependencyTree {
|
||||
let currChild = getOrMakeProperty(currNode, property);
|
||||
currChild.accessType = merge(
|
||||
currChild.accessType,
|
||||
PropertyAccessType.ConditionalAccess
|
||||
PropertyAccessType.ConditionalAccess,
|
||||
);
|
||||
currNode = currChild;
|
||||
}
|
||||
@@ -119,7 +119,7 @@ export class ReactiveScopeDependencyTree {
|
||||
// The final node should be marked as a conditional dependency.
|
||||
currNode.accessType = merge(
|
||||
currNode.accessType,
|
||||
PropertyAccessType.ConditionalDependency
|
||||
PropertyAccessType.ConditionalDependency,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -130,15 +130,15 @@ export class ReactiveScopeDependencyTree {
|
||||
const deps = deriveMinimalDependenciesInSubtree(rootNode);
|
||||
CompilerError.invariant(
|
||||
deps.every(
|
||||
(dep) => dep.accessType === PropertyAccessType.UnconditionalDependency
|
||||
dep => dep.accessType === PropertyAccessType.UnconditionalDependency,
|
||||
),
|
||||
{
|
||||
reason:
|
||||
"[PropagateScopeDependencies] All dependencies must be reduced to unconditional dependencies.",
|
||||
'[PropagateScopeDependencies] All dependencies must be reduced to unconditional dependencies.',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
for (const dep of deps) {
|
||||
@@ -155,10 +155,10 @@ export class ReactiveScopeDependencyTree {
|
||||
addDepsFromInnerScope(
|
||||
depsFromInnerScope: ReactiveScopeDependencyTree,
|
||||
innerScopeInConditionalWithinParent: boolean,
|
||||
checkValidDepIdFn: (dep: ReactiveScopeDependency) => boolean
|
||||
checkValidDepIdFn: (dep: ReactiveScopeDependency) => boolean,
|
||||
): void {
|
||||
for (const [id, otherRoot] of depsFromInnerScope.#roots) {
|
||||
if (!checkValidDepIdFn({ identifier: id, path: [] })) {
|
||||
if (!checkValidDepIdFn({identifier: id, path: []})) {
|
||||
continue;
|
||||
}
|
||||
let currRoot = this.#getOrCreateRoot(id);
|
||||
@@ -172,17 +172,17 @@ export class ReactiveScopeDependencyTree {
|
||||
}
|
||||
|
||||
promoteDepsFromExhaustiveConditionals(
|
||||
trees: Array<ReactiveScopeDependencyTree>
|
||||
trees: Array<ReactiveScopeDependencyTree>,
|
||||
): void {
|
||||
CompilerError.invariant(trees.length > 1, {
|
||||
reason: "Expected trees to be at least 2 elements long.",
|
||||
reason: 'Expected trees to be at least 2 elements long.',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
for (const [id, root] of this.#roots) {
|
||||
const nodesForRootId = mapNonNull(trees, (tree) => {
|
||||
const nodesForRootId = mapNonNull(trees, tree => {
|
||||
const node = tree.#roots.get(id);
|
||||
if (node != null && isUnconditional(node.accessType)) {
|
||||
return node;
|
||||
@@ -193,7 +193,7 @@ export class ReactiveScopeDependencyTree {
|
||||
if (nodesForRootId) {
|
||||
addSubtreeIntersection(
|
||||
root.properties,
|
||||
nodesForRootId.map((root) => root.properties)
|
||||
nodesForRootId.map(root => root.properties),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -209,11 +209,11 @@ export class ReactiveScopeDependencyTree {
|
||||
|
||||
for (const [rootId, rootNode] of this.#roots.entries()) {
|
||||
const rootResults = printSubtree(rootNode, includeAccesses).map(
|
||||
(result) => `${printIdentifier(rootId)}.${result}`
|
||||
result => `${printIdentifier(rootId)}.${result}`,
|
||||
);
|
||||
res.push(rootResults);
|
||||
}
|
||||
return res.flat().join("\n");
|
||||
return res.flat().join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,10 +237,10 @@ export class ReactiveScopeDependencyTree {
|
||||
* ```
|
||||
*/
|
||||
enum PropertyAccessType {
|
||||
ConditionalAccess = "ConditionalAccess",
|
||||
UnconditionalAccess = "UnconditionalAccess",
|
||||
ConditionalDependency = "ConditionalDependency",
|
||||
UnconditionalDependency = "UnconditionalDependency",
|
||||
ConditionalAccess = 'ConditionalAccess',
|
||||
UnconditionalAccess = 'UnconditionalAccess',
|
||||
ConditionalDependency = 'ConditionalDependency',
|
||||
UnconditionalDependency = 'UnconditionalDependency',
|
||||
}
|
||||
|
||||
const MIN_ACCESS_TYPE = PropertyAccessType.ConditionalAccess;
|
||||
@@ -259,7 +259,7 @@ function isDependency(access: PropertyAccessType): boolean {
|
||||
|
||||
function merge(
|
||||
access1: PropertyAccessType,
|
||||
access2: PropertyAccessType
|
||||
access2: PropertyAccessType,
|
||||
): PropertyAccessType {
|
||||
const resultIsUnconditional =
|
||||
isUnconditional(access1) || isUnconditional(access2);
|
||||
@@ -318,17 +318,17 @@ const promoteCondResult = [
|
||||
* @returns a minimal list of dependencies in this subtree.
|
||||
*/
|
||||
function deriveMinimalDependenciesInSubtree(
|
||||
dep: DependencyNode
|
||||
dep: DependencyNode,
|
||||
): Array<ReduceResultNode> {
|
||||
const results: Array<ReduceResultNode> = [];
|
||||
for (const [childName, childNode] of dep.properties) {
|
||||
const childResult = deriveMinimalDependenciesInSubtree(childNode).map(
|
||||
({ relativePath, accessType }) => {
|
||||
({relativePath, accessType}) => {
|
||||
return {
|
||||
relativePath: [childName, ...relativePath],
|
||||
accessType,
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
results.push(...childResult);
|
||||
}
|
||||
@@ -340,8 +340,8 @@ function deriveMinimalDependenciesInSubtree(
|
||||
case PropertyAccessType.UnconditionalAccess: {
|
||||
if (
|
||||
results.every(
|
||||
({ accessType }) =>
|
||||
accessType === PropertyAccessType.UnconditionalDependency
|
||||
({accessType}) =>
|
||||
accessType === PropertyAccessType.UnconditionalDependency,
|
||||
)
|
||||
) {
|
||||
// all children are unconditional dependencies, return them to preserve granularity
|
||||
@@ -358,8 +358,8 @@ function deriveMinimalDependenciesInSubtree(
|
||||
case PropertyAccessType.ConditionalDependency: {
|
||||
if (
|
||||
results.every(
|
||||
({ accessType }) =>
|
||||
accessType === PropertyAccessType.ConditionalDependency
|
||||
({accessType}) =>
|
||||
accessType === PropertyAccessType.ConditionalDependency,
|
||||
)
|
||||
) {
|
||||
/*
|
||||
@@ -379,7 +379,7 @@ function deriveMinimalDependenciesInSubtree(
|
||||
default: {
|
||||
assertExhaustive(
|
||||
dep.accessType,
|
||||
"[PropgateScopeDependencies] Unhandled access type!"
|
||||
'[PropgateScopeDependencies] Unhandled access type!',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -395,7 +395,7 @@ function demoteSubtreeToConditional(subtree: DependencyNode): void {
|
||||
|
||||
let node;
|
||||
while ((node = stack.pop()) !== undefined) {
|
||||
const { accessType, properties } = node;
|
||||
const {accessType, properties} = node;
|
||||
if (!isUnconditional(accessType)) {
|
||||
// A conditionally accessed node should not have unconditional children
|
||||
continue;
|
||||
@@ -435,7 +435,7 @@ function demoteSubtreeToConditional(subtree: DependencyNode): void {
|
||||
function addSubtree(
|
||||
currNode: DependencyNode,
|
||||
otherNode: DependencyNode,
|
||||
demoteOtherNode: boolean
|
||||
demoteOtherNode: boolean,
|
||||
): void {
|
||||
let otherType = otherNode.accessType;
|
||||
if (demoteOtherNode) {
|
||||
@@ -488,11 +488,11 @@ function addSubtree(
|
||||
*/
|
||||
function addSubtreeIntersection(
|
||||
currProperties: Map<string, DependencyNode>,
|
||||
otherProperties: Array<Map<string, DependencyNode>>
|
||||
otherProperties: Array<Map<string, DependencyNode>>,
|
||||
): void {
|
||||
CompilerError.invariant(otherProperties.length > 1, {
|
||||
reason:
|
||||
"[DeriveMinimalDependencies] Expected otherProperties to be at least 2 elements long.",
|
||||
'[DeriveMinimalDependencies] Expected otherProperties to be at least 2 elements long.',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -506,7 +506,7 @@ function addSubtreeIntersection(
|
||||
*/
|
||||
|
||||
for (const [propertyName, currNode] of currProperties) {
|
||||
const otherNodes = mapNonNull(otherProperties, (properties) => {
|
||||
const otherNodes = mapNonNull(otherProperties, properties => {
|
||||
const node = properties.get(propertyName);
|
||||
if (node != null && isUnconditional(node.accessType)) {
|
||||
return node;
|
||||
@@ -522,10 +522,10 @@ function addSubtreeIntersection(
|
||||
if (otherNodes) {
|
||||
addSubtreeIntersection(
|
||||
currNode.properties,
|
||||
otherNodes.map((node) => node.properties)
|
||||
otherNodes.map(node => node.properties),
|
||||
);
|
||||
|
||||
const isDep = otherNodes.some((tree) => isDependency(tree.accessType));
|
||||
const isDep = otherNodes.some(tree => isDependency(tree.accessType));
|
||||
const externalAccessType = isDep
|
||||
? PropertyAccessType.UnconditionalDependency
|
||||
: PropertyAccessType.UnconditionalAccess;
|
||||
@@ -536,7 +536,7 @@ function addSubtreeIntersection(
|
||||
|
||||
function printSubtree(
|
||||
node: DependencyNode,
|
||||
includeAccesses: boolean
|
||||
includeAccesses: boolean,
|
||||
): Array<string> {
|
||||
const results: Array<string> = [];
|
||||
for (const [propertyName, propertyNode] of node.properties) {
|
||||
@@ -544,16 +544,14 @@ function printSubtree(
|
||||
results.push(`${propertyName} (${propertyNode.accessType})`);
|
||||
}
|
||||
const propertyResults = printSubtree(propertyNode, includeAccesses);
|
||||
results.push(
|
||||
...propertyResults.map((result) => `${propertyName}.${result}`)
|
||||
);
|
||||
results.push(...propertyResults.map(result => `${propertyName}.${result}`));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
function getOrMakeProperty(
|
||||
node: DependencyNode,
|
||||
property: string
|
||||
property: string,
|
||||
): DependencyNode {
|
||||
let child = node.properties.get(property);
|
||||
if (child == null) {
|
||||
@@ -568,7 +566,7 @@ function getOrMakeProperty(
|
||||
|
||||
function mapNonNull<T extends NonNullable<V>, V, U>(
|
||||
arr: Array<U>,
|
||||
fn: (arg0: U) => T | undefined | null
|
||||
fn: (arg0: U) => T | undefined | null,
|
||||
): Array<T> | null {
|
||||
const result = [];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
|
||||
+14
-14
@@ -16,13 +16,13 @@ import {
|
||||
ReactiveScopeBlock,
|
||||
ReactiveStatement,
|
||||
promoteTemporary,
|
||||
} from "../HIR";
|
||||
import { eachPatternOperand, mapPatternOperands } from "../HIR/visitors";
|
||||
} from '../HIR';
|
||||
import {eachPatternOperand, mapPatternOperands} from '../HIR/visitors';
|
||||
import {
|
||||
ReactiveFunctionTransform,
|
||||
Transformed,
|
||||
visitReactiveFunction,
|
||||
} from "./visitors";
|
||||
} from './visitors';
|
||||
|
||||
/*
|
||||
* Destructuring statements may sometimes define some variables which are declared by the scope,
|
||||
@@ -74,7 +74,7 @@ import {
|
||||
*
|
||||
*/
|
||||
export function extractScopeDeclarationsFromDestructuring(
|
||||
fn: ReactiveFunction
|
||||
fn: ReactiveFunction,
|
||||
): void {
|
||||
const state = new State(fn.env);
|
||||
visitReactiveFunction(fn, new Visitor(), state);
|
||||
@@ -99,34 +99,34 @@ class Visitor extends ReactiveFunctionTransform<State> {
|
||||
|
||||
override transformInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
state: State
|
||||
state: State,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitInstruction(instruction, state);
|
||||
|
||||
if (instruction.value.kind === "Destructure") {
|
||||
if (instruction.value.kind === 'Destructure') {
|
||||
const transformed = transformDestructuring(
|
||||
state,
|
||||
instruction,
|
||||
instruction.value
|
||||
instruction.value,
|
||||
);
|
||||
if (transformed) {
|
||||
return {
|
||||
kind: "replace-many",
|
||||
value: transformed.map((instruction) => ({
|
||||
kind: "instruction",
|
||||
kind: 'replace-many',
|
||||
value: transformed.map(instruction => ({
|
||||
kind: 'instruction',
|
||||
instruction,
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
}
|
||||
|
||||
function transformDestructuring(
|
||||
state: State,
|
||||
instr: ReactiveInstruction,
|
||||
destructure: Destructure
|
||||
destructure: Destructure,
|
||||
): null | Array<ReactiveInstruction> {
|
||||
let reassigned: Set<IdentifierId> = new Set();
|
||||
let hasDeclaration = false;
|
||||
@@ -146,7 +146,7 @@ function transformDestructuring(
|
||||
*/
|
||||
const instructions: Array<ReactiveInstruction> = [];
|
||||
const renamed: Map<Place, Place> = new Map();
|
||||
mapPatternOperands(destructure.lvalue.pattern, (place) => {
|
||||
mapPatternOperands(destructure.lvalue.pattern, place => {
|
||||
if (!reassigned.has(place.identifier.id)) {
|
||||
return place;
|
||||
}
|
||||
@@ -169,7 +169,7 @@ function transformDestructuring(
|
||||
id: instr.id,
|
||||
lvalue: null,
|
||||
value: {
|
||||
kind: "StoreLocal",
|
||||
kind: 'StoreLocal',
|
||||
lvalue: {
|
||||
kind: InstructionKind.Reassign,
|
||||
place: original,
|
||||
|
||||
+22
-22
@@ -11,13 +11,13 @@ import {
|
||||
ReactiveStatement,
|
||||
ReactiveTerminal,
|
||||
ReactiveTerminalStatement,
|
||||
} from "../HIR/HIR";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
} from '../HIR/HIR';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {
|
||||
ReactiveFunctionTransform,
|
||||
Transformed,
|
||||
visitReactiveFunction,
|
||||
} from "./visitors";
|
||||
} from './visitors';
|
||||
|
||||
/*
|
||||
* Given a reactive function, flattens any scopes contained within a loop construct.
|
||||
@@ -30,53 +30,53 @@ export function flattenReactiveLoops(fn: ReactiveFunction): void {
|
||||
class Transform extends ReactiveFunctionTransform<boolean> {
|
||||
override transformScope(
|
||||
scope: ReactiveScopeBlock,
|
||||
isWithinLoop: boolean
|
||||
isWithinLoop: boolean,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitScope(scope, isWithinLoop);
|
||||
if (isWithinLoop) {
|
||||
return {
|
||||
kind: "replace",
|
||||
kind: 'replace',
|
||||
value: {
|
||||
kind: "pruned-scope",
|
||||
kind: 'pruned-scope',
|
||||
scope: scope.scope,
|
||||
instructions: scope.instructions,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
}
|
||||
|
||||
override visitTerminal(
|
||||
stmt: ReactiveTerminalStatement<ReactiveTerminal>,
|
||||
isWithinLoop: boolean
|
||||
isWithinLoop: boolean,
|
||||
): void {
|
||||
switch (stmt.terminal.kind) {
|
||||
// Loop terminals flatten nested scopes
|
||||
case "do-while":
|
||||
case "while":
|
||||
case "for":
|
||||
case "for-of":
|
||||
case "for-in": {
|
||||
case 'do-while':
|
||||
case 'while':
|
||||
case 'for':
|
||||
case 'for-of':
|
||||
case 'for-in': {
|
||||
this.traverseTerminal(stmt, true);
|
||||
break;
|
||||
}
|
||||
// Non-loop terminals passthrough is contextual, inherits the parent isWithinScope
|
||||
case "try":
|
||||
case "label":
|
||||
case "break":
|
||||
case "continue":
|
||||
case "if":
|
||||
case "return":
|
||||
case "switch":
|
||||
case "throw": {
|
||||
case 'try':
|
||||
case 'label':
|
||||
case 'break':
|
||||
case 'continue':
|
||||
case 'if':
|
||||
case 'return':
|
||||
case 'switch':
|
||||
case 'throw': {
|
||||
this.traverseTerminal(stmt, isWithinLoop);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
stmt.terminal,
|
||||
`Unexpected terminal kind \`${(stmt.terminal as any).kind}\``
|
||||
`Unexpected terminal kind \`${(stmt.terminal as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+28
-28
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { BlockId, HIRFunction, PrunedScopeTerminal } from "../HIR";
|
||||
import { assertExhaustive, retainWhere } from "../Utils/utils";
|
||||
import {BlockId, HIRFunction, PrunedScopeTerminal} from '../HIR';
|
||||
import {assertExhaustive, retainWhere} from '../Utils/utils';
|
||||
|
||||
/**
|
||||
* Prunes any reactive scopes that are within a loop (for, while, etc). We don't yet
|
||||
@@ -18,21 +18,21 @@ import { assertExhaustive, retainWhere } from "../Utils/utils";
|
||||
export function flattenReactiveLoopsHIR(fn: HIRFunction): void {
|
||||
const activeLoops = Array<BlockId>();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
retainWhere(activeLoops, (id) => id !== block.id);
|
||||
const { terminal } = block;
|
||||
retainWhere(activeLoops, id => id !== block.id);
|
||||
const {terminal} = block;
|
||||
switch (terminal.kind) {
|
||||
case "do-while":
|
||||
case "for":
|
||||
case "for-in":
|
||||
case "for-of":
|
||||
case "while": {
|
||||
case 'do-while':
|
||||
case 'for':
|
||||
case 'for-in':
|
||||
case 'for-of':
|
||||
case 'while': {
|
||||
activeLoops.push(terminal.fallthrough);
|
||||
break;
|
||||
}
|
||||
case "scope": {
|
||||
case 'scope': {
|
||||
if (activeLoops.length !== 0) {
|
||||
block.terminal = {
|
||||
kind: "pruned-scope",
|
||||
kind: 'pruned-scope',
|
||||
block: terminal.block,
|
||||
fallthrough: terminal.fallthrough,
|
||||
id: terminal.id,
|
||||
@@ -42,28 +42,28 @@ export function flattenReactiveLoopsHIR(fn: HIRFunction): void {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "branch":
|
||||
case "goto":
|
||||
case "if":
|
||||
case "label":
|
||||
case "logical":
|
||||
case "maybe-throw":
|
||||
case "optional":
|
||||
case "pruned-scope":
|
||||
case "return":
|
||||
case "sequence":
|
||||
case "switch":
|
||||
case "ternary":
|
||||
case "throw":
|
||||
case "try":
|
||||
case "unreachable":
|
||||
case "unsupported": {
|
||||
case 'branch':
|
||||
case 'goto':
|
||||
case 'if':
|
||||
case 'label':
|
||||
case 'logical':
|
||||
case 'maybe-throw':
|
||||
case 'optional':
|
||||
case 'pruned-scope':
|
||||
case 'return':
|
||||
case 'sequence':
|
||||
case 'switch':
|
||||
case 'ternary':
|
||||
case 'throw':
|
||||
case 'try':
|
||||
case 'unreachable':
|
||||
case 'unsupported': {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
terminal,
|
||||
`Unexpected terminal kind \`${(terminal as any).kind}\``
|
||||
`Unexpected terminal kind \`${(terminal as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -14,12 +14,12 @@ import {
|
||||
ReactiveValue,
|
||||
getHookKind,
|
||||
isUseOperator,
|
||||
} from "../HIR";
|
||||
} from '../HIR';
|
||||
import {
|
||||
ReactiveFunctionTransform,
|
||||
Transformed,
|
||||
visitReactiveFunction,
|
||||
} from "./visitors";
|
||||
} from './visitors';
|
||||
|
||||
/**
|
||||
* For simplicity the majority of compiler passes do not treat hooks specially. However, hooks are different
|
||||
@@ -57,7 +57,7 @@ type State = {
|
||||
class Transform extends ReactiveFunctionTransform<State> {
|
||||
override transformScope(
|
||||
scope: ReactiveScopeBlock,
|
||||
outerState: State
|
||||
outerState: State,
|
||||
): Transformed<ReactiveStatement> {
|
||||
const innerState: State = {
|
||||
env: outerState.env,
|
||||
@@ -72,7 +72,7 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
* flatten it away
|
||||
*/
|
||||
return {
|
||||
kind: "replace-many",
|
||||
kind: 'replace-many',
|
||||
value: scope.instructions,
|
||||
};
|
||||
}
|
||||
@@ -81,26 +81,26 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
* mark it as pruned
|
||||
*/
|
||||
return {
|
||||
kind: "replace",
|
||||
kind: 'replace',
|
||||
value: {
|
||||
kind: "pruned-scope",
|
||||
kind: 'pruned-scope',
|
||||
scope: scope.scope,
|
||||
instructions: scope.instructions,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
}
|
||||
|
||||
override visitValue(
|
||||
id: InstructionId,
|
||||
value: ReactiveValue,
|
||||
state: State
|
||||
state: State,
|
||||
): void {
|
||||
this.traverseValue(id, value, state);
|
||||
switch (value.kind) {
|
||||
case "CallExpression": {
|
||||
case 'CallExpression': {
|
||||
if (
|
||||
getHookKind(state.env, value.callee.identifier) != null ||
|
||||
isUseOperator(value.callee.identifier)
|
||||
@@ -109,7 +109,7 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "MethodCall": {
|
||||
case 'MethodCall': {
|
||||
if (
|
||||
getHookKind(state.env, value.property.identifier) != null ||
|
||||
isUseOperator(value.property.identifier)
|
||||
|
||||
+15
-15
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "..";
|
||||
import {CompilerError} from '..';
|
||||
import {
|
||||
BlockId,
|
||||
HIRFunction,
|
||||
@@ -14,8 +14,8 @@ import {
|
||||
ReactiveScope,
|
||||
getHookKind,
|
||||
isUseOperator,
|
||||
} from "../HIR";
|
||||
import { retainWhere } from "../Utils/utils";
|
||||
} from '../HIR';
|
||||
import {retainWhere} from '../Utils/utils';
|
||||
|
||||
/**
|
||||
* For simplicity the majority of compiler passes do not treat hooks specially. However, hooks are different
|
||||
@@ -39,31 +39,31 @@ import { retainWhere } from "../Utils/utils";
|
||||
* to ensure the hook call does not inadvertently become conditional.
|
||||
*/
|
||||
export function flattenScopesWithHooksOrUseHIR(fn: HIRFunction): void {
|
||||
const activeScopes: Array<{ block: BlockId; scope: ReactiveScope }> = [];
|
||||
const activeScopes: Array<{block: BlockId; scope: ReactiveScope}> = [];
|
||||
const prune: Array<BlockId> = [];
|
||||
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
const firstId = block.instructions[0]?.id ?? block.terminal.id;
|
||||
retainWhere(activeScopes, (current) => current.scope.range.end > firstId);
|
||||
retainWhere(activeScopes, current => current.scope.range.end > firstId);
|
||||
|
||||
for (const instr of block.instructions) {
|
||||
const { value } = instr;
|
||||
const {value} = instr;
|
||||
switch (value.kind) {
|
||||
case "MethodCall":
|
||||
case "CallExpression": {
|
||||
case 'MethodCall':
|
||||
case 'CallExpression': {
|
||||
const callee =
|
||||
value.kind === "MethodCall" ? value.property : value.callee;
|
||||
value.kind === 'MethodCall' ? value.property : value.callee;
|
||||
if (
|
||||
getHookKind(fn.env, callee.identifier) != null ||
|
||||
isUseOperator(callee.identifier)
|
||||
) {
|
||||
prune.push(...activeScopes.map((entry) => entry.block));
|
||||
prune.push(...activeScopes.map(entry => entry.block));
|
||||
activeScopes.length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (block.terminal.kind === "scope") {
|
||||
if (block.terminal.kind === 'scope') {
|
||||
activeScopes.push({
|
||||
block: block.id,
|
||||
scope: block.terminal.scope,
|
||||
@@ -74,7 +74,7 @@ export function flattenScopesWithHooksOrUseHIR(fn: HIRFunction): void {
|
||||
for (const id of prune) {
|
||||
const block = fn.body.blocks.get(id)!;
|
||||
const terminal = block.terminal;
|
||||
CompilerError.invariant(terminal.kind === "scope", {
|
||||
CompilerError.invariant(terminal.kind === 'scope', {
|
||||
reason: `Expected block to have a scope terminal`,
|
||||
description: `Expected block bb${block.id} to end in a scope terminal`,
|
||||
loc: terminal.loc,
|
||||
@@ -82,7 +82,7 @@ export function flattenScopesWithHooksOrUseHIR(fn: HIRFunction): void {
|
||||
const body = fn.body.blocks.get(terminal.block)!;
|
||||
if (
|
||||
body.instructions.length === 1 &&
|
||||
body.terminal.kind === "goto" &&
|
||||
body.terminal.kind === 'goto' &&
|
||||
body.terminal.block === terminal.fallthrough
|
||||
) {
|
||||
/*
|
||||
@@ -91,7 +91,7 @@ export function flattenScopesWithHooksOrUseHIR(fn: HIRFunction): void {
|
||||
* flattening
|
||||
*/
|
||||
block.terminal = {
|
||||
kind: "label",
|
||||
kind: 'label',
|
||||
block: terminal.block,
|
||||
fallthrough: terminal.fallthrough,
|
||||
id: terminal.id,
|
||||
@@ -101,7 +101,7 @@ export function flattenScopesWithHooksOrUseHIR(fn: HIRFunction): void {
|
||||
}
|
||||
|
||||
block.terminal = {
|
||||
kind: "pruned-scope",
|
||||
kind: 'pruned-scope',
|
||||
block: terminal.block,
|
||||
fallthrough: terminal.fallthrough,
|
||||
id: terminal.id,
|
||||
|
||||
+63
-63
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError, SourceLocation } from "..";
|
||||
import { Environment } from "../HIR";
|
||||
import {CompilerError, SourceLocation} from '..';
|
||||
import {Environment} from '../HIR';
|
||||
import {
|
||||
GeneratedSource,
|
||||
HIRFunction,
|
||||
@@ -15,15 +15,15 @@ import {
|
||||
Place,
|
||||
ReactiveScope,
|
||||
makeInstructionId,
|
||||
} from "../HIR/HIR";
|
||||
} from '../HIR/HIR';
|
||||
import {
|
||||
doesPatternContainSpreadElement,
|
||||
eachInstructionOperand,
|
||||
eachPatternOperand,
|
||||
} from "../HIR/visitors";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
import { logHIRFunction } from "../Utils/logger";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
} from '../HIR/visitors';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
import {logHIRFunction} from '../Utils/logger';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
/*
|
||||
* Note: this is the 1st of 4 passes that determine how to break a function into discrete
|
||||
@@ -118,11 +118,11 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
|
||||
scope.range.start = identifier.mutableRange.start;
|
||||
} else if (identifier.mutableRange.start !== 0) {
|
||||
scope.range.start = makeInstructionId(
|
||||
Math.min(scope.range.start, identifier.mutableRange.start)
|
||||
Math.min(scope.range.start, identifier.mutableRange.start),
|
||||
);
|
||||
}
|
||||
scope.range.end = makeInstructionId(
|
||||
Math.max(scope.range.end, identifier.mutableRange.end)
|
||||
Math.max(scope.range.end, identifier.mutableRange.end),
|
||||
);
|
||||
scope.loc = mergeLocation(scope.loc, identifier.loc);
|
||||
}
|
||||
@@ -136,7 +136,7 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
|
||||
maxInstruction = makeInstructionId(Math.max(maxInstruction, instr.id));
|
||||
}
|
||||
maxInstruction = makeInstructionId(
|
||||
Math.max(maxInstruction, block.terminal.id)
|
||||
Math.max(maxInstruction, block.terminal.id),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
|
||||
scope.range.end > maxInstruction + 1
|
||||
) {
|
||||
// Make it easier to debug why the error occurred
|
||||
logHIRFunction("InferReactiveScopeVariables (invalid scope)", fn);
|
||||
logHIRFunction('InferReactiveScopeVariables (invalid scope)', fn);
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Invalid mutable range for scope`,
|
||||
loc: GeneratedSource,
|
||||
@@ -185,76 +185,76 @@ function mergeLocation(l: SourceLocation, r: SourceLocation): SourceLocation {
|
||||
}
|
||||
|
||||
// Is the operand mutable at this given instruction
|
||||
export function isMutable({ id }: Instruction, place: Place): boolean {
|
||||
export function isMutable({id}: Instruction, place: Place): boolean {
|
||||
const range = place.identifier.mutableRange;
|
||||
return id >= range.start && id < range.end;
|
||||
}
|
||||
|
||||
function mayAllocate(env: Environment, instruction: Instruction): boolean {
|
||||
const { value } = instruction;
|
||||
const {value} = instruction;
|
||||
switch (value.kind) {
|
||||
case "Destructure": {
|
||||
case 'Destructure': {
|
||||
return doesPatternContainSpreadElement(value.lvalue.pattern);
|
||||
}
|
||||
case "PostfixUpdate":
|
||||
case "PrefixUpdate":
|
||||
case "Await":
|
||||
case "DeclareLocal":
|
||||
case "DeclareContext":
|
||||
case "StoreLocal":
|
||||
case "LoadGlobal":
|
||||
case "MetaProperty":
|
||||
case "TypeCastExpression":
|
||||
case "LoadLocal":
|
||||
case "LoadContext":
|
||||
case "StoreContext":
|
||||
case "PropertyDelete":
|
||||
case "ComputedLoad":
|
||||
case "ComputedDelete":
|
||||
case "JSXText":
|
||||
case "TemplateLiteral":
|
||||
case "Primitive":
|
||||
case "GetIterator":
|
||||
case "IteratorNext":
|
||||
case "NextPropertyOf":
|
||||
case "Debugger":
|
||||
case "StartMemoize":
|
||||
case "FinishMemoize":
|
||||
case "UnaryExpression":
|
||||
case "BinaryExpression":
|
||||
case "PropertyLoad":
|
||||
case "StoreGlobal": {
|
||||
case 'PostfixUpdate':
|
||||
case 'PrefixUpdate':
|
||||
case 'Await':
|
||||
case 'DeclareLocal':
|
||||
case 'DeclareContext':
|
||||
case 'StoreLocal':
|
||||
case 'LoadGlobal':
|
||||
case 'MetaProperty':
|
||||
case 'TypeCastExpression':
|
||||
case 'LoadLocal':
|
||||
case 'LoadContext':
|
||||
case 'StoreContext':
|
||||
case 'PropertyDelete':
|
||||
case 'ComputedLoad':
|
||||
case 'ComputedDelete':
|
||||
case 'JSXText':
|
||||
case 'TemplateLiteral':
|
||||
case 'Primitive':
|
||||
case 'GetIterator':
|
||||
case 'IteratorNext':
|
||||
case 'NextPropertyOf':
|
||||
case 'Debugger':
|
||||
case 'StartMemoize':
|
||||
case 'FinishMemoize':
|
||||
case 'UnaryExpression':
|
||||
case 'BinaryExpression':
|
||||
case 'PropertyLoad':
|
||||
case 'StoreGlobal': {
|
||||
return false;
|
||||
}
|
||||
case "CallExpression":
|
||||
case "MethodCall": {
|
||||
return instruction.lvalue.identifier.type.kind !== "Primitive";
|
||||
case 'CallExpression':
|
||||
case 'MethodCall': {
|
||||
return instruction.lvalue.identifier.type.kind !== 'Primitive';
|
||||
}
|
||||
case "RegExpLiteral":
|
||||
case "PropertyStore":
|
||||
case "ComputedStore":
|
||||
case "ArrayExpression":
|
||||
case "JsxExpression":
|
||||
case "JsxFragment":
|
||||
case "NewExpression":
|
||||
case "ObjectExpression":
|
||||
case "UnsupportedNode":
|
||||
case "ObjectMethod":
|
||||
case "FunctionExpression":
|
||||
case "TaggedTemplateExpression": {
|
||||
case 'RegExpLiteral':
|
||||
case 'PropertyStore':
|
||||
case 'ComputedStore':
|
||||
case 'ArrayExpression':
|
||||
case 'JsxExpression':
|
||||
case 'JsxFragment':
|
||||
case 'NewExpression':
|
||||
case 'ObjectExpression':
|
||||
case 'UnsupportedNode':
|
||||
case 'ObjectMethod':
|
||||
case 'FunctionExpression':
|
||||
case 'TaggedTemplateExpression': {
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
value,
|
||||
`Unexpected value kind \`${(value as any).kind}\``
|
||||
`Unexpected value kind \`${(value as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function findDisjointMutableValues(
|
||||
fn: HIRFunction
|
||||
fn: HIRFunction,
|
||||
): DisjointSet<Identifier> {
|
||||
const scopeIdentifiers = new DisjointSet<Identifier>();
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
@@ -286,8 +286,8 @@ export function findDisjointMutableValues(
|
||||
operands.push(instr.lvalue!.identifier);
|
||||
}
|
||||
if (
|
||||
instr.value.kind === "StoreLocal" ||
|
||||
instr.value.kind === "StoreContext"
|
||||
instr.value.kind === 'StoreLocal' ||
|
||||
instr.value.kind === 'StoreContext'
|
||||
) {
|
||||
if (
|
||||
instr.value.lvalue.place.identifier.mutableRange.end >
|
||||
@@ -301,7 +301,7 @@ export function findDisjointMutableValues(
|
||||
) {
|
||||
operands.push(instr.value.value.identifier);
|
||||
}
|
||||
} else if (instr.value.kind === "Destructure") {
|
||||
} else if (instr.value.kind === 'Destructure') {
|
||||
for (const place of eachPatternOperand(instr.value.lvalue.pattern)) {
|
||||
if (
|
||||
place.identifier.mutableRange.end >
|
||||
@@ -316,7 +316,7 @@ export function findDisjointMutableValues(
|
||||
) {
|
||||
operands.push(instr.value.value.identifier);
|
||||
}
|
||||
} else if (instr.value.kind === "MethodCall") {
|
||||
} else if (instr.value.kind === 'MethodCall') {
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
if (
|
||||
isMutable(instr, operand) &&
|
||||
|
||||
+29
-29
@@ -11,8 +11,8 @@ import {
|
||||
makeInstructionId,
|
||||
Place,
|
||||
ReactiveValue,
|
||||
} from "../HIR";
|
||||
import { eachReactiveValueOperand } from "./visitors";
|
||||
} from '../HIR';
|
||||
import {eachReactiveValueOperand} from './visitors';
|
||||
|
||||
/**
|
||||
* This pass supports the `fbt` translation system (https://facebook.github.io/fbt/)
|
||||
@@ -40,7 +40,7 @@ import { eachReactiveValueOperand } from "./visitors";
|
||||
* `customMacros` environment configuration.
|
||||
*/
|
||||
export function memoizeFbtAndMacroOperandsInSameScope(
|
||||
fn: HIRFunction
|
||||
fn: HIRFunction,
|
||||
): Set<IdentifierId> {
|
||||
const fbtMacroTags = new Set([
|
||||
...FBT_TAGS,
|
||||
@@ -58,30 +58,30 @@ export function memoizeFbtAndMacroOperandsInSameScope(
|
||||
}
|
||||
|
||||
export const FBT_TAGS: Set<string> = new Set([
|
||||
"fbt",
|
||||
"fbt:param",
|
||||
"fbs",
|
||||
"fbs:param",
|
||||
'fbt',
|
||||
'fbt:param',
|
||||
'fbs',
|
||||
'fbs:param',
|
||||
]);
|
||||
export const SINGLE_CHILD_FBT_TAGS: Set<string> = new Set([
|
||||
"fbt:param",
|
||||
"fbs:param",
|
||||
'fbt:param',
|
||||
'fbs:param',
|
||||
]);
|
||||
|
||||
function visit(
|
||||
fn: HIRFunction,
|
||||
fbtMacroTags: Set<string>,
|
||||
fbtValues: Set<IdentifierId>
|
||||
fbtValues: Set<IdentifierId>,
|
||||
): void {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instruction of block.instructions) {
|
||||
const { lvalue, value } = instruction;
|
||||
const {lvalue, value} = instruction;
|
||||
if (lvalue === null) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
value.kind === "Primitive" &&
|
||||
typeof value.value === "string" &&
|
||||
value.kind === 'Primitive' &&
|
||||
typeof value.value === 'string' &&
|
||||
fbtMacroTags.has(value.value)
|
||||
) {
|
||||
/*
|
||||
@@ -90,7 +90,7 @@ function visit(
|
||||
*/
|
||||
fbtValues.add(lvalue.identifier.id);
|
||||
} else if (
|
||||
value.kind === "LoadGlobal" &&
|
||||
value.kind === 'LoadGlobal' &&
|
||||
fbtMacroTags.has(value.binding.name)
|
||||
) {
|
||||
// Record references to `fbt` as a global
|
||||
@@ -113,8 +113,8 @@ function visit(
|
||||
fbtScope.range.start = makeInstructionId(
|
||||
Math.min(
|
||||
fbtScope.range.start,
|
||||
operand.identifier.mutableRange.start
|
||||
)
|
||||
operand.identifier.mutableRange.start,
|
||||
),
|
||||
);
|
||||
fbtValues.add(operand.identifier.id);
|
||||
}
|
||||
@@ -139,8 +139,8 @@ function visit(
|
||||
fbtScope.range.start = makeInstructionId(
|
||||
Math.min(
|
||||
fbtScope.range.start,
|
||||
operand.identifier.mutableRange.start
|
||||
)
|
||||
operand.identifier.mutableRange.start,
|
||||
),
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -158,7 +158,7 @@ function visit(
|
||||
for (const operand of eachReactiveValueOperand(value)) {
|
||||
if (
|
||||
operand.identifier.name !== null &&
|
||||
operand.identifier.name.kind === "named"
|
||||
operand.identifier.name.kind === 'named'
|
||||
) {
|
||||
/*
|
||||
* named identifiers were already locals, we only have to force temporaries
|
||||
@@ -172,8 +172,8 @@ function visit(
|
||||
fbtScope.range.start = makeInstructionId(
|
||||
Math.min(
|
||||
fbtScope.range.start,
|
||||
operand.identifier.mutableRange.start
|
||||
)
|
||||
operand.identifier.mutableRange.start,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -183,33 +183,33 @@ function visit(
|
||||
|
||||
function isFbtCallExpression(
|
||||
fbtValues: Set<IdentifierId>,
|
||||
value: ReactiveValue
|
||||
value: ReactiveValue,
|
||||
): boolean {
|
||||
return (
|
||||
value.kind === "CallExpression" && fbtValues.has(value.callee.identifier.id)
|
||||
value.kind === 'CallExpression' && fbtValues.has(value.callee.identifier.id)
|
||||
);
|
||||
}
|
||||
|
||||
function isFbtJsxExpression(
|
||||
fbtMacroTags: Set<string>,
|
||||
fbtValues: Set<IdentifierId>,
|
||||
value: ReactiveValue
|
||||
value: ReactiveValue,
|
||||
): boolean {
|
||||
return (
|
||||
value.kind === "JsxExpression" &&
|
||||
((value.tag.kind === "Identifier" &&
|
||||
value.kind === 'JsxExpression' &&
|
||||
((value.tag.kind === 'Identifier' &&
|
||||
fbtValues.has(value.tag.identifier.id)) ||
|
||||
(value.tag.kind === "BuiltinTag" && fbtMacroTags.has(value.tag.name)))
|
||||
(value.tag.kind === 'BuiltinTag' && fbtMacroTags.has(value.tag.name)))
|
||||
);
|
||||
}
|
||||
|
||||
function isFbtJsxChild(
|
||||
fbtValues: Set<IdentifierId>,
|
||||
lvalue: Place | null,
|
||||
value: ReactiveValue
|
||||
value: ReactiveValue,
|
||||
): boolean {
|
||||
return (
|
||||
(value.kind === "JsxExpression" || value.kind === "JsxFragment") &&
|
||||
(value.kind === 'JsxExpression' || value.kind === 'JsxFragment') &&
|
||||
lvalue !== null &&
|
||||
fbtValues.has(lvalue.identifier.id)
|
||||
);
|
||||
|
||||
+17
-17
@@ -14,11 +14,11 @@ import {
|
||||
ReactiveInstruction,
|
||||
ReactiveScope,
|
||||
ScopeId,
|
||||
} from "../HIR";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
import { retainWhere } from "../Utils/utils";
|
||||
import { getPlaceScope } from "./BuildReactiveBlocks";
|
||||
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
} from '../HIR';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
import {retainWhere} from '../Utils/utils';
|
||||
import {getPlaceScope} from './BuildReactiveBlocks';
|
||||
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
|
||||
/*
|
||||
* Note: this is the 3rd of 4 passes that determine how to break a function into discrete
|
||||
@@ -120,12 +120,12 @@ class Visitor extends ReactiveFunctionVisitor<Context> {
|
||||
}
|
||||
override visitInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
state: Context
|
||||
state: Context,
|
||||
): void {
|
||||
if (
|
||||
instruction.value.kind === "ConditionalExpression" ||
|
||||
instruction.value.kind === "LogicalExpression" ||
|
||||
instruction.value.kind === "OptionalExpression"
|
||||
instruction.value.kind === 'ConditionalExpression' ||
|
||||
instruction.value.kind === 'LogicalExpression' ||
|
||||
instruction.value.kind === 'OptionalExpression'
|
||||
) {
|
||||
state.enter(() => {
|
||||
super.visitInstruction(instruction, state);
|
||||
@@ -154,7 +154,7 @@ class Context {
|
||||
|
||||
visitId(id: InstructionId): void {
|
||||
const currentBlock = this.scopes[this.scopes.length - 1]!;
|
||||
retainWhere(currentBlock.scopes, (pending) => {
|
||||
retainWhere(currentBlock.scopes, pending => {
|
||||
if (pending.scope.range.end > id) {
|
||||
return true;
|
||||
} else {
|
||||
@@ -175,7 +175,7 @@ class Context {
|
||||
if (!this.seenScopes.has(scope.id)) {
|
||||
this.seenScopes.add(scope.id);
|
||||
currentBlock.seen.add(scope.id);
|
||||
currentBlock.scopes.push({ shadowedBy: null, scope });
|
||||
currentBlock.scopes.push({shadowedBy: null, scope});
|
||||
return;
|
||||
}
|
||||
// Scope has already been seen, find it in the current block or a parent
|
||||
@@ -186,7 +186,7 @@ class Context {
|
||||
* scopes that cross control-flow boundaries are merged with overlapping
|
||||
* scopes
|
||||
*/
|
||||
this.joinedScopes.union([scope, ...nextBlock.scopes.map((s) => s.scope)]);
|
||||
this.joinedScopes.union([scope, ...nextBlock.scopes.map(s => s.scope)]);
|
||||
index--;
|
||||
if (index < 0) {
|
||||
/*
|
||||
@@ -213,7 +213,7 @@ class Context {
|
||||
* }
|
||||
*/
|
||||
currentBlock.seen.add(scope.id);
|
||||
currentBlock.scopes.push({ shadowedBy: null, scope });
|
||||
currentBlock.scopes.push({shadowedBy: null, scope});
|
||||
return;
|
||||
}
|
||||
nextBlock = this.scopes[index]!;
|
||||
@@ -240,7 +240,7 @@ class Context {
|
||||
* a scope relative to its eventual post-merge mutable range
|
||||
*/
|
||||
const end = makeInstructionId(
|
||||
Math.max(current.scope.range.end, scope.range.end)
|
||||
Math.max(current.scope.range.end, scope.range.end),
|
||||
);
|
||||
current.scope.range.end = end;
|
||||
scope.range.end = end;
|
||||
@@ -250,7 +250,7 @@ class Context {
|
||||
}
|
||||
if (!currentBlock.seen.has(scope.id)) {
|
||||
currentBlock.seen.add(scope.id);
|
||||
currentBlock.scopes.push({ shadowedBy: null, scope });
|
||||
currentBlock.scopes.push({shadowedBy: null, scope});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,10 +264,10 @@ class Context {
|
||||
this.joinedScopes.forEach((scope, groupScope) => {
|
||||
if (scope !== groupScope) {
|
||||
groupScope.range.start = makeInstructionId(
|
||||
Math.min(groupScope.range.start, scope.range.start)
|
||||
Math.min(groupScope.range.start, scope.range.start),
|
||||
);
|
||||
groupScope.range.end = makeInstructionId(
|
||||
Math.max(groupScope.range.end, scope.range.end)
|
||||
Math.max(groupScope.range.end, scope.range.end),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
+54
-54
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "..";
|
||||
import {CompilerError} from '..';
|
||||
import {
|
||||
IdentifierId,
|
||||
InstructionId,
|
||||
@@ -20,22 +20,22 @@ import {
|
||||
ReactiveStatement,
|
||||
Type,
|
||||
makeInstructionId,
|
||||
} from "../HIR";
|
||||
} from '../HIR';
|
||||
import {
|
||||
BuiltInArrayId,
|
||||
BuiltInFunctionId,
|
||||
BuiltInJsxId,
|
||||
BuiltInObjectId,
|
||||
} from "../HIR/ObjectShape";
|
||||
import { eachInstructionLValue } from "../HIR/visitors";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
import { printReactiveScopeSummary } from "./PrintReactiveFunction";
|
||||
} from '../HIR/ObjectShape';
|
||||
import {eachInstructionLValue} from '../HIR/visitors';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {printReactiveScopeSummary} from './PrintReactiveFunction';
|
||||
import {
|
||||
ReactiveFunctionTransform,
|
||||
ReactiveFunctionVisitor,
|
||||
Transformed,
|
||||
visitReactiveFunction,
|
||||
} from "./visitors";
|
||||
} from './visitors';
|
||||
|
||||
/*
|
||||
* The primary goal of this pass is to reduce memoization overhead, specifically:
|
||||
@@ -82,7 +82,7 @@ import {
|
||||
* better to flatten.
|
||||
*/
|
||||
export function mergeReactiveScopesThatInvalidateTogether(
|
||||
fn: ReactiveFunction
|
||||
fn: ReactiveFunction,
|
||||
): void {
|
||||
const lastUsageVisitor = new FindLastUsageVisitor();
|
||||
visitReactiveFunction(fn, lastUsageVisitor, undefined);
|
||||
@@ -119,22 +119,22 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
|
||||
override transformScope(
|
||||
scopeBlock: ReactiveScopeBlock,
|
||||
state: ReactiveScopeDependencies | null
|
||||
state: ReactiveScopeDependencies | null,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitScope(scopeBlock, scopeBlock.scope.dependencies);
|
||||
if (
|
||||
state !== null &&
|
||||
areEqualDependencies(state, scopeBlock.scope.dependencies)
|
||||
) {
|
||||
return { kind: "replace-many", value: scopeBlock.instructions };
|
||||
return {kind: 'replace-many', value: scopeBlock.instructions};
|
||||
} else {
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
}
|
||||
|
||||
override visitBlock(
|
||||
block: ReactiveBlock,
|
||||
state: ReactiveScopeDependencies | null
|
||||
state: ReactiveScopeDependencies | null,
|
||||
): void {
|
||||
// Pass 1: visit nested blocks to potentially merge their scopes
|
||||
this.traverseBlock(block, state);
|
||||
@@ -152,7 +152,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
CompilerError.invariant(current !== null, {
|
||||
loc: null,
|
||||
reason:
|
||||
"MergeConsecutiveScopes: expected current scope to be non-null if reset()",
|
||||
'MergeConsecutiveScopes: expected current scope to be non-null if reset()',
|
||||
suggestions: null,
|
||||
description: null,
|
||||
});
|
||||
@@ -164,37 +164,37 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
for (let i = 0; i < block.length; i++) {
|
||||
const instr = block[i]!;
|
||||
switch (instr.kind) {
|
||||
case "terminal": {
|
||||
case 'terminal': {
|
||||
// For now we don't merge across terminals
|
||||
if (current !== null) {
|
||||
log(
|
||||
`Reset scope @${current.block.scope.id} from terminal [${instr.terminal.id}]`
|
||||
`Reset scope @${current.block.scope.id} from terminal [${instr.terminal.id}]`,
|
||||
);
|
||||
reset();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "pruned-scope": {
|
||||
case 'pruned-scope': {
|
||||
// For now we don't merge across pruned scopes
|
||||
if (current !== null) {
|
||||
log(
|
||||
`Reset scope @${current.block.scope.id} from pruned scope @${instr.scope.id}`
|
||||
`Reset scope @${current.block.scope.id} from pruned scope @${instr.scope.id}`,
|
||||
);
|
||||
reset();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "instruction": {
|
||||
case 'instruction': {
|
||||
switch (instr.instruction.value.kind) {
|
||||
case "BinaryExpression":
|
||||
case "ComputedLoad":
|
||||
case "JSXText":
|
||||
case "LoadGlobal":
|
||||
case "LoadLocal":
|
||||
case "Primitive":
|
||||
case "PropertyLoad":
|
||||
case "TemplateLiteral":
|
||||
case "UnaryExpression": {
|
||||
case 'BinaryExpression':
|
||||
case 'ComputedLoad':
|
||||
case 'JSXText':
|
||||
case 'LoadGlobal':
|
||||
case 'LoadLocal':
|
||||
case 'Primitive':
|
||||
case 'PropertyLoad':
|
||||
case 'TemplateLiteral':
|
||||
case 'UnaryExpression': {
|
||||
/*
|
||||
* We can merge two scopes if there are intervening instructions, but:
|
||||
* - Only if the instructions are simple and it's okay to make them
|
||||
@@ -208,7 +208,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "StoreLocal": {
|
||||
case 'StoreLocal': {
|
||||
/**
|
||||
* It's safe to have intervening StoreLocal instructions _if_ they are const
|
||||
* and the last usage of the variable is at or before the next scope. This is
|
||||
@@ -222,13 +222,13 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
instr.instruction.value.lvalue.kind === InstructionKind.Const
|
||||
) {
|
||||
for (const lvalue of eachInstructionLValue(
|
||||
instr.instruction
|
||||
instr.instruction,
|
||||
)) {
|
||||
current.lvalues.add(lvalue.identifier.id);
|
||||
}
|
||||
} else {
|
||||
log(
|
||||
`Reset scope @${current.block.scope.id} from StoreLocal in [${instr.instruction.id}]`
|
||||
`Reset scope @${current.block.scope.id} from StoreLocal in [${instr.instruction.id}]`,
|
||||
);
|
||||
reset();
|
||||
}
|
||||
@@ -239,7 +239,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
// Other instructions are known to prevent merging, so we reset the scope if present
|
||||
if (current !== null) {
|
||||
log(
|
||||
`Reset scope @${current.block.scope.id} from instruction [${instr.instruction.id}]`
|
||||
`Reset scope @${current.block.scope.id} from instruction [${instr.instruction.id}]`,
|
||||
);
|
||||
reset();
|
||||
}
|
||||
@@ -247,23 +247,23 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "scope": {
|
||||
case 'scope': {
|
||||
if (
|
||||
current !== null &&
|
||||
canMergeScopes(current.block, instr) &&
|
||||
areLValuesLastUsedByScope(
|
||||
instr.scope,
|
||||
current.lvalues,
|
||||
this.lastUsage
|
||||
this.lastUsage,
|
||||
)
|
||||
) {
|
||||
// The current and next scopes can merge!
|
||||
log(
|
||||
`Can merge scope @${current.block.scope.id} with @${instr.scope.id}`
|
||||
`Can merge scope @${current.block.scope.id} with @${instr.scope.id}`,
|
||||
);
|
||||
// Update the merged scope's range
|
||||
current.block.scope.range.end = makeInstructionId(
|
||||
Math.max(current.block.scope.range.end, instr.scope.range.end)
|
||||
Math.max(current.block.scope.range.end, instr.scope.range.end),
|
||||
);
|
||||
// Add declarations
|
||||
for (const [key, value] of instr.scope.declarations) {
|
||||
@@ -287,7 +287,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
* inputs change, so it is not a candidate for future merging
|
||||
*/
|
||||
log(
|
||||
` but scope @${instr.scope.id} doesnt guaranteed invalidate so it cannot merge further`
|
||||
` but scope @${instr.scope.id} doesnt guaranteed invalidate so it cannot merge further`,
|
||||
);
|
||||
reset();
|
||||
}
|
||||
@@ -296,7 +296,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
if (current !== null) {
|
||||
// Reset if necessary
|
||||
log(
|
||||
`Reset scope @${current.block.scope.id}, not mergeable with subsequent scope @${instr.scope.id}`
|
||||
`Reset scope @${current.block.scope.id}, not mergeable with subsequent scope @${instr.scope.id}`,
|
||||
);
|
||||
reset();
|
||||
}
|
||||
@@ -310,7 +310,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
};
|
||||
} else {
|
||||
log(
|
||||
`scope @${instr.scope.id} doesnt guaranteed invalidate so it cannot merge further`
|
||||
`scope @${instr.scope.id} doesnt guaranteed invalidate so it cannot merge further`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -319,7 +319,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
default: {
|
||||
assertExhaustive(
|
||||
instr,
|
||||
`Unexpected instruction kind \`${(instr as any).kind}\``
|
||||
`Unexpected instruction kind \`${(instr as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -332,7 +332,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
for (const entry of merged) {
|
||||
log(
|
||||
printReactiveScopeSummary(entry.block.scope) +
|
||||
` from=${entry.from} to=${entry.to}`
|
||||
` from=${entry.from} to=${entry.to}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -350,10 +350,10 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
index = entry.from;
|
||||
}
|
||||
const mergedScope = block[entry.from]!;
|
||||
CompilerError.invariant(mergedScope.kind === "scope", {
|
||||
CompilerError.invariant(mergedScope.kind === 'scope', {
|
||||
loc: null,
|
||||
reason:
|
||||
"MergeConsecutiveScopes: Expected scope starting index to be a scope",
|
||||
'MergeConsecutiveScopes: Expected scope starting index to be a scope',
|
||||
description: null,
|
||||
suggestions: null,
|
||||
});
|
||||
@@ -361,7 +361,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
index++;
|
||||
while (index < entry.to) {
|
||||
const instr = block[index++]!;
|
||||
if (instr.kind === "scope") {
|
||||
if (instr.kind === 'scope') {
|
||||
mergedScope.instructions.push(...instr.instructions);
|
||||
mergedScope.scope.merged.add(instr.scope.id);
|
||||
} else {
|
||||
@@ -383,7 +383,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
*/
|
||||
function updateScopeDeclarations(
|
||||
scope: ReactiveScope,
|
||||
lastUsage: Map<IdentifierId, InstructionId>
|
||||
lastUsage: Map<IdentifierId, InstructionId>,
|
||||
): void {
|
||||
for (const [key] of scope.declarations) {
|
||||
const lastUsedAt = lastUsage.get(key)!;
|
||||
@@ -401,7 +401,7 @@ function updateScopeDeclarations(
|
||||
function areLValuesLastUsedByScope(
|
||||
scope: ReactiveScope,
|
||||
lvalues: Set<IdentifierId>,
|
||||
lastUsage: Map<IdentifierId, InstructionId>
|
||||
lastUsage: Map<IdentifierId, InstructionId>,
|
||||
): boolean {
|
||||
for (const lvalue of lvalues) {
|
||||
const lastUsedAt = lastUsage.get(lvalue)!;
|
||||
@@ -415,7 +415,7 @@ function areLValuesLastUsedByScope(
|
||||
|
||||
function canMergeScopes(
|
||||
current: ReactiveScopeBlock,
|
||||
next: ReactiveScopeBlock
|
||||
next: ReactiveScopeBlock,
|
||||
): boolean {
|
||||
// Don't merge scopes with reassignments
|
||||
if (
|
||||
@@ -444,18 +444,18 @@ function canMergeScopes(
|
||||
if (
|
||||
areEqualDependencies(
|
||||
new Set(
|
||||
[...current.scope.declarations.values()].map((declaration) => ({
|
||||
[...current.scope.declarations.values()].map(declaration => ({
|
||||
identifier: declaration.identifier,
|
||||
path: [],
|
||||
}))
|
||||
})),
|
||||
),
|
||||
next.scope.dependencies
|
||||
next.scope.dependencies,
|
||||
) ||
|
||||
(next.scope.dependencies.size !== 0 &&
|
||||
[...next.scope.dependencies].every(
|
||||
(dep) =>
|
||||
dep =>
|
||||
current.scope.declarations.has(dep.identifier.id) &&
|
||||
isAlwaysInvalidatingType(dep.identifier.type)
|
||||
isAlwaysInvalidatingType(dep.identifier.type),
|
||||
))
|
||||
) {
|
||||
log(` outputs of prev are input to current`);
|
||||
@@ -468,7 +468,7 @@ function canMergeScopes(
|
||||
}
|
||||
|
||||
function isAlwaysInvalidatingType(type: Type): boolean {
|
||||
if (type.kind === "Object") {
|
||||
if (type.kind === 'Object') {
|
||||
switch (type.shapeId) {
|
||||
case BuiltInArrayId:
|
||||
case BuiltInObjectId:
|
||||
@@ -483,7 +483,7 @@ function isAlwaysInvalidatingType(type: Type): boolean {
|
||||
|
||||
function areEqualDependencies(
|
||||
a: Set<ReactiveScopeDependency>,
|
||||
b: Set<ReactiveScopeDependency>
|
||||
b: Set<ReactiveScopeDependency>,
|
||||
): boolean {
|
||||
if (a.size !== b.size) {
|
||||
return false;
|
||||
@@ -529,6 +529,6 @@ function scopeIsEligibleForMerging(scopeBlock: ReactiveScopeBlock): boolean {
|
||||
return true;
|
||||
}
|
||||
return [...scopeBlock.scope.declarations].some(([, decl]) =>
|
||||
isAlwaysInvalidatingType(decl.identifier.type)
|
||||
isAlwaysInvalidatingType(decl.identifier.type),
|
||||
);
|
||||
}
|
||||
|
||||
+87
-87
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {
|
||||
PrunedReactiveScopeBlock,
|
||||
ReactiveFunction,
|
||||
@@ -15,23 +15,23 @@ import {
|
||||
ReactiveStatement,
|
||||
ReactiveTerminal,
|
||||
ReactiveValue,
|
||||
} from "../HIR/HIR";
|
||||
} from '../HIR/HIR';
|
||||
import {
|
||||
printFunction,
|
||||
printIdentifier,
|
||||
printInstructionValue,
|
||||
printPlace,
|
||||
printType,
|
||||
} from "../HIR/PrintHIR";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
} from '../HIR/PrintHIR';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
export function printReactiveFunctionWithOutlined(
|
||||
fn: ReactiveFunction
|
||||
fn: ReactiveFunction,
|
||||
): string {
|
||||
const writer = new Writer();
|
||||
writeReactiveFunction(fn, writer);
|
||||
for (const outlined of fn.env.getOutlinedFunctions()) {
|
||||
writer.writeLine("\nfunction " + printFunction(outlined.fn));
|
||||
writer.writeLine('\nfunction ' + printFunction(outlined.fn));
|
||||
}
|
||||
return writer.complete();
|
||||
}
|
||||
@@ -43,81 +43,81 @@ export function printReactiveFunction(fn: ReactiveFunction): string {
|
||||
}
|
||||
|
||||
function writeReactiveFunction(fn: ReactiveFunction, writer: Writer): void {
|
||||
writer.writeLine(`function ${fn.id !== null ? fn.id : "<unknown>"}(`);
|
||||
writer.writeLine(`function ${fn.id !== null ? fn.id : '<unknown>'}(`);
|
||||
writer.indented(() => {
|
||||
for (const param of fn.params) {
|
||||
if (param.kind === "Identifier") {
|
||||
if (param.kind === 'Identifier') {
|
||||
writer.writeLine(`${printPlace(param)},`);
|
||||
} else {
|
||||
writer.writeLine(`...${printPlace(param.place)},`);
|
||||
}
|
||||
}
|
||||
});
|
||||
writer.writeLine(") {");
|
||||
writer.writeLine(') {');
|
||||
writeReactiveInstructions(writer, fn.body);
|
||||
writer.writeLine("}");
|
||||
writer.writeLine('}');
|
||||
}
|
||||
|
||||
export function printReactiveScopeSummary(scope: ReactiveScope): string {
|
||||
const items = [];
|
||||
// If the scope has a return value it needs a label
|
||||
items.push("scope");
|
||||
items.push('scope');
|
||||
items.push(`@${scope.id}`);
|
||||
items.push(`[${scope.range.start}:${scope.range.end}]`);
|
||||
items.push(
|
||||
`dependencies=[${Array.from(scope.dependencies)
|
||||
.map((dep) => printDependency(dep))
|
||||
.join(", ")}]`
|
||||
.map(dep => printDependency(dep))
|
||||
.join(', ')}]`,
|
||||
);
|
||||
items.push(
|
||||
`declarations=[${Array.from(scope.declarations)
|
||||
.map(([, decl]) =>
|
||||
printIdentifier({ ...decl.identifier, scope: decl.scope })
|
||||
printIdentifier({...decl.identifier, scope: decl.scope}),
|
||||
)
|
||||
.join(", ")}]`
|
||||
.join(', ')}]`,
|
||||
);
|
||||
items.push(
|
||||
`reassignments=[${Array.from(scope.reassignments).map((reassign) =>
|
||||
printIdentifier(reassign)
|
||||
)}]`
|
||||
`reassignments=[${Array.from(scope.reassignments).map(reassign =>
|
||||
printIdentifier(reassign),
|
||||
)}]`,
|
||||
);
|
||||
if (scope.earlyReturnValue !== null) {
|
||||
items.push(
|
||||
`earlyReturn={id: ${printIdentifier(
|
||||
scope.earlyReturnValue.value
|
||||
)}, label: ${scope.earlyReturnValue.label}}}`
|
||||
scope.earlyReturnValue.value,
|
||||
)}, label: ${scope.earlyReturnValue.label}}}`,
|
||||
);
|
||||
}
|
||||
return items.join(" ");
|
||||
return items.join(' ');
|
||||
}
|
||||
|
||||
export function writeReactiveBlock(
|
||||
writer: Writer,
|
||||
block: ReactiveScopeBlock
|
||||
block: ReactiveScopeBlock,
|
||||
): void {
|
||||
writer.writeLine(`${printReactiveScopeSummary(block.scope)} {`);
|
||||
writeReactiveInstructions(writer, block.instructions);
|
||||
writer.writeLine("}");
|
||||
writer.writeLine('}');
|
||||
}
|
||||
|
||||
export function writePrunedScope(
|
||||
writer: Writer,
|
||||
block: PrunedReactiveScopeBlock
|
||||
block: PrunedReactiveScopeBlock,
|
||||
): void {
|
||||
writer.writeLine(`<pruned> ${printReactiveScopeSummary(block.scope)} {`);
|
||||
writeReactiveInstructions(writer, block.instructions);
|
||||
writer.writeLine("}");
|
||||
writer.writeLine('}');
|
||||
}
|
||||
|
||||
export function printDependency(dependency: ReactiveScopeDependency): string {
|
||||
const identifier =
|
||||
printIdentifier(dependency.identifier) +
|
||||
printType(dependency.identifier.type);
|
||||
return `${identifier}${dependency.path.map((prop) => `.${prop}`).join("")}`;
|
||||
return `${identifier}${dependency.path.map(prop => `.${prop}`).join('')}`;
|
||||
}
|
||||
|
||||
export function printReactiveInstructions(
|
||||
instructions: Array<ReactiveStatement>
|
||||
instructions: Array<ReactiveStatement>,
|
||||
): string {
|
||||
const writer = new Writer();
|
||||
writeReactiveInstructions(writer, instructions);
|
||||
@@ -126,7 +126,7 @@ export function printReactiveInstructions(
|
||||
|
||||
export function writeReactiveInstructions(
|
||||
writer: Writer,
|
||||
instructions: Array<ReactiveStatement>
|
||||
instructions: Array<ReactiveStatement>,
|
||||
): void {
|
||||
writer.indented(() => {
|
||||
for (const instr of instructions) {
|
||||
@@ -137,11 +137,11 @@ export function writeReactiveInstructions(
|
||||
|
||||
function writeReactiveInstruction(
|
||||
writer: Writer,
|
||||
instr: ReactiveStatement
|
||||
instr: ReactiveStatement,
|
||||
): void {
|
||||
switch (instr.kind) {
|
||||
case "instruction": {
|
||||
const { instruction } = instr;
|
||||
case 'instruction': {
|
||||
const {instruction} = instr;
|
||||
const id = `[${instruction.id}]`;
|
||||
|
||||
if (instruction.lvalue !== null) {
|
||||
@@ -155,15 +155,15 @@ function writeReactiveInstruction(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "scope": {
|
||||
case 'scope': {
|
||||
writeReactiveBlock(writer, instr);
|
||||
break;
|
||||
}
|
||||
case "pruned-scope": {
|
||||
case 'pruned-scope': {
|
||||
writePrunedScope(writer, instr);
|
||||
break;
|
||||
}
|
||||
case "terminal": {
|
||||
case 'terminal': {
|
||||
if (instr.label !== null) {
|
||||
writer.write(`bb${instr.label.id}: `);
|
||||
}
|
||||
@@ -173,7 +173,7 @@ function writeReactiveInstruction(
|
||||
default: {
|
||||
assertExhaustive(
|
||||
instr,
|
||||
`Unexpected terminal kind \`${(instr as any).kind}\``
|
||||
`Unexpected terminal kind \`${(instr as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -187,7 +187,7 @@ export function printReactiveValue(value: ReactiveValue): string {
|
||||
|
||||
function writeReactiveValue(writer: Writer, value: ReactiveValue): void {
|
||||
switch (value.kind) {
|
||||
case "ConditionalExpression": {
|
||||
case 'ConditionalExpression': {
|
||||
writer.writeLine(`Ternary `);
|
||||
writer.indented(() => {
|
||||
writeReactiveValue(writer, value.test);
|
||||
@@ -203,7 +203,7 @@ function writeReactiveValue(writer: Writer, value: ReactiveValue): void {
|
||||
writer.newline();
|
||||
break;
|
||||
}
|
||||
case "LogicalExpression": {
|
||||
case 'LogicalExpression': {
|
||||
writer.writeLine(`Logical`);
|
||||
writer.indented(() => {
|
||||
writeReactiveValue(writer, value.left);
|
||||
@@ -213,15 +213,15 @@ function writeReactiveValue(writer: Writer, value: ReactiveValue): void {
|
||||
writer.newline();
|
||||
break;
|
||||
}
|
||||
case "SequenceExpression": {
|
||||
case 'SequenceExpression': {
|
||||
writer.writeLine(`Sequence`);
|
||||
writer.indented(() => {
|
||||
writer.indented(() => {
|
||||
value.instructions.forEach((instr) =>
|
||||
value.instructions.forEach(instr =>
|
||||
writeReactiveInstruction(writer, {
|
||||
kind: "instruction",
|
||||
kind: 'instruction',
|
||||
instruction: instr,
|
||||
})
|
||||
}),
|
||||
);
|
||||
writer.write(`[${value.id}] `);
|
||||
writeReactiveValue(writer, value.value);
|
||||
@@ -230,7 +230,7 @@ function writeReactiveValue(writer: Writer, value: ReactiveValue): void {
|
||||
writer.newline();
|
||||
break;
|
||||
}
|
||||
case "OptionalExpression": {
|
||||
case 'OptionalExpression': {
|
||||
writer.append(`OptionalExpression optional=${value.optional}`);
|
||||
writer.newline();
|
||||
writer.indented(() => {
|
||||
@@ -241,7 +241,7 @@ function writeReactiveValue(writer: Writer, value: ReactiveValue): void {
|
||||
}
|
||||
default: {
|
||||
const printed = printInstructionValue(value);
|
||||
const lines = printed.split("\n");
|
||||
const lines = printed.split('\n');
|
||||
if (lines.length === 1) {
|
||||
writer.writeLine(printed);
|
||||
} else {
|
||||
@@ -257,130 +257,130 @@ function writeReactiveValue(writer: Writer, value: ReactiveValue): void {
|
||||
|
||||
function writeTerminal(writer: Writer, terminal: ReactiveTerminal): void {
|
||||
switch (terminal.kind) {
|
||||
case "break": {
|
||||
case 'break': {
|
||||
const id = terminal.id !== null ? `[${terminal.id}]` : [];
|
||||
writer.writeLine(
|
||||
`${id} break bb${terminal.target} (${terminal.targetKind})`
|
||||
`${id} break bb${terminal.target} (${terminal.targetKind})`,
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
case "continue": {
|
||||
case 'continue': {
|
||||
const id = `[${terminal.id}]`;
|
||||
writer.writeLine(
|
||||
`${id} continue bb${terminal.target} (${terminal.targetKind})`
|
||||
`${id} continue bb${terminal.target} (${terminal.targetKind})`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "do-while": {
|
||||
case 'do-while': {
|
||||
writer.writeLine(`[${terminal.id}] do-while {`);
|
||||
writeReactiveInstructions(writer, terminal.loop);
|
||||
writer.writeLine("} (");
|
||||
writer.writeLine('} (');
|
||||
writer.indented(() => {
|
||||
writeReactiveValue(writer, terminal.test);
|
||||
});
|
||||
writer.writeLine(")");
|
||||
writer.writeLine(')');
|
||||
break;
|
||||
}
|
||||
case "while": {
|
||||
case 'while': {
|
||||
writer.writeLine(`[${terminal.id}] while (`);
|
||||
writer.indented(() => {
|
||||
writeReactiveValue(writer, terminal.test);
|
||||
});
|
||||
writer.writeLine(") {");
|
||||
writer.writeLine(') {');
|
||||
writeReactiveInstructions(writer, terminal.loop);
|
||||
writer.writeLine("}");
|
||||
writer.writeLine('}');
|
||||
break;
|
||||
}
|
||||
case "if": {
|
||||
const { test, consequent, alternate } = terminal;
|
||||
case 'if': {
|
||||
const {test, consequent, alternate} = terminal;
|
||||
writer.writeLine(`[${terminal.id}] if (${printPlace(test)}) {`);
|
||||
writeReactiveInstructions(writer, consequent);
|
||||
if (alternate !== null) {
|
||||
writer.writeLine("} else {");
|
||||
writer.writeLine('} else {');
|
||||
writeReactiveInstructions(writer, alternate);
|
||||
}
|
||||
writer.writeLine("}");
|
||||
writer.writeLine('}');
|
||||
break;
|
||||
}
|
||||
case "switch": {
|
||||
case 'switch': {
|
||||
writer.writeLine(
|
||||
`[${terminal.id}] switch (${printPlace(terminal.test)}) {`
|
||||
`[${terminal.id}] switch (${printPlace(terminal.test)}) {`,
|
||||
);
|
||||
writer.indented(() => {
|
||||
for (const case_ of terminal.cases) {
|
||||
let prefix =
|
||||
case_.test !== null ? `case ${printPlace(case_.test)}` : "default";
|
||||
case_.test !== null ? `case ${printPlace(case_.test)}` : 'default';
|
||||
writer.writeLine(`${prefix}: {`);
|
||||
writer.indented(() => {
|
||||
const block = case_.block;
|
||||
CompilerError.invariant(block != null, {
|
||||
reason: "Expected case to have a block",
|
||||
reason: 'Expected case to have a block',
|
||||
description: null,
|
||||
loc: case_.test?.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
writeReactiveInstructions(writer, block);
|
||||
});
|
||||
writer.writeLine("}");
|
||||
writer.writeLine('}');
|
||||
}
|
||||
});
|
||||
writer.writeLine("}");
|
||||
writer.writeLine('}');
|
||||
break;
|
||||
}
|
||||
case "for": {
|
||||
case 'for': {
|
||||
writer.writeLine(`[${terminal.id}] for (`);
|
||||
writer.indented(() => {
|
||||
writeReactiveValue(writer, terminal.init);
|
||||
writer.writeLine(";");
|
||||
writer.writeLine(';');
|
||||
writeReactiveValue(writer, terminal.test);
|
||||
writer.writeLine(";");
|
||||
writer.writeLine(';');
|
||||
if (terminal.update !== null) {
|
||||
writeReactiveValue(writer, terminal.update);
|
||||
}
|
||||
});
|
||||
writer.writeLine(") {");
|
||||
writer.writeLine(') {');
|
||||
writeReactiveInstructions(writer, terminal.loop);
|
||||
writer.writeLine("}");
|
||||
writer.writeLine('}');
|
||||
break;
|
||||
}
|
||||
case "for-of": {
|
||||
case 'for-of': {
|
||||
writer.writeLine(`[${terminal.id}] for-of (`);
|
||||
writer.indented(() => {
|
||||
writeReactiveValue(writer, terminal.init);
|
||||
writer.writeLine(";");
|
||||
writer.writeLine(';');
|
||||
writeReactiveValue(writer, terminal.test);
|
||||
});
|
||||
writer.writeLine(") {");
|
||||
writer.writeLine(') {');
|
||||
writeReactiveInstructions(writer, terminal.loop);
|
||||
writer.writeLine("}");
|
||||
writer.writeLine('}');
|
||||
break;
|
||||
}
|
||||
case "for-in": {
|
||||
case 'for-in': {
|
||||
writer.writeLine(`[${terminal.id}] for-in (`);
|
||||
writer.indented(() => {
|
||||
writeReactiveValue(writer, terminal.init);
|
||||
});
|
||||
writer.writeLine(") {");
|
||||
writer.writeLine(') {');
|
||||
writeReactiveInstructions(writer, terminal.loop);
|
||||
writer.writeLine("}");
|
||||
writer.writeLine('}');
|
||||
break;
|
||||
}
|
||||
case "throw": {
|
||||
case 'throw': {
|
||||
writer.writeLine(`[${terminal.id}] throw ${printPlace(terminal.value)}`);
|
||||
break;
|
||||
}
|
||||
case "return": {
|
||||
case 'return': {
|
||||
writer.writeLine(`[${terminal.id}] return ${printPlace(terminal.value)}`);
|
||||
break;
|
||||
}
|
||||
case "label": {
|
||||
writer.writeLine("{");
|
||||
case 'label': {
|
||||
writer.writeLine('{');
|
||||
writeReactiveInstructions(writer, terminal.block);
|
||||
writer.writeLine("}");
|
||||
writer.writeLine('}');
|
||||
break;
|
||||
}
|
||||
case "try": {
|
||||
case 'try': {
|
||||
writer.writeLine(`[${terminal.id}] try {`);
|
||||
writeReactiveInstructions(writer, terminal.block);
|
||||
writer.write(`} catch `);
|
||||
@@ -390,7 +390,7 @@ function writeTerminal(writer: Writer, terminal: ReactiveTerminal): void {
|
||||
writer.writeLine(`{`);
|
||||
}
|
||||
writeReactiveInstructions(writer, terminal.handler);
|
||||
writer.writeLine("}");
|
||||
writer.writeLine('}');
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -403,9 +403,9 @@ export class Writer {
|
||||
#line: string;
|
||||
#depth: number;
|
||||
|
||||
constructor({ depth }: { depth: number } = { depth: 0 }) {
|
||||
constructor({depth}: {depth: number} = {depth: 0}) {
|
||||
this.#depth = Math.max(depth, 0);
|
||||
this.#line = "";
|
||||
this.#line = '';
|
||||
}
|
||||
|
||||
complete(): string {
|
||||
@@ -413,7 +413,7 @@ export class Writer {
|
||||
if (line.length > 0) {
|
||||
this.#out.push(line);
|
||||
}
|
||||
return this.#out.join("\n");
|
||||
return this.#out.join('\n');
|
||||
}
|
||||
|
||||
append(s: string): void {
|
||||
@@ -425,13 +425,13 @@ export class Writer {
|
||||
if (line.length > 0) {
|
||||
this.#out.push(line);
|
||||
}
|
||||
this.#line = "";
|
||||
this.#line = '';
|
||||
}
|
||||
|
||||
write(s: string): void {
|
||||
if (this.#line.length === 0 && this.#depth > 0) {
|
||||
// indent before writing
|
||||
this.#line = " ".repeat(this.#depth);
|
||||
this.#line = ' '.repeat(this.#depth);
|
||||
}
|
||||
this.#line += s;
|
||||
}
|
||||
|
||||
+16
-16
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import { GeneratedSource } from "../HIR";
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {GeneratedSource} from '../HIR';
|
||||
import {
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
@@ -19,14 +19,14 @@ import {
|
||||
ScopeId,
|
||||
promoteTemporary,
|
||||
promoteTemporaryJsxTag,
|
||||
} from "../HIR/HIR";
|
||||
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
} from '../HIR/HIR';
|
||||
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
|
||||
class Visitor extends ReactiveFunctionVisitor<State> {
|
||||
override visitScope(scopeBlock: ReactiveScopeBlock, state: State): void {
|
||||
this.traverseScope(scopeBlock, state);
|
||||
for (const dep of scopeBlock.scope.dependencies) {
|
||||
const { identifier } = dep;
|
||||
const {identifier} = dep;
|
||||
if (identifier.name == null) {
|
||||
promoteIdentifier(identifier, state);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class Visitor extends ReactiveFunctionVisitor<State> {
|
||||
|
||||
override visitPrunedScope(
|
||||
scopeBlock: PrunedReactiveScopeBlock,
|
||||
state: State
|
||||
state: State,
|
||||
): void {
|
||||
this.traversePrunedScope(scopeBlock, state);
|
||||
for (const [, declaration] of scopeBlock.scope.declarations) {
|
||||
@@ -69,10 +69,10 @@ class Visitor extends ReactiveFunctionVisitor<State> {
|
||||
override visitValue(
|
||||
id: InstructionId,
|
||||
value: ReactiveValue,
|
||||
state: State
|
||||
state: State,
|
||||
): void {
|
||||
this.traverseValue(id, value, state);
|
||||
if (value.kind === "FunctionExpression" || value.kind === "ObjectMethod") {
|
||||
if (value.kind === 'FunctionExpression' || value.kind === 'ObjectMethod') {
|
||||
this.visitHirFunction(value.loweredFunc.func, state);
|
||||
}
|
||||
}
|
||||
@@ -81,10 +81,10 @@ class Visitor extends ReactiveFunctionVisitor<State> {
|
||||
_id: InstructionId,
|
||||
_dependencies: Array<Place>,
|
||||
fn: ReactiveFunction,
|
||||
state: State
|
||||
state: State,
|
||||
): void {
|
||||
for (const operand of fn.params) {
|
||||
const place = operand.kind === "Identifier" ? operand : operand.place;
|
||||
const place = operand.kind === 'Identifier' ? operand : operand.place;
|
||||
if (place.identifier.name === null) {
|
||||
promoteIdentifier(place.identifier, state);
|
||||
}
|
||||
@@ -98,7 +98,7 @@ type State = {
|
||||
tags: JsxExpressionTags;
|
||||
pruned: Map<
|
||||
IdentifierId,
|
||||
{ activeScopes: Array<ScopeId>; usedOutsideScope: boolean }
|
||||
{activeScopes: Array<ScopeId>; usedOutsideScope: boolean}
|
||||
>; // true if referenced within another scope, false if only accessed outside of scopes
|
||||
};
|
||||
|
||||
@@ -120,17 +120,17 @@ class CollectPromotableTemporaries extends ReactiveFunctionVisitor<State> {
|
||||
override visitValue(
|
||||
id: InstructionId,
|
||||
value: ReactiveValue,
|
||||
state: State
|
||||
state: State,
|
||||
): void {
|
||||
this.traverseValue(id, value, state);
|
||||
if (value.kind === "JsxExpression" && value.tag.kind === "Identifier") {
|
||||
if (value.kind === 'JsxExpression' && value.tag.kind === 'Identifier') {
|
||||
state.tags.add(value.tag.identifier.id);
|
||||
}
|
||||
}
|
||||
|
||||
override visitPrunedScope(
|
||||
scopeBlock: PrunedReactiveScopeBlock,
|
||||
state: State
|
||||
state: State,
|
||||
): void {
|
||||
for (const [id] of scopeBlock.scope.declarations) {
|
||||
state.pruned.set(id, {
|
||||
@@ -154,7 +154,7 @@ export function promoteUsedTemporaries(fn: ReactiveFunction): void {
|
||||
};
|
||||
visitReactiveFunction(fn, new CollectPromotableTemporaries(), state);
|
||||
for (const operand of fn.params) {
|
||||
const place = operand.kind === "Identifier" ? operand : operand.place;
|
||||
const place = operand.kind === 'Identifier' ? operand : operand.place;
|
||||
if (place.identifier.name === null) {
|
||||
promoteIdentifier(place.identifier, state);
|
||||
}
|
||||
@@ -165,7 +165,7 @@ export function promoteUsedTemporaries(fn: ReactiveFunction): void {
|
||||
function promoteIdentifier(identifier: Identifier, state: State): void {
|
||||
CompilerError.invariant(identifier.name === null, {
|
||||
reason:
|
||||
"promoteTemporary: Expected to be called only for temporary variables",
|
||||
'promoteTemporary: Expected to be called only for temporary variables',
|
||||
description: null,
|
||||
loc: GeneratedSource,
|
||||
suggestions: null,
|
||||
|
||||
+41
-41
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { visitReactiveFunction } from ".";
|
||||
import { Effect } from "..";
|
||||
import {visitReactiveFunction} from '.';
|
||||
import {Effect} from '..';
|
||||
import {
|
||||
Environment,
|
||||
GeneratedSource,
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
ReactiveTerminalStatement,
|
||||
makeInstructionId,
|
||||
promoteTemporary,
|
||||
} from "../HIR";
|
||||
import { createTemporaryPlace } from "../HIR/HIRBuilder";
|
||||
import { EARLY_RETURN_SENTINEL } from "./CodegenReactiveFunction";
|
||||
import { ReactiveFunctionTransform, Transformed } from "./visitors";
|
||||
} from '../HIR';
|
||||
import {createTemporaryPlace} from '../HIR/HIRBuilder';
|
||||
import {EARLY_RETURN_SENTINEL} from './CodegenReactiveFunction';
|
||||
import {ReactiveFunctionTransform, Transformed} from './visitors';
|
||||
|
||||
/**
|
||||
* This pass ensures that reactive blocks honor the control flow behavior of the
|
||||
@@ -119,7 +119,7 @@ type State = {
|
||||
* Store early return information to bubble it back up to the outermost
|
||||
* reactive scope
|
||||
*/
|
||||
earlyReturnValue: ReactiveScope["earlyReturnValue"];
|
||||
earlyReturnValue: ReactiveScope['earlyReturnValue'];
|
||||
};
|
||||
|
||||
class Transform extends ReactiveFunctionTransform<State> {
|
||||
@@ -131,7 +131,7 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
|
||||
override visitScope(
|
||||
scopeBlock: ReactiveScopeBlock,
|
||||
parentState: State
|
||||
parentState: State,
|
||||
): void {
|
||||
/**
|
||||
* Exit early if an earlier pass has already created an early return,
|
||||
@@ -165,56 +165,56 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
const argTemp = createTemporaryPlace(this.env, loc);
|
||||
scopeBlock.instructions = [
|
||||
{
|
||||
kind: "instruction",
|
||||
kind: 'instruction',
|
||||
instruction: {
|
||||
id: makeInstructionId(0),
|
||||
loc,
|
||||
lvalue: { ...symbolTemp },
|
||||
lvalue: {...symbolTemp},
|
||||
value: {
|
||||
kind: "LoadGlobal",
|
||||
kind: 'LoadGlobal',
|
||||
binding: {
|
||||
kind: "Global",
|
||||
name: "Symbol",
|
||||
kind: 'Global',
|
||||
name: 'Symbol',
|
||||
},
|
||||
loc,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "instruction",
|
||||
kind: 'instruction',
|
||||
instruction: {
|
||||
id: makeInstructionId(0),
|
||||
loc,
|
||||
lvalue: { ...forTemp },
|
||||
lvalue: {...forTemp},
|
||||
value: {
|
||||
kind: "PropertyLoad",
|
||||
object: { ...symbolTemp },
|
||||
property: "for",
|
||||
kind: 'PropertyLoad',
|
||||
object: {...symbolTemp},
|
||||
property: 'for',
|
||||
loc,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "instruction",
|
||||
kind: 'instruction',
|
||||
instruction: {
|
||||
id: makeInstructionId(0),
|
||||
loc,
|
||||
lvalue: { ...argTemp },
|
||||
lvalue: {...argTemp},
|
||||
value: {
|
||||
kind: "Primitive",
|
||||
kind: 'Primitive',
|
||||
value: EARLY_RETURN_SENTINEL,
|
||||
loc,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "instruction",
|
||||
kind: 'instruction',
|
||||
instruction: {
|
||||
id: makeInstructionId(0),
|
||||
loc,
|
||||
lvalue: { ...sentinelTemp },
|
||||
lvalue: {...sentinelTemp},
|
||||
value: {
|
||||
kind: "MethodCall",
|
||||
kind: 'MethodCall',
|
||||
receiver: symbolTemp,
|
||||
property: forTemp,
|
||||
args: [argTemp],
|
||||
@@ -223,37 +223,37 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "instruction",
|
||||
kind: 'instruction',
|
||||
instruction: {
|
||||
id: makeInstructionId(0),
|
||||
loc,
|
||||
lvalue: null,
|
||||
value: {
|
||||
kind: "StoreLocal",
|
||||
kind: 'StoreLocal',
|
||||
loc,
|
||||
type: null,
|
||||
lvalue: {
|
||||
kind: InstructionKind.Let,
|
||||
place: {
|
||||
kind: "Identifier",
|
||||
kind: 'Identifier',
|
||||
effect: Effect.ConditionallyMutate,
|
||||
loc,
|
||||
reactive: true,
|
||||
identifier: earlyReturnValue.value,
|
||||
},
|
||||
},
|
||||
value: { ...sentinelTemp },
|
||||
value: {...sentinelTemp},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "terminal",
|
||||
kind: 'terminal',
|
||||
label: {
|
||||
id: earlyReturnValue.label,
|
||||
implicit: false,
|
||||
},
|
||||
terminal: {
|
||||
kind: "label",
|
||||
kind: 'label',
|
||||
id: makeInstructionId(0),
|
||||
loc: GeneratedSource,
|
||||
block: instructions,
|
||||
@@ -272,11 +272,11 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
|
||||
override transformTerminal(
|
||||
stmt: ReactiveTerminalStatement,
|
||||
state: State
|
||||
state: State,
|
||||
): Transformed<ReactiveStatement> {
|
||||
if (state.withinReactiveScope && stmt.terminal.kind === "return") {
|
||||
if (state.withinReactiveScope && stmt.terminal.kind === 'return') {
|
||||
const loc = stmt.terminal.value.loc;
|
||||
let earlyReturnValue: ReactiveScope["earlyReturnValue"];
|
||||
let earlyReturnValue: ReactiveScope['earlyReturnValue'];
|
||||
if (state.earlyReturnValue !== null) {
|
||||
earlyReturnValue = state.earlyReturnValue;
|
||||
} else {
|
||||
@@ -290,22 +290,22 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
}
|
||||
state.earlyReturnValue = earlyReturnValue;
|
||||
return {
|
||||
kind: "replace-many",
|
||||
kind: 'replace-many',
|
||||
value: [
|
||||
{
|
||||
kind: "instruction",
|
||||
kind: 'instruction',
|
||||
instruction: {
|
||||
id: makeInstructionId(0),
|
||||
loc,
|
||||
lvalue: null,
|
||||
value: {
|
||||
kind: "StoreLocal",
|
||||
kind: 'StoreLocal',
|
||||
loc,
|
||||
type: null,
|
||||
lvalue: {
|
||||
kind: InstructionKind.Reassign,
|
||||
place: {
|
||||
kind: "Identifier",
|
||||
kind: 'Identifier',
|
||||
identifier: earlyReturnValue.value,
|
||||
effect: Effect.Capture,
|
||||
loc,
|
||||
@@ -317,13 +317,13 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "terminal",
|
||||
kind: 'terminal',
|
||||
label: null,
|
||||
terminal: {
|
||||
kind: "break",
|
||||
kind: 'break',
|
||||
id: makeInstructionId(0),
|
||||
loc,
|
||||
targetKind: "labeled",
|
||||
targetKind: 'labeled',
|
||||
target: earlyReturnValue.label,
|
||||
},
|
||||
},
|
||||
@@ -331,6 +331,6 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
};
|
||||
}
|
||||
this.traverseTerminal(stmt, state);
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
}
|
||||
|
||||
+84
-89
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {
|
||||
BlockId,
|
||||
GeneratedSource,
|
||||
@@ -27,18 +27,15 @@ import {
|
||||
ReactiveTerminalStatement,
|
||||
ReactiveValue,
|
||||
ScopeId,
|
||||
} from "../HIR/HIR";
|
||||
import {
|
||||
eachInstructionValueOperand,
|
||||
eachPatternOperand,
|
||||
} from "../HIR/visitors";
|
||||
import { empty, Stack } from "../Utils/Stack";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
} from '../HIR/HIR';
|
||||
import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors';
|
||||
import {empty, Stack} from '../Utils/Stack';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {
|
||||
ReactiveScopeDependencyTree,
|
||||
ReactiveScopePropertyDependency,
|
||||
} from "./DeriveMinimalDependencies";
|
||||
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
} from './DeriveMinimalDependencies';
|
||||
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
|
||||
/*
|
||||
* Infers the dependencies of each scope to include variables whose values
|
||||
@@ -55,7 +52,7 @@ export function propagateScopeDependencies(fn: ReactiveFunction): void {
|
||||
|
||||
const context = new Context(escapingTemporaries.usedOutsideDeclaringScope);
|
||||
for (const param of fn.params) {
|
||||
if (param.kind === "Identifier") {
|
||||
if (param.kind === 'Identifier') {
|
||||
context.declare(param.identifier, {
|
||||
id: makeInstructionId(0),
|
||||
scope: empty(),
|
||||
@@ -70,7 +67,7 @@ export function propagateScopeDependencies(fn: ReactiveFunction): void {
|
||||
visitReactiveFunction(
|
||||
fn,
|
||||
new PropagationVisitor(fn.env.config.enableTreatFunctionDepsAsConditional),
|
||||
context
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,7 +85,7 @@ class FindPromotedTemporaries extends ReactiveFunctionVisitor<TemporariesUsedOut
|
||||
|
||||
override visitScope(
|
||||
scope: ReactiveScopeBlock,
|
||||
state: TemporariesUsedOutsideDefiningScope
|
||||
state: TemporariesUsedOutsideDefiningScope,
|
||||
): void {
|
||||
this.scopes.push(scope.scope.id);
|
||||
this.traverseScope(scope, state);
|
||||
@@ -97,7 +94,7 @@ class FindPromotedTemporaries extends ReactiveFunctionVisitor<TemporariesUsedOut
|
||||
|
||||
override visitInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
state: TemporariesUsedOutsideDefiningScope
|
||||
state: TemporariesUsedOutsideDefiningScope,
|
||||
): void {
|
||||
// Visit all places first, then record temporaries which may need to be promoted
|
||||
this.traverseInstruction(instruction, state);
|
||||
@@ -107,9 +104,9 @@ class FindPromotedTemporaries extends ReactiveFunctionVisitor<TemporariesUsedOut
|
||||
return;
|
||||
}
|
||||
switch (instruction.value.kind) {
|
||||
case "LoadLocal":
|
||||
case "LoadContext":
|
||||
case "PropertyLoad": {
|
||||
case 'LoadLocal':
|
||||
case 'LoadContext':
|
||||
case 'PropertyLoad': {
|
||||
state.declarations.set(instruction.lvalue.identifier.id, scope);
|
||||
break;
|
||||
}
|
||||
@@ -122,7 +119,7 @@ class FindPromotedTemporaries extends ReactiveFunctionVisitor<TemporariesUsedOut
|
||||
override visitPlace(
|
||||
_id: InstructionId,
|
||||
place: Place,
|
||||
state: TemporariesUsedOutsideDefiningScope
|
||||
state: TemporariesUsedOutsideDefiningScope,
|
||||
): void {
|
||||
const declaringScope = state.declarations.get(place.identifier.id);
|
||||
if (declaringScope === undefined) {
|
||||
@@ -164,7 +161,7 @@ class PoisonState {
|
||||
constructor(
|
||||
poisonedBlocks: Set<BlockId>,
|
||||
poisonedScopes: Set<ScopeId>,
|
||||
isPoisoned: boolean
|
||||
isPoisoned: boolean,
|
||||
) {
|
||||
this.poisonedBlocks = poisonedBlocks;
|
||||
this.poisonedScopes = poisonedScopes;
|
||||
@@ -175,7 +172,7 @@ class PoisonState {
|
||||
return new PoisonState(
|
||||
new Set(this.poisonedBlocks),
|
||||
new Set(this.poisonedScopes),
|
||||
this.isPoisoned
|
||||
this.isPoisoned,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -183,7 +180,7 @@ class PoisonState {
|
||||
const copy = new PoisonState(
|
||||
this.poisonedBlocks,
|
||||
this.poisonedScopes,
|
||||
this.isPoisoned
|
||||
this.isPoisoned,
|
||||
);
|
||||
this.poisonedBlocks = other.poisonedBlocks;
|
||||
this.poisonedScopes = other.poisonedScopes;
|
||||
@@ -193,7 +190,7 @@ class PoisonState {
|
||||
|
||||
merge(
|
||||
others: Array<PoisonState>,
|
||||
currentScope: ScopeTraversalState | null
|
||||
currentScope: ScopeTraversalState | null,
|
||||
): void {
|
||||
for (const other of others) {
|
||||
for (const id of other.poisonedBlocks) {
|
||||
@@ -212,9 +209,7 @@ class PoisonState {
|
||||
this.isPoisoned = true;
|
||||
return;
|
||||
} else if (
|
||||
currentScope.ownBlocks.find((blockId) =>
|
||||
this.poisonedBlocks.has(blockId)
|
||||
)
|
||||
currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId))
|
||||
) {
|
||||
this.isPoisoned = true;
|
||||
return;
|
||||
@@ -233,7 +228,7 @@ class PoisonState {
|
||||
*/
|
||||
addPoisonTarget(
|
||||
target: BlockId | null,
|
||||
activeScopes: Stack<ScopeTraversalState>
|
||||
activeScopes: Stack<ScopeTraversalState>,
|
||||
): void {
|
||||
const currentScope = activeScopes.value;
|
||||
if (target == null && currentScope != null) {
|
||||
@@ -255,7 +250,7 @@ class PoisonState {
|
||||
this.poisonedBlocks.add(target);
|
||||
if (
|
||||
!this.isPoisoned &&
|
||||
currentScope?.ownBlocks.find((blockId) => blockId === target)
|
||||
currentScope?.ownBlocks.find(blockId => blockId === target)
|
||||
) {
|
||||
this.isPoisoned = true;
|
||||
}
|
||||
@@ -269,7 +264,7 @@ class PoisonState {
|
||||
*/
|
||||
removeMaybePoisonedScope(
|
||||
id: ScopeId,
|
||||
currentScope: ScopeTraversalState | null
|
||||
currentScope: ScopeTraversalState | null,
|
||||
): void {
|
||||
this.poisonedScopes.delete(id);
|
||||
this.#invalidate(currentScope);
|
||||
@@ -277,7 +272,7 @@ class PoisonState {
|
||||
|
||||
removeMaybePoisonedBlock(
|
||||
id: BlockId,
|
||||
currentScope: ScopeTraversalState | null
|
||||
currentScope: ScopeTraversalState | null,
|
||||
): void {
|
||||
this.poisonedBlocks.delete(id);
|
||||
this.#invalidate(currentScope);
|
||||
@@ -365,7 +360,7 @@ class Context {
|
||||
this.#dependencies.addDepsFromInnerScope(
|
||||
scopedDependencies,
|
||||
this.#inConditionalWithinScope || this.isPoisoned,
|
||||
this.#checkValidDependency.bind(this)
|
||||
this.#checkValidDependency.bind(this),
|
||||
);
|
||||
|
||||
if (prevDepsInConditional != null) {
|
||||
@@ -373,7 +368,7 @@ class Context {
|
||||
prevDepsInConditional.addDepsFromInnerScope(
|
||||
this.#depsInCurrentConditional,
|
||||
true,
|
||||
this.#checkValidDependency.bind(this)
|
||||
this.#checkValidDependency.bind(this),
|
||||
);
|
||||
this.#depsInCurrentConditional = prevDepsInConditional;
|
||||
}
|
||||
@@ -428,13 +423,13 @@ class Context {
|
||||
* @param depsInConditionals
|
||||
*/
|
||||
promoteDepsFromExhaustiveConditionals(
|
||||
depsInConditionals: Array<ReactiveScopeDependencyTree>
|
||||
depsInConditionals: Array<ReactiveScopeDependencyTree>,
|
||||
): void {
|
||||
this.#dependencies.promoteDepsFromExhaustiveConditionals(
|
||||
depsInConditionals
|
||||
depsInConditionals,
|
||||
);
|
||||
this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals(
|
||||
depsInConditionals
|
||||
depsInConditionals,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -462,7 +457,7 @@ class Context {
|
||||
#getProperty(
|
||||
object: Place,
|
||||
property: string,
|
||||
isConditional: boolean
|
||||
isConditional: boolean,
|
||||
): ReactiveScopePropertyDependency {
|
||||
const resolvedObject = this.resolveTemporary(object);
|
||||
const resolvedDependency = this.#properties.get(resolvedObject.identifier);
|
||||
@@ -513,7 +508,7 @@ class Context {
|
||||
// ref.current access is not a valid dep
|
||||
if (
|
||||
isUseRefType(maybeDependency.identifier) &&
|
||||
maybeDependency.path.at(0) === "current"
|
||||
maybeDependency.path.at(0) === 'current'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -553,7 +548,7 @@ class Context {
|
||||
if (this.#scopes === null) {
|
||||
return false;
|
||||
}
|
||||
return this.#scopes.find((state) => state.value === scope);
|
||||
return this.#scopes.find(state => state.value === scope);
|
||||
}
|
||||
|
||||
get currentScope(): Stack<ScopeTraversalState> {
|
||||
@@ -579,7 +574,7 @@ class Context {
|
||||
if (resolved.identifier.name === null) {
|
||||
const propertyDependency = this.#properties.get(resolved.identifier);
|
||||
if (propertyDependency !== undefined) {
|
||||
dependency = { ...propertyDependency };
|
||||
dependency = {...propertyDependency};
|
||||
}
|
||||
}
|
||||
this.visitDependency(dependency);
|
||||
@@ -604,13 +599,13 @@ class Context {
|
||||
* (all other decls e.g. `let x;` should be initialized in BuildHIR)
|
||||
*/
|
||||
const originalDeclaration = this.#declarations.get(
|
||||
maybeDependency.identifier.id
|
||||
maybeDependency.identifier.id,
|
||||
);
|
||||
if (
|
||||
originalDeclaration !== undefined &&
|
||||
originalDeclaration.scope.value !== null
|
||||
) {
|
||||
originalDeclaration.scope.each((scope) => {
|
||||
originalDeclaration.scope.each(scope => {
|
||||
if (!this.#isScopeActive(scope.value)) {
|
||||
scope.value.declarations.set(maybeDependency.identifier.id, {
|
||||
identifier: maybeDependency.identifier,
|
||||
@@ -629,7 +624,7 @@ class Context {
|
||||
*/
|
||||
this.#dependencies.add(
|
||||
maybeDependency,
|
||||
this.#inConditionalWithinScope || isPoisoned
|
||||
this.#inConditionalWithinScope || isPoisoned,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -643,9 +638,9 @@ class Context {
|
||||
if (
|
||||
currentScope != null &&
|
||||
!Array.from(currentScope.reassignments).some(
|
||||
(identifier) => identifier.id === place.identifier.id
|
||||
identifier => identifier.id === place.identifier.id,
|
||||
) &&
|
||||
this.#checkValidDependency({ identifier: place.identifier, path: [] })
|
||||
this.#checkValidDependency({identifier: place.identifier, path: []})
|
||||
) {
|
||||
currentScope.reassignments.add(place.identifier);
|
||||
}
|
||||
@@ -664,7 +659,7 @@ class Context {
|
||||
currentScope.ownBlocks = currentScope.ownBlocks.pop();
|
||||
|
||||
CompilerError.invariant(last != null && last === id, {
|
||||
reason: "[PropagateScopeDependencies] Misformed block stack",
|
||||
reason: '[PropagateScopeDependencies] Misformed block stack',
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
}
|
||||
@@ -690,7 +685,7 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
|
||||
override visitPrunedScope(
|
||||
scopeBlock: PrunedReactiveScopeBlock,
|
||||
context: Context
|
||||
context: Context,
|
||||
): void {
|
||||
/*
|
||||
* NOTE: we explicitly throw away the deps, we only enter() the scope to record its
|
||||
@@ -703,9 +698,9 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
|
||||
override visitInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
context: Context
|
||||
context: Context,
|
||||
): void {
|
||||
const { id, value, lvalue } = instruction;
|
||||
const {id, value, lvalue} = instruction;
|
||||
this.visitInstructionValue(context, id, value, lvalue);
|
||||
if (lvalue == null) {
|
||||
return;
|
||||
@@ -719,19 +714,19 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
visitReactiveValue(
|
||||
context: Context,
|
||||
id: InstructionId,
|
||||
value: ReactiveValue
|
||||
value: ReactiveValue,
|
||||
): void {
|
||||
switch (value.kind) {
|
||||
case "OptionalExpression": {
|
||||
case 'OptionalExpression': {
|
||||
const inner = value.value;
|
||||
/*
|
||||
* OptionalExpression value is a SequenceExpression where the instructions
|
||||
* represent the code prior to the `?` and the final value represents the
|
||||
* conditional code that follows.
|
||||
*/
|
||||
CompilerError.invariant(inner.kind === "SequenceExpression", {
|
||||
CompilerError.invariant(inner.kind === 'SequenceExpression', {
|
||||
reason:
|
||||
"Expected OptionalExpression value to be a SequenceExpression",
|
||||
'Expected OptionalExpression value to be a SequenceExpression',
|
||||
description: `Found a \`${value.kind}\``,
|
||||
loc: value.loc,
|
||||
suggestions: null,
|
||||
@@ -746,14 +741,14 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "LogicalExpression": {
|
||||
case 'LogicalExpression': {
|
||||
this.visitReactiveValue(context, id, value.left);
|
||||
context.enterConditional(() => {
|
||||
this.visitReactiveValue(context, id, value.right);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "ConditionalExpression": {
|
||||
case 'ConditionalExpression': {
|
||||
this.visitReactiveValue(context, id, value.test);
|
||||
|
||||
const consequentDeps = context.enterConditional(() => {
|
||||
@@ -768,14 +763,14 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
]);
|
||||
break;
|
||||
}
|
||||
case "SequenceExpression": {
|
||||
case 'SequenceExpression': {
|
||||
for (const instr of value.instructions) {
|
||||
this.visitInstruction(instr, context);
|
||||
}
|
||||
this.visitInstructionValue(context, id, value.value, null);
|
||||
break;
|
||||
}
|
||||
case "FunctionExpression": {
|
||||
case 'FunctionExpression': {
|
||||
if (this.enableTreatFunctionDepsAsConditional) {
|
||||
context.enterConditional(() => {
|
||||
for (const operand of eachInstructionValueOperand(value)) {
|
||||
@@ -789,7 +784,7 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ReactiveFunctionValue": {
|
||||
case 'ReactiveFunctionValue': {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected ReactiveFunctionValue`,
|
||||
loc: value.loc,
|
||||
@@ -809,9 +804,9 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
context: Context,
|
||||
id: InstructionId,
|
||||
value: ReactiveValue,
|
||||
lvalue: Place | null
|
||||
lvalue: Place | null,
|
||||
): void {
|
||||
if (value.kind === "LoadLocal" && lvalue !== null) {
|
||||
if (value.kind === 'LoadLocal' && lvalue !== null) {
|
||||
if (
|
||||
value.place.identifier.name !== null &&
|
||||
lvalue.identifier.name === null &&
|
||||
@@ -821,13 +816,13 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
} else {
|
||||
context.visitOperand(value.place);
|
||||
}
|
||||
} else if (value.kind === "PropertyLoad") {
|
||||
} else if (value.kind === 'PropertyLoad') {
|
||||
if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) {
|
||||
context.declareProperty(lvalue, value.object, value.property);
|
||||
} else {
|
||||
context.visitProperty(value.object, value.property);
|
||||
}
|
||||
} else if (value.kind === "StoreLocal") {
|
||||
} else if (value.kind === 'StoreLocal') {
|
||||
context.visitOperand(value.value);
|
||||
if (value.lvalue.kind === InstructionKind.Reassign) {
|
||||
context.visitReassignment(value.lvalue.place);
|
||||
@@ -837,8 +832,8 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
scope: context.currentScope,
|
||||
});
|
||||
} else if (
|
||||
value.kind === "DeclareLocal" ||
|
||||
value.kind === "DeclareContext"
|
||||
value.kind === 'DeclareLocal' ||
|
||||
value.kind === 'DeclareContext'
|
||||
) {
|
||||
/*
|
||||
* Some variables may be declared and never initialized. We need
|
||||
@@ -856,7 +851,7 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
} else if (value.kind === "Destructure") {
|
||||
} else if (value.kind === 'Destructure') {
|
||||
context.visitOperand(value.value);
|
||||
for (const place of eachPatternOperand(value.lvalue.pattern)) {
|
||||
if (value.lvalue.kind === InstructionKind.Reassign) {
|
||||
@@ -878,16 +873,16 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
}
|
||||
const terminal = stmt.terminal;
|
||||
switch (terminal.kind) {
|
||||
case "continue":
|
||||
case "break": {
|
||||
case 'continue':
|
||||
case 'break': {
|
||||
context.poisonState.addPoisonTarget(
|
||||
terminal.target,
|
||||
context.currentScope
|
||||
context.currentScope,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "throw":
|
||||
case "return": {
|
||||
case 'throw':
|
||||
case 'return': {
|
||||
context.poisonState.addPoisonTarget(null, context.currentScope);
|
||||
break;
|
||||
}
|
||||
@@ -901,24 +896,24 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
|
||||
override visitTerminal(
|
||||
stmt: ReactiveTerminalStatement,
|
||||
context: Context
|
||||
context: Context,
|
||||
): void {
|
||||
this.enterTerminal(stmt, context);
|
||||
const terminal = stmt.terminal;
|
||||
switch (terminal.kind) {
|
||||
case "break":
|
||||
case "continue": {
|
||||
case 'break':
|
||||
case 'continue': {
|
||||
break;
|
||||
}
|
||||
case "return": {
|
||||
case 'return': {
|
||||
context.visitOperand(terminal.value);
|
||||
break;
|
||||
}
|
||||
case "throw": {
|
||||
case 'throw': {
|
||||
context.visitOperand(terminal.value);
|
||||
break;
|
||||
}
|
||||
case "for": {
|
||||
case 'for': {
|
||||
this.visitReactiveValue(context, terminal.id, terminal.init);
|
||||
this.visitReactiveValue(context, terminal.id, terminal.test);
|
||||
context.enterConditional(() => {
|
||||
@@ -929,37 +924,37 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "for-of": {
|
||||
case 'for-of': {
|
||||
this.visitReactiveValue(context, terminal.id, terminal.init);
|
||||
context.enterConditional(() => {
|
||||
this.visitBlock(terminal.loop, context);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "for-in": {
|
||||
case 'for-in': {
|
||||
this.visitReactiveValue(context, terminal.id, terminal.init);
|
||||
context.enterConditional(() => {
|
||||
this.visitBlock(terminal.loop, context);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "do-while": {
|
||||
case 'do-while': {
|
||||
this.visitBlock(terminal.loop, context);
|
||||
context.enterConditional(() => {
|
||||
this.visitReactiveValue(context, terminal.id, terminal.test);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "while": {
|
||||
case 'while': {
|
||||
this.visitReactiveValue(context, terminal.id, terminal.test);
|
||||
context.enterConditional(() => {
|
||||
this.visitBlock(terminal.loop, context);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "if": {
|
||||
case 'if': {
|
||||
context.visitOperand(terminal.test);
|
||||
const { consequent, alternate } = terminal;
|
||||
const {consequent, alternate} = terminal;
|
||||
/*
|
||||
* Consequent and alternate branches are mutually exclusive,
|
||||
* so we save and restore the poison state here.
|
||||
@@ -975,13 +970,13 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
});
|
||||
context.poisonState.merge(
|
||||
[ifPoisonState],
|
||||
context.currentScope.value
|
||||
context.currentScope.value,
|
||||
);
|
||||
context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "switch": {
|
||||
case 'switch': {
|
||||
context.visitOperand(terminal.test);
|
||||
const isDefaultOnly =
|
||||
terminal.cases.length === 1 && terminal.cases[0].test == null;
|
||||
@@ -1004,7 +999,7 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
* CFG representation for fallthrough. This is safe. It only
|
||||
* reduces granularity of dependencies.
|
||||
*/
|
||||
for (const { test, block } of terminal.cases) {
|
||||
for (const {test, block} of terminal.cases) {
|
||||
if (test !== null) {
|
||||
context.visitOperand(test);
|
||||
} else {
|
||||
@@ -1012,12 +1007,12 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
}
|
||||
if (block !== undefined) {
|
||||
mutExPoisonStates.push(
|
||||
context.poisonState.take(prevPoisonState.clone())
|
||||
context.poisonState.take(prevPoisonState.clone()),
|
||||
);
|
||||
depsInCases.push(
|
||||
context.enterConditional(() => {
|
||||
this.visitBlock(block, context);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1026,15 +1021,15 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
}
|
||||
context.poisonState.merge(
|
||||
mutExPoisonStates,
|
||||
context.currentScope.value
|
||||
context.currentScope.value,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "label": {
|
||||
case 'label': {
|
||||
this.visitBlock(terminal.block, context);
|
||||
break;
|
||||
}
|
||||
case "try": {
|
||||
case 'try': {
|
||||
this.visitBlock(terminal.block, context);
|
||||
this.visitBlock(terminal.handler, context);
|
||||
break;
|
||||
@@ -1042,7 +1037,7 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
|
||||
default: {
|
||||
assertExhaustive(
|
||||
terminal,
|
||||
`Unexpected terminal kind \`${(terminal as any).kind}\``
|
||||
`Unexpected terminal kind \`${(terminal as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -9,12 +9,12 @@ import {
|
||||
ReactiveFunction,
|
||||
ReactiveScopeBlock,
|
||||
ReactiveStatement,
|
||||
} from "../HIR/HIR";
|
||||
} from '../HIR/HIR';
|
||||
import {
|
||||
ReactiveFunctionTransform,
|
||||
Transformed,
|
||||
visitReactiveFunction,
|
||||
} from "./visitors";
|
||||
} from './visitors';
|
||||
|
||||
/*
|
||||
* Removes *all* reactive scopes. Intended for experimentation only, to allow
|
||||
@@ -28,9 +28,9 @@ export function pruneAllReactiveScopes(fn: ReactiveFunction): void {
|
||||
class Transform extends ReactiveFunctionTransform<void> {
|
||||
override transformScope(
|
||||
scopeBlock: ReactiveScopeBlock,
|
||||
state: void
|
||||
state: void,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitScope(scopeBlock, state);
|
||||
return { kind: "replace-many", value: scopeBlock.instructions };
|
||||
return {kind: 'replace-many', value: scopeBlock.instructions};
|
||||
}
|
||||
}
|
||||
|
||||
+16
-20
@@ -5,18 +5,14 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {
|
||||
ReactiveFunctionTransform,
|
||||
Transformed,
|
||||
visitReactiveFunction,
|
||||
} from ".";
|
||||
import {ReactiveFunctionTransform, Transformed, visitReactiveFunction} from '.';
|
||||
import {
|
||||
Identifier,
|
||||
ReactiveFunction,
|
||||
ReactiveInstruction,
|
||||
ReactiveScopeBlock,
|
||||
ReactiveStatement,
|
||||
} from "../HIR";
|
||||
} from '../HIR';
|
||||
|
||||
/**
|
||||
* Some instructions will *always* produce a new value, and unless memoized will *always*
|
||||
@@ -38,17 +34,17 @@ class Transform extends ReactiveFunctionTransform<boolean> {
|
||||
|
||||
override transformInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
withinScope: boolean
|
||||
withinScope: boolean,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitInstruction(instruction, withinScope);
|
||||
|
||||
const { lvalue, value } = instruction;
|
||||
const {lvalue, value} = instruction;
|
||||
switch (value.kind) {
|
||||
case "ArrayExpression":
|
||||
case "ObjectExpression":
|
||||
case "JsxExpression":
|
||||
case "JsxFragment":
|
||||
case "NewExpression": {
|
||||
case 'ArrayExpression':
|
||||
case 'ObjectExpression':
|
||||
case 'JsxExpression':
|
||||
case 'JsxFragment':
|
||||
case 'NewExpression': {
|
||||
if (lvalue !== null) {
|
||||
this.alwaysInvalidatingValues.add(lvalue.identifier);
|
||||
if (!withinScope) {
|
||||
@@ -57,7 +53,7 @@ class Transform extends ReactiveFunctionTransform<boolean> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "StoreLocal": {
|
||||
case 'StoreLocal': {
|
||||
if (this.alwaysInvalidatingValues.has(value.value.identifier)) {
|
||||
this.alwaysInvalidatingValues.add(value.lvalue.place.identifier);
|
||||
}
|
||||
@@ -66,7 +62,7 @@ class Transform extends ReactiveFunctionTransform<boolean> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "LoadLocal": {
|
||||
case 'LoadLocal': {
|
||||
if (
|
||||
lvalue !== null &&
|
||||
this.alwaysInvalidatingValues.has(value.place.identifier)
|
||||
@@ -82,12 +78,12 @@ class Transform extends ReactiveFunctionTransform<boolean> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
|
||||
override transformScope(
|
||||
scopeBlock: ReactiveScopeBlock,
|
||||
_withinScope: boolean
|
||||
_withinScope: boolean,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitScope(scopeBlock, true);
|
||||
|
||||
@@ -108,15 +104,15 @@ class Transform extends ReactiveFunctionTransform<boolean> {
|
||||
}
|
||||
}
|
||||
return {
|
||||
kind: "replace",
|
||||
kind: 'replace',
|
||||
value: {
|
||||
kind: "pruned-scope",
|
||||
kind: 'pruned-scope',
|
||||
scope: scopeBlock.scope,
|
||||
instructions: scopeBlock.instructions,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
}
|
||||
|
||||
+11
-11
@@ -11,12 +11,12 @@ import {
|
||||
ReactiveFunction,
|
||||
ReactiveInstruction,
|
||||
ReactiveStatement,
|
||||
} from "../HIR";
|
||||
} from '../HIR';
|
||||
import {
|
||||
ReactiveFunctionTransform,
|
||||
Transformed,
|
||||
visitReactiveFunction,
|
||||
} from "./visitors";
|
||||
} from './visitors';
|
||||
|
||||
/*
|
||||
* Prunes DeclareContexts lowered for HoistedConsts, and transforms any references back to its
|
||||
@@ -32,25 +32,25 @@ type HoistedIdentifiers = Set<Identifier>;
|
||||
class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
|
||||
override transformInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
state: HoistedIdentifiers
|
||||
state: HoistedIdentifiers,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitInstruction(instruction, state);
|
||||
if (
|
||||
instruction.value.kind === "DeclareContext" &&
|
||||
instruction.value.lvalue.kind === "HoistedConst"
|
||||
instruction.value.kind === 'DeclareContext' &&
|
||||
instruction.value.lvalue.kind === 'HoistedConst'
|
||||
) {
|
||||
state.add(instruction.value.lvalue.place.identifier);
|
||||
return { kind: "remove" };
|
||||
return {kind: 'remove'};
|
||||
}
|
||||
|
||||
if (
|
||||
instruction.value.kind === "StoreContext" &&
|
||||
instruction.value.kind === 'StoreContext' &&
|
||||
state.has(instruction.value.lvalue.place.identifier)
|
||||
) {
|
||||
return {
|
||||
kind: "replace",
|
||||
kind: 'replace',
|
||||
value: {
|
||||
kind: "instruction",
|
||||
kind: 'instruction',
|
||||
instruction: {
|
||||
...instruction,
|
||||
value: {
|
||||
@@ -60,13 +60,13 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
|
||||
kind: InstructionKind.Const,
|
||||
},
|
||||
type: null,
|
||||
kind: "StoreLocal",
|
||||
kind: 'StoreLocal',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
}
|
||||
|
||||
+49
-49
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {
|
||||
Environment,
|
||||
Identifier,
|
||||
@@ -20,11 +20,11 @@ import {
|
||||
getHookKind,
|
||||
isUseRefType,
|
||||
isUseStateType,
|
||||
} from "../HIR";
|
||||
import { eachCallArgument, eachInstructionLValue } from "../HIR/visitors";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
} from '../HIR';
|
||||
import {eachCallArgument, eachInstructionLValue} from '../HIR/visitors';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
|
||||
/**
|
||||
* This pass is built based on the observation by @jbrown215 that arguments
|
||||
@@ -57,7 +57,7 @@ import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
* from the block.
|
||||
*/
|
||||
|
||||
type CreateUpdate = "Create" | "Update" | "Unknown";
|
||||
type CreateUpdate = 'Create' | 'Update' | 'Unknown';
|
||||
|
||||
type KindMap = Map<IdentifierId, CreateUpdate>;
|
||||
|
||||
@@ -70,7 +70,7 @@ class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
|
||||
constructor(
|
||||
env: Environment,
|
||||
aliases: DisjointSet<IdentifierId>,
|
||||
paths: Map<IdentifierId, Map<string, IdentifierId>>
|
||||
paths: Map<IdentifierId, Map<string, IdentifierId>>,
|
||||
) {
|
||||
super();
|
||||
this.aliases = aliases;
|
||||
@@ -80,16 +80,16 @@ class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
|
||||
|
||||
join(values: Array<CreateUpdate>): CreateUpdate {
|
||||
function join2(l: CreateUpdate, r: CreateUpdate): CreateUpdate {
|
||||
if (l === "Update" || r === "Update") {
|
||||
return "Update";
|
||||
} else if (l === "Create" || r === "Create") {
|
||||
return "Create";
|
||||
} else if (l === "Unknown" || r === "Unknown") {
|
||||
return "Unknown";
|
||||
if (l === 'Update' || r === 'Update') {
|
||||
return 'Update';
|
||||
} else if (l === 'Create' || r === 'Create') {
|
||||
return 'Create';
|
||||
} else if (l === 'Unknown' || r === 'Unknown') {
|
||||
return 'Unknown';
|
||||
}
|
||||
assertExhaustive(r, `Unhandled variable kind ${r}`);
|
||||
}
|
||||
return values.reduce(join2, "Unknown");
|
||||
return values.reduce(join2, 'Unknown');
|
||||
}
|
||||
|
||||
isCreateOnlyHook(id: Identifier): boolean {
|
||||
@@ -99,11 +99,11 @@ class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
|
||||
override visitPlace(
|
||||
_: InstructionId,
|
||||
place: Place,
|
||||
state: CreateUpdate
|
||||
state: CreateUpdate,
|
||||
): void {
|
||||
this.map.set(
|
||||
place.identifier.id,
|
||||
this.join([state, this.map.get(place.identifier.id) ?? "Unknown"])
|
||||
this.join([state, this.map.get(place.identifier.id) ?? 'Unknown']),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -114,17 +114,17 @@ class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
|
||||
override visitInstruction(instruction: ReactiveInstruction): void {
|
||||
const state = this.join(
|
||||
[...eachInstructionLValue(instruction)].map(
|
||||
(operand) => this.map.get(operand.identifier.id) ?? "Unknown"
|
||||
)
|
||||
operand => this.map.get(operand.identifier.id) ?? 'Unknown',
|
||||
),
|
||||
);
|
||||
|
||||
const visitCallOrMethodNonArgs = (): void => {
|
||||
switch (instruction.value.kind) {
|
||||
case "CallExpression": {
|
||||
case 'CallExpression': {
|
||||
this.visitPlace(instruction.id, instruction.value.callee, state);
|
||||
break;
|
||||
}
|
||||
case "MethodCall": {
|
||||
case 'MethodCall': {
|
||||
this.visitPlace(instruction.id, instruction.value.property, state);
|
||||
this.visitPlace(instruction.id, instruction.value.receiver, state);
|
||||
break;
|
||||
@@ -135,11 +135,11 @@ class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
|
||||
const isHook = (): boolean => {
|
||||
let callee = null;
|
||||
switch (instruction.value.kind) {
|
||||
case "CallExpression": {
|
||||
case 'CallExpression': {
|
||||
callee = instruction.value.callee.identifier;
|
||||
break;
|
||||
}
|
||||
case "MethodCall": {
|
||||
case 'MethodCall': {
|
||||
callee = instruction.value.property.identifier;
|
||||
break;
|
||||
}
|
||||
@@ -148,18 +148,18 @@ class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
|
||||
};
|
||||
|
||||
switch (instruction.value.kind) {
|
||||
case "CallExpression":
|
||||
case "MethodCall": {
|
||||
case 'CallExpression':
|
||||
case 'MethodCall': {
|
||||
if (
|
||||
instruction.lvalue &&
|
||||
this.isCreateOnlyHook(instruction.lvalue.identifier)
|
||||
) {
|
||||
[...eachCallArgument(instruction.value.args)].forEach((operand) =>
|
||||
this.visitPlace(instruction.id, operand, "Create")
|
||||
[...eachCallArgument(instruction.value.args)].forEach(operand =>
|
||||
this.visitPlace(instruction.id, operand, 'Create'),
|
||||
);
|
||||
visitCallOrMethodNonArgs();
|
||||
} else {
|
||||
this.traverseInstruction(instruction, isHook() ? "Update" : state);
|
||||
this.traverseInstruction(instruction, isHook() ? 'Update' : state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -173,17 +173,17 @@ class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
|
||||
const state = this.join(
|
||||
[
|
||||
...scope.scope.declarations.keys(),
|
||||
...[...scope.scope.reassignments.values()].map((ident) => ident.id),
|
||||
].map((id) => this.map.get(id) ?? "Unknown")
|
||||
...[...scope.scope.reassignments.values()].map(ident => ident.id),
|
||||
].map(id => this.map.get(id) ?? 'Unknown'),
|
||||
);
|
||||
super.visitScope(scope, state);
|
||||
[...scope.scope.dependencies].forEach((ident) => {
|
||||
[...scope.scope.dependencies].forEach(ident => {
|
||||
let target: undefined | IdentifierId =
|
||||
this.aliases.find(ident.identifier.id) ?? ident.identifier.id;
|
||||
ident.path.forEach((key) => {
|
||||
ident.path.forEach(key => {
|
||||
target &&= this.paths.get(target)?.get(key);
|
||||
});
|
||||
if (target && this.map.get(target) === "Create") {
|
||||
if (target && this.map.get(target) === 'Create') {
|
||||
scope.scope.dependencies.delete(ident);
|
||||
}
|
||||
});
|
||||
@@ -191,9 +191,9 @@ class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
|
||||
|
||||
override visitTerminal(
|
||||
stmt: ReactiveTerminalStatement,
|
||||
state: CreateUpdate
|
||||
state: CreateUpdate,
|
||||
): void {
|
||||
CompilerError.invariant(state !== "Create", {
|
||||
CompilerError.invariant(state !== 'Create', {
|
||||
reason: "Visiting a terminal statement with state 'Create'",
|
||||
loc: stmt.terminal.loc,
|
||||
});
|
||||
@@ -204,24 +204,24 @@ class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
|
||||
_id: InstructionId,
|
||||
_dependencies: Array<Place>,
|
||||
fn: ReactiveFunction,
|
||||
state: CreateUpdate
|
||||
state: CreateUpdate,
|
||||
): void {
|
||||
visitReactiveFunction(fn, this, state);
|
||||
}
|
||||
}
|
||||
|
||||
export default function pruneInitializationDependencies(
|
||||
fn: ReactiveFunction
|
||||
fn: ReactiveFunction,
|
||||
): void {
|
||||
const [aliases, paths] = getAliases(fn);
|
||||
visitReactiveFunction(fn, new Visitor(fn.env, aliases, paths), "Update");
|
||||
visitReactiveFunction(fn, new Visitor(fn.env, aliases, paths), 'Update');
|
||||
}
|
||||
|
||||
function update(
|
||||
map: Map<IdentifierId, Map<string, IdentifierId>>,
|
||||
key: IdentifierId,
|
||||
path: string,
|
||||
value: IdentifierId
|
||||
value: IdentifierId,
|
||||
): void {
|
||||
const inner = map.get(key) ?? new Map();
|
||||
inner.set(path, value);
|
||||
@@ -234,43 +234,43 @@ class AliasVisitor extends ReactiveFunctionVisitor {
|
||||
|
||||
override visitInstruction(instr: ReactiveInstruction): void {
|
||||
if (
|
||||
instr.value.kind === "StoreLocal" ||
|
||||
instr.value.kind === "StoreContext"
|
||||
instr.value.kind === 'StoreLocal' ||
|
||||
instr.value.kind === 'StoreContext'
|
||||
) {
|
||||
this.scopeIdentifiers.union([
|
||||
instr.value.lvalue.place.identifier.id,
|
||||
instr.value.value.identifier.id,
|
||||
]);
|
||||
} else if (
|
||||
instr.value.kind === "LoadLocal" ||
|
||||
instr.value.kind === "LoadContext"
|
||||
instr.value.kind === 'LoadLocal' ||
|
||||
instr.value.kind === 'LoadContext'
|
||||
) {
|
||||
instr.lvalue &&
|
||||
this.scopeIdentifiers.union([
|
||||
instr.lvalue.identifier.id,
|
||||
instr.value.place.identifier.id,
|
||||
]);
|
||||
} else if (instr.value.kind === "PropertyLoad") {
|
||||
} else if (instr.value.kind === 'PropertyLoad') {
|
||||
instr.lvalue &&
|
||||
update(
|
||||
this.scopePaths,
|
||||
instr.value.object.identifier.id,
|
||||
instr.value.property,
|
||||
instr.lvalue.identifier.id
|
||||
instr.lvalue.identifier.id,
|
||||
);
|
||||
} else if (instr.value.kind === "PropertyStore") {
|
||||
} else if (instr.value.kind === 'PropertyStore') {
|
||||
update(
|
||||
this.scopePaths,
|
||||
instr.value.object.identifier.id,
|
||||
instr.value.property,
|
||||
instr.value.value.identifier.id
|
||||
instr.value.value.identifier.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getAliases(
|
||||
fn: ReactiveFunction
|
||||
fn: ReactiveFunction,
|
||||
): [DisjointSet<IdentifierId>, Map<IdentifierId, Map<string, IdentifierId>>] {
|
||||
const visitor = new AliasVisitor();
|
||||
visitReactiveFunction(fn, visitor, null);
|
||||
@@ -282,7 +282,7 @@ function getAliases(
|
||||
scopePaths,
|
||||
disjoint.find(key) ?? key,
|
||||
path,
|
||||
disjoint.find(id) ?? id
|
||||
disjoint.find(id) ?? id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+137
-137
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {
|
||||
Environment,
|
||||
IdentifierId,
|
||||
@@ -22,17 +22,17 @@ import {
|
||||
ScopeId,
|
||||
getHookKind,
|
||||
isMutableEffect,
|
||||
} from "../HIR";
|
||||
import { getFunctionCallSignature } from "../Inference/InferReferenceEffects";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
import { getPlaceScope } from "./BuildReactiveBlocks";
|
||||
} from '../HIR';
|
||||
import {getFunctionCallSignature} from '../Inference/InferReferenceEffects';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {getPlaceScope} from './BuildReactiveBlocks';
|
||||
import {
|
||||
ReactiveFunctionTransform,
|
||||
ReactiveFunctionVisitor,
|
||||
Transformed,
|
||||
eachReactiveValueOperand,
|
||||
visitReactiveFunction,
|
||||
} from "./visitors";
|
||||
} from './visitors';
|
||||
|
||||
/*
|
||||
* This pass prunes reactive scopes that are not necessary to bound downstream computation.
|
||||
@@ -114,7 +114,7 @@ export function pruneNonEscapingScopes(fn: ReactiveFunction): void {
|
||||
*/
|
||||
const state = new State(fn.env);
|
||||
for (const param of fn.params) {
|
||||
if (param.kind === "Identifier") {
|
||||
if (param.kind === 'Identifier') {
|
||||
state.declare(param.identifier.id);
|
||||
} else {
|
||||
state.declare(param.place.identifier.id);
|
||||
@@ -146,19 +146,19 @@ export type MemoizationOptions = {
|
||||
// Describes how to determine whether a value should be memoized, relative to dependees and dependencies
|
||||
enum MemoizationLevel {
|
||||
// The value should be memoized if it escapes
|
||||
Memoized = "Memoized",
|
||||
Memoized = 'Memoized',
|
||||
/*
|
||||
* Values that are memoized if their dependencies are memoized (used for logical/ternary and
|
||||
* other expressions that propagate dependencies wo changing them)
|
||||
*/
|
||||
Conditional = "Conditional",
|
||||
Conditional = 'Conditional',
|
||||
/*
|
||||
* Values that cannot be compared with Object.is, but which by default don't need to be memoized
|
||||
* unless forced
|
||||
*/
|
||||
Unmemoized = "Unmemoized",
|
||||
Unmemoized = 'Unmemoized',
|
||||
// The value will never be memoized: used for values that can be cheaply compared w Object.is
|
||||
Never = "Never",
|
||||
Never = 'Never',
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -167,7 +167,7 @@ enum MemoizationLevel {
|
||||
*/
|
||||
function joinAliases(
|
||||
kind1: MemoizationLevel,
|
||||
kind2: MemoizationLevel
|
||||
kind2: MemoizationLevel,
|
||||
): MemoizationLevel {
|
||||
if (
|
||||
kind1 === MemoizationLevel.Memoized ||
|
||||
@@ -240,21 +240,21 @@ class State {
|
||||
visitOperand(
|
||||
id: InstructionId,
|
||||
place: Place,
|
||||
identifier: IdentifierId
|
||||
identifier: IdentifierId,
|
||||
): void {
|
||||
const scope = getPlaceScope(id, place);
|
||||
if (scope !== null) {
|
||||
let node = this.scopes.get(scope.id);
|
||||
if (node === undefined) {
|
||||
node = {
|
||||
dependencies: [...scope.dependencies].map((dep) => dep.identifier.id),
|
||||
dependencies: [...scope.dependencies].map(dep => dep.identifier.id),
|
||||
seen: false,
|
||||
};
|
||||
this.scopes.set(scope.id, node);
|
||||
}
|
||||
const identifierNode = this.identifiers.get(identifier);
|
||||
CompilerError.invariant(identifierNode !== undefined, {
|
||||
reason: "Expected identifier to be initialized",
|
||||
reason: 'Expected identifier to be initialized',
|
||||
description: null,
|
||||
loc: place.loc,
|
||||
suggestions: null,
|
||||
@@ -318,7 +318,7 @@ function computeMemoizedIdentifiers(state: State): Set<IdentifierId> {
|
||||
function forceMemoizeScopeDependencies(id: ScopeId): void {
|
||||
const node = state.scopes.get(id);
|
||||
CompilerError.invariant(node !== undefined, {
|
||||
reason: "Expected a node for all scopes",
|
||||
reason: 'Expected a node for all scopes',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -358,19 +358,19 @@ function computeMemoizationInputs(
|
||||
env: Environment,
|
||||
value: ReactiveValue,
|
||||
lvalue: Place | null,
|
||||
options: MemoizationOptions
|
||||
options: MemoizationOptions,
|
||||
): {
|
||||
// can optionally return a custom set of lvalues per instruction
|
||||
lvalues: Array<LValueMemoization>;
|
||||
rvalues: Array<Place>;
|
||||
} {
|
||||
switch (value.kind) {
|
||||
case "ConditionalExpression": {
|
||||
case 'ConditionalExpression': {
|
||||
return {
|
||||
// Only need to memoize if the rvalues are memoized
|
||||
lvalues:
|
||||
lvalue !== null
|
||||
? [{ place: lvalue, level: MemoizationLevel.Conditional }]
|
||||
? [{place: lvalue, level: MemoizationLevel.Conditional}]
|
||||
: [],
|
||||
rvalues: [
|
||||
// Conditionals do not alias their test value.
|
||||
@@ -381,12 +381,12 @@ function computeMemoizationInputs(
|
||||
],
|
||||
};
|
||||
}
|
||||
case "LogicalExpression": {
|
||||
case 'LogicalExpression': {
|
||||
return {
|
||||
// Only need to memoize if the rvalues are memoized
|
||||
lvalues:
|
||||
lvalue !== null
|
||||
? [{ place: lvalue, level: MemoizationLevel.Conditional }]
|
||||
? [{place: lvalue, level: MemoizationLevel.Conditional}]
|
||||
: [],
|
||||
rvalues: [
|
||||
...computeMemoizationInputs(env, value.left, null, options).rvalues,
|
||||
@@ -394,12 +394,12 @@ function computeMemoizationInputs(
|
||||
],
|
||||
};
|
||||
}
|
||||
case "SequenceExpression": {
|
||||
case 'SequenceExpression': {
|
||||
return {
|
||||
// Only need to memoize if the rvalues are memoized
|
||||
lvalues:
|
||||
lvalue !== null
|
||||
? [{ place: lvalue, level: MemoizationLevel.Conditional }]
|
||||
? [{place: lvalue, level: MemoizationLevel.Conditional}]
|
||||
: [],
|
||||
/*
|
||||
* Only the final value of the sequence is a true rvalue:
|
||||
@@ -410,13 +410,13 @@ function computeMemoizationInputs(
|
||||
.rvalues,
|
||||
};
|
||||
}
|
||||
case "JsxExpression": {
|
||||
case 'JsxExpression': {
|
||||
const operands: Array<Place> = [];
|
||||
if (value.tag.kind === "Identifier") {
|
||||
if (value.tag.kind === 'Identifier') {
|
||||
operands.push(value.tag);
|
||||
}
|
||||
for (const prop of value.props) {
|
||||
if (prop.kind === "JsxAttribute") {
|
||||
if (prop.kind === 'JsxAttribute') {
|
||||
operands.push(prop.place);
|
||||
} else {
|
||||
operands.push(prop.argument);
|
||||
@@ -435,11 +435,11 @@ function computeMemoizationInputs(
|
||||
* JSX elements themselves are not memoized unless forced to
|
||||
* avoid breaking downstream memoization
|
||||
*/
|
||||
lvalues: lvalue !== null ? [{ place: lvalue, level }] : [],
|
||||
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
|
||||
rvalues: operands,
|
||||
};
|
||||
}
|
||||
case "JsxFragment": {
|
||||
case 'JsxFragment': {
|
||||
const level = options.memoizeJsxElements
|
||||
? MemoizationLevel.Memoized
|
||||
: MemoizationLevel.Unmemoized;
|
||||
@@ -448,89 +448,89 @@ function computeMemoizationInputs(
|
||||
* JSX elements themselves are not memoized unless forced to
|
||||
* avoid breaking downstream memoization
|
||||
*/
|
||||
lvalues: lvalue !== null ? [{ place: lvalue, level }] : [],
|
||||
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
|
||||
rvalues: value.children,
|
||||
};
|
||||
}
|
||||
case "NextPropertyOf":
|
||||
case "StartMemoize":
|
||||
case "FinishMemoize":
|
||||
case "Debugger":
|
||||
case "ComputedDelete":
|
||||
case "PropertyDelete":
|
||||
case "LoadGlobal":
|
||||
case "MetaProperty":
|
||||
case "TemplateLiteral":
|
||||
case "Primitive":
|
||||
case "JSXText":
|
||||
case "BinaryExpression":
|
||||
case "UnaryExpression": {
|
||||
case 'NextPropertyOf':
|
||||
case 'StartMemoize':
|
||||
case 'FinishMemoize':
|
||||
case 'Debugger':
|
||||
case 'ComputedDelete':
|
||||
case 'PropertyDelete':
|
||||
case 'LoadGlobal':
|
||||
case 'MetaProperty':
|
||||
case 'TemplateLiteral':
|
||||
case 'Primitive':
|
||||
case 'JSXText':
|
||||
case 'BinaryExpression':
|
||||
case 'UnaryExpression': {
|
||||
const level = options.forceMemoizePrimitives
|
||||
? MemoizationLevel.Memoized
|
||||
: MemoizationLevel.Never;
|
||||
return {
|
||||
// All of these instructions return a primitive value and never need to be memoized
|
||||
lvalues: lvalue !== null ? [{ place: lvalue, level }] : [],
|
||||
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
|
||||
rvalues: [],
|
||||
};
|
||||
}
|
||||
case "Await":
|
||||
case "TypeCastExpression": {
|
||||
case 'Await':
|
||||
case 'TypeCastExpression': {
|
||||
return {
|
||||
// Indirection for the inner value, memoized if the value is
|
||||
lvalues:
|
||||
lvalue !== null
|
||||
? [{ place: lvalue, level: MemoizationLevel.Conditional }]
|
||||
? [{place: lvalue, level: MemoizationLevel.Conditional}]
|
||||
: [],
|
||||
rvalues: [value.value],
|
||||
};
|
||||
}
|
||||
case "IteratorNext": {
|
||||
case 'IteratorNext': {
|
||||
return {
|
||||
// Indirection for the inner value, memoized if the value is
|
||||
lvalues:
|
||||
lvalue !== null
|
||||
? [{ place: lvalue, level: MemoizationLevel.Conditional }]
|
||||
? [{place: lvalue, level: MemoizationLevel.Conditional}]
|
||||
: [],
|
||||
rvalues: [value.iterator, value.collection],
|
||||
};
|
||||
}
|
||||
case "GetIterator": {
|
||||
case 'GetIterator': {
|
||||
return {
|
||||
// Indirection for the inner value, memoized if the value is
|
||||
lvalues:
|
||||
lvalue !== null
|
||||
? [{ place: lvalue, level: MemoizationLevel.Conditional }]
|
||||
? [{place: lvalue, level: MemoizationLevel.Conditional}]
|
||||
: [],
|
||||
rvalues: [value.collection],
|
||||
};
|
||||
}
|
||||
case "LoadLocal": {
|
||||
case 'LoadLocal': {
|
||||
return {
|
||||
// Indirection for the inner value, memoized if the value is
|
||||
lvalues:
|
||||
lvalue !== null
|
||||
? [{ place: lvalue, level: MemoizationLevel.Conditional }]
|
||||
? [{place: lvalue, level: MemoizationLevel.Conditional}]
|
||||
: [],
|
||||
rvalues: [value.place],
|
||||
};
|
||||
}
|
||||
case "LoadContext": {
|
||||
case 'LoadContext': {
|
||||
return {
|
||||
// Should never be pruned
|
||||
lvalues:
|
||||
lvalue !== null
|
||||
? [{ place: lvalue, level: MemoizationLevel.Conditional }]
|
||||
? [{place: lvalue, level: MemoizationLevel.Conditional}]
|
||||
: [],
|
||||
rvalues: [value.place],
|
||||
};
|
||||
}
|
||||
case "DeclareContext": {
|
||||
case 'DeclareContext': {
|
||||
const lvalues = [
|
||||
{ place: value.lvalue.place, level: MemoizationLevel.Memoized },
|
||||
{place: value.lvalue.place, level: MemoizationLevel.Memoized},
|
||||
];
|
||||
if (lvalue !== null) {
|
||||
lvalues.push({ place: lvalue, level: MemoizationLevel.Unmemoized });
|
||||
lvalues.push({place: lvalue, level: MemoizationLevel.Unmemoized});
|
||||
}
|
||||
return {
|
||||
lvalues,
|
||||
@@ -538,25 +538,25 @@ function computeMemoizationInputs(
|
||||
};
|
||||
}
|
||||
|
||||
case "DeclareLocal": {
|
||||
case 'DeclareLocal': {
|
||||
const lvalues = [
|
||||
{ place: value.lvalue.place, level: MemoizationLevel.Unmemoized },
|
||||
{place: value.lvalue.place, level: MemoizationLevel.Unmemoized},
|
||||
];
|
||||
if (lvalue !== null) {
|
||||
lvalues.push({ place: lvalue, level: MemoizationLevel.Unmemoized });
|
||||
lvalues.push({place: lvalue, level: MemoizationLevel.Unmemoized});
|
||||
}
|
||||
return {
|
||||
lvalues,
|
||||
rvalues: [],
|
||||
};
|
||||
}
|
||||
case "PrefixUpdate":
|
||||
case "PostfixUpdate": {
|
||||
case 'PrefixUpdate':
|
||||
case 'PostfixUpdate': {
|
||||
const lvalues = [
|
||||
{ place: value.lvalue, level: MemoizationLevel.Conditional },
|
||||
{place: value.lvalue, level: MemoizationLevel.Conditional},
|
||||
];
|
||||
if (lvalue !== null) {
|
||||
lvalues.push({ place: lvalue, level: MemoizationLevel.Conditional });
|
||||
lvalues.push({place: lvalue, level: MemoizationLevel.Conditional});
|
||||
}
|
||||
return {
|
||||
// Indirection for the inner value, memoized if the value is
|
||||
@@ -564,12 +564,12 @@ function computeMemoizationInputs(
|
||||
rvalues: [value.value],
|
||||
};
|
||||
}
|
||||
case "StoreLocal": {
|
||||
case 'StoreLocal': {
|
||||
const lvalues = [
|
||||
{ place: value.lvalue.place, level: MemoizationLevel.Conditional },
|
||||
{place: value.lvalue.place, level: MemoizationLevel.Conditional},
|
||||
];
|
||||
if (lvalue !== null) {
|
||||
lvalues.push({ place: lvalue, level: MemoizationLevel.Conditional });
|
||||
lvalues.push({place: lvalue, level: MemoizationLevel.Conditional});
|
||||
}
|
||||
return {
|
||||
// Indirection for the inner value, memoized if the value is
|
||||
@@ -577,13 +577,13 @@ function computeMemoizationInputs(
|
||||
rvalues: [value.value],
|
||||
};
|
||||
}
|
||||
case "StoreContext": {
|
||||
case 'StoreContext': {
|
||||
// Should never be pruned
|
||||
const lvalues = [
|
||||
{ place: value.lvalue.place, level: MemoizationLevel.Memoized },
|
||||
{place: value.lvalue.place, level: MemoizationLevel.Memoized},
|
||||
];
|
||||
if (lvalue !== null) {
|
||||
lvalues.push({ place: lvalue, level: MemoizationLevel.Conditional });
|
||||
lvalues.push({place: lvalue, level: MemoizationLevel.Conditional});
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -591,10 +591,10 @@ function computeMemoizationInputs(
|
||||
rvalues: [value.value],
|
||||
};
|
||||
}
|
||||
case "StoreGlobal": {
|
||||
case 'StoreGlobal': {
|
||||
const lvalues = [];
|
||||
if (lvalue !== null) {
|
||||
lvalues.push({ place: lvalue, level: MemoizationLevel.Unmemoized });
|
||||
lvalues.push({place: lvalue, level: MemoizationLevel.Unmemoized});
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -602,11 +602,11 @@ function computeMemoizationInputs(
|
||||
rvalues: [value.value],
|
||||
};
|
||||
}
|
||||
case "Destructure": {
|
||||
case 'Destructure': {
|
||||
// Indirection for the inner value, memoized if the value is
|
||||
const lvalues = [];
|
||||
if (lvalue !== null) {
|
||||
lvalues.push({ place: lvalue, level: MemoizationLevel.Conditional });
|
||||
lvalues.push({place: lvalue, level: MemoizationLevel.Conditional});
|
||||
}
|
||||
lvalues.push(...computePatternLValues(value.lvalue.pattern));
|
||||
return {
|
||||
@@ -614,14 +614,14 @@ function computeMemoizationInputs(
|
||||
rvalues: [value.value],
|
||||
};
|
||||
}
|
||||
case "ComputedLoad":
|
||||
case "PropertyLoad": {
|
||||
case 'ComputedLoad':
|
||||
case 'PropertyLoad': {
|
||||
const level = options.forceMemoizePrimitives
|
||||
? MemoizationLevel.Memoized
|
||||
: MemoizationLevel.Conditional;
|
||||
return {
|
||||
// Indirection for the inner value, memoized if the value is
|
||||
lvalues: lvalue !== null ? [{ place: lvalue, level }] : [],
|
||||
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
|
||||
/*
|
||||
* Only the object is aliased to the result, and the result only needs to be
|
||||
* memoized if the object is
|
||||
@@ -629,27 +629,27 @@ function computeMemoizationInputs(
|
||||
rvalues: [value.object],
|
||||
};
|
||||
}
|
||||
case "ComputedStore": {
|
||||
case 'ComputedStore': {
|
||||
/*
|
||||
* The object being stored to acts as an lvalue (it aliases the value), but
|
||||
* the computed key is not aliased
|
||||
*/
|
||||
const lvalues = [
|
||||
{ place: value.object, level: MemoizationLevel.Conditional },
|
||||
{place: value.object, level: MemoizationLevel.Conditional},
|
||||
];
|
||||
if (lvalue !== null) {
|
||||
lvalues.push({ place: lvalue, level: MemoizationLevel.Conditional });
|
||||
lvalues.push({place: lvalue, level: MemoizationLevel.Conditional});
|
||||
}
|
||||
return {
|
||||
lvalues,
|
||||
rvalues: [value.value],
|
||||
};
|
||||
}
|
||||
case "OptionalExpression": {
|
||||
case 'OptionalExpression': {
|
||||
// Indirection for the inner value, memoized if the value is
|
||||
const lvalues = [];
|
||||
if (lvalue !== null) {
|
||||
lvalues.push({ place: lvalue, level: MemoizationLevel.Conditional });
|
||||
lvalues.push({place: lvalue, level: MemoizationLevel.Conditional});
|
||||
}
|
||||
return {
|
||||
lvalues: lvalues,
|
||||
@@ -658,15 +658,15 @@ function computeMemoizationInputs(
|
||||
],
|
||||
};
|
||||
}
|
||||
case "CallExpression": {
|
||||
case 'CallExpression': {
|
||||
const signature = getFunctionCallSignature(
|
||||
env,
|
||||
value.callee.identifier.type
|
||||
value.callee.identifier.type,
|
||||
);
|
||||
const operands = [...eachReactiveValueOperand(value)];
|
||||
let lvalues = [];
|
||||
if (lvalue !== null) {
|
||||
lvalues.push({ place: lvalue, level: MemoizationLevel.Memoized });
|
||||
lvalues.push({place: lvalue, level: MemoizationLevel.Memoized});
|
||||
}
|
||||
if (signature?.noAlias === true) {
|
||||
return {
|
||||
@@ -676,23 +676,23 @@ function computeMemoizationInputs(
|
||||
}
|
||||
lvalues.push(
|
||||
...operands
|
||||
.filter((operand) => isMutableEffect(operand.effect, operand.loc))
|
||||
.map((place) => ({ place, level: MemoizationLevel.Memoized }))
|
||||
.filter(operand => isMutableEffect(operand.effect, operand.loc))
|
||||
.map(place => ({place, level: MemoizationLevel.Memoized})),
|
||||
);
|
||||
return {
|
||||
lvalues,
|
||||
rvalues: operands,
|
||||
};
|
||||
}
|
||||
case "MethodCall": {
|
||||
case 'MethodCall': {
|
||||
const signature = getFunctionCallSignature(
|
||||
env,
|
||||
value.property.identifier.type
|
||||
value.property.identifier.type,
|
||||
);
|
||||
const operands = [...eachReactiveValueOperand(value)];
|
||||
let lvalues = [];
|
||||
if (lvalue !== null) {
|
||||
lvalues.push({ place: lvalue, level: MemoizationLevel.Memoized });
|
||||
lvalues.push({place: lvalue, level: MemoizationLevel.Memoized});
|
||||
}
|
||||
if (signature?.noAlias === true) {
|
||||
return {
|
||||
@@ -702,39 +702,39 @@ function computeMemoizationInputs(
|
||||
}
|
||||
lvalues.push(
|
||||
...operands
|
||||
.filter((operand) => isMutableEffect(operand.effect, operand.loc))
|
||||
.map((place) => ({ place, level: MemoizationLevel.Memoized }))
|
||||
.filter(operand => isMutableEffect(operand.effect, operand.loc))
|
||||
.map(place => ({place, level: MemoizationLevel.Memoized})),
|
||||
);
|
||||
return {
|
||||
lvalues,
|
||||
rvalues: operands,
|
||||
};
|
||||
}
|
||||
case "RegExpLiteral":
|
||||
case "ObjectMethod":
|
||||
case "FunctionExpression":
|
||||
case "TaggedTemplateExpression":
|
||||
case "ArrayExpression":
|
||||
case "NewExpression":
|
||||
case "ObjectExpression":
|
||||
case "PropertyStore": {
|
||||
case 'RegExpLiteral':
|
||||
case 'ObjectMethod':
|
||||
case 'FunctionExpression':
|
||||
case 'TaggedTemplateExpression':
|
||||
case 'ArrayExpression':
|
||||
case 'NewExpression':
|
||||
case 'ObjectExpression':
|
||||
case 'PropertyStore': {
|
||||
/*
|
||||
* All of these instructions may produce new values which must be memoized if
|
||||
* reachable from a return value. Any mutable rvalue may alias any other rvalue
|
||||
*/
|
||||
const operands = [...eachReactiveValueOperand(value)];
|
||||
const lvalues = operands
|
||||
.filter((operand) => isMutableEffect(operand.effect, operand.loc))
|
||||
.map((place) => ({ place, level: MemoizationLevel.Memoized }));
|
||||
.filter(operand => isMutableEffect(operand.effect, operand.loc))
|
||||
.map(place => ({place, level: MemoizationLevel.Memoized}));
|
||||
if (lvalue !== null) {
|
||||
lvalues.push({ place: lvalue, level: MemoizationLevel.Memoized });
|
||||
lvalues.push({place: lvalue, level: MemoizationLevel.Memoized});
|
||||
}
|
||||
return {
|
||||
lvalues,
|
||||
rvalues: operands,
|
||||
};
|
||||
}
|
||||
case "ReactiveFunctionValue": {
|
||||
case 'ReactiveFunctionValue': {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected ReactiveFunctionValue node`,
|
||||
description: null,
|
||||
@@ -742,7 +742,7 @@ function computeMemoizationInputs(
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
case "UnsupportedNode": {
|
||||
case 'UnsupportedNode': {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected unsupported node`,
|
||||
description: null,
|
||||
@@ -753,7 +753,7 @@ function computeMemoizationInputs(
|
||||
default: {
|
||||
assertExhaustive(
|
||||
value,
|
||||
`Unexpected value kind \`${(value as any).kind}\``
|
||||
`Unexpected value kind \`${(value as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -762,19 +762,19 @@ function computeMemoizationInputs(
|
||||
function computePatternLValues(pattern: Pattern): Array<LValueMemoization> {
|
||||
const lvalues: Array<LValueMemoization> = [];
|
||||
switch (pattern.kind) {
|
||||
case "ArrayPattern": {
|
||||
case 'ArrayPattern': {
|
||||
for (const item of pattern.items) {
|
||||
if (item.kind === "Identifier") {
|
||||
lvalues.push({ place: item, level: MemoizationLevel.Conditional });
|
||||
} else if (item.kind === "Spread") {
|
||||
lvalues.push({ place: item.place, level: MemoizationLevel.Memoized });
|
||||
if (item.kind === 'Identifier') {
|
||||
lvalues.push({place: item, level: MemoizationLevel.Conditional});
|
||||
} else if (item.kind === 'Spread') {
|
||||
lvalues.push({place: item.place, level: MemoizationLevel.Memoized});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ObjectPattern": {
|
||||
case 'ObjectPattern': {
|
||||
for (const property of pattern.properties) {
|
||||
if (property.kind === "ObjectProperty") {
|
||||
if (property.kind === 'ObjectProperty') {
|
||||
lvalues.push({
|
||||
place: property.place,
|
||||
level: MemoizationLevel.Conditional,
|
||||
@@ -791,7 +791,7 @@ function computePatternLValues(pattern: Pattern): Array<LValueMemoization> {
|
||||
default: {
|
||||
assertExhaustive(
|
||||
pattern,
|
||||
`Unexpected pattern kind \`${(pattern as any).kind}\``
|
||||
`Unexpected pattern kind \`${(pattern as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -817,7 +817,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<State> {
|
||||
|
||||
override visitInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
state: State
|
||||
state: State,
|
||||
): void {
|
||||
this.traverseInstruction(instruction, state);
|
||||
|
||||
@@ -826,7 +826,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<State> {
|
||||
this.env,
|
||||
instruction.value,
|
||||
instruction.lvalue,
|
||||
this.options
|
||||
this.options,
|
||||
);
|
||||
|
||||
// Associate all the rvalues with the instruction's scope if it has one
|
||||
@@ -837,7 +837,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<State> {
|
||||
}
|
||||
|
||||
// Add the operands as dependencies of all lvalues.
|
||||
for (const { place: lvalue, level } of aliasing.lvalues) {
|
||||
for (const {place: lvalue, level} of aliasing.lvalues) {
|
||||
const lvalueId =
|
||||
state.definitions.get(lvalue.identifier.id) ?? lvalue.identifier.id;
|
||||
let node = state.identifiers.get(lvalueId);
|
||||
@@ -868,23 +868,23 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<State> {
|
||||
state.visitOperand(instruction.id, lvalue, lvalueId);
|
||||
}
|
||||
|
||||
if (instruction.value.kind === "LoadLocal" && instruction.lvalue !== null) {
|
||||
if (instruction.value.kind === 'LoadLocal' && instruction.lvalue !== null) {
|
||||
state.definitions.set(
|
||||
instruction.lvalue.identifier.id,
|
||||
instruction.value.place.identifier.id
|
||||
instruction.value.place.identifier.id,
|
||||
);
|
||||
} else if (
|
||||
instruction.value.kind === "CallExpression" ||
|
||||
instruction.value.kind === "MethodCall"
|
||||
instruction.value.kind === 'CallExpression' ||
|
||||
instruction.value.kind === 'MethodCall'
|
||||
) {
|
||||
let callee =
|
||||
instruction.value.kind === "CallExpression"
|
||||
instruction.value.kind === 'CallExpression'
|
||||
? instruction.value.callee
|
||||
: instruction.value.property;
|
||||
if (getHookKind(state.env, callee.identifier) != null) {
|
||||
const signature = getFunctionCallSignature(
|
||||
this.env,
|
||||
callee.identifier.type
|
||||
callee.identifier.type,
|
||||
);
|
||||
/*
|
||||
* Hook values are assumed to escape by default since they can be inputs
|
||||
@@ -896,7 +896,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<State> {
|
||||
return;
|
||||
}
|
||||
for (const operand of instruction.value.args) {
|
||||
const place = operand.kind === "Spread" ? operand.place : operand;
|
||||
const place = operand.kind === 'Spread' ? operand.place : operand;
|
||||
state.escapingValues.add(place.identifier.id);
|
||||
}
|
||||
}
|
||||
@@ -905,11 +905,11 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<State> {
|
||||
|
||||
override visitTerminal(
|
||||
stmt: ReactiveTerminalStatement<ReactiveTerminal>,
|
||||
state: State
|
||||
state: State,
|
||||
): void {
|
||||
this.traverseTerminal(stmt, state);
|
||||
|
||||
if (stmt.terminal.kind === "return") {
|
||||
if (stmt.terminal.kind === 'return') {
|
||||
state.escapingValues.add(stmt.terminal.value.identifier.id);
|
||||
}
|
||||
}
|
||||
@@ -923,7 +923,7 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
|
||||
|
||||
override transformScope(
|
||||
scopeBlock: ReactiveScopeBlock,
|
||||
state: Set<IdentifierId>
|
||||
state: Set<IdentifierId>,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitScope(scopeBlock, state);
|
||||
|
||||
@@ -941,22 +941,22 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
|
||||
scopeBlock.scope.reassignments.size === 0) ||
|
||||
scopeBlock.scope.earlyReturnValue !== null
|
||||
) {
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
|
||||
const hasMemoizedOutput =
|
||||
Array.from(scopeBlock.scope.declarations.keys()).some((id) =>
|
||||
state.has(id)
|
||||
Array.from(scopeBlock.scope.declarations.keys()).some(id =>
|
||||
state.has(id),
|
||||
) ||
|
||||
Array.from(scopeBlock.scope.reassignments).some((identifier) =>
|
||||
state.has(identifier.id)
|
||||
Array.from(scopeBlock.scope.reassignments).some(identifier =>
|
||||
state.has(identifier.id),
|
||||
);
|
||||
if (hasMemoizedOutput) {
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
} else {
|
||||
this.prunedScopes.add(scopeBlock.scope.id);
|
||||
return {
|
||||
kind: "replace-many",
|
||||
kind: 'replace-many',
|
||||
value: scopeBlock.instructions,
|
||||
};
|
||||
}
|
||||
@@ -964,7 +964,7 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
|
||||
|
||||
override transformInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
state: Set<IdentifierId>
|
||||
state: Set<IdentifierId>,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.traverseInstruction(instruction, state);
|
||||
|
||||
@@ -973,7 +973,7 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
|
||||
* need to be memoized. Remove associated `Memoize` instructions so that
|
||||
* we don't report false positives on "missing" memoization of these values.
|
||||
*/
|
||||
if (instruction.value.kind === "FinishMemoize") {
|
||||
if (instruction.value.kind === 'FinishMemoize') {
|
||||
const identifier = instruction.value.decl.identifier;
|
||||
if (
|
||||
identifier.scope !== null &&
|
||||
@@ -983,6 +983,6 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
|
||||
}
|
||||
}
|
||||
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -11,10 +11,10 @@ import {
|
||||
ReactiveInstruction,
|
||||
ReactiveScopeBlock,
|
||||
isStableType,
|
||||
} from "../HIR";
|
||||
import { eachPatternOperand } from "../HIR/visitors";
|
||||
import { collectReactiveIdentifiers } from "./CollectReactiveIdentifiers";
|
||||
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
} from '../HIR';
|
||||
import {eachPatternOperand} from '../HIR/visitors';
|
||||
import {collectReactiveIdentifiers} from './CollectReactiveIdentifiers';
|
||||
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
|
||||
/*
|
||||
* PropagateScopeDependencies infers dependencies without considering whether dependencies
|
||||
@@ -32,19 +32,19 @@ type ReactiveIdentifiers = Set<IdentifierId>;
|
||||
class Visitor extends ReactiveFunctionVisitor<ReactiveIdentifiers> {
|
||||
override visitInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
state: ReactiveIdentifiers
|
||||
state: ReactiveIdentifiers,
|
||||
): void {
|
||||
this.traverseInstruction(instruction, state);
|
||||
|
||||
const { lvalue, value } = instruction;
|
||||
const {lvalue, value} = instruction;
|
||||
switch (value.kind) {
|
||||
case "LoadLocal": {
|
||||
case 'LoadLocal': {
|
||||
if (lvalue !== null && state.has(value.place.identifier.id)) {
|
||||
state.add(lvalue.identifier.id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "StoreLocal": {
|
||||
case 'StoreLocal': {
|
||||
if (state.has(value.value.identifier.id)) {
|
||||
state.add(value.lvalue.place.identifier.id);
|
||||
if (lvalue !== null) {
|
||||
@@ -53,7 +53,7 @@ class Visitor extends ReactiveFunctionVisitor<ReactiveIdentifiers> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Destructure": {
|
||||
case 'Destructure': {
|
||||
if (state.has(value.value.identifier.id)) {
|
||||
for (const lvalue of eachPatternOperand(value.lvalue.pattern)) {
|
||||
if (isStableType(lvalue.identifier)) {
|
||||
@@ -67,7 +67,7 @@ class Visitor extends ReactiveFunctionVisitor<ReactiveIdentifiers> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "PropertyLoad": {
|
||||
case 'PropertyLoad': {
|
||||
if (
|
||||
lvalue !== null &&
|
||||
state.has(value.object.identifier.id) &&
|
||||
@@ -77,7 +77,7 @@ class Visitor extends ReactiveFunctionVisitor<ReactiveIdentifiers> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ComputedLoad": {
|
||||
case 'ComputedLoad': {
|
||||
if (
|
||||
lvalue !== null &&
|
||||
(state.has(value.object.identifier.id) ||
|
||||
@@ -92,7 +92,7 @@ class Visitor extends ReactiveFunctionVisitor<ReactiveIdentifiers> {
|
||||
|
||||
override visitScope(
|
||||
scopeBlock: ReactiveScopeBlock,
|
||||
state: ReactiveIdentifiers
|
||||
state: ReactiveIdentifiers,
|
||||
): void {
|
||||
this.traverseScope(scopeBlock, state);
|
||||
for (const dep of scopeBlock.scope.dependencies) {
|
||||
|
||||
+3
-3
@@ -11,8 +11,8 @@ import {
|
||||
Place,
|
||||
ReactiveFunction,
|
||||
ReactiveInstruction,
|
||||
} from "../HIR/HIR";
|
||||
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
} from '../HIR/HIR';
|
||||
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
|
||||
/*
|
||||
* Nulls out lvalues for temporary variables that are never accessed later. This only
|
||||
@@ -34,7 +34,7 @@ class Visitor extends ReactiveFunctionVisitor<LValues> {
|
||||
}
|
||||
override visitInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
state: LValues
|
||||
state: LValues,
|
||||
): void {
|
||||
this.traverseInstruction(instruction, state);
|
||||
if (
|
||||
|
||||
+11
-11
@@ -10,12 +10,12 @@ import {
|
||||
ReactiveFunction,
|
||||
ReactiveStatement,
|
||||
ReactiveTerminalStatement,
|
||||
} from "../HIR/HIR";
|
||||
} from '../HIR/HIR';
|
||||
import {
|
||||
ReactiveFunctionTransform,
|
||||
Transformed,
|
||||
visitReactiveFunction,
|
||||
} from "./visitors";
|
||||
} from './visitors';
|
||||
|
||||
/*
|
||||
* Flattens labeled terminals where the label is not reachable, and
|
||||
@@ -31,36 +31,36 @@ type Labels = Set<BlockId>;
|
||||
class Transform extends ReactiveFunctionTransform<Labels> {
|
||||
override transformTerminal(
|
||||
stmt: ReactiveTerminalStatement,
|
||||
state: Labels
|
||||
state: Labels,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.traverseTerminal(stmt, state);
|
||||
const { terminal } = stmt;
|
||||
const {terminal} = stmt;
|
||||
if (
|
||||
(terminal.kind === "break" || terminal.kind === "continue") &&
|
||||
terminal.targetKind === "labeled"
|
||||
(terminal.kind === 'break' || terminal.kind === 'continue') &&
|
||||
terminal.targetKind === 'labeled'
|
||||
) {
|
||||
state.add(terminal.target);
|
||||
}
|
||||
// Is this terminal reachable via a break/continue to its label?
|
||||
const isReachableLabel = stmt.label !== null && state.has(stmt.label.id);
|
||||
if (stmt.terminal.kind === "label" && !isReachableLabel) {
|
||||
if (stmt.terminal.kind === 'label' && !isReachableLabel) {
|
||||
// Flatten labeled terminals where the label isn't necessary
|
||||
const block = [...stmt.terminal.block];
|
||||
const last = block.at(-1);
|
||||
if (
|
||||
last !== undefined &&
|
||||
last.kind === "terminal" &&
|
||||
last.terminal.kind === "break" &&
|
||||
last.kind === 'terminal' &&
|
||||
last.terminal.kind === 'break' &&
|
||||
last.terminal.target === null
|
||||
) {
|
||||
block.pop();
|
||||
}
|
||||
return { kind: "replace-many", value: block };
|
||||
return {kind: 'replace-many', value: block};
|
||||
} else {
|
||||
if (!isReachableLabel && stmt.label != null) {
|
||||
stmt.label.implicit = true;
|
||||
}
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -10,12 +10,12 @@ import {
|
||||
ReactiveScopeBlock,
|
||||
ReactiveStatement,
|
||||
ReactiveTerminalStatement,
|
||||
} from "../HIR/HIR";
|
||||
} from '../HIR/HIR';
|
||||
import {
|
||||
ReactiveFunctionTransform,
|
||||
Transformed,
|
||||
visitReactiveFunction,
|
||||
} from "./visitors";
|
||||
} from './visitors';
|
||||
|
||||
// Converts scopes without outputs into regular blocks.
|
||||
export function pruneUnusedScopes(fn: ReactiveFunction): void {
|
||||
@@ -31,15 +31,15 @@ type State = {
|
||||
class Transform extends ReactiveFunctionTransform<State> {
|
||||
override visitTerminal(stmt: ReactiveTerminalStatement, state: State): void {
|
||||
this.traverseTerminal(stmt, state);
|
||||
if (stmt.terminal.kind === "return") {
|
||||
if (stmt.terminal.kind === 'return') {
|
||||
state.hasReturnStatement = true;
|
||||
}
|
||||
}
|
||||
override transformScope(
|
||||
scopeBlock: ReactiveScopeBlock,
|
||||
_state: State
|
||||
_state: State,
|
||||
): Transformed<ReactiveStatement> {
|
||||
const scopeState: State = { hasReturnStatement: false };
|
||||
const scopeState: State = {hasReturnStatement: false};
|
||||
this.visitScope(scopeBlock, scopeState);
|
||||
if (
|
||||
!scopeState.hasReturnStatement &&
|
||||
@@ -52,15 +52,15 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
!hasOwnDeclaration(scopeBlock))
|
||||
) {
|
||||
return {
|
||||
kind: "replace",
|
||||
kind: 'replace',
|
||||
value: {
|
||||
kind: "pruned-scope",
|
||||
kind: 'pruned-scope',
|
||||
scope: scopeBlock.scope,
|
||||
instructions: scopeBlock.instructions,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-11
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
@@ -21,9 +21,9 @@ import {
|
||||
isPromotedJsxTemporary,
|
||||
isPromotedTemporary,
|
||||
makeIdentifierName,
|
||||
} from "../HIR/HIR";
|
||||
import { collectReferencedGlobals } from "./CollectReferencedGlobals";
|
||||
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
} from '../HIR/HIR';
|
||||
import {collectReferencedGlobals} from './CollectReferencedGlobals';
|
||||
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
|
||||
/**
|
||||
* Ensures that each named variable in the given function has a unique name
|
||||
@@ -55,11 +55,11 @@ export function renameVariables(fn: ReactiveFunction): Set<string> {
|
||||
function renameVariablesImpl(
|
||||
fn: ReactiveFunction,
|
||||
visitor: Visitor,
|
||||
scopes: Scopes
|
||||
scopes: Scopes,
|
||||
): void {
|
||||
scopes.enter(() => {
|
||||
for (const param of fn.params) {
|
||||
if (param.kind === "Identifier") {
|
||||
if (param.kind === 'Identifier') {
|
||||
scopes.visit(param.identifier);
|
||||
} else {
|
||||
scopes.visit(param.place.identifier);
|
||||
@@ -87,7 +87,7 @@ class Visitor extends ReactiveFunctionVisitor<Scopes> {
|
||||
|
||||
override visitPrunedScope(
|
||||
scopeBlock: PrunedReactiveScopeBlock,
|
||||
state: Scopes
|
||||
state: Scopes,
|
||||
): void {
|
||||
this.traverseBlock(scopeBlock.instructions, state);
|
||||
}
|
||||
@@ -102,10 +102,10 @@ class Visitor extends ReactiveFunctionVisitor<Scopes> {
|
||||
override visitValue(
|
||||
id: InstructionId,
|
||||
value: ReactiveValue,
|
||||
state: Scopes
|
||||
state: Scopes,
|
||||
): void {
|
||||
this.traverseValue(id, value, state);
|
||||
if (value.kind === "FunctionExpression" || value.kind === "ObjectMethod") {
|
||||
if (value.kind === 'FunctionExpression' || value.kind === 'ObjectMethod') {
|
||||
this.visitHirFunction(value.loweredFunc.func, state);
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ class Visitor extends ReactiveFunctionVisitor<Scopes> {
|
||||
_id: InstructionId,
|
||||
_dependencies: Array<Place>,
|
||||
_fn: ReactiveFunction,
|
||||
_state: Scopes
|
||||
_state: Scopes,
|
||||
): void {
|
||||
renameVariablesImpl(_fn, this, _state);
|
||||
}
|
||||
@@ -180,7 +180,7 @@ class Scopes {
|
||||
fn();
|
||||
const last = this.#stack.pop();
|
||||
CompilerError.invariant(last === next, {
|
||||
reason: "Mismatch push/pop calls",
|
||||
reason: 'Mismatch push/pop calls',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
|
||||
+11
-11
@@ -4,9 +4,9 @@ import {
|
||||
ReactiveScopeBlock,
|
||||
ReactiveTerminalStatement,
|
||||
makeBlockId,
|
||||
} from "../HIR";
|
||||
import { getOrInsertDefault } from "../Utils/utils";
|
||||
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
} from '../HIR';
|
||||
import {getOrInsertDefault} from '../Utils/utils';
|
||||
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
|
||||
export function stabilizeBlockIds(fn: ReactiveFunction): void {
|
||||
const referenced: Set<BlockId> = new Set();
|
||||
@@ -22,7 +22,7 @@ export function stabilizeBlockIds(fn: ReactiveFunction): void {
|
||||
|
||||
class CollectReferencedLabels extends ReactiveFunctionVisitor<Set<BlockId>> {
|
||||
override visitScope(scope: ReactiveScopeBlock, state: Set<BlockId>): void {
|
||||
const { earlyReturnValue } = scope.scope;
|
||||
const {earlyReturnValue} = scope.scope;
|
||||
if (earlyReturnValue != null) {
|
||||
state.add(earlyReturnValue.label);
|
||||
}
|
||||
@@ -30,7 +30,7 @@ class CollectReferencedLabels extends ReactiveFunctionVisitor<Set<BlockId>> {
|
||||
}
|
||||
override visitTerminal(
|
||||
stmt: ReactiveTerminalStatement,
|
||||
state: Set<BlockId>
|
||||
state: Set<BlockId>,
|
||||
): void {
|
||||
if (stmt.label != null) {
|
||||
if (!stmt.label.implicit) {
|
||||
@@ -44,14 +44,14 @@ class CollectReferencedLabels extends ReactiveFunctionVisitor<Set<BlockId>> {
|
||||
class RewriteBlockIds extends ReactiveFunctionVisitor<Map<BlockId, BlockId>> {
|
||||
override visitScope(
|
||||
scope: ReactiveScopeBlock,
|
||||
state: Map<BlockId, BlockId>
|
||||
state: Map<BlockId, BlockId>,
|
||||
): void {
|
||||
const { earlyReturnValue } = scope.scope;
|
||||
const {earlyReturnValue} = scope.scope;
|
||||
if (earlyReturnValue != null) {
|
||||
const rewrittenId = getOrInsertDefault(
|
||||
state,
|
||||
earlyReturnValue.label,
|
||||
state.size
|
||||
state.size,
|
||||
);
|
||||
earlyReturnValue.label = makeBlockId(rewrittenId);
|
||||
}
|
||||
@@ -59,7 +59,7 @@ class RewriteBlockIds extends ReactiveFunctionVisitor<Map<BlockId, BlockId>> {
|
||||
}
|
||||
override visitTerminal(
|
||||
stmt: ReactiveTerminalStatement,
|
||||
state: Map<BlockId, BlockId>
|
||||
state: Map<BlockId, BlockId>,
|
||||
): void {
|
||||
if (stmt.label != null) {
|
||||
const rewrittenId = getOrInsertDefault(state, stmt.label.id, state.size);
|
||||
@@ -67,11 +67,11 @@ class RewriteBlockIds extends ReactiveFunctionVisitor<Map<BlockId, BlockId>> {
|
||||
}
|
||||
|
||||
const terminal = stmt.terminal;
|
||||
if (terminal.kind === "break" || terminal.kind === "continue") {
|
||||
if (terminal.kind === 'break' || terminal.kind === 'continue') {
|
||||
const rewrittenId = getOrInsertDefault(
|
||||
state,
|
||||
terminal.target,
|
||||
state.size
|
||||
state.size,
|
||||
);
|
||||
terminal.target = makeBlockId(rewrittenId);
|
||||
}
|
||||
|
||||
@@ -5,39 +5,36 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export { alignObjectMethodScopes } from "./AlignObjectMethodScopes";
|
||||
export { alignReactiveScopesToBlockScopes } from "./AlignReactiveScopesToBlockScopes";
|
||||
export { assertScopeInstructionsWithinScopes } from "./AssertScopeInstructionsWithinScope";
|
||||
export { assertWellFormedBreakTargets } from "./AssertWellFormedBreakTargets";
|
||||
export { buildReactiveBlocks } from "./BuildReactiveBlocks";
|
||||
export { buildReactiveFunction } from "./BuildReactiveFunction";
|
||||
export {
|
||||
codegenFunction,
|
||||
type CodegenFunction,
|
||||
} from "./CodegenReactiveFunction";
|
||||
export { extractScopeDeclarationsFromDestructuring } from "./ExtractScopeDeclarationsFromDestructuring";
|
||||
export { flattenReactiveLoops } from "./FlattenReactiveLoops";
|
||||
export { flattenScopesWithHooksOrUse } from "./FlattenScopesWithHooksOrUse";
|
||||
export { inferReactiveScopeVariables } from "./InferReactiveScopeVariables";
|
||||
export { memoizeFbtAndMacroOperandsInSameScope as memoizeFbtOperandsInSameScope } from "./MemoizeFbtAndMacroOperandsInSameScope";
|
||||
export { mergeOverlappingReactiveScopes } from "./MergeOverlappingReactiveScopes";
|
||||
export { mergeReactiveScopesThatInvalidateTogether } from "./MergeReactiveScopesThatInvalidateTogether";
|
||||
export { printReactiveFunction } from "./PrintReactiveFunction";
|
||||
export { promoteUsedTemporaries } from "./PromoteUsedTemporaries";
|
||||
export { propagateEarlyReturns } from "./PropagateEarlyReturns";
|
||||
export { propagateScopeDependencies } from "./PropagateScopeDependencies";
|
||||
export { pruneAllReactiveScopes } from "./PruneAllReactiveScopes";
|
||||
export { pruneHoistedContexts } from "./PruneHoistedContexts";
|
||||
export { pruneNonEscapingScopes } from "./PruneNonEscapingScopes";
|
||||
export { pruneNonReactiveDependencies } from "./PruneNonReactiveDependencies";
|
||||
export { pruneTemporaryLValues as pruneUnusedLValues } from "./PruneTemporaryLValues";
|
||||
export { pruneUnusedLabels } from "./PruneUnusedLabels";
|
||||
export { pruneUnusedScopes } from "./PruneUnusedScopes";
|
||||
export { renameVariables } from "./RenameVariables";
|
||||
export { stabilizeBlockIds } from "./StabilizeBlockIds";
|
||||
export {alignObjectMethodScopes} from './AlignObjectMethodScopes';
|
||||
export {alignReactiveScopesToBlockScopes} from './AlignReactiveScopesToBlockScopes';
|
||||
export {assertScopeInstructionsWithinScopes} from './AssertScopeInstructionsWithinScope';
|
||||
export {assertWellFormedBreakTargets} from './AssertWellFormedBreakTargets';
|
||||
export {buildReactiveBlocks} from './BuildReactiveBlocks';
|
||||
export {buildReactiveFunction} from './BuildReactiveFunction';
|
||||
export {codegenFunction, type CodegenFunction} from './CodegenReactiveFunction';
|
||||
export {extractScopeDeclarationsFromDestructuring} from './ExtractScopeDeclarationsFromDestructuring';
|
||||
export {flattenReactiveLoops} from './FlattenReactiveLoops';
|
||||
export {flattenScopesWithHooksOrUse} from './FlattenScopesWithHooksOrUse';
|
||||
export {inferReactiveScopeVariables} from './InferReactiveScopeVariables';
|
||||
export {memoizeFbtAndMacroOperandsInSameScope as memoizeFbtOperandsInSameScope} from './MemoizeFbtAndMacroOperandsInSameScope';
|
||||
export {mergeOverlappingReactiveScopes} from './MergeOverlappingReactiveScopes';
|
||||
export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesThatInvalidateTogether';
|
||||
export {printReactiveFunction} from './PrintReactiveFunction';
|
||||
export {promoteUsedTemporaries} from './PromoteUsedTemporaries';
|
||||
export {propagateEarlyReturns} from './PropagateEarlyReturns';
|
||||
export {propagateScopeDependencies} from './PropagateScopeDependencies';
|
||||
export {pruneAllReactiveScopes} from './PruneAllReactiveScopes';
|
||||
export {pruneHoistedContexts} from './PruneHoistedContexts';
|
||||
export {pruneNonEscapingScopes} from './PruneNonEscapingScopes';
|
||||
export {pruneNonReactiveDependencies} from './PruneNonReactiveDependencies';
|
||||
export {pruneTemporaryLValues as pruneUnusedLValues} from './PruneTemporaryLValues';
|
||||
export {pruneUnusedLabels} from './PruneUnusedLabels';
|
||||
export {pruneUnusedScopes} from './PruneUnusedScopes';
|
||||
export {renameVariables} from './RenameVariables';
|
||||
export {stabilizeBlockIds} from './StabilizeBlockIds';
|
||||
export {
|
||||
ReactiveFunctionTransform,
|
||||
eachReactiveValueOperand,
|
||||
visitReactiveFunction,
|
||||
type Transformed,
|
||||
} from "./visitors";
|
||||
} from './visitors';
|
||||
|
||||
@@ -18,18 +18,18 @@ import {
|
||||
ReactiveTerminal,
|
||||
ReactiveTerminalStatement,
|
||||
ReactiveValue,
|
||||
} from "../HIR/HIR";
|
||||
} from '../HIR/HIR';
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionValueOperand,
|
||||
eachTerminalOperand,
|
||||
} from "../HIR/visitors";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
} from '../HIR/visitors';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
export function visitReactiveFunction<TState>(
|
||||
fn: ReactiveFunction,
|
||||
visitor: ReactiveFunctionVisitor<TState>,
|
||||
state: TState
|
||||
state: TState,
|
||||
): void {
|
||||
visitor.visitBlock(fn.body, state);
|
||||
}
|
||||
@@ -43,7 +43,7 @@ export class ReactiveFunctionVisitor<TState = void> {
|
||||
_id: InstructionId,
|
||||
_dependencies: Array<Place>,
|
||||
_fn: ReactiveFunction,
|
||||
_state: TState
|
||||
_state: TState,
|
||||
): void {}
|
||||
|
||||
visitValue(id: InstructionId, value: ReactiveValue, state: TState): void {
|
||||
@@ -51,34 +51,34 @@ export class ReactiveFunctionVisitor<TState = void> {
|
||||
}
|
||||
traverseValue(id: InstructionId, value: ReactiveValue, state: TState): void {
|
||||
switch (value.kind) {
|
||||
case "OptionalExpression": {
|
||||
case 'OptionalExpression': {
|
||||
this.visitValue(id, value.value, state);
|
||||
break;
|
||||
}
|
||||
case "LogicalExpression": {
|
||||
case 'LogicalExpression': {
|
||||
this.visitValue(id, value.left, state);
|
||||
this.visitValue(id, value.right, state);
|
||||
break;
|
||||
}
|
||||
case "ConditionalExpression": {
|
||||
case 'ConditionalExpression': {
|
||||
this.visitValue(id, value.test, state);
|
||||
this.visitValue(id, value.consequent, state);
|
||||
this.visitValue(id, value.alternate, state);
|
||||
break;
|
||||
}
|
||||
case "SequenceExpression": {
|
||||
case 'SequenceExpression': {
|
||||
for (const instr of value.instructions) {
|
||||
this.visitInstruction(instr, state);
|
||||
}
|
||||
this.visitValue(value.id, value.value, state);
|
||||
break;
|
||||
}
|
||||
case "ReactiveFunctionValue": {
|
||||
case 'ReactiveFunctionValue': {
|
||||
this.visitReactiveFunctionValue(
|
||||
id,
|
||||
value.dependencies,
|
||||
value.fn,
|
||||
state
|
||||
state,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -105,24 +105,24 @@ export class ReactiveFunctionVisitor<TState = void> {
|
||||
this.traverseTerminal(stmt, state);
|
||||
}
|
||||
traverseTerminal(stmt: ReactiveTerminalStatement, state: TState): void {
|
||||
const { terminal } = stmt;
|
||||
const {terminal} = stmt;
|
||||
if (terminal.id !== null) {
|
||||
this.visitID(terminal.id, state);
|
||||
}
|
||||
switch (terminal.kind) {
|
||||
case "break":
|
||||
case "continue": {
|
||||
case 'break':
|
||||
case 'continue': {
|
||||
break;
|
||||
}
|
||||
case "return": {
|
||||
case 'return': {
|
||||
this.visitPlace(terminal.id, terminal.value, state);
|
||||
break;
|
||||
}
|
||||
case "throw": {
|
||||
case 'throw': {
|
||||
this.visitPlace(terminal.id, terminal.value, state);
|
||||
break;
|
||||
}
|
||||
case "for": {
|
||||
case 'for': {
|
||||
this.visitValue(terminal.id, terminal.init, state);
|
||||
this.visitValue(terminal.id, terminal.test, state);
|
||||
this.visitBlock(terminal.loop, state);
|
||||
@@ -131,28 +131,28 @@ export class ReactiveFunctionVisitor<TState = void> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "for-of": {
|
||||
case 'for-of': {
|
||||
this.visitValue(terminal.id, terminal.init, state);
|
||||
this.visitValue(terminal.id, terminal.test, state);
|
||||
this.visitBlock(terminal.loop, state);
|
||||
break;
|
||||
}
|
||||
case "for-in": {
|
||||
case 'for-in': {
|
||||
this.visitValue(terminal.id, terminal.init, state);
|
||||
this.visitBlock(terminal.loop, state);
|
||||
break;
|
||||
}
|
||||
case "do-while": {
|
||||
case 'do-while': {
|
||||
this.visitBlock(terminal.loop, state);
|
||||
this.visitValue(terminal.id, terminal.test, state);
|
||||
break;
|
||||
}
|
||||
case "while": {
|
||||
case 'while': {
|
||||
this.visitValue(terminal.id, terminal.test, state);
|
||||
this.visitBlock(terminal.loop, state);
|
||||
break;
|
||||
}
|
||||
case "if": {
|
||||
case 'if': {
|
||||
this.visitPlace(terminal.id, terminal.test, state);
|
||||
this.visitBlock(terminal.consequent, state);
|
||||
if (terminal.alternate !== null) {
|
||||
@@ -160,7 +160,7 @@ export class ReactiveFunctionVisitor<TState = void> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "switch": {
|
||||
case 'switch': {
|
||||
this.visitPlace(terminal.id, terminal.test, state);
|
||||
for (const case_ of terminal.cases) {
|
||||
if (case_.test !== null) {
|
||||
@@ -172,11 +172,11 @@ export class ReactiveFunctionVisitor<TState = void> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "label": {
|
||||
case 'label': {
|
||||
this.visitBlock(terminal.block, state);
|
||||
break;
|
||||
}
|
||||
case "try": {
|
||||
case 'try': {
|
||||
this.visitBlock(terminal.block, state);
|
||||
this.visitBlock(terminal.handler, state);
|
||||
break;
|
||||
@@ -184,7 +184,7 @@ export class ReactiveFunctionVisitor<TState = void> {
|
||||
default: {
|
||||
assertExhaustive(
|
||||
terminal,
|
||||
`Unexpected terminal kind \`${(terminal as any).kind}\``
|
||||
`Unexpected terminal kind \`${(terminal as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -202,7 +202,7 @@ export class ReactiveFunctionVisitor<TState = void> {
|
||||
}
|
||||
traversePrunedScope(
|
||||
scopeBlock: PrunedReactiveScopeBlock,
|
||||
state: TState
|
||||
state: TState,
|
||||
): void {
|
||||
this.visitBlock(scopeBlock.instructions, state);
|
||||
}
|
||||
@@ -213,26 +213,26 @@ export class ReactiveFunctionVisitor<TState = void> {
|
||||
traverseBlock(block: ReactiveBlock, state: TState): void {
|
||||
for (const instr of block) {
|
||||
switch (instr.kind) {
|
||||
case "instruction": {
|
||||
case 'instruction': {
|
||||
this.visitInstruction(instr.instruction, state);
|
||||
break;
|
||||
}
|
||||
case "scope": {
|
||||
case 'scope': {
|
||||
this.visitScope(instr, state);
|
||||
break;
|
||||
}
|
||||
case "pruned-scope": {
|
||||
case 'pruned-scope': {
|
||||
this.visitPrunedScope(instr, state);
|
||||
break;
|
||||
}
|
||||
case "terminal": {
|
||||
case 'terminal': {
|
||||
this.visitTerminal(instr, state);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
instr,
|
||||
`Unexpected instruction kind \`${(instr as any).kind}\``
|
||||
`Unexpected instruction kind \`${(instr as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -241,15 +241,15 @@ export class ReactiveFunctionVisitor<TState = void> {
|
||||
|
||||
visitHirFunction(fn: HIRFunction, state: TState): void {
|
||||
for (const param of fn.params) {
|
||||
const place = param.kind === "Identifier" ? param : param.place;
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
this.visitParam(place, state);
|
||||
}
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
this.visitInstruction(instr, state);
|
||||
if (
|
||||
instr.value.kind === "FunctionExpression" ||
|
||||
instr.value.kind === "ObjectMethod"
|
||||
instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod'
|
||||
) {
|
||||
this.visitHirFunction(instr.value.loweredFunc.func, state);
|
||||
}
|
||||
@@ -262,14 +262,14 @@ export class ReactiveFunctionVisitor<TState = void> {
|
||||
}
|
||||
|
||||
export type TransformedValue =
|
||||
| { kind: "keep" }
|
||||
| { kind: "replace"; value: ReactiveValue };
|
||||
| {kind: 'keep'}
|
||||
| {kind: 'replace'; value: ReactiveValue};
|
||||
|
||||
export type Transformed<T> =
|
||||
| { kind: "remove" }
|
||||
| { kind: "keep" }
|
||||
| { kind: "replace"; value: T }
|
||||
| { kind: "replace-many"; value: Array<T> };
|
||||
| {kind: 'remove'}
|
||||
| {kind: 'keep'}
|
||||
| {kind: 'replace'; value: T}
|
||||
| {kind: 'replace-many'; value: Array<T>};
|
||||
|
||||
export class ReactiveFunctionTransform<
|
||||
TState = void,
|
||||
@@ -280,48 +280,48 @@ export class ReactiveFunctionTransform<
|
||||
const instr = block[i]!;
|
||||
let transformed: Transformed<ReactiveStatement>;
|
||||
switch (instr.kind) {
|
||||
case "instruction": {
|
||||
case 'instruction': {
|
||||
transformed = this.transformInstruction(instr.instruction, state);
|
||||
break;
|
||||
}
|
||||
case "scope": {
|
||||
case 'scope': {
|
||||
transformed = this.transformScope(instr, state);
|
||||
break;
|
||||
}
|
||||
case "pruned-scope": {
|
||||
case 'pruned-scope': {
|
||||
transformed = this.transformPrunedScope(instr, state);
|
||||
break;
|
||||
}
|
||||
case "terminal": {
|
||||
case 'terminal': {
|
||||
transformed = this.transformTerminal(instr, state);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
instr,
|
||||
`Unexpected instruction kind \`${(instr as any).kind}\``
|
||||
`Unexpected instruction kind \`${(instr as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
switch (transformed.kind) {
|
||||
case "keep": {
|
||||
case 'keep': {
|
||||
if (nextBlock !== null) {
|
||||
nextBlock.push(instr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "remove": {
|
||||
case 'remove': {
|
||||
if (nextBlock === null) {
|
||||
nextBlock = block.slice(0, i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "replace": {
|
||||
case 'replace': {
|
||||
nextBlock ??= block.slice(0, i);
|
||||
nextBlock.push(transformed.value);
|
||||
break;
|
||||
}
|
||||
case "replace-many": {
|
||||
case 'replace-many': {
|
||||
nextBlock ??= block.slice(0, i);
|
||||
nextBlock.push(...transformed.value);
|
||||
break;
|
||||
@@ -336,112 +336,112 @@ export class ReactiveFunctionTransform<
|
||||
|
||||
transformInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
state: TState
|
||||
state: TState,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitInstruction(instruction, state);
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
|
||||
transformTerminal(
|
||||
stmt: ReactiveTerminalStatement,
|
||||
state: TState
|
||||
state: TState,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitTerminal(stmt, state);
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
|
||||
transformScope(
|
||||
scope: ReactiveScopeBlock,
|
||||
state: TState
|
||||
state: TState,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitScope(scope, state);
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
|
||||
transformPrunedScope(
|
||||
scope: PrunedReactiveScopeBlock,
|
||||
state: TState
|
||||
state: TState,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitPrunedScope(scope, state);
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
|
||||
transformValue(
|
||||
id: InstructionId,
|
||||
value: ReactiveValue,
|
||||
state: TState
|
||||
state: TState,
|
||||
): TransformedValue {
|
||||
this.visitValue(id, value, state);
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
|
||||
transformReactiveFunctionValue(
|
||||
id: InstructionId,
|
||||
dependencies: Array<Place>,
|
||||
fn: ReactiveFunction,
|
||||
state: TState
|
||||
): { kind: "keep" } | { kind: "replace"; value: ReactiveFunction } {
|
||||
state: TState,
|
||||
): {kind: 'keep'} | {kind: 'replace'; value: ReactiveFunction} {
|
||||
this.visitReactiveFunctionValue(id, dependencies, fn, state);
|
||||
return { kind: "keep" };
|
||||
return {kind: 'keep'};
|
||||
}
|
||||
|
||||
override traverseValue(
|
||||
id: InstructionId,
|
||||
value: ReactiveValue,
|
||||
state: TState
|
||||
state: TState,
|
||||
): void {
|
||||
switch (value.kind) {
|
||||
case "OptionalExpression": {
|
||||
case 'OptionalExpression': {
|
||||
const nextValue = this.transformValue(id, value.value, state);
|
||||
if (nextValue.kind === "replace") {
|
||||
if (nextValue.kind === 'replace') {
|
||||
value.value = nextValue.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "LogicalExpression": {
|
||||
case 'LogicalExpression': {
|
||||
const left = this.transformValue(id, value.left, state);
|
||||
if (left.kind === "replace") {
|
||||
if (left.kind === 'replace') {
|
||||
value.left = left.value;
|
||||
}
|
||||
const right = this.transformValue(id, value.right, state);
|
||||
if (right.kind === "replace") {
|
||||
if (right.kind === 'replace') {
|
||||
value.right = right.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ConditionalExpression": {
|
||||
case 'ConditionalExpression': {
|
||||
const test = this.transformValue(id, value.test, state);
|
||||
if (test.kind === "replace") {
|
||||
if (test.kind === 'replace') {
|
||||
value.test = test.value;
|
||||
}
|
||||
const consequent = this.transformValue(id, value.consequent, state);
|
||||
if (consequent.kind === "replace") {
|
||||
if (consequent.kind === 'replace') {
|
||||
value.consequent = consequent.value;
|
||||
}
|
||||
const alternate = this.transformValue(id, value.alternate, state);
|
||||
if (alternate.kind === "replace") {
|
||||
if (alternate.kind === 'replace') {
|
||||
value.alternate = alternate.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "SequenceExpression": {
|
||||
case 'SequenceExpression': {
|
||||
for (const instr of value.instructions) {
|
||||
this.visitInstruction(instr, state);
|
||||
}
|
||||
const nextValue = this.transformValue(value.id, value.value, state);
|
||||
if (nextValue.kind === "replace") {
|
||||
if (nextValue.kind === 'replace') {
|
||||
value.value = nextValue.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ReactiveFunctionValue": {
|
||||
case 'ReactiveFunctionValue': {
|
||||
const nextValue = this.transformReactiveFunctionValue(
|
||||
id,
|
||||
value.dependencies,
|
||||
value.fn,
|
||||
state
|
||||
state,
|
||||
);
|
||||
if (nextValue.kind === "replace") {
|
||||
if (nextValue.kind === 'replace') {
|
||||
value.fn = nextValue.value;
|
||||
}
|
||||
break;
|
||||
@@ -456,7 +456,7 @@ export class ReactiveFunctionTransform<
|
||||
|
||||
override traverseInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
state: TState
|
||||
state: TState,
|
||||
): void {
|
||||
this.visitID(instruction.id, state);
|
||||
for (const operand of eachInstructionLValue(instruction)) {
|
||||
@@ -465,93 +465,93 @@ export class ReactiveFunctionTransform<
|
||||
const nextValue = this.transformValue(
|
||||
instruction.id,
|
||||
instruction.value,
|
||||
state
|
||||
state,
|
||||
);
|
||||
if (nextValue.kind === "replace") {
|
||||
if (nextValue.kind === 'replace') {
|
||||
instruction.value = nextValue.value;
|
||||
}
|
||||
}
|
||||
|
||||
override traverseTerminal(
|
||||
stmt: ReactiveTerminalStatement,
|
||||
state: TState
|
||||
state: TState,
|
||||
): void {
|
||||
const { terminal } = stmt;
|
||||
const {terminal} = stmt;
|
||||
if (terminal.id !== null) {
|
||||
this.visitID(terminal.id, state);
|
||||
}
|
||||
switch (terminal.kind) {
|
||||
case "break":
|
||||
case "continue": {
|
||||
case 'break':
|
||||
case 'continue': {
|
||||
break;
|
||||
}
|
||||
case "return": {
|
||||
case 'return': {
|
||||
this.visitPlace(terminal.id, terminal.value, state);
|
||||
break;
|
||||
}
|
||||
case "throw": {
|
||||
case 'throw': {
|
||||
this.visitPlace(terminal.id, terminal.value, state);
|
||||
break;
|
||||
}
|
||||
case "for": {
|
||||
case 'for': {
|
||||
const init = this.transformValue(terminal.id, terminal.init, state);
|
||||
if (init.kind === "replace") {
|
||||
if (init.kind === 'replace') {
|
||||
terminal.init = init.value;
|
||||
}
|
||||
const test = this.transformValue(terminal.id, terminal.test, state);
|
||||
if (test.kind === "replace") {
|
||||
if (test.kind === 'replace') {
|
||||
terminal.test = test.value;
|
||||
}
|
||||
if (terminal.update !== null) {
|
||||
const update = this.transformValue(
|
||||
terminal.id,
|
||||
terminal.update,
|
||||
state
|
||||
state,
|
||||
);
|
||||
if (update.kind === "replace") {
|
||||
if (update.kind === 'replace') {
|
||||
terminal.update = update.value;
|
||||
}
|
||||
}
|
||||
this.visitBlock(terminal.loop, state);
|
||||
break;
|
||||
}
|
||||
case "for-of": {
|
||||
case 'for-of': {
|
||||
const init = this.transformValue(terminal.id, terminal.init, state);
|
||||
if (init.kind === "replace") {
|
||||
if (init.kind === 'replace') {
|
||||
terminal.init = init.value;
|
||||
}
|
||||
const test = this.transformValue(terminal.id, terminal.test, state);
|
||||
if (test.kind === "replace") {
|
||||
if (test.kind === 'replace') {
|
||||
terminal.test = test.value;
|
||||
}
|
||||
this.visitBlock(terminal.loop, state);
|
||||
break;
|
||||
}
|
||||
case "for-in": {
|
||||
case 'for-in': {
|
||||
const init = this.transformValue(terminal.id, terminal.init, state);
|
||||
if (init.kind === "replace") {
|
||||
if (init.kind === 'replace') {
|
||||
terminal.init = init.value;
|
||||
}
|
||||
this.visitBlock(terminal.loop, state);
|
||||
break;
|
||||
}
|
||||
case "do-while": {
|
||||
case 'do-while': {
|
||||
this.visitBlock(terminal.loop, state);
|
||||
const test = this.transformValue(terminal.id, terminal.test, state);
|
||||
if (test.kind === "replace") {
|
||||
if (test.kind === 'replace') {
|
||||
terminal.test = test.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "while": {
|
||||
case 'while': {
|
||||
const test = this.transformValue(terminal.id, terminal.test, state);
|
||||
if (test.kind === "replace") {
|
||||
if (test.kind === 'replace') {
|
||||
terminal.test = test.value;
|
||||
}
|
||||
this.visitBlock(terminal.loop, state);
|
||||
break;
|
||||
}
|
||||
case "if": {
|
||||
case 'if': {
|
||||
this.visitPlace(terminal.id, terminal.test, state);
|
||||
this.visitBlock(terminal.consequent, state);
|
||||
if (terminal.alternate !== null) {
|
||||
@@ -559,7 +559,7 @@ export class ReactiveFunctionTransform<
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "switch": {
|
||||
case 'switch': {
|
||||
this.visitPlace(terminal.id, terminal.test, state);
|
||||
for (const case_ of terminal.cases) {
|
||||
if (case_.test !== null) {
|
||||
@@ -571,11 +571,11 @@ export class ReactiveFunctionTransform<
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "label": {
|
||||
case 'label': {
|
||||
this.visitBlock(terminal.block, state);
|
||||
break;
|
||||
}
|
||||
case "try": {
|
||||
case 'try': {
|
||||
this.visitBlock(terminal.block, state);
|
||||
if (terminal.handlerBinding !== null) {
|
||||
this.visitPlace(terminal.id, terminal.handlerBinding, state);
|
||||
@@ -586,7 +586,7 @@ export class ReactiveFunctionTransform<
|
||||
default: {
|
||||
assertExhaustive(
|
||||
terminal,
|
||||
`Unexpected terminal kind \`${(terminal as any).kind}\``
|
||||
`Unexpected terminal kind \`${(terminal as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -594,32 +594,32 @@ export class ReactiveFunctionTransform<
|
||||
}
|
||||
|
||||
export function* eachReactiveValueOperand(
|
||||
instrValue: ReactiveValue
|
||||
instrValue: ReactiveValue,
|
||||
): Iterable<Place> {
|
||||
switch (instrValue.kind) {
|
||||
case "OptionalExpression": {
|
||||
case 'OptionalExpression': {
|
||||
yield* eachReactiveValueOperand(instrValue.value);
|
||||
break;
|
||||
}
|
||||
case "LogicalExpression": {
|
||||
case 'LogicalExpression': {
|
||||
yield* eachReactiveValueOperand(instrValue.left);
|
||||
yield* eachReactiveValueOperand(instrValue.right);
|
||||
break;
|
||||
}
|
||||
case "SequenceExpression": {
|
||||
case 'SequenceExpression': {
|
||||
for (const instr of instrValue.instructions) {
|
||||
yield* eachReactiveValueOperand(instr.value);
|
||||
}
|
||||
yield* eachReactiveValueOperand(instrValue.value);
|
||||
break;
|
||||
}
|
||||
case "ConditionalExpression": {
|
||||
case 'ConditionalExpression': {
|
||||
yield* eachReactiveValueOperand(instrValue.test);
|
||||
yield* eachReactiveValueOperand(instrValue.consequent);
|
||||
yield* eachReactiveValueOperand(instrValue.alternate);
|
||||
break;
|
||||
}
|
||||
case "ReactiveFunctionValue": {
|
||||
case 'ReactiveFunctionValue': {
|
||||
yield* instrValue.dependencies;
|
||||
break;
|
||||
}
|
||||
@@ -631,40 +631,40 @@ export function* eachReactiveValueOperand(
|
||||
|
||||
export function mapTerminalBlocks(
|
||||
terminal: ReactiveTerminal,
|
||||
fn: (block: ReactiveBlock) => ReactiveBlock
|
||||
fn: (block: ReactiveBlock) => ReactiveBlock,
|
||||
): void {
|
||||
switch (terminal.kind) {
|
||||
case "break":
|
||||
case "continue":
|
||||
case "return":
|
||||
case "throw": {
|
||||
case 'break':
|
||||
case 'continue':
|
||||
case 'return':
|
||||
case 'throw': {
|
||||
break;
|
||||
}
|
||||
case "for": {
|
||||
case 'for': {
|
||||
terminal.loop = fn(terminal.loop);
|
||||
break;
|
||||
}
|
||||
case "for-of": {
|
||||
case 'for-of': {
|
||||
terminal.loop = fn(terminal.loop);
|
||||
break;
|
||||
}
|
||||
case "for-in": {
|
||||
case 'for-in': {
|
||||
terminal.loop = fn(terminal.loop);
|
||||
break;
|
||||
}
|
||||
case "do-while":
|
||||
case "while": {
|
||||
case 'do-while':
|
||||
case 'while': {
|
||||
terminal.loop = fn(terminal.loop);
|
||||
break;
|
||||
}
|
||||
case "if": {
|
||||
case 'if': {
|
||||
terminal.consequent = fn(terminal.consequent);
|
||||
if (terminal.alternate !== null) {
|
||||
terminal.alternate = fn(terminal.alternate);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "switch": {
|
||||
case 'switch': {
|
||||
for (const case_ of terminal.cases) {
|
||||
if (case_.block !== undefined) {
|
||||
case_.block = fn(case_.block);
|
||||
@@ -672,11 +672,11 @@ export function mapTerminalBlocks(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "label": {
|
||||
case 'label': {
|
||||
terminal.block = fn(terminal.block);
|
||||
break;
|
||||
}
|
||||
case "try": {
|
||||
case 'try': {
|
||||
terminal.block = fn(terminal.block);
|
||||
terminal.handler = fn(terminal.handler);
|
||||
break;
|
||||
@@ -684,7 +684,7 @@ export function mapTerminalBlocks(
|
||||
default: {
|
||||
assertExhaustive(
|
||||
terminal,
|
||||
`Unexpected terminal kind \`${(terminal as any).kind}\``
|
||||
`Unexpected terminal kind \`${(terminal as any).kind}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user