Compare commits
19 Commits
pr35044
...
gh/mofeiZ/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53afbf0fb6 | ||
|
|
c088ad09f1 | ||
|
|
e0acdd81ed | ||
|
|
2c9fdc4838 | ||
|
|
79198b0cd7 | ||
|
|
f8026d06f5 | ||
|
|
f399ed896b | ||
|
|
1c55d8810f | ||
|
|
a2f6bbe6a4 | ||
|
|
f8855cfd1e | ||
|
|
b718a77516 | ||
|
|
3fa1cceed8 | ||
|
|
9194615949 | ||
|
|
d720486b12 | ||
|
|
1ba72191a5 | ||
|
|
2d6a434df5 | ||
|
|
bec373555a | ||
|
|
0bceafbe97 | ||
|
|
2827cbc594 |
@@ -96,6 +96,7 @@ import {
|
||||
validatePreservedManualMemoization,
|
||||
validateUseMemo,
|
||||
} from "../Validation";
|
||||
import { propagateScopeDependenciesHIR } from "../HIR/PropagateScopeDependenciesHIR";
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
| { kind: "ast"; name: string; value: CodegenFunction }
|
||||
@@ -306,6 +307,13 @@ function* runWithEnvironment(
|
||||
});
|
||||
assertTerminalSuccessorsExist(hir);
|
||||
assertTerminalPredsExist(hir);
|
||||
|
||||
propagateScopeDependenciesHIR(hir);
|
||||
yield log({
|
||||
kind: "hir",
|
||||
name: "PropagateScopeDependenciesHIR",
|
||||
value: hir,
|
||||
});
|
||||
}
|
||||
|
||||
const reactiveFunction = buildReactiveFunction(hir);
|
||||
@@ -359,17 +367,16 @@ function* runWithEnvironment(
|
||||
name: "FlattenScopesWithHooks",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
assertScopeInstructionsWithinScopes(reactiveFunction);
|
||||
|
||||
propagateScopeDependencies(reactiveFunction);
|
||||
yield log({
|
||||
kind: "reactive",
|
||||
name: "PropagateScopeDependencies",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
}
|
||||
|
||||
assertScopeInstructionsWithinScopes(reactiveFunction);
|
||||
|
||||
propagateScopeDependencies(reactiveFunction);
|
||||
yield log({
|
||||
kind: "reactive",
|
||||
name: "PropagateScopeDependencies",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneNonEscapingScopes(reactiveFunction);
|
||||
yield log({
|
||||
kind: "reactive",
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import { isMutable } from "../ReactiveScopes/InferReactiveScopeVariables";
|
||||
import { Set_intersect, Set_union, getOrInsertDefault } from "../Utils/utils";
|
||||
import {
|
||||
BlockId,
|
||||
GeneratedSource,
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
InstructionId,
|
||||
Place,
|
||||
ReactiveScopeDependency,
|
||||
ScopeId,
|
||||
} from "./HIR";
|
||||
|
||||
type CollectHoistablePropertyLoadsResult = {
|
||||
nodes: Map<BlockId, BlockInfo>;
|
||||
temporaries: Map<Identifier, Identifier>;
|
||||
properties: Map<Identifier, ReactiveScopeDependency>;
|
||||
};
|
||||
|
||||
export function collectHoistablePropertyLoads(
|
||||
fn: HIRFunction,
|
||||
usedOutsideDeclaringScope: Set<IdentifierId>
|
||||
): CollectHoistablePropertyLoadsResult {
|
||||
const result = new TemporariesSideMap();
|
||||
|
||||
const functionExprRvals = fn.env.config.enableTreatFunctionDepsAsConditional
|
||||
? collectFunctionExpressionRValues(fn)
|
||||
: new Set<IdentifierId>();
|
||||
const nodes = collectNodes(
|
||||
fn,
|
||||
functionExprRvals,
|
||||
usedOutsideDeclaringScope,
|
||||
result
|
||||
);
|
||||
deriveNonNull(fn, nodes);
|
||||
|
||||
return {
|
||||
nodes,
|
||||
temporaries: result.temporaries,
|
||||
properties: result.properties,
|
||||
};
|
||||
}
|
||||
|
||||
export type BlockInfo = {
|
||||
blockId: BlockId;
|
||||
scope: ScopeId | null;
|
||||
preds: Set<BlockId>;
|
||||
assumedNonNullObjects: Set<PropertyLoadNode>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tree data structure to dedupe property loads (e.g. a.b.c)
|
||||
* and make computing sets and intersections simpler.
|
||||
*/
|
||||
type RootNode = {
|
||||
properties: Map<string, PropertyLoadNode>;
|
||||
parent: null;
|
||||
// Recorded to make later computations simpler
|
||||
fullPath: ReactiveScopeDependency;
|
||||
root: Identifier;
|
||||
};
|
||||
|
||||
type PropertyLoadNode =
|
||||
| {
|
||||
properties: Map<string, PropertyLoadNode>;
|
||||
parent: PropertyLoadNode;
|
||||
fullPath: ReactiveScopeDependency;
|
||||
}
|
||||
| RootNode;
|
||||
|
||||
class Tree {
|
||||
roots: Map<Identifier, RootNode> = new Map();
|
||||
|
||||
#getOrCreateRoot(identifier: Identifier): PropertyLoadNode {
|
||||
// roots can always be accessed unconditionally in JS
|
||||
let rootNode = this.roots.get(identifier);
|
||||
|
||||
if (rootNode === undefined) {
|
||||
rootNode = {
|
||||
root: identifier,
|
||||
properties: new Map(),
|
||||
fullPath: {
|
||||
identifier,
|
||||
path: [],
|
||||
},
|
||||
parent: null,
|
||||
};
|
||||
this.roots.set(identifier, rootNode);
|
||||
}
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
static #getOrMakeProperty(
|
||||
node: PropertyLoadNode,
|
||||
property: string
|
||||
): PropertyLoadNode {
|
||||
let child = node.properties.get(property);
|
||||
if (child == null) {
|
||||
child = {
|
||||
properties: new Map(),
|
||||
parent: node,
|
||||
fullPath: {
|
||||
identifier: node.fullPath.identifier,
|
||||
path: node.fullPath.path.concat([property]),
|
||||
},
|
||||
};
|
||||
node.properties.set(property, child);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
add(n: ReactiveScopeDependency): PropertyLoadNode {
|
||||
let currNode = this.#getOrCreateRoot(n.identifier);
|
||||
// We add ReactiveScopeDependencies sequentially (e.g. a.b before a.b.c),
|
||||
// so subpaths should already exist.
|
||||
for (let i = 0; i < n.path.length - 1; i++) {
|
||||
currNode = assertNonNull(currNode.properties.get(n.path[i]));
|
||||
}
|
||||
|
||||
currNode = Tree.#getOrMakeProperty(currNode, n.path.at(-1)!);
|
||||
return currNode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We currently lower function expression dependencies inline before the
|
||||
* function expression instruction. This causes our HIR to deviate from
|
||||
* JS specs.
|
||||
*
|
||||
* For example, note that instructions 0-2 in the below HIR are incorrectly
|
||||
* hoisted.
|
||||
* ```js
|
||||
* // Input
|
||||
* function Component(props) {
|
||||
* const fn = () => cond && read(props.a.b);
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* // HIR:
|
||||
* [0] $0 = LoadLocal "props"
|
||||
* [1] $1 = PropertyLoad $0, "a"
|
||||
* [2] $2 = PropertyLoad $1, "b"
|
||||
* [3] $3 = FunctionExpression deps=[$2] context=[$0] {
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* TODO: rewrite function expression deps
|
||||
*/
|
||||
function collectFunctionExpressionRValues(fn: HIRFunction): Set<IdentifierId> {
|
||||
const result = new Set<IdentifierId>();
|
||||
const loads = new Map<IdentifierId, IdentifierId>();
|
||||
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
if (instr.value.kind === "LoadLocal") {
|
||||
loads.set(instr.lvalue.identifier.id, instr.value.place.identifier.id);
|
||||
} else if (instr.value.kind === "PropertyLoad") {
|
||||
loads.set(instr.lvalue.identifier.id, instr.value.object.identifier.id);
|
||||
} else if (instr.value.kind === "FunctionExpression") {
|
||||
for (const dep of instr.value.loweredFunc.dependencies) {
|
||||
result.add(dep.identifier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// don't iterate newly added objects as optimization
|
||||
for (const res of result) {
|
||||
let curr = loads.get(res);
|
||||
while (curr != null) {
|
||||
result.add(curr);
|
||||
curr = loads.get(curr);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class TemporariesSideMap {
|
||||
temporaries: Map<Identifier, Identifier> = new Map();
|
||||
properties: Map<Identifier, ReactiveScopeDependency> = new Map();
|
||||
tree: Tree = new Tree();
|
||||
|
||||
declareTemporary(from: Identifier, to: Identifier): void {
|
||||
this.temporaries.set(from, to);
|
||||
}
|
||||
|
||||
declareProperty(
|
||||
lvalue: Place,
|
||||
object: Place,
|
||||
propertyName: string,
|
||||
shouldDeclare: boolean
|
||||
): PropertyLoadNode {
|
||||
// temporaries contains object if this is a property load chain from a named variable
|
||||
// Otherwise, there is a non-trivial expression
|
||||
const resolvedObject =
|
||||
this.temporaries.get(object.identifier) ?? object.identifier;
|
||||
|
||||
const resolvedDependency = this.properties.get(resolvedObject);
|
||||
let property: ReactiveScopeDependency;
|
||||
if (resolvedDependency == null) {
|
||||
property = {
|
||||
identifier: resolvedObject,
|
||||
path: [propertyName],
|
||||
};
|
||||
} else {
|
||||
property = {
|
||||
identifier: resolvedDependency.identifier,
|
||||
path: [...resolvedDependency.path, propertyName],
|
||||
};
|
||||
}
|
||||
|
||||
if (shouldDeclare) {
|
||||
this.properties.set(lvalue.identifier, property);
|
||||
}
|
||||
return this.tree.add(property);
|
||||
}
|
||||
}
|
||||
|
||||
function collectNodes(
|
||||
fn: HIRFunction,
|
||||
functionExprRvals: Set<IdentifierId>,
|
||||
usedOutsideDeclaringScope: Set<IdentifierId>,
|
||||
c: TemporariesSideMap
|
||||
): Map<BlockId, BlockInfo> {
|
||||
const nodes = new Map<BlockId, BlockInfo>();
|
||||
const scopeStartBlocks = new Map<BlockId, ScopeId>();
|
||||
for (const [blockId, block] of fn.body.blocks) {
|
||||
const assumedNonNullObjects = new Set<PropertyLoadNode>();
|
||||
for (const instr of block.instructions) {
|
||||
const { value, lvalue } = instr;
|
||||
const usedOutside = usedOutsideDeclaringScope.has(lvalue.identifier.id);
|
||||
if (value.kind === "PropertyLoad") {
|
||||
const propertyNode = c.declareProperty(
|
||||
lvalue,
|
||||
value.object,
|
||||
value.property,
|
||||
!usedOutside
|
||||
);
|
||||
if (
|
||||
!functionExprRvals.has(lvalue.identifier.id) &&
|
||||
!isMutable(instr, value.object)
|
||||
) {
|
||||
let curr = propertyNode.parent;
|
||||
while (curr != null) {
|
||||
assumedNonNullObjects.add(curr);
|
||||
curr = curr.parent;
|
||||
}
|
||||
}
|
||||
} else if (value.kind === "LoadLocal") {
|
||||
if (
|
||||
lvalue.identifier.name == null &&
|
||||
value.place.identifier.name !== null &&
|
||||
!usedOutside
|
||||
) {
|
||||
c.declareTemporary(lvalue.identifier, value.place.identifier);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Note that we do not record StoreLocals as this runs after ExitSSA.
|
||||
* As a result, an expression like `(a ?? b).c` is represented as two
|
||||
* StoreLocals to the same identifier id.
|
||||
*/
|
||||
}
|
||||
|
||||
if (
|
||||
block.terminal.kind === "scope" ||
|
||||
block.terminal.kind === "pruned-scope"
|
||||
) {
|
||||
scopeStartBlocks.set(block.terminal.block, block.terminal.scope.id);
|
||||
}
|
||||
|
||||
nodes.set(blockId, {
|
||||
blockId,
|
||||
scope: scopeStartBlocks.get(blockId) ?? null,
|
||||
preds: block.preds,
|
||||
assumedNonNullObjects,
|
||||
});
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function deriveNonNull(fn: HIRFunction, nodes: Map<BlockId, BlockInfo>): void {
|
||||
// block -> successors sidemap
|
||||
const succ = new Map<BlockId, Set<BlockId>>();
|
||||
const terminalPreds = new Set<BlockId>();
|
||||
|
||||
for (const [blockId, block] of fn.body.blocks) {
|
||||
for (const pred of block.preds) {
|
||||
const predVal = getOrInsertDefault(succ, pred, new Set());
|
||||
predVal.add(blockId);
|
||||
}
|
||||
if (block.terminal.kind === "throw" || block.terminal.kind === "return") {
|
||||
terminalPreds.add(blockId);
|
||||
}
|
||||
}
|
||||
|
||||
function recursivelyDeriveNonNull(
|
||||
nodeId: BlockId,
|
||||
kind: "succ" | "pred",
|
||||
traversalState: Map<BlockId, "active" | "done">,
|
||||
result: Map<BlockId, Set<PropertyLoadNode>>
|
||||
): boolean {
|
||||
if (traversalState.has(nodeId)) {
|
||||
return false;
|
||||
}
|
||||
traversalState.set(nodeId, "active");
|
||||
|
||||
const node = nodes.get(nodeId);
|
||||
if (node == null) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Bad node ${nodeId}, kind: ${kind}`,
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
}
|
||||
const neighbors = Array.from(
|
||||
kind === "succ" ? succ.get(nodeId) ?? [] : node.preds
|
||||
);
|
||||
|
||||
let changed = false;
|
||||
for (const pred of neighbors) {
|
||||
if (!traversalState.has(pred)) {
|
||||
const neighborChanged = recursivelyDeriveNonNull(
|
||||
pred,
|
||||
kind,
|
||||
traversalState,
|
||||
result
|
||||
);
|
||||
changed ||= neighborChanged;
|
||||
}
|
||||
}
|
||||
// active neighbors can be filtered out as we're solving for the following
|
||||
// relation.
|
||||
// X = Intersect(X_neighbors, X)
|
||||
// non-active neighbors with no recorded results can occur due to backedges.
|
||||
// it's not safe to assume they can be filtered out (e.g. not intersected)
|
||||
const neighborAccesses = Set_intersect([
|
||||
...(Array.from(neighbors)
|
||||
.filter((n) => traversalState.get(n) !== "active")
|
||||
.map((n) => result.get(n) ?? new Set()) as Array<
|
||||
Set<PropertyLoadNode>
|
||||
>),
|
||||
]);
|
||||
|
||||
const prevSize = result.get(nodeId)?.size;
|
||||
// const prevPrinted = [...(result.get(nodeId) ?? [])].map(
|
||||
// printDependencyNode
|
||||
// );
|
||||
result.set(nodeId, Set_union(node.assumedNonNullObjects, neighborAccesses));
|
||||
traversalState.set(nodeId, "done");
|
||||
|
||||
// const newPrinted = [...(result.get(nodeId) ?? [])].map(printDependencyNode);
|
||||
// llog(" - ", nodeId, prevPrinted, newPrinted);
|
||||
|
||||
changed ||= prevSize !== result.get(nodeId)!.size;
|
||||
CompilerError.invariant(
|
||||
prevSize == null || prevSize <= result.get(nodeId)!.size,
|
||||
{
|
||||
reason: "[CollectHoistablePropertyLoads] Nodes shrank!",
|
||||
description: `${nodeId} ${kind} ${prevSize} ${
|
||||
result.get(nodeId)!.size
|
||||
}`,
|
||||
loc: GeneratedSource,
|
||||
}
|
||||
);
|
||||
return changed;
|
||||
}
|
||||
const fromEntry = new Map<BlockId, Set<PropertyLoadNode>>();
|
||||
const fromExit = new Map<BlockId, Set<PropertyLoadNode>>();
|
||||
let changed = true;
|
||||
const traversalState = new Map<BlockId, "done" | "active">();
|
||||
const reversedBlocks = [...fn.body.blocks];
|
||||
reversedBlocks.reverse();
|
||||
let i = 0;
|
||||
|
||||
while (changed) {
|
||||
i++;
|
||||
changed = false;
|
||||
for (const [blockId] of fn.body.blocks) {
|
||||
const changed_ = recursivelyDeriveNonNull(
|
||||
blockId,
|
||||
"pred",
|
||||
traversalState,
|
||||
fromEntry
|
||||
);
|
||||
changed ||= changed_;
|
||||
}
|
||||
traversalState.clear();
|
||||
for (const [blockId] of reversedBlocks) {
|
||||
const changed_ = recursivelyDeriveNonNull(
|
||||
blockId,
|
||||
"succ",
|
||||
traversalState,
|
||||
fromExit
|
||||
);
|
||||
changed ||= changed_;
|
||||
}
|
||||
traversalState.clear();
|
||||
}
|
||||
|
||||
// TODO: I can't come up with a case that requires fixed-point iteration
|
||||
CompilerError.invariant(i === 2, {
|
||||
reason: "require fixed-point iteration",
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
|
||||
CompilerError.invariant(
|
||||
fromEntry.size === fromExit.size && fromEntry.size === nodes.size,
|
||||
{
|
||||
reason:
|
||||
"bad sizes after calculating fromEntry + fromExit " +
|
||||
`${fromEntry.size} ${fromExit.size} ${nodes.size}`,
|
||||
loc: GeneratedSource,
|
||||
}
|
||||
);
|
||||
|
||||
for (const [id, node] of nodes) {
|
||||
node.assumedNonNullObjects = Set_union(
|
||||
assertNonNull(fromEntry.get(id)),
|
||||
assertNonNull(fromExit.get(id))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertNonNull<T extends NonNullable<U>, U>(
|
||||
value: T | null | undefined,
|
||||
source?: string
|
||||
): T {
|
||||
CompilerError.invariant(value != null, {
|
||||
reason: "Unexpected null",
|
||||
description: source != null ? `(from ${source})` : null,
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
return value;
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import { GeneratedSource, Identifier, ReactiveScopeDependency } from "../HIR";
|
||||
import { printIdentifier } from "../HIR/PrintHIR";
|
||||
import { ReactiveScopePropertyDependency } from "../ReactiveScopes/DeriveMinimalDependencies";
|
||||
|
||||
const ENABLE_DEBUG_INVARIANTS = true;
|
||||
|
||||
/*
|
||||
* Finalizes a set of ReactiveScopeDependencies to produce a set of minimal unconditional
|
||||
* dependencies, preserving granular accesses when possible.
|
||||
*
|
||||
* Correctness properties:
|
||||
* - All dependencies to a ReactiveBlock must be tracked.
|
||||
* We can always truncate a dependency's path to a subpath, due to Forget assuming
|
||||
* deep immutability. If the value produced by a subpath has not changed, then
|
||||
* dependency must have not changed.
|
||||
* i.e. props.a === $[..] implies props.a.b === $[..]
|
||||
*
|
||||
* Note the inverse is not true, but this only means a false positive (we run the
|
||||
* reactive block more than needed).
|
||||
* i.e. props.a !== $[..] does not imply props.a.b !== $[..]
|
||||
*
|
||||
* - The dependencies of a finalized ReactiveBlock must be all safe to access
|
||||
* unconditionally (i.e. preserve program semantics with respect to nullthrows).
|
||||
* If a dependency is only accessed within a conditional, we must track the nearest
|
||||
* unconditionally accessed subpath instead.
|
||||
* @param initialDeps
|
||||
* @returns
|
||||
*/
|
||||
export class ReactiveScopeDependencyTreeHIR {
|
||||
#roots: Map<Identifier, DependencyNode> = new Map();
|
||||
|
||||
#getOrCreateRoot(identifier: Identifier, isNonNull: boolean): DependencyNode {
|
||||
// roots can always be accessed unconditionally in JS
|
||||
let rootNode = this.#roots.get(identifier);
|
||||
|
||||
if (rootNode === undefined) {
|
||||
rootNode = {
|
||||
properties: new Map(),
|
||||
accessType: isNonNull
|
||||
? PropertyAccessType.NonNullAccess
|
||||
: PropertyAccessType.MaybeNullAccess,
|
||||
};
|
||||
this.#roots.set(identifier, rootNode);
|
||||
}
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
addDependency(dep: ReactiveScopePropertyDependency): void {
|
||||
const { path, optionalPath } = dep;
|
||||
let currNode = this.#getOrCreateRoot(dep.identifier, false);
|
||||
|
||||
const accessType = PropertyAccessType.MaybeNullAccess;
|
||||
|
||||
currNode.accessType = merge(currNode.accessType, accessType);
|
||||
|
||||
for (const property of path) {
|
||||
// all properties read 'on the way' to a dependency are marked as 'access'
|
||||
let currChild = getOrMakeProperty(currNode, property);
|
||||
currChild.accessType = merge(currChild.accessType, accessType);
|
||||
currNode = currChild;
|
||||
}
|
||||
|
||||
if (optionalPath.length === 0) {
|
||||
/*
|
||||
* If this property does not have a conditional path (i.e. a.b.c), the
|
||||
* final property node should be marked as an conditional/unconditional
|
||||
* `dependency` as based on control flow.
|
||||
*/
|
||||
currNode.accessType = merge(
|
||||
currNode.accessType,
|
||||
PropertyAccessType.MaybeNullDependency
|
||||
);
|
||||
} else {
|
||||
/*
|
||||
* Technically, we only depend on whether unconditional path `dep.path`
|
||||
* is nullish (not its actual value). As long as we preserve the nullthrows
|
||||
* behavior of `dep.path`, we can keep it as an access (and not promote
|
||||
* to a dependency).
|
||||
* See test `reduce-reactive-cond-memberexpr-join` for example.
|
||||
*/
|
||||
|
||||
/*
|
||||
* If this property has an optional path (i.e. a?.b.c), all optional
|
||||
* nodes should be marked accordingly.
|
||||
*/
|
||||
for (const property of optionalPath) {
|
||||
let currChild = getOrMakeProperty(currNode, property);
|
||||
currChild.accessType = merge(
|
||||
currChild.accessType,
|
||||
PropertyAccessType.MaybeNullAccess
|
||||
);
|
||||
currNode = currChild;
|
||||
}
|
||||
|
||||
// The final node should be marked as a conditional dependency.
|
||||
currNode.accessType = merge(
|
||||
currNode.accessType,
|
||||
PropertyAccessType.MaybeNullDependency
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
markNodesNonNull(dep: ReactiveScopePropertyDependency): void {
|
||||
const accessType = PropertyAccessType.NonNullAccess;
|
||||
let currNode = this.#roots.get(dep.identifier);
|
||||
|
||||
let cursor = 0;
|
||||
while (currNode != null && cursor < dep.path.length) {
|
||||
currNode.accessType = merge(currNode.accessType, accessType);
|
||||
currNode = currNode.properties.get(dep.path[cursor++]);
|
||||
}
|
||||
if (currNode != null) {
|
||||
currNode.accessType = merge(currNode.accessType, accessType);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Derive a set of minimal dependencies that are safe to
|
||||
* access unconditionally (with respect to nullthrows behavior)
|
||||
*/
|
||||
deriveMinimalDependencies(): Set<ReactiveScopeDependency> {
|
||||
const results = new Set<ReactiveScopeDependency>();
|
||||
for (const [rootId, rootNode] of this.#roots.entries()) {
|
||||
if (ENABLE_DEBUG_INVARIANTS) {
|
||||
assertWellFormedTree(rootNode);
|
||||
}
|
||||
const deps = deriveMinimalDependenciesInSubtree(rootNode, []);
|
||||
|
||||
for (const dep of deps) {
|
||||
results.add({
|
||||
identifier: rootId,
|
||||
path: dep.path,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prints dependency tree to string for debugging.
|
||||
* @param includeAccesses
|
||||
* @returns string representation of DependencyTree
|
||||
*/
|
||||
printDeps(includeAccesses: boolean): string {
|
||||
let res: Array<Array<string>> = [];
|
||||
|
||||
for (const [rootId, rootNode] of this.#roots.entries()) {
|
||||
const rootResults = printSubtree(rootNode, includeAccesses).map(
|
||||
(result) => `${printIdentifier(rootId)}.${result}`
|
||||
);
|
||||
res.push(rootResults);
|
||||
}
|
||||
return res.flat().join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
enum PropertyAccessType {
|
||||
MaybeNullAccess = "MaybeNullAccess",
|
||||
NonNullAccess = "NonNullAccess",
|
||||
MaybeNullDependency = "MaybeNullDependency",
|
||||
NonNullDependency = "NonNullDependency",
|
||||
}
|
||||
|
||||
const MIN_ACCESS_TYPE = PropertyAccessType.MaybeNullAccess;
|
||||
function isNonNull(access: PropertyAccessType): boolean {
|
||||
return (
|
||||
access === PropertyAccessType.NonNullAccess ||
|
||||
access === PropertyAccessType.NonNullDependency
|
||||
);
|
||||
}
|
||||
function isDependency(access: PropertyAccessType): boolean {
|
||||
return (
|
||||
access === PropertyAccessType.MaybeNullDependency ||
|
||||
access === PropertyAccessType.NonNullDependency
|
||||
);
|
||||
}
|
||||
|
||||
function merge(
|
||||
access1: PropertyAccessType,
|
||||
access2: PropertyAccessType
|
||||
): PropertyAccessType {
|
||||
const resultIsNonNull = isNonNull(access1) || isNonNull(access2);
|
||||
const resultIsDependency = isDependency(access1) || isDependency(access2);
|
||||
|
||||
/*
|
||||
* Straightforward merge.
|
||||
* This can be represented as bitwise OR, but is written out for readability
|
||||
*
|
||||
* Observe that `NonNullAccess | MaybeNullDependency` produces an
|
||||
* unconditionally accessed conditional dependency. We currently use these
|
||||
* as we use unconditional dependencies. (i.e. to codegen change variables)
|
||||
*/
|
||||
if (resultIsNonNull) {
|
||||
if (resultIsDependency) {
|
||||
return PropertyAccessType.NonNullDependency;
|
||||
} else {
|
||||
return PropertyAccessType.NonNullAccess;
|
||||
}
|
||||
} else {
|
||||
if (resultIsDependency) {
|
||||
return PropertyAccessType.MaybeNullDependency;
|
||||
} else {
|
||||
return PropertyAccessType.MaybeNullAccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DependencyNode = {
|
||||
properties: Map<string, DependencyNode>;
|
||||
accessType: PropertyAccessType;
|
||||
};
|
||||
|
||||
type ReduceResultNode = {
|
||||
path: Array<string>;
|
||||
};
|
||||
function assertWellFormedTree(node: DependencyNode): void {
|
||||
let nonNullInChildren = false;
|
||||
for (const childNode of node.properties.values()) {
|
||||
assertWellFormedTree(childNode);
|
||||
nonNullInChildren ||= isNonNull(childNode.accessType);
|
||||
}
|
||||
if (nonNullInChildren) {
|
||||
CompilerError.invariant(isNonNull(node.accessType), {
|
||||
reason:
|
||||
"[DeriveMinimialDependencies] Not well formed tree, unexpected nonnull node",
|
||||
description: node.accessType,
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
}
|
||||
}
|
||||
function deriveMinimalDependenciesInSubtree(
|
||||
node: DependencyNode,
|
||||
path: Array<string>
|
||||
): Array<ReduceResultNode> {
|
||||
if (isDependency(node.accessType)) {
|
||||
/**
|
||||
* If this node is a dependency, we truncate the subtree
|
||||
* and return this node. e.g. deps=[`obj.a`, `obj.a.b`]
|
||||
* reduces to deps=[`obj.a`]
|
||||
*/
|
||||
return [{ path }];
|
||||
} else {
|
||||
if (isNonNull(node.accessType)) {
|
||||
/*
|
||||
* Only recurse into subtree dependencies if this node
|
||||
* is known to be non-null.
|
||||
*/
|
||||
const result: Array<ReduceResultNode> = [];
|
||||
for (const [childName, childNode] of node.properties) {
|
||||
result.push(
|
||||
...deriveMinimalDependenciesInSubtree(childNode, [...path, childName])
|
||||
);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
/*
|
||||
* This only occurs when this subtree contains a dependency,
|
||||
* but this node is potentially nullish. As we currently
|
||||
* don't record optional property paths as scope dependencies,
|
||||
* we truncate and record this node as a dependency.
|
||||
*/
|
||||
return [{ path }];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Demote all unconditional accesses + dependencies in subtree to the
|
||||
* conditional equivalent, mutating subtree in place.
|
||||
* @param subtree unconditional node representing a subtree of dependencies
|
||||
*/
|
||||
function _demoteSubtreeToConditional(subtree: DependencyNode): void {
|
||||
const stack: Array<DependencyNode> = [subtree];
|
||||
|
||||
let node;
|
||||
while ((node = stack.pop()) !== undefined) {
|
||||
const { accessType, properties } = node;
|
||||
if (!isNonNull(accessType)) {
|
||||
// A conditionally accessed node should not have unconditional children
|
||||
continue;
|
||||
}
|
||||
node.accessType = isDependency(accessType)
|
||||
? PropertyAccessType.MaybeNullDependency
|
||||
: PropertyAccessType.MaybeNullAccess;
|
||||
|
||||
for (const childNode of properties.values()) {
|
||||
if (isNonNull(accessType)) {
|
||||
/*
|
||||
* No conditional node can have an unconditional node as a child, so
|
||||
* we only process childNode if it is unconditional
|
||||
*/
|
||||
stack.push(childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printSubtree(
|
||||
node: DependencyNode,
|
||||
includeAccesses: boolean
|
||||
): Array<string> {
|
||||
const results: Array<string> = [];
|
||||
for (const [propertyName, propertyNode] of node.properties) {
|
||||
if (includeAccesses || isDependency(propertyNode.accessType)) {
|
||||
results.push(`${propertyName} (${propertyNode.accessType})`);
|
||||
}
|
||||
const propertyResults = printSubtree(propertyNode, includeAccesses);
|
||||
results.push(
|
||||
...propertyResults.map((result) => `${propertyName}.${result}`)
|
||||
);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
function getOrMakeProperty(
|
||||
node: DependencyNode,
|
||||
property: string
|
||||
): DependencyNode {
|
||||
let child = node.properties.get(property);
|
||||
if (child == null) {
|
||||
child = {
|
||||
properties: new Map(),
|
||||
accessType: MIN_ACCESS_TYPE,
|
||||
};
|
||||
node.properties.set(property, child);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
function mapNonNull<T extends NonNullable<V>, V, U>(
|
||||
arr: Array<U>,
|
||||
fn: (arg0: U) => T | undefined | null
|
||||
): Array<T> | null {
|
||||
const result = [];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const element = fn(arr[i]);
|
||||
if (element) {
|
||||
result.push(element);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
import {
|
||||
IdentifierId,
|
||||
ScopeId,
|
||||
HIRFunction,
|
||||
Place,
|
||||
Instruction,
|
||||
ReactiveScopeDependency,
|
||||
BlockId,
|
||||
Identifier,
|
||||
ReactiveScope,
|
||||
isObjectMethodType,
|
||||
isRefValueType,
|
||||
isUseRefType,
|
||||
makeInstructionId,
|
||||
InstructionId,
|
||||
InstructionKind,
|
||||
GeneratedSource,
|
||||
} from "./HIR";
|
||||
import {
|
||||
BlockInfo,
|
||||
collectHoistablePropertyLoads,
|
||||
} from "./CollectHoistablePropertyLoads";
|
||||
import {
|
||||
NO_OP,
|
||||
ScopeBlockTraversal,
|
||||
eachInstructionOperand,
|
||||
eachInstructionValueOperand,
|
||||
eachPatternOperand,
|
||||
eachTerminalOperand,
|
||||
} from "./visitors";
|
||||
import { ReactiveScopeDependencyTreeHIR } from "./DeriveMinimalDependenciesHIR";
|
||||
import { Stack, empty } from "../Utils/Stack";
|
||||
import { CompilerError } from "../CompilerError";
|
||||
|
||||
export function llog(..._args: any): void {
|
||||
console.log(..._args);
|
||||
}
|
||||
type TemporariesUsedOutsideDefiningScope = {
|
||||
/*
|
||||
* tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad)
|
||||
* and the scope where they are defined
|
||||
*/
|
||||
declarations: Map<IdentifierId, ScopeId>;
|
||||
// temporaries used outside of their defining scope
|
||||
usedOutsideDeclaringScope: Set<IdentifierId>;
|
||||
};
|
||||
|
||||
function findPromotedTemporaries(
|
||||
fn: HIRFunction,
|
||||
{
|
||||
declarations,
|
||||
usedOutsideDeclaringScope,
|
||||
}: TemporariesUsedOutsideDefiningScope
|
||||
): void {
|
||||
const prunedScopes = new Set<ScopeId>();
|
||||
const scopeTraversal = new ScopeBlockTraversal<null, null>(
|
||||
null,
|
||||
(scope, pruned) => {
|
||||
if (pruned) {
|
||||
prunedScopes.add(scope.id);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
NO_OP
|
||||
);
|
||||
|
||||
function handlePlace(place: Place): void {
|
||||
const declaringScope = declarations.get(place.identifier.id);
|
||||
if (
|
||||
declaringScope != null &&
|
||||
scopeTraversal.activeScopes.indexOf(declaringScope) === -1 &&
|
||||
!prunedScopes.has(declaringScope)
|
||||
) {
|
||||
// Declaring scope is not active === used outside declaring scope
|
||||
usedOutsideDeclaringScope.add(place.identifier.id);
|
||||
}
|
||||
}
|
||||
|
||||
function handleInstruction(instr: Instruction): void {
|
||||
const scope = scopeTraversal.activeScopes.at(-1);
|
||||
if (scope === undefined || prunedScopes.has(scope)) {
|
||||
return;
|
||||
}
|
||||
switch (instr.value.kind) {
|
||||
case "LoadLocal":
|
||||
case "LoadContext":
|
||||
case "PropertyLoad": {
|
||||
declarations.set(instr.lvalue.identifier.id, scope);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
scopeTraversal.handleBlock(block);
|
||||
for (const instr of block.instructions) {
|
||||
for (const place of eachInstructionOperand(instr)) {
|
||||
handlePlace(place);
|
||||
}
|
||||
handleInstruction(instr);
|
||||
}
|
||||
|
||||
for (const place of eachTerminalOperand(block.terminal)) {
|
||||
handlePlace(place);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Decl = {
|
||||
id: InstructionId;
|
||||
scope: Stack<ReactiveScope>;
|
||||
};
|
||||
|
||||
class Context {
|
||||
#temporariesUsedOutsideScope: Set<IdentifierId>;
|
||||
#declarations: Map<IdentifierId, Decl> = new Map();
|
||||
#reassignments: Map<Identifier, Decl> = new Map();
|
||||
// Reactive dependencies used in the current reactive scope.
|
||||
#dependencies: Array<ReactiveScopeDependency> = [];
|
||||
/*
|
||||
* We keep a sidemap for temporaries created by PropertyLoads, and do
|
||||
* not store any control flow (i.e. #inConditionalWithinScope) here.
|
||||
* - a ReactiveScope (A) containing a PropertyLoad may differ from the
|
||||
* ReactiveScope (B) that uses the produced temporary.
|
||||
* - codegen will inline these PropertyLoads back into scope (B)
|
||||
*/
|
||||
#properties: Map<Identifier, ReactiveScopeDependency>;
|
||||
#temporaries: Map<Identifier, Identifier>;
|
||||
#scopes: Stack<ReactiveScope> = empty();
|
||||
deps: Map<ReactiveScope, Array<ReactiveScopeDependency>> = new Map();
|
||||
|
||||
get properties(): Map<Identifier, ReactiveScopeDependency> {
|
||||
return this.#properties;
|
||||
}
|
||||
constructor(
|
||||
temporariesUsedOutsideScope: Set<IdentifierId>,
|
||||
temporaries: Map<Identifier, Identifier>,
|
||||
properties: Map<Identifier, ReactiveScopeDependency>
|
||||
) {
|
||||
this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope;
|
||||
this.#temporaries = temporaries;
|
||||
this.#properties = properties;
|
||||
}
|
||||
|
||||
static enterScope(
|
||||
scope: ReactiveScope,
|
||||
_pruned: boolean,
|
||||
context: Context
|
||||
): { previousDeps: Array<ReactiveScopeDependency> } {
|
||||
// Save context of previous scope
|
||||
const previousDeps = context.#dependencies;
|
||||
|
||||
/*
|
||||
* Set context for new scope
|
||||
*/
|
||||
context.#dependencies = [];
|
||||
context.#scopes = context.#scopes.push(scope);
|
||||
return { previousDeps };
|
||||
}
|
||||
|
||||
static exitScope(
|
||||
scope: ReactiveScope,
|
||||
pruned: boolean,
|
||||
context: Context,
|
||||
state: { previousDeps: Array<ReactiveScopeDependency> }
|
||||
): void {
|
||||
const scopedDependencies = context.#dependencies;
|
||||
|
||||
// Restore context of previous scope
|
||||
context.#scopes = context.#scopes.pop();
|
||||
context.#dependencies = state.previousDeps;
|
||||
|
||||
/*
|
||||
* Collect dependencies we recorded for the exiting scope and propagate
|
||||
* them upward using the same rules as normal dependency collection.
|
||||
* Child scopes may have dependencies on values created within the outer
|
||||
* scope, which necessarily cannot be dependencies of the outer scope.
|
||||
*/
|
||||
for (const dep of scopedDependencies) {
|
||||
if (context.#checkValidDependency(dep)) {
|
||||
context.#dependencies.push(dep);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pruned) {
|
||||
context.deps.set(scope, scopedDependencies);
|
||||
}
|
||||
}
|
||||
|
||||
isUsedOutsideDeclaringScope(place: Place): boolean {
|
||||
return this.#temporariesUsedOutsideScope.has(place.identifier.id);
|
||||
}
|
||||
|
||||
/*
|
||||
* Records where a value was declared, and optionally, the scope where the value originated from.
|
||||
* This is later used to determine if a dependency should be added to a scope; if the current
|
||||
* scope we are visiting is the same scope where the value originates, it can't be a dependency
|
||||
* on itself.
|
||||
*/
|
||||
declare(identifier: Identifier, decl: Decl): void {
|
||||
if (!this.#declarations.has(identifier.id)) {
|
||||
this.#declarations.set(identifier.id, decl);
|
||||
}
|
||||
this.#reassignments.set(identifier, decl);
|
||||
}
|
||||
|
||||
resolveTemporary(place: Place): Identifier {
|
||||
return this.#temporaries.get(place.identifier) ?? place.identifier;
|
||||
}
|
||||
|
||||
getProperty(object: Place, property: string): ReactiveScopeDependency {
|
||||
return this.#getProperty(object, property, false);
|
||||
}
|
||||
|
||||
#getProperty(
|
||||
object: Place,
|
||||
property: string,
|
||||
_isConditional: boolean
|
||||
): ReactiveScopeDependency {
|
||||
/**
|
||||
Example 1:
|
||||
$0 = LoadLocal x
|
||||
$1 = PropertyLoad $0.y
|
||||
resolvedObject = x, resolvedDependency = null
|
||||
|
||||
Example 2:
|
||||
$0 = LoadLocal x
|
||||
$1 = PropertyLoad $0.y
|
||||
$2 = PropertyLoad $1.z
|
||||
resolvedObject = null, resolvedDependency = x.y
|
||||
|
||||
Example 3:
|
||||
$0 = Call(...)
|
||||
$1 = PropertyLoad $0.y
|
||||
resolvedObject = null, resolvedDependency = null
|
||||
*/
|
||||
const resolvedObject = this.resolveTemporary(object);
|
||||
const resolvedDependency = this.#properties.get(resolvedObject);
|
||||
let objectDependency: ReactiveScopeDependency;
|
||||
/*
|
||||
* (1) Create the base property dependency as either a LoadLocal (from a temporary)
|
||||
* or a deep copy of an existing property dependency.
|
||||
*/
|
||||
if (resolvedDependency === undefined) {
|
||||
objectDependency = {
|
||||
identifier: resolvedObject,
|
||||
path: [],
|
||||
};
|
||||
} else {
|
||||
objectDependency = {
|
||||
identifier: resolvedDependency.identifier,
|
||||
path: [...resolvedDependency.path],
|
||||
};
|
||||
}
|
||||
|
||||
objectDependency.path.push(property);
|
||||
return objectDependency;
|
||||
}
|
||||
|
||||
// Checks if identifier is a valid dependency in the current scope
|
||||
#checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean {
|
||||
// ref.current access is not a valid dep
|
||||
if (
|
||||
isUseRefType(maybeDependency.identifier) &&
|
||||
maybeDependency.path.at(0) === "current"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ref value is not a valid dep
|
||||
if (isRefValueType(maybeDependency.identifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* object methods are not deps because they will be codegen'ed back in to
|
||||
* the object literal.
|
||||
*/
|
||||
if (isObjectMethodType(maybeDependency.identifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const identifier = maybeDependency.identifier;
|
||||
/*
|
||||
* If this operand is used in a scope, has a dynamic value, and was defined
|
||||
* before this scope, then its a dependency of the scope.
|
||||
*/
|
||||
const currentDeclaration =
|
||||
this.#reassignments.get(identifier) ??
|
||||
this.#declarations.get(identifier.id);
|
||||
const currentScope = this.currentScope.value;
|
||||
return (
|
||||
currentScope != null &&
|
||||
currentDeclaration !== undefined &&
|
||||
currentDeclaration.id < currentScope.range.start
|
||||
);
|
||||
}
|
||||
|
||||
#isScopeActive(scope: ReactiveScope): boolean {
|
||||
if (this.#scopes === null) {
|
||||
return false;
|
||||
}
|
||||
return this.#scopes.find((state) => state === scope);
|
||||
}
|
||||
|
||||
get currentScope(): Stack<ReactiveScope> {
|
||||
return this.#scopes;
|
||||
}
|
||||
|
||||
visitOperand(place: Place): void {
|
||||
const resolved = this.resolveTemporary(place);
|
||||
/*
|
||||
* if this operand is a temporary created for a property load, try to resolve it to
|
||||
* the expanded Place. Fall back to using the operand as-is.
|
||||
*/
|
||||
|
||||
let dependency: ReactiveScopeDependency | null = null;
|
||||
if (resolved.name === null) {
|
||||
const propertyDependency = this.#properties.get(resolved);
|
||||
if (propertyDependency !== undefined) {
|
||||
dependency = { ...propertyDependency };
|
||||
}
|
||||
}
|
||||
// console.log(
|
||||
// `resolving ${place.identifier.id} -> ${dependency ? printDependency(dependency) : ""}`
|
||||
// );
|
||||
this.visitDependency(
|
||||
dependency ?? {
|
||||
identifier: resolved,
|
||||
path: [],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
visitProperty(object: Place, property: string): void {
|
||||
const nextDependency = this.#getProperty(object, property, false);
|
||||
// if (object.identifier.id === 32) {
|
||||
// console.log(printDependency(nextDependency));
|
||||
// }
|
||||
this.visitDependency(nextDependency);
|
||||
}
|
||||
|
||||
visitDependency(maybeDependency: ReactiveScopeDependency): void {
|
||||
/*
|
||||
* Any value used after its originally defining scope has concluded must be added as an
|
||||
* output of its defining scope. Regardless of whether its a const or not,
|
||||
* some later code needs access to the value. If the current
|
||||
* scope we are visiting is the same scope where the value originates, it can't be a dependency
|
||||
* on itself.
|
||||
*/
|
||||
|
||||
/*
|
||||
* if originalDeclaration is undefined here, then this is not a local var
|
||||
* (all decls e.g. `let x;` should be initialized in BuildHIR)
|
||||
*/
|
||||
const originalDeclaration = this.#declarations.get(
|
||||
maybeDependency.identifier.id
|
||||
);
|
||||
if (
|
||||
originalDeclaration !== undefined &&
|
||||
originalDeclaration.scope.value !== null
|
||||
) {
|
||||
originalDeclaration.scope.each((scope) => {
|
||||
if (!this.#isScopeActive(scope)) {
|
||||
scope.declarations.set(maybeDependency.identifier.id, {
|
||||
identifier: maybeDependency.identifier,
|
||||
scope: originalDeclaration.scope.value!,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.#checkValidDependency(maybeDependency)) {
|
||||
this.#dependencies.push(maybeDependency);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Record a variable that is declared in some other scope and that is being reassigned in the
|
||||
* current one as a {@link ReactiveScope.reassignments}
|
||||
*/
|
||||
visitReassignment(place: Place): void {
|
||||
const currentScope = this.currentScope.value;
|
||||
if (
|
||||
currentScope != null &&
|
||||
!Array.from(currentScope.reassignments).some(
|
||||
(identifier) => identifier.id === place.identifier.id
|
||||
) &&
|
||||
this.#checkValidDependency({ identifier: place.identifier, path: [] })
|
||||
) {
|
||||
currentScope.reassignments.add(place.identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleInstruction(instr: Instruction, context: Context) {
|
||||
const { id, value, lvalue } = instr;
|
||||
if (value.kind === "LoadLocal") {
|
||||
if (
|
||||
value.place.identifier.name === null ||
|
||||
lvalue.identifier.name !== null ||
|
||||
context.isUsedOutsideDeclaringScope(lvalue)
|
||||
) {
|
||||
context.visitOperand(value.place);
|
||||
}
|
||||
} else if (value.kind === "PropertyLoad") {
|
||||
if (context.isUsedOutsideDeclaringScope(lvalue)) {
|
||||
context.visitProperty(value.object, value.property);
|
||||
} else {
|
||||
const nextDependency = context.getProperty(value.object, value.property);
|
||||
context.properties.set(lvalue.identifier, nextDependency);
|
||||
}
|
||||
} else if (value.kind === "StoreLocal") {
|
||||
context.visitOperand(value.value);
|
||||
if (value.lvalue.kind === InstructionKind.Reassign) {
|
||||
context.visitReassignment(value.lvalue.place);
|
||||
}
|
||||
context.declare(value.lvalue.place.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
} else if (value.kind === "DeclareLocal" || value.kind === "DeclareContext") {
|
||||
/*
|
||||
* Some variables may be declared and never initialized. We need
|
||||
* to retain (and hoist) these declarations if they are included
|
||||
* in a reactive scope. One approach is to simply add all `DeclareLocal`s
|
||||
* as scope declarations.
|
||||
*/
|
||||
|
||||
/*
|
||||
* We add context variable declarations here, not at `StoreContext`, since
|
||||
* context Store / Loads are modeled as reads and mutates to the underlying
|
||||
* variable reference (instead of through intermediate / inlined temporaries)
|
||||
*/
|
||||
context.declare(value.lvalue.place.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
} else if (value.kind === "Destructure") {
|
||||
context.visitOperand(value.value);
|
||||
for (const place of eachPatternOperand(value.lvalue.pattern)) {
|
||||
if (value.lvalue.kind === InstructionKind.Reassign) {
|
||||
context.visitReassignment(place);
|
||||
}
|
||||
context.declare(place.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (const operand of eachInstructionValueOperand(value)) {
|
||||
context.visitOperand(operand);
|
||||
}
|
||||
}
|
||||
|
||||
context.declare(lvalue.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
}
|
||||
|
||||
function collectDependencies(
|
||||
fn: HIRFunction,
|
||||
usedOutsideDeclaringScope: Set<IdentifierId>,
|
||||
temporaries: Map<Identifier, Identifier>,
|
||||
properties: Map<Identifier, ReactiveScopeDependency>
|
||||
) {
|
||||
const context = new Context(
|
||||
usedOutsideDeclaringScope,
|
||||
temporaries,
|
||||
properties
|
||||
);
|
||||
|
||||
for (const param of fn.params) {
|
||||
if (param.kind === "Identifier") {
|
||||
context.declare(param.identifier, {
|
||||
id: makeInstructionId(0),
|
||||
scope: empty(),
|
||||
});
|
||||
} else {
|
||||
context.declare(param.place.identifier, {
|
||||
id: makeInstructionId(0),
|
||||
scope: empty(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type ScopeTraversalContext = { previousDeps: Array<ReactiveScopeDependency> };
|
||||
const scopeTraversal = new ScopeBlockTraversal<
|
||||
Context,
|
||||
ScopeTraversalContext
|
||||
>(context, Context.enterScope, Context.exitScope);
|
||||
|
||||
// TODO don't count optional load rvals as dep (e.g. collectOptionalLoadRValues(...))
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
// Handle scopes that begin or end at this block
|
||||
scopeTraversal.handleBlock(block);
|
||||
|
||||
for (const instr of block.instructions) {
|
||||
handleInstruction(instr, context);
|
||||
}
|
||||
for (const place of eachTerminalOperand(block.terminal)) {
|
||||
context.visitOperand(place);
|
||||
}
|
||||
}
|
||||
return context.deps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the set of hoistable property reads.
|
||||
*/
|
||||
function recordHoistablePropertyReads(
|
||||
nodes: Map<BlockId, BlockInfo>,
|
||||
scopeId: ScopeId,
|
||||
tree: ReactiveScopeDependencyTreeHIR
|
||||
): void {
|
||||
let nonNullObjects: Array<ReactiveScopeDependency> | null = null;
|
||||
for (const [_blockId, node] of nodes) {
|
||||
if (node.scope === scopeId) {
|
||||
nonNullObjects = [...node.assumedNonNullObjects].map((n) => n.fullPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
CompilerError.invariant(nonNullObjects != null, {
|
||||
reason: "[PropagateScopeDependencies] Scope not found in tracked blocks",
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
|
||||
for (const node of nonNullObjects) {
|
||||
tree.markNodesNonNull({
|
||||
...node,
|
||||
optionalPath: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
|
||||
const escapingTemporaries: TemporariesUsedOutsideDefiningScope = {
|
||||
declarations: new Map(),
|
||||
usedOutsideDeclaringScope: new Set(),
|
||||
};
|
||||
findPromotedTemporaries(fn, escapingTemporaries);
|
||||
const { nodes, temporaries, properties } = collectHoistablePropertyLoads(
|
||||
fn,
|
||||
escapingTemporaries.usedOutsideDeclaringScope
|
||||
);
|
||||
|
||||
const scopeDeps = collectDependencies(
|
||||
fn,
|
||||
escapingTemporaries.usedOutsideDeclaringScope,
|
||||
temporaries,
|
||||
properties
|
||||
);
|
||||
|
||||
/**
|
||||
* Derive the minimal set of hoistable dependencies for each scope.
|
||||
*/
|
||||
for (const [scope, deps] of scopeDeps) {
|
||||
const tree = new ReactiveScopeDependencyTreeHIR();
|
||||
|
||||
/**
|
||||
* Step 1: Add every dependency used by this scope (e.g. `a.b.c`)
|
||||
*/
|
||||
for (const dep of deps) {
|
||||
tree.addDependency({ ...dep, optionalPath: [] });
|
||||
}
|
||||
/**
|
||||
* Step 2: Mark "hoistable" property reads, i.e. ones that will
|
||||
* unconditionally run, given the basic block in which the scope
|
||||
* begins.
|
||||
*/
|
||||
recordHoistablePropertyReads(nodes, scope.id, tree);
|
||||
scope.dependencies = tree.deriveMinimalDependencies();
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,10 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "..";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
import {
|
||||
BasicBlock,
|
||||
BlockId,
|
||||
Instruction,
|
||||
InstructionValue,
|
||||
@@ -14,7 +16,9 @@ import {
|
||||
Pattern,
|
||||
Place,
|
||||
ReactiveInstruction,
|
||||
ReactiveScope,
|
||||
ReactiveValue,
|
||||
ScopeId,
|
||||
SpreadPattern,
|
||||
Terminal,
|
||||
} from "./HIR";
|
||||
@@ -1147,3 +1151,112 @@ export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const NO_OP = () => null;
|
||||
|
||||
/**
|
||||
* Helper class for traversing scope blocks in HIR-form.
|
||||
*/
|
||||
export class ScopeBlockTraversal<TContext, TState> {
|
||||
#blockInfos: Map<
|
||||
BlockId,
|
||||
| {
|
||||
kind: "end";
|
||||
scope: ReactiveScope;
|
||||
pruned: boolean;
|
||||
state: TState;
|
||||
}
|
||||
| {
|
||||
kind: "begin";
|
||||
scope: ReactiveScope;
|
||||
pruned: boolean;
|
||||
fallthrough: BlockId;
|
||||
}
|
||||
> = new Map();
|
||||
#context: TContext;
|
||||
#enterCallback: (
|
||||
scope: ReactiveScope,
|
||||
pruned: boolean,
|
||||
context: TContext
|
||||
) => TState;
|
||||
#exitCallback: (
|
||||
scope: ReactiveScope,
|
||||
pruned: boolean,
|
||||
context: TContext,
|
||||
state: TState
|
||||
) => void;
|
||||
activeScopes: Array<ScopeId> = [];
|
||||
|
||||
constructor(
|
||||
context: TContext,
|
||||
enter: (scope: ReactiveScope, pruned: boolean, context: TContext) => TState,
|
||||
exit: (
|
||||
scope: ReactiveScope,
|
||||
pruned: boolean,
|
||||
context: TContext,
|
||||
state: TState
|
||||
) => void
|
||||
) {
|
||||
this.#context = context;
|
||||
this.#enterCallback = enter ?? null;
|
||||
this.#exitCallback = exit ?? null;
|
||||
}
|
||||
|
||||
// Handle scopes that begin or end at the start of the given block,
|
||||
// invoking scope enter/exit callbacks as applicable
|
||||
handleBlock(block: BasicBlock): void {
|
||||
const blockInfo = this.#blockInfos.get(block.id);
|
||||
if (blockInfo?.kind === "begin") {
|
||||
this.#blockInfos.delete(block.id);
|
||||
this.activeScopes.push(blockInfo.scope.id);
|
||||
const state = this.#enterCallback(
|
||||
blockInfo.scope,
|
||||
blockInfo.pruned,
|
||||
this.#context
|
||||
);
|
||||
|
||||
CompilerError.invariant(!this.#blockInfos.has(blockInfo.fallthrough), {
|
||||
reason: "Expected unique scope blocks and fallthroughs",
|
||||
loc: blockInfo.scope.loc,
|
||||
});
|
||||
this.#blockInfos.set(blockInfo.fallthrough, {
|
||||
kind: "end",
|
||||
scope: blockInfo.scope,
|
||||
pruned: blockInfo.pruned,
|
||||
state,
|
||||
});
|
||||
} else if (blockInfo?.kind === "end") {
|
||||
this.#blockInfos.delete(block.id);
|
||||
const top = this.activeScopes.at(-1);
|
||||
CompilerError.invariant(blockInfo.scope.id === top, {
|
||||
reason:
|
||||
"Expected traversed block fallthrough to match top-most active scope",
|
||||
loc: block.instructions[0]?.loc ?? block.terminal.id,
|
||||
});
|
||||
this.activeScopes.pop();
|
||||
this.#exitCallback?.(
|
||||
blockInfo.scope,
|
||||
blockInfo.pruned,
|
||||
this.#context,
|
||||
blockInfo.state
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
block.terminal.kind === "scope" ||
|
||||
block.terminal.kind === "pruned-scope"
|
||||
) {
|
||||
CompilerError.invariant(!this.#blockInfos.has(block.terminal.block), {
|
||||
reason: "Expected unique scope blocks and fallthroughs",
|
||||
loc: block.terminal.loc,
|
||||
});
|
||||
this.#blockInfos.set(block.terminal.block, {
|
||||
kind: "begin",
|
||||
scope: block.terminal.scope,
|
||||
pruned: block.terminal.kind === "pruned-scope",
|
||||
fallthrough: block.terminal.fallthrough,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,9 @@ import { assertExhaustive } from "../Utils/utils";
|
||||
* path: ['b', 'c'],
|
||||
* optionalPath: ['d', 'e', 'f'].
|
||||
* }
|
||||
* TODO remove optionalPath or rewrite our PropagateScopeDeps logic to understand
|
||||
* optional load expressions
|
||||
*
|
||||
*/
|
||||
export type ReactiveScopePropertyDependency = ReactiveScopeDependency & {
|
||||
optionalPath: Array<string>;
|
||||
|
||||
@@ -84,15 +84,32 @@ export function getOrInsertDefault<U, V>(
|
||||
}
|
||||
|
||||
export function Set_union<T>(a: Set<T>, b: Set<T>): Set<T> {
|
||||
const union = new Set<T>();
|
||||
for (const item of a) {
|
||||
if (b.has(item)) {
|
||||
union.add(item);
|
||||
}
|
||||
const union = new Set<T>(a);
|
||||
for (const item of b) {
|
||||
union.add(item);
|
||||
}
|
||||
return union;
|
||||
}
|
||||
|
||||
export function Set_intersect<T>(sets: Array<Set<T>>): Set<T> {
|
||||
if (sets.length === 0 || sets.some((s) => s.size === 0)) {
|
||||
return new Set();
|
||||
} else if (sets.length === 1) {
|
||||
return new Set(sets[0]);
|
||||
}
|
||||
const result: Set<T> = new Set();
|
||||
const first = sets[0];
|
||||
outer: for (const e of first) {
|
||||
for (let i = 1; i < sets.length; i++) {
|
||||
if (!sets[i].has(e)) {
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
result.add(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function nonNull<T extends NonNullable<U>, U>(
|
||||
value: T | null | undefined
|
||||
): value is T {
|
||||
|
||||
@@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /**
|
||||
* props.b *does* influence `a`
|
||||
*/
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(5);
|
||||
let a;
|
||||
if ($[0] !== props) {
|
||||
if (
|
||||
$[0] !== props.a ||
|
||||
$[1] !== props.b ||
|
||||
$[2] !== props.c ||
|
||||
$[3] !== props.d
|
||||
) {
|
||||
a = [];
|
||||
a.push(props.a);
|
||||
bb0: {
|
||||
@@ -47,10 +52,13 @@ function Component(props) {
|
||||
}
|
||||
|
||||
a.push(props.d);
|
||||
$[0] = props;
|
||||
$[1] = a;
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.c;
|
||||
$[3] = props.d;
|
||||
$[4] = a;
|
||||
} else {
|
||||
a = $[1];
|
||||
a = $[4];
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
@@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /**
|
||||
* props.b does *not* influence `a`
|
||||
*/
|
||||
function ComponentA(props) {
|
||||
const $ = _c(3);
|
||||
const $ = _c(5);
|
||||
let a_DEBUG;
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) {
|
||||
t0 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
a_DEBUG = [];
|
||||
@@ -85,12 +85,14 @@ function ComponentA(props) {
|
||||
|
||||
a_DEBUG.push(props.d);
|
||||
}
|
||||
$[0] = props;
|
||||
$[1] = a_DEBUG;
|
||||
$[2] = t0;
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.d;
|
||||
$[3] = a_DEBUG;
|
||||
$[4] = t0;
|
||||
} else {
|
||||
a_DEBUG = $[1];
|
||||
t0 = $[2];
|
||||
a_DEBUG = $[3];
|
||||
t0 = $[4];
|
||||
}
|
||||
if (t0 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t0;
|
||||
@@ -102,9 +104,14 @@ function ComponentA(props) {
|
||||
* props.b *does* influence `a`
|
||||
*/
|
||||
function ComponentB(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(5);
|
||||
let a;
|
||||
if ($[0] !== props) {
|
||||
if (
|
||||
$[0] !== props.a ||
|
||||
$[1] !== props.b ||
|
||||
$[2] !== props.c ||
|
||||
$[3] !== props.d
|
||||
) {
|
||||
a = [];
|
||||
a.push(props.a);
|
||||
if (props.b) {
|
||||
@@ -112,10 +119,13 @@ function ComponentB(props) {
|
||||
}
|
||||
|
||||
a.push(props.d);
|
||||
$[0] = props;
|
||||
$[1] = a;
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.c;
|
||||
$[3] = props.d;
|
||||
$[4] = a;
|
||||
} else {
|
||||
a = $[1];
|
||||
a = $[4];
|
||||
}
|
||||
return a;
|
||||
}
|
||||
@@ -124,10 +134,15 @@ function ComponentB(props) {
|
||||
* props.b *does* influence `a`, but only in a way that is never observable
|
||||
*/
|
||||
function ComponentC(props) {
|
||||
const $ = _c(3);
|
||||
const $ = _c(6);
|
||||
let a;
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if (
|
||||
$[0] !== props.a ||
|
||||
$[1] !== props.b ||
|
||||
$[2] !== props.c ||
|
||||
$[3] !== props.d
|
||||
) {
|
||||
t0 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
a = [];
|
||||
@@ -140,12 +155,15 @@ function ComponentC(props) {
|
||||
|
||||
a.push(props.d);
|
||||
}
|
||||
$[0] = props;
|
||||
$[1] = a;
|
||||
$[2] = t0;
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.c;
|
||||
$[3] = props.d;
|
||||
$[4] = a;
|
||||
$[5] = t0;
|
||||
} else {
|
||||
a = $[1];
|
||||
t0 = $[2];
|
||||
a = $[4];
|
||||
t0 = $[5];
|
||||
}
|
||||
if (t0 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t0;
|
||||
@@ -157,10 +175,15 @@ function ComponentC(props) {
|
||||
* props.b *does* influence `a`
|
||||
*/
|
||||
function ComponentD(props) {
|
||||
const $ = _c(3);
|
||||
const $ = _c(6);
|
||||
let a;
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if (
|
||||
$[0] !== props.a ||
|
||||
$[1] !== props.b ||
|
||||
$[2] !== props.c ||
|
||||
$[3] !== props.d
|
||||
) {
|
||||
t0 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
a = [];
|
||||
@@ -173,12 +196,15 @@ function ComponentD(props) {
|
||||
|
||||
a.push(props.d);
|
||||
}
|
||||
$[0] = props;
|
||||
$[1] = a;
|
||||
$[2] = t0;
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.c;
|
||||
$[3] = props.d;
|
||||
$[4] = a;
|
||||
$[5] = t0;
|
||||
} else {
|
||||
a = $[1];
|
||||
t0 = $[2];
|
||||
a = $[4];
|
||||
t0 = $[5];
|
||||
}
|
||||
if (t0 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t0;
|
||||
|
||||
@@ -36,9 +36,9 @@ function mayMutate() {}
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function ComponentA(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(4);
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) {
|
||||
const a = [];
|
||||
const b = [];
|
||||
if (b) {
|
||||
@@ -49,18 +49,20 @@ function ComponentA(props) {
|
||||
}
|
||||
|
||||
t0 = <Foo a={a} b={b} />;
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
$[0] = props.p0;
|
||||
$[1] = props.p1;
|
||||
$[2] = props.p2;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t0 = $[3];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
function ComponentB(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(4);
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) {
|
||||
const a = [];
|
||||
const b = [];
|
||||
if (mayMutate(b)) {
|
||||
@@ -71,10 +73,12 @@ function ComponentB(props) {
|
||||
}
|
||||
|
||||
t0 = <Foo a={a} b={b} />;
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
$[0] = props.p0;
|
||||
$[1] = props.p1;
|
||||
$[2] = props.p2;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t0 = $[3];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import { identity } from "shared-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
let x;
|
||||
if ($[0] !== props.value) {
|
||||
if ($[0] !== props) {
|
||||
const [t0] = props.value;
|
||||
x = t0;
|
||||
const foo = () => {
|
||||
@@ -38,7 +38,7 @@ function Component(props) {
|
||||
};
|
||||
|
||||
foo();
|
||||
$[0] = props.value;
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
|
||||
@@ -29,7 +29,7 @@ import { identity } from "shared-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
let x;
|
||||
if ($[0] !== props.value) {
|
||||
if ($[0] !== props) {
|
||||
const [t0] = props.value;
|
||||
x = t0;
|
||||
const foo = () => {
|
||||
@@ -37,7 +37,7 @@ function Component(props) {
|
||||
};
|
||||
|
||||
foo();
|
||||
$[0] = props.value;
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
|
||||
@@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(5);
|
||||
const $ = _c(7);
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) {
|
||||
t0 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
const x = [];
|
||||
@@ -41,12 +41,12 @@ function Component(props) {
|
||||
x.push(props.a);
|
||||
if (props.b) {
|
||||
let t1;
|
||||
if ($[2] !== props.b) {
|
||||
if ($[4] !== props.b) {
|
||||
t1 = [props.b];
|
||||
$[2] = props.b;
|
||||
$[3] = t1;
|
||||
$[4] = props.b;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t1 = $[5];
|
||||
}
|
||||
const y = t1;
|
||||
x.push(y);
|
||||
@@ -58,20 +58,22 @@ function Component(props) {
|
||||
break bb0;
|
||||
} else {
|
||||
let t1;
|
||||
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = foo();
|
||||
$[4] = t1;
|
||||
$[6] = t1;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
t1 = $[6];
|
||||
}
|
||||
t0 = t1;
|
||||
break bb0;
|
||||
}
|
||||
}
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
$[0] = props.cond;
|
||||
$[1] = props.a;
|
||||
$[2] = props.b;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t0 = $[3];
|
||||
}
|
||||
if (t0 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t0;
|
||||
|
||||
@@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(6);
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) {
|
||||
t0 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
const x = [];
|
||||
@@ -57,21 +57,23 @@ function Component(props) {
|
||||
break bb0;
|
||||
} else {
|
||||
let t1;
|
||||
if ($[2] !== props.b) {
|
||||
if ($[4] !== props.b) {
|
||||
t1 = makeArray(props.b);
|
||||
$[2] = props.b;
|
||||
$[3] = t1;
|
||||
$[4] = props.b;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t1 = $[5];
|
||||
}
|
||||
t0 = t1;
|
||||
break bb0;
|
||||
}
|
||||
}
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
$[0] = props.cond;
|
||||
$[1] = props.a;
|
||||
$[2] = props.b;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t0 = $[3];
|
||||
}
|
||||
if (t0 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t0;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableTreatFunctionDepsAsConditional
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Component({ props }) {
|
||||
const f = () => props.a.b;
|
||||
|
||||
return <Stringify f={props == null ? () => {} : f} />;
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ props: null }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(5);
|
||||
const { props } = t0;
|
||||
let t1;
|
||||
if ($[0] !== props) {
|
||||
t1 = () => props.a.b;
|
||||
$[0] = props;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const f = t1;
|
||||
let t2;
|
||||
if ($[2] !== props || $[3] !== f) {
|
||||
t2 = <Stringify f={props == null ? () => {} : f} />;
|
||||
$[2] = props;
|
||||
$[3] = f;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ props: null }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"f":"[[ function params=0 ]]"}</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
// @enableTreatFunctionDepsAsConditional
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Component({ props }) {
|
||||
const f = () => props.a.b;
|
||||
|
||||
return <Stringify f={props == null ? () => {} : f} />;
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ props: null }],
|
||||
};
|
||||
@@ -25,11 +25,11 @@ import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsC
|
||||
function Component(props) {
|
||||
const $ = _c(5);
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.bar) {
|
||||
t0 = function getLength() {
|
||||
return props.bar.length;
|
||||
};
|
||||
$[0] = props;
|
||||
$[0] = props.bar;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
|
||||
@@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(3);
|
||||
let items;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.cond || $[1] !== props.a) {
|
||||
let t0;
|
||||
if (props.cond) {
|
||||
t0 = [];
|
||||
@@ -38,10 +38,11 @@ function Component(props) {
|
||||
items = t0;
|
||||
|
||||
items?.push(props.a);
|
||||
$[0] = props;
|
||||
$[1] = items;
|
||||
$[0] = props.cond;
|
||||
$[1] = props.a;
|
||||
$[2] = items;
|
||||
} else {
|
||||
items = $[1];
|
||||
items = $[2];
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import { makeObject_Primitives } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(3);
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.cond || $[1] !== props.value) {
|
||||
t0 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
const object = makeObject_Primitives();
|
||||
@@ -45,10 +45,11 @@ function Component(props) {
|
||||
break bb0;
|
||||
}
|
||||
}
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
$[0] = props.cond;
|
||||
$[1] = props.value;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t0 = $[2];
|
||||
}
|
||||
if (t0 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t0;
|
||||
|
||||
@@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(6);
|
||||
let y;
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) {
|
||||
t0 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
const x = [];
|
||||
@@ -43,11 +43,11 @@ function Component(props) {
|
||||
break bb0;
|
||||
} else {
|
||||
let t1;
|
||||
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = foo();
|
||||
$[3] = t1;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t1 = $[5];
|
||||
}
|
||||
y = t1;
|
||||
if (props.b) {
|
||||
@@ -56,12 +56,14 @@ function Component(props) {
|
||||
}
|
||||
}
|
||||
}
|
||||
$[0] = props;
|
||||
$[1] = y;
|
||||
$[2] = t0;
|
||||
$[0] = props.cond;
|
||||
$[1] = props.a;
|
||||
$[2] = props.b;
|
||||
$[3] = y;
|
||||
$[4] = t0;
|
||||
} else {
|
||||
y = $[1];
|
||||
t0 = $[2];
|
||||
y = $[3];
|
||||
t0 = $[4];
|
||||
}
|
||||
if (t0 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t0;
|
||||
|
||||
@@ -36,9 +36,9 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(3);
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.cond || $[1] !== props.value) {
|
||||
const x = {};
|
||||
let y;
|
||||
if (props.cond) {
|
||||
@@ -50,10 +50,11 @@ function Component(props) {
|
||||
y.push(x);
|
||||
|
||||
t0 = [x, y];
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
$[0] = props.cond;
|
||||
$[1] = props.value;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @debug
|
||||
function Component(props) {
|
||||
const $ = _c(5);
|
||||
const $ = _c(6);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = {};
|
||||
@@ -42,7 +42,7 @@ function Component(props) {
|
||||
}
|
||||
const x = t0;
|
||||
let y;
|
||||
if ($[1] !== props) {
|
||||
if ($[1] !== props.cond || $[2] !== props.a) {
|
||||
if (props.cond) {
|
||||
y = {};
|
||||
} else {
|
||||
@@ -50,18 +50,19 @@ function Component(props) {
|
||||
}
|
||||
|
||||
y.x = x;
|
||||
$[1] = props;
|
||||
$[2] = y;
|
||||
$[1] = props.cond;
|
||||
$[2] = props.a;
|
||||
$[3] = y;
|
||||
} else {
|
||||
y = $[2];
|
||||
y = $[3];
|
||||
}
|
||||
let t1;
|
||||
if ($[3] !== y) {
|
||||
if ($[4] !== y) {
|
||||
t1 = [x, y];
|
||||
$[3] = y;
|
||||
$[4] = t1;
|
||||
$[4] = y;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
t1 = $[5];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
@@ -61,20 +61,13 @@ import { c as _c } from "react/compiler-runtime"; // This tests an optimization,
|
||||
import { CONST_TRUE, setProperty } from "shared-runtime";
|
||||
|
||||
function useJoinCondDepsInUncondScopes(props) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== props.a.b) {
|
||||
const y = {};
|
||||
let x;
|
||||
if ($[2] !== props) {
|
||||
x = {};
|
||||
if (CONST_TRUE) {
|
||||
setProperty(x, props.a.b);
|
||||
}
|
||||
$[2] = props;
|
||||
$[3] = x;
|
||||
} else {
|
||||
x = $[3];
|
||||
const x = {};
|
||||
if (CONST_TRUE) {
|
||||
setProperty(x, props.a.b);
|
||||
}
|
||||
|
||||
setProperty(y, props.a.b);
|
||||
|
||||
@@ -34,19 +34,20 @@ import { identity } from "shared-runtime";
|
||||
|
||||
// and promote it to an unconditional dependency.
|
||||
function usePromoteUnconditionalAccessToDependency(props, other) {
|
||||
const $ = _c(3);
|
||||
const $ = _c(4);
|
||||
let x;
|
||||
if ($[0] !== props.a || $[1] !== other) {
|
||||
if ($[0] !== props.a.a.a || $[1] !== props.a.b || $[2] !== other) {
|
||||
x = {};
|
||||
x.a = props.a.a.a;
|
||||
if (identity(other)) {
|
||||
x.c = props.a.b.c;
|
||||
}
|
||||
$[0] = props.a;
|
||||
$[1] = other;
|
||||
$[2] = x;
|
||||
$[0] = props.a.a.a;
|
||||
$[1] = props.a.b;
|
||||
$[2] = other;
|
||||
$[3] = x;
|
||||
} else {
|
||||
x = $[2];
|
||||
x = $[3];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function useFoo(a, b, c) {
|
||||
let x = {};
|
||||
write(x, a);
|
||||
|
||||
const y = [];
|
||||
if (x.a != null) {
|
||||
y.push(x.a.b);
|
||||
}
|
||||
y.push(b);
|
||||
|
||||
x = makeThing();
|
||||
write(x.a.b);
|
||||
|
||||
return [y, x.a.b];
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function useFoo(a, b, c) {
|
||||
const $ = _c(9);
|
||||
let x;
|
||||
if ($[0] !== a) {
|
||||
x = {};
|
||||
write(x, a);
|
||||
$[0] = a;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
let y;
|
||||
if ($[2] !== x.a || $[3] !== b) {
|
||||
y = [];
|
||||
if (x.a != null) {
|
||||
y.push(x.a.b);
|
||||
}
|
||||
|
||||
y.push(b);
|
||||
$[2] = x.a;
|
||||
$[3] = b;
|
||||
$[4] = y;
|
||||
} else {
|
||||
y = $[4];
|
||||
}
|
||||
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
x = makeThing();
|
||||
write(x.a.b);
|
||||
$[5] = x;
|
||||
} else {
|
||||
x = $[5];
|
||||
}
|
||||
let t0;
|
||||
if ($[6] !== y || $[7] !== x.a.b) {
|
||||
t0 = [y, x.a.b];
|
||||
$[6] = y;
|
||||
$[7] = x.a.b;
|
||||
$[8] = t0;
|
||||
} else {
|
||||
t0 = $[8];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,15 @@
|
||||
function useFoo(a, b, c) {
|
||||
let x = {};
|
||||
write(x, a);
|
||||
|
||||
const y = [];
|
||||
if (x.a != null) {
|
||||
y.push(x.a.b);
|
||||
}
|
||||
y.push(b);
|
||||
|
||||
x = makeThing();
|
||||
write(x.a.b);
|
||||
|
||||
return [y, x.a.b];
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// x.a.b was accessed unconditionally within the mutable range of x.
|
||||
// As a result, we cannot infer anything about whether `x` or `x.a`
|
||||
// may be null. This means that it's not safe to hoist reads from x
|
||||
// (e.g. take `x.a` or `x.a.b` as a dependency).
|
||||
|
||||
import { identity, makeObject_Primitives, setProperty } from "shared-runtime";
|
||||
|
||||
function Component({ cond, other }) {
|
||||
const x = makeObject_Primitives();
|
||||
setProperty(x, { b: 3, other }, "a");
|
||||
identity(x.a.b);
|
||||
if (!cond) {
|
||||
x.a = null;
|
||||
}
|
||||
|
||||
const y = [identity(cond) && x.a.b];
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false }],
|
||||
sequentialRenders: [
|
||||
{ cond: false },
|
||||
{ cond: false },
|
||||
{ cond: false, other: 8 },
|
||||
{ cond: true },
|
||||
{ cond: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // x.a.b was accessed unconditionally within the mutable range of x.
|
||||
// As a result, we cannot infer anything about whether `x` or `x.a`
|
||||
// may be null. This means that it's not safe to hoist reads from x
|
||||
// (e.g. take `x.a` or `x.a.b` as a dependency).
|
||||
|
||||
import { identity, makeObject_Primitives, setProperty } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(8);
|
||||
const { cond, other } = t0;
|
||||
let x;
|
||||
if ($[0] !== other || $[1] !== cond) {
|
||||
x = makeObject_Primitives();
|
||||
setProperty(x, { b: 3, other }, "a");
|
||||
identity(x.a.b);
|
||||
if (!cond) {
|
||||
x.a = null;
|
||||
}
|
||||
$[0] = other;
|
||||
$[1] = cond;
|
||||
$[2] = x;
|
||||
} else {
|
||||
x = $[2];
|
||||
}
|
||||
let t1;
|
||||
if ($[3] !== cond || $[4] !== x) {
|
||||
t1 = identity(cond) && x.a.b;
|
||||
$[3] = cond;
|
||||
$[4] = x;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t1 = $[5];
|
||||
}
|
||||
let t2;
|
||||
if ($[6] !== t1) {
|
||||
t2 = [t1];
|
||||
$[6] = t1;
|
||||
$[7] = t2;
|
||||
} else {
|
||||
t2 = $[7];
|
||||
}
|
||||
const y = t2;
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false }],
|
||||
sequentialRenders: [
|
||||
{ cond: false },
|
||||
{ cond: false },
|
||||
{ cond: false, other: 8 },
|
||||
{ cond: true },
|
||||
{ cond: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [false]
|
||||
[false]
|
||||
[false]
|
||||
[3]
|
||||
[3]
|
||||
@@ -0,0 +1,30 @@
|
||||
// x.a.b was accessed unconditionally within the mutable range of x.
|
||||
// As a result, we cannot infer anything about whether `x` or `x.a`
|
||||
// may be null. This means that it's not safe to hoist reads from x
|
||||
// (e.g. take `x.a` or `x.a.b` as a dependency).
|
||||
|
||||
import { identity, makeObject_Primitives, setProperty } from "shared-runtime";
|
||||
|
||||
function Component({ cond, other }) {
|
||||
const x = makeObject_Primitives();
|
||||
setProperty(x, { b: 3, other }, "a");
|
||||
identity(x.a.b);
|
||||
if (!cond) {
|
||||
x.a = null;
|
||||
}
|
||||
|
||||
const y = [identity(cond) && x.a.b];
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false }],
|
||||
sequentialRenders: [
|
||||
{ cond: false },
|
||||
{ cond: false },
|
||||
{ cond: false, other: 8 },
|
||||
{ cond: true },
|
||||
{ cond: true },
|
||||
],
|
||||
};
|
||||
@@ -31,10 +31,16 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(7);
|
||||
let x = 0;
|
||||
let values;
|
||||
if ($[0] !== props || $[1] !== x) {
|
||||
if (
|
||||
$[0] !== props.a ||
|
||||
$[1] !== props.b ||
|
||||
$[2] !== props.c ||
|
||||
$[3] !== props.d ||
|
||||
$[4] !== x
|
||||
) {
|
||||
values = [];
|
||||
const y = props.a || props.b;
|
||||
values.push(y);
|
||||
@@ -48,13 +54,16 @@ function Component(props) {
|
||||
}
|
||||
|
||||
values.push(x);
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
$[2] = values;
|
||||
$[3] = x;
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.c;
|
||||
$[3] = props.d;
|
||||
$[4] = x;
|
||||
$[5] = values;
|
||||
$[6] = x;
|
||||
} else {
|
||||
values = $[2];
|
||||
x = $[3];
|
||||
values = $[5];
|
||||
x = $[6];
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ function Component(props) {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(3);
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.p0 || $[1] !== props.p1) {
|
||||
const x = [];
|
||||
let y;
|
||||
if (props.p0) {
|
||||
@@ -40,10 +40,11 @@ function Component(props) {
|
||||
{y}
|
||||
</Component>
|
||||
);
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
$[0] = props.p0;
|
||||
$[1] = props.p1;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
@@ -17,17 +17,19 @@ function foo(props) {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function foo(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(4);
|
||||
let x;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) {
|
||||
x = [];
|
||||
x.push(props.bar);
|
||||
props.cond ? (([x] = [[]]), x.push(props.foo)) : null;
|
||||
mut(x);
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
$[0] = props.bar;
|
||||
$[1] = props.cond;
|
||||
$[2] = props.foo;
|
||||
$[3] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
x = $[3];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function foo(props) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(5);
|
||||
let x;
|
||||
if ($[0] !== props.bar) {
|
||||
x = [];
|
||||
@@ -32,12 +32,13 @@ function foo(props) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
if ($[2] !== props) {
|
||||
if ($[2] !== props.cond || $[3] !== props.foo) {
|
||||
props.cond ? (([x] = [[]]), x.push(props.foo)) : null;
|
||||
$[2] = props;
|
||||
$[3] = x;
|
||||
$[2] = props.cond;
|
||||
$[3] = props.foo;
|
||||
$[4] = x;
|
||||
} else {
|
||||
x = $[3];
|
||||
x = $[4];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -17,17 +17,19 @@ function foo(props) {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function foo(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(4);
|
||||
let x;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) {
|
||||
x = [];
|
||||
x.push(props.bar);
|
||||
props.cond ? ((x = []), x.push(props.foo)) : null;
|
||||
mut(x);
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
$[0] = props.bar;
|
||||
$[1] = props.cond;
|
||||
$[2] = props.foo;
|
||||
$[3] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
x = $[3];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function foo(props) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(5);
|
||||
let x;
|
||||
if ($[0] !== props.bar) {
|
||||
x = [];
|
||||
@@ -32,12 +32,13 @@ function foo(props) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
if ($[2] !== props) {
|
||||
if ($[2] !== props.cond || $[3] !== props.foo) {
|
||||
props.cond ? ((x = []), x.push(props.foo)) : null;
|
||||
$[2] = props;
|
||||
$[3] = x;
|
||||
$[2] = props.cond;
|
||||
$[3] = props.foo;
|
||||
$[4] = x;
|
||||
} else {
|
||||
x = $[3];
|
||||
x = $[4];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -31,17 +31,19 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { arrayPush } from "shared-runtime";
|
||||
function foo(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(4);
|
||||
let x;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) {
|
||||
x = [];
|
||||
x.push(props.bar);
|
||||
props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar));
|
||||
arrayPush(x, 4);
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
$[0] = props.bar;
|
||||
$[1] = props.cond;
|
||||
$[2] = props.foo;
|
||||
$[3] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
x = $[3];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function foo(props) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(6);
|
||||
let x;
|
||||
if ($[0] !== props.bar) {
|
||||
x = [];
|
||||
@@ -34,12 +34,14 @@ function foo(props) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
if ($[2] !== props) {
|
||||
if ($[2] !== props.cond || $[3] !== props.foo || $[4] !== props.bar) {
|
||||
props.cond ? ((x = []), x.push(props.foo)) : ((x = []), x.push(props.bar));
|
||||
$[2] = props;
|
||||
$[3] = x;
|
||||
$[2] = props.cond;
|
||||
$[3] = props.foo;
|
||||
$[4] = props.bar;
|
||||
$[5] = x;
|
||||
} else {
|
||||
x = $[3];
|
||||
x = $[5];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ function foo(props) {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function foo(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(4);
|
||||
let x;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) {
|
||||
x = [];
|
||||
x.push(props.bar);
|
||||
if (props.cond) {
|
||||
@@ -39,10 +39,12 @@ function foo(props) {
|
||||
}
|
||||
|
||||
mut(x);
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
$[0] = props.bar;
|
||||
$[1] = props.cond;
|
||||
$[2] = props.foo;
|
||||
$[3] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
x = $[3];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ function foo(props) {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function foo(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(4);
|
||||
let x;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) {
|
||||
({ x } = { x: [] });
|
||||
x.push(props.bar);
|
||||
if (props.cond) {
|
||||
@@ -32,10 +32,12 @@ function foo(props) {
|
||||
}
|
||||
|
||||
mut(x);
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
$[0] = props.bar;
|
||||
$[1] = props.cond;
|
||||
$[2] = props.foo;
|
||||
$[3] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
x = $[3];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ function foo(props) {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function foo(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(4);
|
||||
let x;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.bar || $[1] !== props.cond || $[2] !== props.foo) {
|
||||
x = [];
|
||||
x.push(props.bar);
|
||||
if (props.cond) {
|
||||
@@ -32,10 +32,12 @@ function foo(props) {
|
||||
}
|
||||
|
||||
mut(x);
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
$[0] = props.bar;
|
||||
$[1] = props.cond;
|
||||
$[2] = props.foo;
|
||||
$[3] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
x = $[3];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -33,10 +33,10 @@ function Component(props) {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(7);
|
||||
const $ = _c(8);
|
||||
let y;
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.p0 || $[1] !== props.p2) {
|
||||
const x = [];
|
||||
bb0: switch (props.p0) {
|
||||
case 1: {
|
||||
@@ -45,11 +45,11 @@ function Component(props) {
|
||||
case true: {
|
||||
x.push(props.p2);
|
||||
let t1;
|
||||
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = [];
|
||||
$[3] = t1;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t1 = $[4];
|
||||
}
|
||||
y = t1;
|
||||
}
|
||||
@@ -62,23 +62,24 @@ function Component(props) {
|
||||
}
|
||||
|
||||
t0 = <Component data={x} />;
|
||||
$[0] = props;
|
||||
$[1] = y;
|
||||
$[2] = t0;
|
||||
$[0] = props.p0;
|
||||
$[1] = props.p2;
|
||||
$[2] = y;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
y = $[1];
|
||||
t0 = $[2];
|
||||
y = $[2];
|
||||
t0 = $[3];
|
||||
}
|
||||
const child = t0;
|
||||
y.push(props.p4);
|
||||
let t1;
|
||||
if ($[4] !== y || $[5] !== child) {
|
||||
if ($[5] !== y || $[6] !== child) {
|
||||
t1 = <Component data={y}>{child}</Component>;
|
||||
$[4] = y;
|
||||
$[5] = child;
|
||||
$[6] = t1;
|
||||
$[5] = y;
|
||||
$[6] = child;
|
||||
$[7] = t1;
|
||||
} else {
|
||||
t1 = $[6];
|
||||
t1 = $[7];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ function Component(props) {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(6);
|
||||
const $ = _c(8);
|
||||
let y;
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.p0 || $[1] !== props.p2 || $[2] !== props.p3) {
|
||||
const x = [];
|
||||
switch (props.p0) {
|
||||
case true: {
|
||||
@@ -44,23 +44,25 @@ function Component(props) {
|
||||
}
|
||||
|
||||
t0 = <Component data={x} />;
|
||||
$[0] = props;
|
||||
$[1] = y;
|
||||
$[2] = t0;
|
||||
$[0] = props.p0;
|
||||
$[1] = props.p2;
|
||||
$[2] = props.p3;
|
||||
$[3] = y;
|
||||
$[4] = t0;
|
||||
} else {
|
||||
y = $[1];
|
||||
t0 = $[2];
|
||||
y = $[3];
|
||||
t0 = $[4];
|
||||
}
|
||||
const child = t0;
|
||||
y.push(props.p4);
|
||||
let t1;
|
||||
if ($[3] !== y || $[4] !== child) {
|
||||
if ($[5] !== y || $[6] !== child) {
|
||||
t1 = <Component data={y}>{child}</Component>;
|
||||
$[3] = y;
|
||||
$[4] = child;
|
||||
$[5] = t1;
|
||||
$[5] = y;
|
||||
$[6] = child;
|
||||
$[7] = t1;
|
||||
} else {
|
||||
t1 = $[5];
|
||||
t1 = $[7];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import { Stringify } from "shared-runtime";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Here, we don't need to memoize Stringify as it is a read off of a global.
|
||||
* TODO: in PropagateScopeDeps (hir), we should produce a sidemap of global rvals
|
||||
* and avoid adding them to `temporariesUsedOutsideDefiningScope`.
|
||||
*/
|
||||
function Component({ num }: { num: number }) {
|
||||
const arr = makeArray(num);
|
||||
return <Stringify value={arr.push(num)}></Stringify>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ num: 2 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify } from "shared-runtime";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Here, we don't need to memoize Stringify as it is a read off of a global.
|
||||
* TODO: in PropagateScopeDeps (hir), we should produce a sidemap of global rvals
|
||||
* and avoid adding them to `temporariesUsedOutsideDefiningScope`.
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(6);
|
||||
const { num } = t0;
|
||||
let T0;
|
||||
let t1;
|
||||
if ($[0] !== num) {
|
||||
const arr = makeArray(num);
|
||||
T0 = Stringify;
|
||||
t1 = arr.push(num);
|
||||
$[0] = num;
|
||||
$[1] = T0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
T0 = $[1];
|
||||
t1 = $[2];
|
||||
}
|
||||
let t2;
|
||||
if ($[3] !== T0 || $[4] !== t1) {
|
||||
t2 = <T0 value={t1} />;
|
||||
$[3] = T0;
|
||||
$[4] = t1;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ num: 2 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"value":2}</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Stringify } from "shared-runtime";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Here, we don't need to memoize Stringify as it is a read off of a global.
|
||||
* TODO: in PropagateScopeDeps (hir), we should produce a sidemap of global rvals
|
||||
* and avoid adding them to `temporariesUsedOutsideDefiningScope`.
|
||||
*/
|
||||
function Component({ num }: { num: number }) {
|
||||
const arr = makeArray(num);
|
||||
return <Stringify value={arr.push(num)}></Stringify>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ num: 2 }],
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import * as SharedRuntime from "shared-runtime";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Here, we don't need to memoize SharedRuntime.Stringify as it is a PropertyLoad
|
||||
* off of a global.
|
||||
* TODO: in PropagateScopeDeps (hir), we should produce a sidemap of global rvals
|
||||
* and avoid adding them to `temporariesUsedOutsideDefiningScope`.
|
||||
*/
|
||||
function Component({ num }: { num: number }) {
|
||||
const arr = makeArray(num);
|
||||
return (
|
||||
<SharedRuntime.Stringify value={arr.push(num)}></SharedRuntime.Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ num: 2 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as SharedRuntime from "shared-runtime";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Here, we don't need to memoize SharedRuntime.Stringify as it is a PropertyLoad
|
||||
* off of a global.
|
||||
* TODO: in PropagateScopeDeps (hir), we should produce a sidemap of global rvals
|
||||
* and avoid adding them to `temporariesUsedOutsideDefiningScope`.
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(6);
|
||||
const { num } = t0;
|
||||
let T0;
|
||||
let t1;
|
||||
if ($[0] !== num) {
|
||||
const arr = makeArray(num);
|
||||
|
||||
T0 = SharedRuntime.Stringify;
|
||||
t1 = arr.push(num);
|
||||
$[0] = num;
|
||||
$[1] = T0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
T0 = $[1];
|
||||
t1 = $[2];
|
||||
}
|
||||
let t2;
|
||||
if ($[3] !== T0 || $[4] !== t1) {
|
||||
t2 = <T0 value={t1} />;
|
||||
$[3] = T0;
|
||||
$[4] = t1;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ num: 2 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"value":2}</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import * as SharedRuntime from "shared-runtime";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Here, we don't need to memoize SharedRuntime.Stringify as it is a PropertyLoad
|
||||
* off of a global.
|
||||
* TODO: in PropagateScopeDeps (hir), we should produce a sidemap of global rvals
|
||||
* and avoid adding them to `temporariesUsedOutsideDefiningScope`.
|
||||
*/
|
||||
function Component({ num }: { num: number }) {
|
||||
const arr = makeArray(num);
|
||||
return (
|
||||
<SharedRuntime.Stringify value={arr.push(num)}></SharedRuntime.Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ num: 2 }],
|
||||
};
|
||||
@@ -28,9 +28,9 @@ import { c as _c } from "react/compiler-runtime";
|
||||
const { shallowCopy, throwErrorWithMessage } = require("shared-runtime");
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(3);
|
||||
const $ = _c(5);
|
||||
let x;
|
||||
if ($[0] !== props.a) {
|
||||
if ($[0] !== props) {
|
||||
x = [];
|
||||
try {
|
||||
let t0;
|
||||
@@ -42,9 +42,17 @@ function Component(props) {
|
||||
}
|
||||
x.push(t0);
|
||||
} catch {
|
||||
x.push(shallowCopy({ a: props.a }));
|
||||
let t0;
|
||||
if ($[3] !== props.a) {
|
||||
t0 = shallowCopy({ a: props.a });
|
||||
$[3] = props.a;
|
||||
$[4] = t0;
|
||||
} else {
|
||||
t0 = $[4];
|
||||
}
|
||||
x.push(t0);
|
||||
}
|
||||
$[0] = props.a;
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
|
||||
@@ -31,9 +31,9 @@ import { c as _c } from "react/compiler-runtime";
|
||||
const { throwInput } = require("shared-runtime");
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(3);
|
||||
const $ = _c(2);
|
||||
let x;
|
||||
if ($[0] !== props.y || $[1] !== props.e) {
|
||||
if ($[0] !== props) {
|
||||
try {
|
||||
const y = [];
|
||||
y.push(props.y);
|
||||
@@ -43,11 +43,10 @@ function Component(props) {
|
||||
e.push(props.e);
|
||||
x = e;
|
||||
}
|
||||
$[0] = props.y;
|
||||
$[1] = props.e;
|
||||
$[2] = x;
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[2];
|
||||
x = $[1];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ import { c as _c } from "react/compiler-runtime";
|
||||
const { throwInput } = require("shared-runtime");
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(3);
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== props.y || $[1] !== props.e) {
|
||||
if ($[0] !== props) {
|
||||
t0 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
try {
|
||||
@@ -46,11 +46,10 @@ function Component(props) {
|
||||
break bb0;
|
||||
}
|
||||
}
|
||||
$[0] = props.y;
|
||||
$[1] = props.e;
|
||||
$[2] = t0;
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
t0 = $[1];
|
||||
}
|
||||
if (t0 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t0;
|
||||
|
||||
@@ -33,11 +33,16 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(3);
|
||||
const $ = _c(6);
|
||||
let t0;
|
||||
bb0: {
|
||||
let y;
|
||||
if ($[0] !== props) {
|
||||
if (
|
||||
$[0] !== props.cond ||
|
||||
$[1] !== props.a ||
|
||||
$[2] !== props.cond2 ||
|
||||
$[3] !== props.b
|
||||
) {
|
||||
y = [];
|
||||
if (props.cond) {
|
||||
y.push(props.a);
|
||||
@@ -48,12 +53,15 @@ function Component(props) {
|
||||
}
|
||||
|
||||
y.push(props.b);
|
||||
$[0] = props;
|
||||
$[1] = y;
|
||||
$[2] = t0;
|
||||
$[0] = props.cond;
|
||||
$[1] = props.a;
|
||||
$[2] = props.cond2;
|
||||
$[3] = props.b;
|
||||
$[4] = y;
|
||||
$[5] = t0;
|
||||
} else {
|
||||
y = $[1];
|
||||
t0 = $[2];
|
||||
y = $[4];
|
||||
t0 = $[5];
|
||||
}
|
||||
t0 = y;
|
||||
}
|
||||
|
||||
@@ -78,21 +78,29 @@ export function mutateAndReturnNewValue<T>(arg: T): string {
|
||||
return "hello!";
|
||||
}
|
||||
|
||||
export function setProperty(arg: any, property: any): void {
|
||||
export function setProperty(
|
||||
arg: any,
|
||||
property: any,
|
||||
propertyName?: string
|
||||
): void {
|
||||
// don't mutate primitive
|
||||
if (arg == null || typeof arg !== "object") {
|
||||
return arg;
|
||||
}
|
||||
|
||||
let count: number = 0;
|
||||
let key;
|
||||
while (true) {
|
||||
key = "wat" + count;
|
||||
if (!Object.hasOwn(arg, key)) {
|
||||
arg[key] = property;
|
||||
return arg;
|
||||
if (propertyName != null && typeof propertyName === "string") {
|
||||
arg[propertyName] = property;
|
||||
} else {
|
||||
let count: number = 0;
|
||||
let key;
|
||||
while (true) {
|
||||
key = "wat" + count;
|
||||
if (!Object.hasOwn(arg, key)) {
|
||||
arg[key] = property;
|
||||
return arg;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user