Compare commits

..

1 Commits

Author SHA1 Message Date
Joe Savona
06beb9f43e [compiler] Null out source locations where not explicitly preserved
Inspired by #32950. Specifically from https://github.com/facebook/react/issues/32950#issuecomment-2837887871, it sounds like Babel by default emits source map information for all nodes, even when they don't have a `loc` property set. Code coverage tools then pick up the synthesized source location information for this, leading to the issue described.

A few google searches didn't turn up any documented way to opt-out of generating source span information, but AI tools answer that explicitly setting `node.loc = null` omits the node from source maps. This PR is a quick hack to confirm that.

[ghstack-poisoned]
2025-04-29 18:06:45 +09:00
33 changed files with 228 additions and 1138 deletions

View File

@@ -129,7 +129,6 @@ function run(
mode,
config,
contextIdentifiers,
func,
logger,
filename,
code,

View File

@@ -70,14 +70,12 @@ import {BuiltInArrayId} from './ObjectShape';
export function lower(
func: NodePath<t.Function>,
env: Environment,
// Bindings captured from the outer function, in case lower() is called recursively (for lambdas)
bindings: Bindings | null = null,
capturedRefs: Array<t.Identifier> = [],
// the outermost function being compiled, in case lower() is called recursively (for lambdas)
parent: NodePath<t.Function> | null = null,
): Result<HIRFunction, CompilerError> {
const builder = new HIRBuilder(env, {
bindings,
context: capturedRefs,
});
const builder = new HIRBuilder(env, parent ?? func, bindings, capturedRefs);
const context: HIRFunction['context'] = [];
for (const ref of capturedRefs ?? []) {
@@ -217,7 +215,7 @@ export function lower(
return Ok({
id,
params,
fnType: bindings == null ? env.fnType : 'Other',
fnType: parent == null ? env.fnType : 'Other',
returnTypeAnnotation: null, // TODO: extract the actual return type node if present
returnType: makeType(),
body: builder.build(),
@@ -3419,7 +3417,7 @@ function lowerFunction(
| t.ObjectMethod
>,
): LoweredFunction | null {
const componentScope: Scope = builder.environment.parentFunction.scope;
const componentScope: Scope = builder.parentFunction.scope;
const capturedContext = gatherCapturedContext(expr, componentScope);
/*
@@ -3430,10 +3428,13 @@ function lowerFunction(
* This isn't a problem in practice because use Babel's scope analysis to
* identify the correct references.
*/
const lowering = lower(expr, builder.environment, builder.bindings, [
...builder.context,
...capturedContext,
]);
const lowering = lower(
expr,
builder.environment,
builder.bindings,
[...builder.context, ...capturedContext],
builder.parentFunction,
);
let loweredFunc: HIRFunction;
if (lowering.isErr()) {
lowering
@@ -3455,7 +3456,7 @@ function lowerExpressionToTemporary(
return lowerValueToTemporary(builder, value);
}
export function lowerValueToTemporary(
function lowerValueToTemporary(
builder: HIRBuilder,
value: InstructionValue,
): Place {

View File

@@ -191,7 +191,6 @@ type TerminalRewriteInfo =
| {
kind: 'StartScope';
blockId: BlockId;
dependencyId: BlockId;
fallthroughId: BlockId;
instrId: InstructionId;
scope: ReactiveScope;
@@ -216,14 +215,12 @@ function pushStartScopeTerminal(
scope: ReactiveScope,
context: ScopeTraversalContext,
): void {
const dependencyId = context.env.nextBlockId;
const blockId = context.env.nextBlockId;
const fallthroughId = context.env.nextBlockId;
context.rewrites.push({
kind: 'StartScope',
blockId,
fallthroughId,
dependencyId,
instrId: scope.range.start,
scope,
});
@@ -265,13 +262,10 @@ type RewriteContext = {
* instr1, instr2, instr3, instr4, [[ original terminal ]]
* Rewritten:
* bb0:
* instr1, [[ scope start dependencies=bb1 block=bb2]]
* instr1, [[ scope start block=bb1]]
* bb1:
* [[ empty, filled in in PropagateScopeDependenciesHIR ]]
* goto bb2
* instr2, instr3, [[ scope end goto=bb2 ]]
* bb2:
* instr2, instr3, [[ scope end goto=bb3 ]]
* bb3:
* instr4, [[ original terminal ]]
*/
function handleRewrite(
@@ -285,7 +279,6 @@ function handleRewrite(
? {
kind: 'scope',
fallthrough: terminalInfo.fallthroughId,
dependencies: terminalInfo.dependencyId,
block: terminalInfo.blockId,
scope: terminalInfo.scope,
id: terminalInfo.instrId,
@@ -312,28 +305,7 @@ function handleRewrite(
context.nextPreds = new Set([currBlockId]);
context.nextBlockId =
terminalInfo.kind === 'StartScope'
? terminalInfo.dependencyId
? terminalInfo.blockId
: terminalInfo.fallthroughId;
context.instrSliceIdx = idx;
if (terminalInfo.kind === 'StartScope') {
const currBlockId = context.nextBlockId;
context.rewrites.push({
kind: context.source.kind,
id: currBlockId,
instructions: [],
preds: context.nextPreds,
// Only the first rewrite should reuse source block phis
phis: new Set(),
terminal: {
kind: 'goto',
variant: GotoVariant.Break,
block: terminal.block,
id: terminalInfo.instrId,
loc: GeneratedSource,
},
});
context.nextPreds = new Set([currBlockId]);
context.nextBlockId = terminalInfo.blockId;
}
}

View File

@@ -241,10 +241,7 @@ type PropertyPathNode =
class PropertyPathRegistry {
roots: Map<IdentifierId, RootNode> = new Map();
getOrCreateIdentifier(
identifier: Identifier,
reactive: boolean,
): PropertyPathNode {
getOrCreateIdentifier(identifier: Identifier): PropertyPathNode {
/**
* Reads from a statically scoped variable are always safe in JS,
* with the exception of TDZ (not addressed by this pass).
@@ -258,19 +255,12 @@ class PropertyPathRegistry {
optionalProperties: new Map(),
fullPath: {
identifier,
reactive,
path: [],
},
hasOptional: false,
parent: null,
};
this.roots.set(identifier.id, rootNode);
} else {
CompilerError.invariant(reactive === rootNode.fullPath.reactive, {
reason:
'[HoistablePropertyLoads] Found inconsistencies in `reactive` flag when deduping identifier reads within the same scope',
loc: identifier.loc,
});
}
return rootNode;
}
@@ -288,7 +278,6 @@ class PropertyPathRegistry {
parent: parent,
fullPath: {
identifier: parent.fullPath.identifier,
reactive: parent.fullPath.reactive,
path: parent.fullPath.path.concat(entry),
},
hasOptional: parent.hasOptional || entry.optional,
@@ -304,7 +293,7 @@ class PropertyPathRegistry {
* so all subpaths of a PropertyLoad should already exist
* (e.g. a.b is added before a.b.c),
*/
let currNode = this.getOrCreateIdentifier(n.identifier, n.reactive);
let currNode = this.getOrCreateIdentifier(n.identifier);
if (n.path.length === 0) {
return currNode;
}
@@ -326,11 +315,10 @@ function getMaybeNonNullInInstruction(
instr: InstructionValue,
context: CollectHoistablePropertyLoadsContext,
): PropertyPathNode | null {
let path: ReactiveScopeDependency | null = null;
let path = null;
if (instr.kind === 'PropertyLoad') {
path = context.temporaries.get(instr.object.identifier.id) ?? {
identifier: instr.object.identifier,
reactive: instr.object.reactive,
path: [],
};
} else if (instr.kind === 'Destructure') {
@@ -393,7 +381,7 @@ function collectNonNullsInBlocks(
) {
const identifier = fn.params[0].identifier;
knownNonNullIdentifiers.add(
context.registry.getOrCreateIdentifier(identifier, true),
context.registry.getOrCreateIdentifier(identifier),
);
}
const nodes = new Map<
@@ -628,11 +616,9 @@ function reduceMaybeOptionalChains(
changed = false;
for (const original of optionalChainNodes) {
let {identifier, path: origPath, reactive} = original.fullPath;
let currNode: PropertyPathNode = registry.getOrCreateIdentifier(
identifier,
reactive,
);
let {identifier, path: origPath} = original.fullPath;
let currNode: PropertyPathNode =
registry.getOrCreateIdentifier(identifier);
for (let i = 0; i < origPath.length; i++) {
const entry = origPath[i];
// If the base is known to be non-null, replace with a non-optional load

View File

@@ -114,7 +114,7 @@ export type OptionalChainSidemap = {
hoistableObjects: ReadonlyMap<BlockId, ReactiveScopeDependency>;
};
export type OptionalTraversalContext = {
type OptionalTraversalContext = {
currFn: HIRFunction;
blocks: ReadonlyMap<BlockId, BasicBlock>;
@@ -235,7 +235,7 @@ function matchOptionalTestBlock(
* property loads. If any part of the optional chain is not hoistable, returns
* null.
*/
export function traverseOptionalBlock(
function traverseOptionalBlock(
optional: TBasicBlock<OptionalTerminal>,
context: OptionalTraversalContext,
outerAlternate: BlockId | null,
@@ -290,7 +290,6 @@ export function traverseOptionalBlock(
);
baseObject = {
identifier: maybeTest.instructions[0].value.place.identifier,
reactive: maybeTest.instructions[0].value.place.reactive,
path,
};
test = maybeTest.terminal;
@@ -392,7 +391,6 @@ export function traverseOptionalBlock(
);
const load = {
identifier: baseObject.identifier,
reactive: baseObject.reactive,
path: [
...baseObject.path,
{

View File

@@ -25,9 +25,8 @@ export class ReactiveScopeDependencyTreeHIR {
* `identifier.path`, or `identifier?.path` is in this map, it is safe to
* evaluate (non-optional) PropertyLoads from.
*/
#hoistableObjects: Map<Identifier, HoistableNode & {reactive: boolean}> =
new Map();
#deps: Map<Identifier, DependencyNode & {reactive: boolean}> = new Map();
#hoistableObjects: Map<Identifier, HoistableNode> = new Map();
#deps: Map<Identifier, DependencyNode> = new Map();
/**
* @param hoistableObjects a set of paths from which we can safely evaluate
@@ -36,10 +35,9 @@ export class ReactiveScopeDependencyTreeHIR {
* duplicates when traversing the CFG.
*/
constructor(hoistableObjects: Iterable<ReactiveScopeDependency>) {
for (const {path, identifier, reactive} of hoistableObjects) {
for (const {path, identifier} of hoistableObjects) {
let currNode = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot(
identifier,
reactive,
this.#hoistableObjects,
path.length > 0 && path[0].optional ? 'Optional' : 'NonNull',
);
@@ -72,8 +70,7 @@ export class ReactiveScopeDependencyTreeHIR {
static #getOrCreateRoot<T extends string>(
identifier: Identifier,
reactive: boolean,
roots: Map<Identifier, TreeNode<T> & {reactive: boolean}>,
roots: Map<Identifier, TreeNode<T>>,
defaultAccessType: T,
): TreeNode<T> {
// roots can always be accessed unconditionally in JS
@@ -82,16 +79,9 @@ export class ReactiveScopeDependencyTreeHIR {
if (rootNode === undefined) {
rootNode = {
properties: new Map(),
reactive,
accessType: defaultAccessType,
};
roots.set(identifier, rootNode);
} else {
CompilerError.invariant(reactive === rootNode.reactive, {
reason: '[DeriveMinimalDependenciesHIR] Conflicting reactive root flag',
description: `Identifier ${printIdentifier(identifier)}`,
loc: GeneratedSource,
});
}
return rootNode;
}
@@ -102,10 +92,9 @@ export class ReactiveScopeDependencyTreeHIR {
* safe-to-evaluate subpath
*/
addDependency(dep: ReactiveScopeDependency): void {
const {identifier, reactive, path} = dep;
const {identifier, path} = dep;
let depCursor = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot(
identifier,
reactive,
this.#deps,
PropertyAccessType.UnconditionalAccess,
);
@@ -183,13 +172,7 @@ export class ReactiveScopeDependencyTreeHIR {
deriveMinimalDependencies(): Set<ReactiveScopeDependency> {
const results = new Set<ReactiveScopeDependency>();
for (const [rootId, rootNode] of this.#deps.entries()) {
collectMinimalDependenciesInSubtree(
rootNode,
rootNode.reactive,
rootId,
[],
results,
);
collectMinimalDependenciesInSubtree(rootNode, rootId, [], results);
}
return results;
@@ -311,24 +294,25 @@ type HoistableNode = TreeNode<'Optional' | 'NonNull'>;
type DependencyNode = TreeNode<PropertyAccessType>;
/**
* TODO: this is directly pasted from DeriveMinimalDependencies. Since we no
* longer have conditionally accessed nodes, we can simplify
*
* Recursively calculates minimal dependencies in a subtree.
* @param node DependencyNode representing a dependency subtree.
* @returns a minimal list of dependencies in this subtree.
*/
function collectMinimalDependenciesInSubtree(
node: DependencyNode,
reactive: boolean,
rootIdentifier: Identifier,
path: Array<DependencyPathEntry>,
results: Set<ReactiveScopeDependency>,
): void {
if (isDependency(node.accessType)) {
results.add({identifier: rootIdentifier, reactive, path});
results.add({identifier: rootIdentifier, path});
} else {
for (const [childName, childNode] of node.properties) {
collectMinimalDependenciesInSubtree(
childNode,
reactive,
rootIdentifier,
[
...path,

View File

@@ -55,7 +55,7 @@ import {
ShapeRegistry,
addHook,
} from './ObjectShape';
import {Scope as BabelScope, NodePath} from '@babel/traverse';
import {Scope as BabelScope} from '@babel/traverse';
import {TypeSchema} from './TypeSchema';
export const ReactElementSymbolSchema = z.object({
@@ -863,7 +863,6 @@ export class Environment {
#contextIdentifiers: Set<t.Identifier>;
#hoistedIdentifiers: Set<t.Identifier>;
parentFunction: NodePath<t.Function>;
constructor(
scope: BabelScope,
@@ -871,7 +870,6 @@ export class Environment {
compilerMode: CompilerMode,
config: EnvironmentConfig,
contextIdentifiers: Set<t.Identifier>,
parentFunction: NodePath<t.Function>, // the outermost function being compiled
logger: Logger | null,
filename: string | null,
code: string | null,
@@ -930,7 +928,6 @@ export class Environment {
this.#moduleTypes.set(REANIMATED_MODULE_NAME, reanimatedModuleType);
}
this.parentFunction = parentFunction;
this.#contextIdentifiers = contextIdentifiers;
this.#hoistedIdentifiers = new Set();
}

View File

@@ -62,14 +62,12 @@ export type ReactiveFunction = {
export type ReactiveScopeBlock = {
kind: 'scope';
dependencyInstructions: Array<ReactiveInstructionStatement>;
scope: ReactiveScope;
instructions: ReactiveBlock;
};
export type PrunedReactiveScopeBlock = {
kind: 'pruned-scope';
dependencyInstructions: Array<ReactiveInstructionStatement>;
scope: ReactiveScope;
instructions: ReactiveBlock;
};
@@ -616,7 +614,6 @@ export type MaybeThrowTerminal = {
export type ReactiveScopeTerminal = {
kind: 'scope';
fallthrough: BlockId;
dependencies: BlockId;
block: BlockId;
scope: ReactiveScope;
id: InstructionId;
@@ -626,7 +623,6 @@ export type ReactiveScopeTerminal = {
export type PrunedScopeTerminal = {
kind: 'pruned-scope';
fallthrough: BlockId;
dependencies: BlockId;
block: BlockId;
scope: ReactiveScope;
id: InstructionId;
@@ -1476,10 +1472,9 @@ export type ReactiveScope = {
range: MutableRange;
/**
* Note the dependencies of a reactive scope are tracked in HIR and
* ReactiveFunction
* The inputs to this reactive scope
*/
dependencies: Array<Place>;
dependencies: ReactiveScopeDependencies;
/**
* The set of values produced by this scope. This may be empty
@@ -1540,18 +1535,6 @@ export type DependencyPathEntry = {
export type DependencyPath = Array<DependencyPathEntry>;
export type ReactiveScopeDependency = {
identifier: Identifier;
/**
* Reflects whether the base identifier is reactive. Note that some reactive
* objects may have non-reactive properties, but we do not currently track
* this.
*
* ```js
* // Technically, result[0] is reactive and result[1] is not.
* // Currently, both dependencies would be marked as reactive.
* const result = useState();
* ```
*/
reactive: boolean;
path: DependencyPath;
};

View File

@@ -110,6 +110,7 @@ export default class HIRBuilder {
#bindings: Bindings;
#env: Environment;
#exceptionHandlerStack: Array<BlockId> = [];
parentFunction: NodePath<t.Function>;
errors: CompilerError = new CompilerError();
/**
* Traversal context: counts the number of `fbt` tag parents
@@ -135,17 +136,16 @@ export default class HIRBuilder {
constructor(
env: Environment,
options?: {
bindings?: Bindings | null;
context?: Array<t.Identifier>;
entryBlockKind?: BlockKind;
},
parentFunction: NodePath<t.Function>, // the outermost function being compiled
bindings: Bindings | null = null,
context: Array<t.Identifier> | null = null,
) {
this.#env = env;
this.#bindings = options?.bindings ?? new Map();
this.#context = options?.context ?? [];
this.#bindings = bindings ?? new Map();
this.parentFunction = parentFunction;
this.#context = context ?? [];
this.#entry = makeBlockId(env.nextBlockId);
this.#current = newBlock(this.#entry, options?.entryBlockKind ?? 'block');
this.#current = newBlock(this.#entry, 'block');
}
currentBlockKind(): BlockKind {
@@ -239,7 +239,7 @@ export default class HIRBuilder {
// Check if the binding is from module scope
const outerBinding =
this.#env.parentFunction.scope.parent.getBinding(originalName);
this.parentFunction.scope.parent.getBinding(originalName);
if (babelBinding === outerBinding) {
const path = babelBinding.path;
if (path.isImportDefaultSpecifier()) {
@@ -293,7 +293,7 @@ export default class HIRBuilder {
const binding = this.#resolveBabelBinding(path);
if (binding) {
// Check if the binding is from module scope, if so return null
const outerBinding = this.#env.parentFunction.scope.parent.getBinding(
const outerBinding = this.parentFunction.scope.parent.getBinding(
path.node.name,
);
if (binding === outerBinding) {
@@ -376,7 +376,7 @@ export default class HIRBuilder {
}
// Terminate the current block w the given terminal, and start a new block
terminate(terminal: Terminal, nextBlockKind: BlockKind | null): BlockId {
terminate(terminal: Terminal, nextBlockKind: BlockKind | null): void {
const {id: blockId, kind, instructions} = this.#current;
this.#completed.set(blockId, {
kind,
@@ -390,7 +390,6 @@ export default class HIRBuilder {
const nextId = this.#env.nextBlockId;
this.#current = newBlock(nextId, nextBlockKind);
}
return blockId;
}
/*
@@ -747,11 +746,6 @@ function getReversePostorderedBlocks(func: HIR): HIR['blocks'] {
* (eg bb2 then bb1), we ensure that they get reversed back to the correct order.
*/
const block = func.blocks.get(blockId)!;
CompilerError.invariant(block != null, {
reason: '[HIRBuilder] Unexpected null block',
description: `expected block ${blockId} to exist`,
loc: GeneratedSource,
});
const successors = [...eachTerminalSuccessor(block.terminal)].reverse();
const fallthrough = terminalFallthrough(block.terminal);

View File

@@ -286,13 +286,13 @@ export function printTerminal(terminal: Terminal): Array<string> | string {
case 'scope': {
value = `[${terminal.id}] Scope ${printReactiveScopeSummary(
terminal.scope,
)} dependencies=bb${terminal.dependencies} block=bb${terminal.block} fallthrough=bb${terminal.fallthrough}`;
)} block=bb${terminal.block} fallthrough=bb${terminal.fallthrough}`;
break;
}
case 'pruned-scope': {
value = `[${terminal.id}] <pruned> Scope ${printReactiveScopeSummary(
terminal.scope,
)} dependencies=bb${terminal.dependencies} block=bb${terminal.block} fallthrough=bb${terminal.fallthrough}`;
)} block=bb${terminal.block} fallthrough=bb${terminal.fallthrough}`;
break;
}
case 'try': {

View File

@@ -47,17 +47,6 @@ import {CompilerError} from '../CompilerError';
import {Iterable_some} from '../Utils/utils';
import {ReactiveScopeDependencyTreeHIR} from './DeriveMinimalDependenciesHIR';
import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies';
import {
fixScopeAndIdentifierRanges,
markInstructionIds,
markPredecessors,
reversePostorderBlocks,
} from './HIRBuilder';
import {printDependency} from '../ReactiveScopes/PrintReactiveFunction';
import {
readScopeDependencies,
writeScopeDependencies,
} from './ScopeDependencyUtils';
export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
const usedOutsideDeclaringScope =
@@ -84,10 +73,8 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
/**
* Derive the minimal set of hoistable dependencies for each scope.
*/
const minimalDeps = new Map<ReactiveScope, Set<ReactiveScopeDependency>>();
for (const [scope, deps] of scopeDeps) {
if (deps.length === 0) {
minimalDeps.set(scope, new Set());
continue;
}
@@ -114,79 +101,17 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
* Step 3: Reduce dependencies to a minimal set.
*/
const candidates = tree.deriveMinimalDependencies();
const dependencies = new Set<ReactiveScopeDependency>();
for (const candidateDep of candidates) {
if (
!Iterable_some(
dependencies,
scope.dependencies,
existingDep =>
existingDep.identifier.declarationId ===
candidateDep.identifier.declarationId &&
areEqualPaths(existingDep.path, candidateDep.path),
)
)
dependencies.add(candidateDep);
}
minimalDeps.set(scope, dependencies);
}
let changed = false;
/**
* Step 4: inject dependencies
*/
for (const [_, {terminal}] of fn.body.blocks) {
if (terminal.kind !== 'scope' && terminal.kind !== 'pruned-scope') {
continue;
}
const scope = terminal.scope;
const deps = minimalDeps.get(scope);
if (deps == null || deps.size === 0) {
continue;
}
writeScopeDependencies(terminal, deps, fn);
changed = true;
}
/**
* Step 5: fix scope and identifier ranges to account for renumbered
* instructions
*/
if (changed) {
reversePostorderBlocks(fn.body);
markPredecessors(fn.body);
markInstructionIds(fn.body);
fixScopeAndIdentifierRanges(fn.body);
}
// Sanity check
{
for (const [scope, deps] of minimalDeps) {
const checkedDeps = [...readScopeDependencies(fn, scope.id)];
CompilerError.invariant(checkedDeps != null, {
reason: '[Rewrite] Cannot find scope dep when reading',
loc: scope.loc,
});
CompilerError.invariant(checkedDeps.length === deps.size, {
reason: '[Rewrite] non matching sizes when reading',
description: `scopeId=${scope.id} deps=${[...deps].map(printDependency)} checkedDeps=${[...checkedDeps].map(printDependency)}`,
loc: scope.loc,
});
for (const dep of deps) {
CompilerError.invariant(
checkedDeps.some(
checkedDep =>
dep.identifier === checkedDep.identifier &&
areEqualPaths(dep.path, checkedDep.path),
),
{
reason:
'[Rewrite] could not find match for dependency when re-reading',
description: `${printDependency(dep)}`,
loc: scope.loc,
},
);
}
scope.dependencies.add(candidateDep);
}
}
}
@@ -384,7 +309,6 @@ function collectTemporariesSidemapImpl(
) {
temporaries.set(lvalue.identifier.id, {
identifier: value.place.identifier,
reactive: value.place.reactive,
path: [],
});
}
@@ -438,13 +362,11 @@ function getProperty(
if (resolvedDependency == null) {
property = {
identifier: object.identifier,
reactive: object.reactive,
path: [{property: propertyName, optional}],
};
} else {
property = {
identifier: resolvedDependency.identifier,
reactive: resolvedDependency.reactive,
path: [...resolvedDependency.path, {property: propertyName, optional}],
};
}
@@ -600,7 +522,6 @@ export class DependencyCollectionContext {
this.visitDependency(
this.#temporaries.get(place.identifier.id) ?? {
identifier: place.identifier,
reactive: place.reactive,
path: [],
},
);
@@ -665,7 +586,6 @@ export class DependencyCollectionContext {
) {
maybeDependency = {
identifier: maybeDependency.identifier,
reactive: maybeDependency.reactive,
path: [],
};
}
@@ -687,11 +607,7 @@ export class DependencyCollectionContext {
identifier =>
identifier.declarationId === place.identifier.declarationId,
) &&
this.#checkValidDependency({
identifier: place.identifier,
reactive: place.reactive,
path: [],
})
this.#checkValidDependency({identifier: place.identifier, path: []})
) {
currentScope.reassignments.add(place.identifier);
}

View File

@@ -1,569 +0,0 @@
import {
ScopeId,
HIRFunction,
Place,
ReactiveScopeDependency,
Identifier,
makeInstructionId,
InstructionKind,
GeneratedSource,
IdentifierId,
BlockId,
makeTemporaryIdentifier,
Effect,
OptionalTerminal,
TBasicBlock,
ReactiveScopeTerminal,
GotoVariant,
PrunedScopeTerminal,
ReactiveInstruction,
ReactiveValue,
ReactiveScopeBlock,
PrunedReactiveScopeBlock,
} from './HIR';
import {CompilerError} from '../CompilerError';
import {
OptionalTraversalContext,
traverseOptionalBlock,
} from './CollectOptionalChainDependencies';
import {Environment} from './Environment';
import HIRBuilder from './HIRBuilder';
import {lowerValueToTemporary} from './BuildHIR';
import {printDependency} from '../ReactiveScopes/PrintReactiveFunction';
import {printPlace} from './PrintHIR';
function writeNonOptionalDependency(
dep: ReactiveScopeDependency,
env: Environment,
builder: HIRBuilder,
): Identifier {
const loc = dep.identifier.loc;
let last: Identifier = makeTemporaryIdentifier(env.nextIdentifierId, loc);
builder.push({
lvalue: {
identifier: last,
kind: 'Identifier',
effect: Effect.Mutate,
reactive: dep.reactive,
loc,
},
value: {
kind: 'LoadLocal',
place: {
identifier: dep.identifier,
kind: 'Identifier',
effect: Effect.Freeze,
reactive: dep.reactive,
loc,
},
loc,
},
id: makeInstructionId(1),
loc: loc,
});
for (const path of dep.path) {
const next = makeTemporaryIdentifier(env.nextIdentifierId, loc);
builder.push({
lvalue: {
identifier: next,
kind: 'Identifier',
effect: Effect.Mutate,
reactive: dep.reactive,
loc,
},
value: {
kind: 'PropertyLoad',
object: {
identifier: last,
kind: 'Identifier',
effect: Effect.Freeze,
reactive: dep.reactive,
loc,
},
property: path.property,
loc,
},
id: makeInstructionId(1),
loc: loc,
});
last = next;
}
return last;
}
export function writeScopeDependencies(
terminal: ReactiveScopeTerminal | PrunedScopeTerminal,
deps: Set<ReactiveScopeDependency>,
fn: HIRFunction,
): void {
const scopeDepBlock = fn.body.blocks.get(terminal.dependencies);
CompilerError.invariant(scopeDepBlock != null, {
reason: 'Expected to find scope dependency block',
loc: terminal.loc,
});
CompilerError.invariant(
scopeDepBlock.instructions.length === 0 &&
scopeDepBlock.terminal.kind === 'goto' &&
scopeDepBlock.terminal.block === terminal.block,
{
reason: 'Expected scope.dependencies to be a goto block (invalid cfg)',
loc: terminal.loc,
},
);
const builder = new HIRBuilder(fn.env, {
entryBlockKind: 'value',
});
for (const dep of deps) {
if (dep.path.every(path => !path.optional)) {
const last = writeNonOptionalDependency(dep, fn.env, builder);
terminal.scope.dependencies.push({
kind: 'Identifier',
identifier: last,
effect: Effect.Freeze,
reactive: dep.reactive,
loc: GeneratedSource,
});
}
}
// Write all optional chaining deps
for (const dep of deps) {
if (!dep.path.every(path => !path.optional)) {
const last = writeOptional(
dep.path.length - 1,
dep,
builder,
terminal,
null,
);
terminal.scope.dependencies.push({
kind: 'Identifier',
identifier: last,
effect: Effect.Freeze,
reactive: dep.reactive,
loc: GeneratedSource,
});
}
}
// Placeholder terminal for HIRBuilder, to be later replaced by a `goto` to an outer block
const lastBlockId = builder.terminate(
{
kind: 'return',
value: {
kind: 'Identifier',
identifier: makeTemporaryIdentifier(
fn.env.nextIdentifierId,
GeneratedSource,
),
effect: Effect.Freeze,
loc: GeneratedSource,
reactive: true,
},
loc: GeneratedSource,
id: makeInstructionId(0),
},
null,
);
const dependenciesHIR = builder.build();
for (const [id, block] of dependenciesHIR.blocks) {
fn.body.blocks.set(id, block);
}
fn.body.blocks.delete(terminal.dependencies);
/**
* Connect the newly constructed inner HIR to the outer HIR
*/
terminal.dependencies = dependenciesHIR.entry;
// Rewire the replaceholder terminal to the correct goto
fn.body.blocks.get(lastBlockId)!.terminal = scopeDepBlock.terminal;
}
function writeOptional(
idx: number,
dep: ReactiveScopeDependency,
builder: HIRBuilder,
terminal: ReactiveScopeTerminal | PrunedScopeTerminal,
parentAlternate: BlockId | null,
): Identifier {
const env = builder.environment;
CompilerError.invariant(
idx >= 0 && !dep.path.slice(0, idx + 1).every(path => !path.optional),
{
reason: '[WriteOptional] Expected optional path',
description: `${idx} ${printDependency(dep)}`,
loc: GeneratedSource,
},
);
const continuationBlock = builder.reserve(builder.currentBlockKind());
const consequent = builder.reserve('value');
const returnPlace: Place = {
kind: 'Identifier',
identifier: makeTemporaryIdentifier(env.nextIdentifierId, GeneratedSource),
effect: Effect.Mutate,
reactive: dep.reactive,
loc: GeneratedSource,
};
let alternate;
if (parentAlternate != null) {
alternate = parentAlternate;
} else {
/**
* Make outermost alternate block
* $N = Primitive undefined
* $M = StoreLocal $OptionalResult = $N
* goto fallthrough
*/
alternate = builder.enter('value', () => {
const temp = lowerValueToTemporary(builder, {
kind: 'Primitive',
value: undefined,
loc: GeneratedSource,
});
lowerValueToTemporary(builder, {
kind: 'StoreLocal',
lvalue: {kind: InstructionKind.Const, place: {...returnPlace}},
value: {...temp},
type: null,
loc: GeneratedSource,
});
return {
kind: 'goto',
variant: GotoVariant.Break,
block: continuationBlock.id,
id: makeInstructionId(0),
loc: GeneratedSource,
};
});
}
let testIdentifier: Identifier | null = null;
const testBlock = builder.enter('value', () => {
const firstOptional = dep.path.findIndex(path => path.optional);
if (idx === firstOptional) {
// Lower test block
testIdentifier = writeNonOptionalDependency(
{
identifier: dep.identifier,
reactive: dep.reactive,
path: dep.path.slice(0, idx),
},
env,
builder,
);
} else {
testIdentifier = writeOptional(
idx - 1,
dep,
builder,
terminal,
alternate,
);
}
return {
kind: 'branch',
test: {
identifier: testIdentifier,
effect: Effect.Freeze,
kind: 'Identifier',
loc: GeneratedSource,
reactive: dep.reactive,
},
consequent: consequent.id,
alternate,
id: makeInstructionId(0),
loc: GeneratedSource,
fallthrough: continuationBlock.id,
};
});
builder.enterReserved(consequent, () => {
CompilerError.invariant(testIdentifier !== null, {
reason: 'Satisfy type checker',
description: null,
loc: null,
suggestions: null,
});
const tmpConsequent = lowerValueToTemporary(builder, {
kind: 'PropertyLoad',
object: {
identifier: testIdentifier,
kind: 'Identifier',
effect: Effect.Freeze,
reactive: dep.reactive,
loc: GeneratedSource,
},
property: dep.path[idx].property,
loc: GeneratedSource,
});
lowerValueToTemporary(builder, {
kind: 'StoreLocal',
lvalue: {kind: InstructionKind.Const, place: {...returnPlace}},
value: {...tmpConsequent},
type: null,
loc: GeneratedSource,
});
return {
kind: 'goto',
variant: GotoVariant.Break,
block: continuationBlock.id,
id: makeInstructionId(0),
loc: GeneratedSource,
};
});
builder.terminateWithContinuation(
{
kind: 'optional',
optional: dep.path[idx].optional,
test: testBlock,
fallthrough: continuationBlock.id,
id: makeInstructionId(0),
loc: GeneratedSource,
},
continuationBlock,
);
return returnPlace.identifier;
}
export function readScopeDependencies(
fn: HIRFunction,
scope: ScopeId,
): Set<ReactiveScopeDependency> {
for (const [_, {terminal}] of fn.body.blocks) {
if (terminal.kind !== 'scope' && terminal.kind !== 'pruned-scope') {
continue;
}
if (terminal.scope.id !== scope) {
continue;
}
const temporaries = new Map<IdentifierId, ReactiveScopeDependency>();
const context: OptionalTraversalContext = {
currFn: fn,
blocks: fn.body.blocks,
seenOptionals: new Set(),
processedInstrsInOptional: new Set(),
temporariesReadInOptional: temporaries,
hoistableObjects: new Map(),
};
/**
* Step 1: read all instructions between within scope dependencies block(s)
*/
let work = terminal.dependencies;
while (true) {
const block = fn.body.blocks.get(work)!;
for (const {lvalue, value} of block.instructions) {
if (value.kind === 'LoadLocal') {
temporaries.set(lvalue.identifier.id, {
identifier: value.place.identifier,
reactive: value.place.reactive,
path: [],
});
} else if (value.kind === 'PropertyLoad') {
const source = temporaries.get(value.object.identifier.id)!;
temporaries.set(lvalue.identifier.id, {
identifier: source.identifier,
reactive: source.reactive,
path: [...source.path, {property: value.property, optional: false}],
});
}
}
if (block.terminal.kind === 'optional') {
traverseOptionalBlock(
block as TBasicBlock<OptionalTerminal>,
context,
null,
);
work = block.terminal.fallthrough;
} else {
CompilerError.invariant(
block.terminal.kind === 'goto' &&
block.terminal.block === terminal.block,
{
reason: 'unexpected terminal',
description: `kind: ${block.terminal.kind}`,
loc: block.terminal.loc,
},
);
break;
}
}
/**
* Step 2: look up scope dependencies from the temporaries sidemap
*/
const scopeOwnDependencies = new Set<ReactiveScopeDependency>();
for (const dep of terminal.scope.dependencies) {
const reactiveScopeDependency = temporaries.get(dep.identifier.id)!;
CompilerError.invariant(reactiveScopeDependency != null, {
reason: 'Expected dependency to be found',
description: `${printPlace(dep)}`,
loc: terminal.scope.loc,
});
scopeOwnDependencies.add(reactiveScopeDependency);
}
return scopeOwnDependencies;
}
CompilerError.invariant(false, {
reason: 'Expected scope to be found',
loc: GeneratedSource,
});
}
function assertNonNull<T>(value: T | null | undefined): T {
if (value == null) {
throw new Error('Expected nonnull value');
}
return value;
}
function readScopeDependenciesRHIRInstr(
instr: ReactiveInstruction,
sidemap: Map<IdentifierId, ReactiveScopeDependency>,
): void {
const value = reacScopeDependenciesRHIRValue(instr.value, sidemap);
if (instr.lvalue != null) {
sidemap.set(instr.lvalue.identifier.id, value);
}
}
function reacScopeDependenciesRHIRValue(
instr: ReactiveValue,
sidemap: Map<IdentifierId, ReactiveScopeDependency>,
): ReactiveScopeDependency {
if (instr.kind === 'LoadLocal') {
const base = sidemap.get(instr.place.identifier.id);
if (base != null) {
return base;
} else {
return {
identifier: instr.place.identifier,
reactive: instr.place.reactive,
path: [],
};
}
} else if (instr.kind === 'PropertyLoad') {
const base = assertNonNull(sidemap.get(instr.object.identifier.id));
return {
identifier: base.identifier,
reactive: base.reactive,
path: [...base.path, {property: instr.property, optional: false}],
};
} else if (instr.kind === 'SequenceExpression') {
for (const inner of instr.instructions) {
readScopeDependenciesRHIRInstr(inner, sidemap);
}
return reacScopeDependenciesRHIRValue(instr.value, sidemap);
} else if (instr.kind === 'OptionalExpression') {
const value = reacScopeDependenciesRHIRValue(instr.value, sidemap);
CompilerError.invariant(
value.path.length > 0 && !value.path.at(-1)!.optional,
{
reason: 'Expected optional chain to be nonempty',
loc: instr.loc,
},
);
return {
...value,
path: [
...value.path.slice(0, -1),
{property: value.path.at(-1)!.property, optional: instr.optional},
],
};
}
CompilerError.invariant(false, {
reason: 'Unexpected value kind',
description: instr.kind,
loc: instr.loc,
});
}
export function readScopeDependenciesRHIR(
scopeBlock: ReactiveScopeBlock | PrunedReactiveScopeBlock,
): Map<Place, ReactiveScopeDependency> {
const sidemap = new Map<IdentifierId, ReactiveScopeDependency>();
for (const instr of scopeBlock.dependencyInstructions) {
readScopeDependenciesRHIRInstr(instr.instruction, sidemap);
}
return new Map<Place, ReactiveScopeDependency>(
scopeBlock.scope.dependencies.map(place => {
return [place, assertNonNull(sidemap.get(place.identifier.id))];
}),
);
}
/**
* Run DCE to delete instructions which are not used by any scope dependencies
*
* Note: this only handles simple pruning i.e. deleted dependency entries,
* not more complex rewrites such as pruned or edited entries.
*/
export function scopeDependenciesDCE(
scopeBlock: ReactiveScopeBlock | PrunedReactiveScopeBlock,
): void {
const usage = new Map<IdentifierId, IdentifierId | null>();
for (const {
instruction: {lvalue, value},
} of scopeBlock.dependencyInstructions) {
if (lvalue == null) {
continue;
}
switch (value.kind) {
case 'LoadLocal': {
usage.set(lvalue.identifier.id, null);
break;
}
case 'PropertyLoad': {
usage.set(lvalue.identifier.id, value.object.identifier.id);
break;
}
case 'OptionalExpression': {
usage.set(lvalue.identifier.id, null);
break;
}
default: {
CompilerError.invariant(false, {
reason: 'Unexpected value kind',
description: value.kind,
loc: value.loc,
});
}
}
}
const notUsed = new Set(usage.keys());
const seen = new Set();
for (const {identifier} of scopeBlock.scope.dependencies) {
let curr: IdentifierId | undefined | null = identifier.id;
while (curr != null) {
CompilerError.invariant(!seen.has(curr), {
reason: 'infinite loop',
loc: GeneratedSource,
});
notUsed.delete(curr);
seen.add(curr);
curr = usage.get(curr);
}
}
/**
* Remove unused instructions in place
*/
let j = 0;
for (let i = 0; i < scopeBlock.dependencyInstructions.length; i++) {
const instr = scopeBlock.dependencyInstructions[i].instruction;
if (instr.lvalue != null && !notUsed.has(instr.lvalue.identifier.id)) {
scopeBlock.dependencyInstructions[j] =
scopeBlock.dependencyInstructions[i];
j++;
}
}
scopeBlock.dependencyInstructions.length = j;
}

View File

@@ -860,13 +860,11 @@ export function mapTerminalSuccessors(
}
case 'scope':
case 'pruned-scope': {
const dependencies = fn(terminal.dependencies);
const block = fn(terminal.block);
const fallthrough = fn(terminal.fallthrough);
return {
kind: terminal.kind,
scope: terminal.scope,
dependencies,
block,
fallthrough,
id: makeInstructionId(0),
@@ -1019,7 +1017,7 @@ export function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId> {
}
case 'scope':
case 'pruned-scope': {
yield terminal.dependencies;
yield terminal.block;
break;
}
case 'unreachable':
@@ -1070,13 +1068,6 @@ export function mapTerminalOperands(
}
break;
}
case 'scope':
case 'pruned-scope': {
for (let i = 0; i < terminal.scope.dependencies.length; i++) {
terminal.scope.dependencies[i] = fn(terminal.scope.dependencies[i]);
}
break;
}
case 'maybe-throw':
case 'sequence':
case 'label':
@@ -1090,7 +1081,9 @@ export function mapTerminalOperands(
case 'for-in':
case 'goto':
case 'unreachable':
case 'unsupported': {
case 'unsupported':
case 'scope':
case 'pruned-scope': {
// no-op
break;
}
@@ -1134,13 +1127,6 @@ export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {
}
break;
}
case 'scope':
case 'pruned-scope': {
for (const dep of terminal.scope.dependencies) {
yield dep;
}
break;
}
case 'maybe-throw':
case 'sequence':
case 'label':
@@ -1154,7 +1140,9 @@ export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {
case 'for-in':
case 'goto':
case 'unreachable':
case 'unsupported': {
case 'unsupported':
case 'scope':
case 'pruned-scope': {
// no-op
break;
}

View File

@@ -44,7 +44,6 @@ import {
DependencyCollectionContext,
handleInstruction,
} from '../HIR/PropagateScopeDependenciesHIR';
import {readScopeDependencies} from '../HIR/ScopeDependencyUtils';
import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors';
import {empty} from '../Utils/Stack';
import {getOrInsertWith} from '../Utils/utils';
@@ -98,7 +97,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
) {
scopeInfos.set(
block.terminal.scope.id,
readScopeDependencies(fn, block.terminal.scope.id),
block.terminal.scope.dependencies,
);
}
}
@@ -486,7 +485,7 @@ function inferDependencies(
start: fnInstr.id,
end: makeInstructionId(fnInstr.id + 1),
},
dependencies: [],
dependencies: new Set(),
reassignments: new Set(),
declarations: new Map(),
earlyReturnValue: null,

View File

@@ -26,7 +26,6 @@ import {
import {PostDominator} from '../HIR/Dominator';
import {
eachInstructionLValue,
eachInstructionOperand,
eachInstructionValueOperand,
eachTerminalOperand,
} from '../HIR/visitors';
@@ -293,7 +292,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
let hasReactiveInput = false;
/*
* NOTE: we want to mark all operands as reactive or not, so we
* avoid short-circuiting here
* avoid short-circuting here
*/
for (const operand of eachInstructionValueOperand(value)) {
const reactive = reactiveIdentifiers.isReactive(operand);
@@ -376,41 +375,6 @@ export function inferReactivePlaces(fn: HIRFunction): void {
}
}
} while (reactiveIdentifiers.snapshot());
function propagateReactivityToInnerFunctions(
fn: HIRFunction,
isOutermost: boolean,
): void {
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
if (!isOutermost) {
for (const operand of eachInstructionOperand(instr)) {
reactiveIdentifiers.isReactive(operand);
}
}
if (
instr.value.kind === 'ObjectMethod' ||
instr.value.kind === 'FunctionExpression'
) {
propagateReactivityToInnerFunctions(
instr.value.loweredFunc.func,
false,
);
}
}
if (!isOutermost) {
for (const operand of eachTerminalOperand(block.terminal)) {
reactiveIdentifiers.isReactive(operand);
}
}
}
}
/**
* Propagate reactivity for inner functions, as we eventually hoist and dedupe
* dependency instructions for scopes.
*/
propagateReactivityToInnerFunctions(fn, true);
}
/*

View File

@@ -387,10 +387,12 @@ export function inlineJsxTransform(
if (block.terminal.kind === 'scope') {
const scope = block.terminal.scope;
/**
* Note that scope dependencies don't need to be renamed explicitly
* as they will be visited when traversing scope terminal successors.
*/
for (const dep of scope.dependencies) {
dep.identifier = handleIdentifier(
dep.identifier,
inlinedJsxDeclarations,
);
}
for (const [origId, decl] of [...scope.declarations]) {
const newDecl = handleIdentifier(

View File

@@ -17,16 +17,11 @@ import {
SourceLocation,
} from '../HIR';
import {
areEqualPaths,
GeneratedSource,
HIRFunction,
PrunedReactiveScopeBlock,
ReactiveBreakTerminal,
ReactiveContinueTerminal,
ReactiveFunction,
ReactiveInstructionStatement,
ReactiveLogicalValue,
ReactiveScopeBlock,
ReactiveSequenceValue,
ReactiveTerminalStatement,
ReactiveTerminalTargetKind,
@@ -34,12 +29,7 @@ import {
ReactiveValue,
Terminal,
} from '../HIR/HIR';
import {
readScopeDependencies,
readScopeDependenciesRHIR,
} from '../HIR/ScopeDependencyUtils';
import {assertExhaustive} from '../Utils/utils';
import {printDependency} from './PrintReactiveFunction';
/*
* Converts from HIR (lower-level CFG) to ReactiveFunction, a tree representation
@@ -48,7 +38,7 @@ import {printDependency} from './PrintReactiveFunction';
* labels for *all* terminals: see PruneUnusedLabels which removes unnecessary labels.
*/
export function buildReactiveFunction(fn: HIRFunction): ReactiveFunction {
const cx = new Context(fn);
const cx = new Context(fn.body);
const driver = new Driver(cx);
const body = driver.traverseBlock(cx.block(fn.body.entry));
return {
@@ -826,64 +816,17 @@ class Driver {
} else {
block = this.traverseBlock(this.cx.ir.blocks.get(terminal.block)!);
}
{
const scheduleId = this.cx.schedule(terminal.block, 'if');
scheduleIds.push(scheduleId);
this.cx.scopeFallthroughs.add(terminal.block);
}
CompilerError.invariant(!this.cx.isScheduled(terminal.dependencies), {
reason: `Unexpected 'scope' where the dependencies block is already scheduled`,
loc: terminal.loc,
});
const dependencies: Array<ReactiveInstructionStatement> =
this.traverseBlock(this.cx.ir.blocks.get(terminal.dependencies)!).map(
dep => {
CompilerError.invariant(dep.kind === 'instruction', {
reason: '[BuildReactiveFunction] Expected reactive instruction',
loc: GeneratedSource,
});
return dep;
},
);
this.cx.unscheduleAll(scheduleIds);
const scopeBlock: ReactiveScopeBlock | PrunedReactiveScopeBlock = {
blockValue.push({
kind: terminal.kind,
dependencyInstructions: dependencies,
instructions: block,
scope: terminal.scope,
};
blockValue.push(scopeBlock);
});
if (fallthroughId !== null) {
this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
}
/**
* Sanity check: check that dependencies stay the same after converting
* to ReactiveFunction.
*/
const hirDeps = readScopeDependencies(this.cx.fn, terminal.scope.id);
const rhirDeps = [...readScopeDependenciesRHIR(scopeBlock)];
CompilerError.invariant(hirDeps.size === rhirDeps.length, {
reason: `[BuildReactiveFunction] Sanity check failed: mismatch in dependencies count`,
loc: terminal.loc,
});
for (const hirDep of hirDeps) {
CompilerError.invariant(
rhirDeps.some(
([, rhirDep]) =>
hirDep.identifier === rhirDep.identifier &&
areEqualPaths(hirDep.path, rhirDep.path),
),
{
reason:
'[BuildReactiveFunction] Sanity check failed: mismatching scope dependencies',
description: `No match found for ${printDependency(hirDep)}. Candidates are ${rhirDeps.map(dep => printDependency(dep[1]))}`,
loc: GeneratedSource,
},
);
}
break;
}
case 'unreachable': {
@@ -1300,7 +1243,6 @@ class Driver {
}
class Context {
fn: HIRFunction;
ir: HIR;
#nextScheduleId: number = 0;
@@ -1331,9 +1273,8 @@ class Context {
*/
#controlFlowStack: Array<ControlFlowTarget> = [];
constructor(fn: HIRFunction) {
this.fn = fn;
this.ir = fn.body;
constructor(ir: HIR) {
this.ir = ir;
}
block(id: BlockId): BasicBlock {

View File

@@ -32,10 +32,10 @@ import {
ReactiveBlock,
ReactiveFunction,
ReactiveInstruction,
ReactiveInstructionStatement,
ReactiveScope,
ReactiveScopeBlock,
ReactiveScopeDeclaration,
ReactiveScopeDependency,
ReactiveTerminal,
ReactiveValue,
SourceLocation,
@@ -48,13 +48,12 @@ import {printIdentifier, printPlace} from '../HIR/PrintHIR';
import {eachPatternOperand} from '../HIR/visitors';
import {Err, Ok, Result} from '../Utils/Result';
import {GuardKind} from '../Utils/RuntimeDiagnosticConstants';
import {assertExhaustive} from '../Utils/utils';
import {assertExhaustive, hasOwnProperty} from '../Utils/utils';
import {buildReactiveFunction} from './BuildReactiveFunction';
import {SINGLE_CHILD_FBT_TAGS} from './MemoizeFbtAndMacroOperandsInSameScope';
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
import {EMIT_FREEZE_GLOBAL_GATING, ReactFunctionType} from '../HIR/Environment';
import {ProgramContext} from '../Entrypoint';
import generate from '@babel/generator';
export const MEMO_CACHE_SENTINEL = 'react.memo_cache_sentinel';
export const EARLY_RETURN_SENTINEL = 'react.early_return_sentinel';
@@ -375,6 +374,8 @@ function codegenReactiveFunction(
const countMemoBlockVisitor = new CountMemoBlockVisitor(fn.env);
visitReactiveFunction(fn, countMemoBlockVisitor, undefined);
setMissingLocationsToNull(body);
return Ok({
type: 'CodegenFunction',
loc: fn.loc,
@@ -540,13 +541,7 @@ function codegenBlockNoReset(
}
case 'scope': {
const temp = new Map(cx.temp);
codegenReactiveScope(
cx,
statements,
item.scope,
item.instructions,
item.dependencyInstructions,
);
codegenReactiveScope(cx, statements, item.scope, item.instructions);
cx.temp = temp;
break;
}
@@ -610,7 +605,6 @@ function codegenReactiveScope(
statements: Array<t.Statement>,
scope: ReactiveScope,
block: ReactiveBlock,
dependencyInstructions: Array<ReactiveInstructionStatement>,
): void {
const cacheStoreStatements: Array<t.Statement> = [];
const cacheLoadStatements: Array<t.Statement> = [];
@@ -623,39 +617,9 @@ function codegenReactiveScope(
const changeExpressionComments: Array<string> = [];
const outputComments: Array<string> = [];
/**
* Step 1: Dependency instructions should codegen their expressions into
* the `Context.Temporaries` map.
*/
for (const instr of dependencyInstructions) {
/**
* codegenInstructionNullable should never return a statement, as all
* dependency instructions should be inlined into `Context.Temporaries`.
*/
const result = codegenInstructionNullable(cx, instr.instruction);
CompilerError.invariant(result == null, {
reason: 'Expected dependency instructions to be inlined',
loc: instr.instruction.loc,
});
}
const sortedDependencies: Array<[string, t.Expression]> = scope.dependencies
.map<[string, t.Expression]>(dep => {
const dependency = codegenPlace(cx, dep);
CompilerError.invariant(dependency.type !== 'JSXText', {
reason: 'Expected dependency to not be JSXText',
loc: GeneratedSource,
});
return [generate(dependency).code, dependency];
})
.sort(([aName], [bName]) => {
if (aName < bName) return -1;
else if (aName > bName) return 1;
else return 0;
});
for (const [code, dep] of sortedDependencies) {
for (const dep of [...scope.dependencies].sort(compareScopeDependency)) {
const index = cx.nextCacheIndex;
changeExpressionComments.push(code);
changeExpressionComments.push(printDependencyComment(dep));
const comparison = t.binaryExpression(
'!==',
t.memberExpression(
@@ -663,7 +627,7 @@ function codegenReactiveScope(
t.numericLiteral(index),
true,
),
dep,
codegenDependency(cx, dep),
);
if (cx.env.config.enableChangeVariableCodegen) {
@@ -690,7 +654,7 @@ function codegenReactiveScope(
t.numericLiteral(index),
true,
),
t.cloneNode(dep, true),
codegenDependency(cx, dep),
),
),
);
@@ -1481,6 +1445,17 @@ function codegenForInit(
}
}
function printDependencyComment(dependency: ReactiveScopeDependency): string {
const identifier = convertIdentifier(dependency.identifier);
let name = identifier.name;
if (dependency.path !== null) {
for (const path of dependency.path) {
name += `.${path.property}`;
}
}
return name;
}
function printDelimitedCommentList(
items: Array<string>,
finalCompletion: string,
@@ -1505,6 +1480,34 @@ function printDelimitedCommentList(
return output.join('');
}
function codegenDependency(
cx: Context,
dependency: ReactiveScopeDependency,
): t.Expression {
let object: t.Expression = convertIdentifier(dependency.identifier);
if (dependency.path.length !== 0) {
const hasOptional = dependency.path.some(path => path.optional);
for (const path of dependency.path) {
const property =
typeof path.property === 'string'
? t.identifier(path.property)
: t.numericLiteral(path.property);
const isComputed = typeof path.property !== 'string';
if (hasOptional) {
object = t.optionalMemberExpression(
object,
property,
isComputed,
path.optional,
);
} else {
object = t.memberExpression(object, property, isComputed);
}
}
}
return object;
}
function withLoc<T extends (...args: Array<any>) => t.Node>(
fn: T,
): (
@@ -2623,6 +2626,30 @@ function convertIdentifier(identifier: Identifier): t.Identifier {
return t.identifier(identifier.name.value);
}
function compareScopeDependency(
a: ReactiveScopeDependency,
b: ReactiveScopeDependency,
): number {
CompilerError.invariant(
a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named',
{
reason: '[Codegen] Expected named identifier for dependency',
loc: a.identifier.loc,
},
);
const aName = [
a.identifier.name.value,
...a.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`),
].join('.');
const bName = [
b.identifier.name.value,
...b.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`),
].join('.');
if (aName < bName) return -1;
else if (aName > bName) return 1;
else return 0;
}
function compareScopeDeclaration(
a: ReactiveScopeDeclaration,
b: ReactiveScopeDeclaration,
@@ -2640,3 +2667,33 @@ function compareScopeDeclaration(
else if (aName > bName) return 1;
else return 0;
}
function setMissingLocationsToNull(ast: any): void {
if (Array.isArray(ast)) {
ast.forEach(item => setMissingLocationsToNull(item));
return;
} else if (
ast == null ||
typeof ast !== 'object' ||
typeof ast['type'] !== 'string'
) {
return;
}
if (ast['loc'] == null) {
ast['loc'] = null;
}
for (const key in ast) {
if (!hasOwnProperty(ast, key)) {
continue;
}
const value = ast[key];
if (typeof value !== 'object') {
/*
* We handle this above too, but avoid extra function calls in the majority of
* cases where we're traversing an AST node's properties
*/
continue;
}
setMissingLocationsToNull(ast[key]);
}
}

View File

@@ -31,16 +31,14 @@ export function flattenReactiveLoopsHIR(fn: HIRFunction): void {
}
case 'scope': {
if (activeLoops.length !== 0) {
const newTerminal: PrunedScopeTerminal = {
block.terminal = {
kind: 'pruned-scope',
block: terminal.block,
dependencies: terminal.dependencies,
fallthrough: terminal.fallthrough,
id: terminal.id,
loc: terminal.loc,
scope: terminal.scope,
};
block.terminal = newTerminal;
} as PrunedScopeTerminal;
}
break;
}

View File

@@ -83,50 +83,28 @@ export function flattenScopesWithHooksOrUseHIR(fn: HIRFunction): void {
body.terminal.kind === 'goto' &&
body.terminal.block === terminal.fallthrough
) {
/**
* Note that scope blocks are unique in that they represent two nested
* goto-labels. We entirely remove dependency blocks here to simplify
* rewrite logic
*/
const dependencyBlock = fn.body.blocks.get(terminal.dependencies);
CompilerError.invariant(
dependencyBlock != null &&
dependencyBlock.instructions.length === 0 &&
dependencyBlock.terminal.kind === 'goto' &&
dependencyBlock.terminal.block === terminal.block,
{
reason: `Expected scope dependency block to have no instructions and goto scope block`,
loc: terminal.loc,
},
);
fn.body.blocks.delete(terminal.dependencies);
const scopeBlock = fn.body.blocks.get(terminal.block)!;
scopeBlock.preds = new Set([block.id]);
/*
* This was a scope just for a hook call, which doesn't need memoization.
* flatten it away. We rely on the PrunedUnusedLabel step to do the actual
* flattening
*/
const newTerminal: LabelTerminal = {
block.terminal = {
kind: 'label',
block: terminal.block,
fallthrough: terminal.fallthrough,
id: terminal.id,
loc: terminal.loc,
};
block.terminal = newTerminal;
} as LabelTerminal;
continue;
}
const newTerminal: PrunedScopeTerminal = {
block.terminal = {
kind: 'pruned-scope',
block: terminal.block,
dependencies: terminal.dependencies,
fallthrough: terminal.fallthrough,
id: terminal.id,
loc: terminal.loc,
scope: terminal.scope,
};
block.terminal = newTerminal;
} as PrunedScopeTerminal;
}
}

View File

@@ -107,7 +107,7 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
scope = {
id: fn.env.nextScopeId,
range: identifier.mutableRange,
dependencies: [],
dependencies: new Set(),
declarations: new Map(),
reassignments: new Set(),
earlyReturnValue: null,

View File

@@ -28,7 +28,6 @@ import {
BuiltInJsxId,
BuiltInObjectId,
} from '../HIR/ObjectShape';
import {readScopeDependenciesRHIR} from '../HIR/ScopeDependencyUtils';
import {eachInstructionLValue} from '../HIR/visitors';
import {assertExhaustive, Iterable_some} from '../Utils/utils';
import {printReactiveScopeSummary} from './PrintReactiveFunction';
@@ -120,29 +119,21 @@ class FindLastUsageVisitor extends ReactiveFunctionVisitor<void> {
class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | null> {
lastUsage: Map<DeclarationId, InstructionId>;
cache: Map<ReactiveScopeBlock, ReactiveScopeDependencies> = new Map();
constructor(lastUsage: Map<DeclarationId, InstructionId>) {
super();
this.lastUsage = lastUsage;
}
dependency(scopeBlock: ReactiveScopeBlock): ReactiveScopeDependencies {
let dependencies = this.cache.get(scopeBlock);
if (dependencies == null) {
dependencies = new Set(readScopeDependenciesRHIR(scopeBlock).values());
this.cache.set(scopeBlock, dependencies);
}
return dependencies;
}
override transformScope(
scopeBlock: ReactiveScopeBlock,
state: ReactiveScopeDependencies | null,
): Transformed<ReactiveStatement> {
const dependencies = this.dependency(scopeBlock);
this.visitScope(scopeBlock, dependencies);
if (state !== null && areEqualDependencies(state, dependencies)) {
this.visitScope(scopeBlock, scopeBlock.scope.dependencies);
if (
state !== null &&
areEqualDependencies(state, scopeBlock.scope.dependencies)
) {
return {kind: 'replace-many', value: scopeBlock.instructions};
} else {
return {kind: 'keep'};
@@ -269,7 +260,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
case 'scope': {
if (
current !== null &&
canMergeScopes(this, current.block, instr) &&
canMergeScopes(current.block, instr) &&
areLValuesLastUsedByScope(
instr.scope,
current.lvalues,
@@ -433,7 +424,6 @@ function areLValuesLastUsedByScope(
}
function canMergeScopes(
transform: Transform,
current: ReactiveScopeBlock,
next: ReactiveScopeBlock,
): boolean {
@@ -445,10 +435,10 @@ function canMergeScopes(
log(` cannot merge, has reassignments`);
return false;
}
const currDeps = transform.dependency(current);
const nextDeps = transform.dependency(next);
// Merge scopes whose dependencies are identical
if (areEqualDependencies(currDeps, nextDeps)) {
if (
areEqualDependencies(current.scope.dependencies, next.scope.dependencies)
) {
log(` canMergeScopes: dependencies are equal`);
return true;
}
@@ -466,14 +456,13 @@ function canMergeScopes(
new Set(
[...current.scope.declarations.values()].map(declaration => ({
identifier: declaration.identifier,
reactive: false,
path: [],
})),
),
nextDeps,
next.scope.dependencies,
) ||
(nextDeps.size !== 0 &&
[...nextDeps].every(
(next.scope.dependencies.size !== 0 &&
[...next.scope.dependencies].every(
dep =>
isAlwaysInvalidatingType(dep.identifier.type) &&
Iterable_some(
@@ -548,7 +537,7 @@ function areEqualDependencies(
* *never* change and it's also eligible for merging.
*/
function scopeIsEligibleForMerging(scopeBlock: ReactiveScopeBlock): boolean {
if (scopeBlock.scope.dependencies.length === 0) {
if (scopeBlock.scope.dependencies.size === 0) {
/*
* Regardless of the type of value produced, if the scope has no dependencies
* then its value will never change.

View File

@@ -64,7 +64,11 @@ export function printReactiveScopeSummary(scope: ReactiveScope): string {
items.push('scope');
items.push(`@${scope.id}`);
items.push(`[${scope.range.start}:${scope.range.end}]`);
items.push(`dependencies=[${scope.dependencies.map(printPlace).join(',')}]`);
items.push(
`dependencies=[${Array.from(scope.dependencies)
.map(dep => printDependency(dep))
.join(', ')}]`,
);
items.push(
`declarations=[${Array.from(scope.declarations)
.map(([, decl]) =>
@@ -92,8 +96,6 @@ export function writeReactiveBlock(
block: ReactiveScopeBlock,
): void {
writer.writeLine(`${printReactiveScopeSummary(block.scope)} {`);
writeReactiveInstructions(writer, block.dependencyInstructions);
writer.writeLine('} /* - end dependencies - */ {');
writeReactiveInstructions(writer, block.instructions);
writer.writeLine('}');
}

View File

@@ -26,13 +26,18 @@ import {
} from '../HIR/HIR';
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
import {eachInstructionValueLValue, eachPatternOperand} from '../HIR/visitors';
import {scopeDependenciesDCE} from '../HIR/ScopeDependencyUtils';
/**
* Phase 2: Promote identifiers which are used in a place that requires a named variable.
*/
class PromoteTemporaries extends ReactiveFunctionVisitor<State> {
override visitScope(scopeBlock: ReactiveScopeBlock, state: State): void {
for (const dep of scopeBlock.scope.dependencies) {
const {identifier} = dep;
if (identifier.name == null) {
promoteIdentifier(identifier, state);
}
}
/*
* This is technically optional. We could prune ReactiveScopes
* whose outputs are not used in another computation or return
@@ -45,29 +50,7 @@ class PromoteTemporaries extends ReactiveFunctionVisitor<State> {
promoteIdentifier(declaration.identifier, state);
}
}
/**
* Run DCE to remove dependency instructions that are no longer used due to
* pruning passes.
*/
scopeDependenciesDCE(scopeBlock);
/**
* Traverse into scope dependency instructions to promote references to
* unnamed outer identifiers.
*/
CompilerError.invariant(state.scopeDepContext == null, {
reason: 'PromoteTemporaries: unexpected nested scopeDepContext',
loc: GeneratedSource,
});
this.visitBlock(scopeBlock.dependencyInstructions, {
...state,
scopeDepContext: {
declarations: new Set(),
dependencies: new Set(
[...scopeBlock.scope.dependencies].map(dep => dep.identifier),
),
},
});
this.visitBlock(scopeBlock.instructions, state);
this.traverseScope(scopeBlock, state);
}
override visitPrunedScope(
@@ -92,33 +75,11 @@ class PromoteTemporaries extends ReactiveFunctionVisitor<State> {
}
}
override visitLValue(_id: InstructionId, lvalue: Place, state: State): void {
// Track lvalues from within scope dependency blocks to avoid promoting them.
state.scopeDepContext?.declarations.add(lvalue.identifier.declarationId);
}
override visitValue(
id: InstructionId,
value: ReactiveValue,
state: State,
): void {
if (
state.scopeDepContext &&
(value.kind === 'LoadLocal' || value.kind === 'LoadContext')
) {
/**
* Scope dependency LoadLocal sources (defined by instructions external to
* that scope) should be promoted, as scope boundaries represent
* re-ordering barriers
*/
const identifier = value.place.identifier;
if (
!state.scopeDepContext.declarations.has(identifier.declarationId) &&
identifier.name === null
) {
promoteIdentifier(identifier, state);
}
}
this.traverseValue(id, value, state);
if (value.kind === 'FunctionExpression' || value.kind === 'ObjectMethod') {
this.visitHirFunction(value.loweredFunc.func, state);
@@ -216,10 +177,6 @@ type State = {
DeclarationId,
{activeScopes: Array<ScopeId>; usedOutsideScope: boolean}
>; // true if referenced within another scope, false if only accessed outside of scopes
scopeDepContext: {
declarations: Set<DeclarationId>;
dependencies: ReadonlySet<Identifier>;
} | null;
};
/**
@@ -470,7 +427,6 @@ export function promoteUsedTemporaries(fn: ReactiveFunction): void {
tags: new Set(),
promoted: new Set(),
pruned: new Map(),
scopeDepContext: null,
};
visitReactiveFunction(fn, new CollectPromotableTemporaries(), state);
for (const operand of fn.params) {

View File

@@ -13,7 +13,6 @@ import {
ReactiveScopeBlock,
ReactiveStatement,
} from '../HIR';
import {readScopeDependenciesRHIR} from '../HIR/ScopeDependencyUtils';
/**
* Some instructions will *always* produce a new value, and unless memoized will *always*
@@ -88,8 +87,7 @@ class Transform extends ReactiveFunctionTransform<boolean> {
): Transformed<ReactiveStatement> {
this.visitScope(scopeBlock, true);
const scopeDeps = readScopeDependenciesRHIR(scopeBlock);
for (const [, dep] of scopeDeps) {
for (const dep of scopeBlock.scope.dependencies) {
if (this.unmemoizedValues.has(dep.identifier)) {
/*
* This scope depends on an always-invalidating value so the scope will always invalidate:
@@ -109,7 +107,6 @@ class Transform extends ReactiveFunctionTransform<boolean> {
kind: 'replace',
value: {
kind: 'pruned-scope',
dependencyInstructions: scopeBlock.dependencyInstructions,
scope: scopeBlock.scope,
instructions: scopeBlock.instructions,
},

View File

@@ -22,7 +22,6 @@ import {
isUseRefType,
isUseStateType,
} from '../HIR';
import {readScopeDependenciesRHIR} from '../HIR/ScopeDependencyUtils';
import {eachCallArgument, eachInstructionLValue} from '../HIR/visitors';
import DisjointSet from '../Utils/DisjointSet';
import {assertExhaustive} from '../Utils/utils';
@@ -179,22 +178,14 @@ class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
].map(id => this.map.get(id) ?? 'Unknown'),
);
super.visitScope(scope, state);
// TODO
const scopeDeps = readScopeDependenciesRHIR(scope);
[...scopeDeps].forEach(([place, dep]) => {
[...scope.scope.dependencies].forEach(ident => {
let target: undefined | IdentifierId =
this.aliases.find(dep.identifier.id) ?? dep.identifier.id;
dep.path.forEach(token => {
this.aliases.find(ident.identifier.id) ?? ident.identifier.id;
ident.path.forEach(token => {
target &&= this.paths.get(target)?.get(token.property);
});
if (target && this.map.get(target) === 'Create') {
const idx = scope.scope.dependencies.indexOf(place);
CompilerError.invariant(idx !== -1, {
reason: 'Expected dependency to be found',
loc: place.loc,
});
scope.scope.dependencies.splice(idx, 1);
scope.scope.dependencies.delete(ident);
}
});
}

View File

@@ -95,19 +95,13 @@ class Visitor extends ReactiveFunctionVisitor<ReactiveIdentifiers> {
state: ReactiveIdentifiers,
): void {
this.traverseScope(scopeBlock, state);
let j = 0;
for (let i = 0; i < scopeBlock.scope.dependencies.length; i++) {
const isReactive = state.has(
scopeBlock.scope.dependencies[i].identifier.id,
);
if (isReactive) {
scopeBlock.scope.dependencies[j] = scopeBlock.scope.dependencies[i];
j++;
for (const dep of scopeBlock.scope.dependencies) {
const isReactive = state.has(dep.identifier.id);
if (!isReactive) {
scopeBlock.scope.dependencies.delete(dep);
}
}
scopeBlock.scope.dependencies.length = j;
if (scopeBlock.scope.dependencies.length !== 0) {
if (scopeBlock.scope.dependencies.size !== 0) {
/**
* If any of a scope's dependencies are reactive, then all of its
* outputs will re-evaluate whenever those dependencies change.

View File

@@ -56,7 +56,6 @@ class Transform extends ReactiveFunctionTransform<State> {
value: {
kind: 'pruned-scope',
scope: scopeBlock.scope,
dependencyInstructions: scopeBlock.dependencyInstructions,
instructions: scopeBlock.instructions,
},
};

View File

@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError} from '..';
import {
HIRFunction,
InstructionId,
@@ -186,20 +185,6 @@ export class ReactiveFunctionVisitor<TState = void> {
this.traverseScope(scope, state);
}
traverseScope(scope: ReactiveScopeBlock, state: TState): void {
this.visitBlock(scope.dependencyInstructions, state);
const lastDependencyInstruction = scope.dependencyInstructions.at(-1);
let lastInstructionId: InstructionId | null = null;
if (lastDependencyInstruction !== undefined) {
lastInstructionId = lastDependencyInstruction.instruction.id;
}
for (const dep of scope.scope.dependencies) {
CompilerError.invariant(lastInstructionId !== null, {
reason:
'[ReactiveFunction] Expected at least one dependency instruction.',
loc: scope.scope.loc,
});
this.visitPlace(lastInstructionId, dep, state);
}
this.visitBlock(scope.instructions, state);
}

View File

@@ -17,7 +17,6 @@ import {
isUseInsertionEffectHookType,
isUseLayoutEffectHookType,
} from '../HIR';
import {readScopeDependenciesRHIR} from '../HIR/ScopeDependencyUtils';
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
import {
ReactiveFunctionVisitor,
@@ -74,7 +73,7 @@ class Visitor extends ReactiveFunctionVisitor<CompilerError> {
* memoized, allowing a transitive memoization check.
*/
let areDependenciesMemoized = true;
for (const [, dep] of readScopeDependenciesRHIR(scopeBlock)) {
for (const dep of scopeBlock.scope.dependencies) {
if (isUnmemoized(dep.identifier, this.scopes)) {
areDependenciesMemoized = false;
break;

View File

@@ -25,7 +25,6 @@ import {
SourceLocation,
} from '../HIR';
import {printIdentifier, printManualMemoDependency} from '../HIR/PrintHIR';
import {readScopeDependenciesRHIR} from '../HIR/ScopeDependencyUtils';
import {eachInstructionValueOperand} from '../HIR/visitors';
import {collectMaybeMemoDependencies} from '../Inference/DropManualMemoization';
import {
@@ -407,8 +406,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
state.manualMemoState != null &&
state.manualMemoState.depsFromSource != null
) {
const deps = readScopeDependenciesRHIR(scopeBlock).values();
for (const dep of deps) {
for (const dep of scopeBlock.scope.dependencies) {
validateInferredDep(
dep,
this.temporaries,

View File

@@ -219,13 +219,13 @@ function addNestedProperty(
return updatePayload;
}
if (enableFastAddPropertiesInDiffing) {
return fastAddProperties(updatePayload, nextProp, validAttributes);
}
if (!isArray(nextProp)) {
// Add each property of the leaf.
return slowAddProperties(updatePayload, nextProp, validAttributes);
if (enableFastAddPropertiesInDiffing) {
return fastAddProperties(updatePayload, nextProp, validAttributes);
} else {
return addProperties(updatePayload, nextProp, validAttributes);
}
}
for (let i = 0; i < nextProp.length; i++) {
@@ -516,7 +516,7 @@ function fastAddProperties(
/**
* addProperties adds all the valid props to the payload after being processed.
*/
function slowAddProperties(
function addProperties(
updatePayload: null | Object,
props: Object,
validAttributes: AttributeConfiguration,

View File

@@ -3355,16 +3355,8 @@ export function requestFormReset(formFiber: Fiber) {
);
}
let stateHook: Hook = ensureFormComponentIsStateful(formFiber);
const stateHook = ensureFormComponentIsStateful(formFiber);
const newResetState = {};
if (stateHook.next === null) {
// Hack alert. If formFiber is the workInProgress Fiber then
// we might get a broken intermediate state. Try the alternate
// instead.
// TODO: We should really stash the Queue somewhere stateful
// just like how setState binds the Queue.
stateHook = (formFiber.alternate: any).memoizedState;
}
const resetStateHook: Hook = (stateHook.next: any);
const resetStateQueue = resetStateHook.queue;
dispatchSetStateInternal(