mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[compiler] Clean up deadcode: DeriveMinimalDeps (non-hir fork) (#32104)
(title) --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32104). * #32287 * __->__ #32104 * #32098 * #32097
This commit is contained in:
parent
498514c04d
commit
19cc5af41e
|
|
@ -13,7 +13,6 @@ import {
|
|||
ReactiveScopeDependency,
|
||||
} from '../HIR';
|
||||
import {printIdentifier} from '../HIR/PrintHIR';
|
||||
import {ReactiveScopePropertyDependency} from '../ReactiveScopes/DeriveMinimalDependencies';
|
||||
|
||||
/**
|
||||
* Simpler fork of DeriveMinimalDependencies, see PropagateScopeDependenciesHIR
|
||||
|
|
@ -91,7 +90,7 @@ export class ReactiveScopeDependencyTreeHIR {
|
|||
* dependency. This effectively truncates @param dep to its maximal
|
||||
* safe-to-evaluate subpath
|
||||
*/
|
||||
addDependency(dep: ReactiveScopePropertyDependency): void {
|
||||
addDependency(dep: ReactiveScopeDependency): void {
|
||||
const {identifier, path} = dep;
|
||||
let depCursor = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot(
|
||||
identifier,
|
||||
|
|
|
|||
|
|
@ -1,639 +0,0 @@
|
|||
/**
|
||||
* 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 {DependencyPath, Identifier, ReactiveScopeDependency} from '../HIR';
|
||||
import {printIdentifier} from '../HIR/PrintHIR';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
/*
|
||||
* We need to understand optional member expressions only when determining
|
||||
* dependencies of a ReactiveScope (i.e. in {@link PropagateScopeDependencies}),
|
||||
* hence why this type lives here (not in HIR.ts)
|
||||
*/
|
||||
export type ReactiveScopePropertyDependency = ReactiveScopeDependency;
|
||||
|
||||
/*
|
||||
* 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 ReactiveScopeDependencyTree {
|
||||
#roots: Map<Identifier, DependencyNode> = new Map();
|
||||
|
||||
#getOrCreateRoot(identifier: Identifier): DependencyNode {
|
||||
// roots can always be accessed unconditionally in JS
|
||||
let rootNode = this.#roots.get(identifier);
|
||||
|
||||
if (rootNode === undefined) {
|
||||
rootNode = {
|
||||
properties: new Map(),
|
||||
accessType: PropertyAccessType.UnconditionalAccess,
|
||||
};
|
||||
this.#roots.set(identifier, rootNode);
|
||||
}
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
add(dep: ReactiveScopePropertyDependency, inConditional: boolean): void {
|
||||
const {path} = dep;
|
||||
let currNode = this.#getOrCreateRoot(dep.identifier);
|
||||
|
||||
for (const item of path) {
|
||||
// all properties read 'on the way' to a dependency are marked as 'access'
|
||||
let currChild = getOrMakeProperty(currNode, item.property);
|
||||
const accessType = inConditional
|
||||
? PropertyAccessType.ConditionalAccess
|
||||
: item.optional
|
||||
? PropertyAccessType.OptionalAccess
|
||||
: PropertyAccessType.UnconditionalAccess;
|
||||
currChild.accessType = merge(currChild.accessType, accessType);
|
||||
currNode = currChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* The final property node should be marked as an conditional/unconditional
|
||||
* `dependency` as based on control flow.
|
||||
*/
|
||||
const depType = inConditional
|
||||
? PropertyAccessType.ConditionalDependency
|
||||
: isOptional(currNode.accessType)
|
||||
? PropertyAccessType.OptionalDependency
|
||||
: PropertyAccessType.UnconditionalDependency;
|
||||
|
||||
currNode.accessType = merge(currNode.accessType, depType);
|
||||
}
|
||||
|
||||
deriveMinimalDependencies(): Set<ReactiveScopeDependency> {
|
||||
const results = new Set<ReactiveScopeDependency>();
|
||||
for (const [rootId, rootNode] of this.#roots.entries()) {
|
||||
const deps = deriveMinimalDependenciesInSubtree(rootNode, null);
|
||||
CompilerError.invariant(
|
||||
deps.every(
|
||||
dep =>
|
||||
dep.accessType === PropertyAccessType.UnconditionalDependency ||
|
||||
dep.accessType == PropertyAccessType.OptionalDependency,
|
||||
),
|
||||
{
|
||||
reason:
|
||||
'[PropagateScopeDependencies] All dependencies must be reduced to unconditional dependencies.',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
|
||||
for (const dep of deps) {
|
||||
results.add({
|
||||
identifier: rootId,
|
||||
path: dep.relativePath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
addDepsFromInnerScope(
|
||||
depsFromInnerScope: ReactiveScopeDependencyTree,
|
||||
innerScopeInConditionalWithinParent: boolean,
|
||||
checkValidDepIdFn: (dep: ReactiveScopeDependency) => boolean,
|
||||
): void {
|
||||
for (const [id, otherRoot] of depsFromInnerScope.#roots) {
|
||||
if (!checkValidDepIdFn({identifier: id, path: []})) {
|
||||
continue;
|
||||
}
|
||||
let currRoot = this.#getOrCreateRoot(id);
|
||||
addSubtree(currRoot, otherRoot, innerScopeInConditionalWithinParent);
|
||||
if (!isUnconditional(currRoot.accessType)) {
|
||||
currRoot.accessType = isDependency(currRoot.accessType)
|
||||
? PropertyAccessType.UnconditionalDependency
|
||||
: PropertyAccessType.UnconditionalAccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
promoteDepsFromExhaustiveConditionals(
|
||||
trees: Array<ReactiveScopeDependencyTree>,
|
||||
): void {
|
||||
CompilerError.invariant(trees.length > 1, {
|
||||
reason: 'Expected trees to be at least 2 elements long.',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
for (const [id, root] of this.#roots) {
|
||||
const nodesForRootId = mapNonNull(trees, tree => {
|
||||
const node = tree.#roots.get(id);
|
||||
if (node != null && isUnconditional(node.accessType)) {
|
||||
return node;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
if (nodesForRootId) {
|
||||
addSubtreeIntersection(
|
||||
root.properties,
|
||||
nodesForRootId.map(root => root.properties),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Prints dependency tree to string for debugging.
|
||||
* @param includeAccesses
|
||||
* @returns string representation of DependencyTree
|
||||
*/
|
||||
printDeps(includeAccesses: boolean): string {
|
||||
let res = [];
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
debug(): string {
|
||||
const buf: Array<string> = [`tree() [`];
|
||||
for (const [rootId, rootNode] of this.#roots) {
|
||||
buf.push(`${printIdentifier(rootId)} (${rootNode.accessType}):`);
|
||||
this.#debugImpl(buf, rootNode, 1);
|
||||
}
|
||||
buf.push(']');
|
||||
return buf.length > 2 ? buf.join('\n') : buf.join('');
|
||||
}
|
||||
|
||||
#debugImpl(
|
||||
buf: Array<string>,
|
||||
node: DependencyNode,
|
||||
depth: number = 0,
|
||||
): void {
|
||||
for (const [property, childNode] of node.properties) {
|
||||
buf.push(`${' '.repeat(depth)}.${property} (${childNode.accessType}):`);
|
||||
this.#debugImpl(buf, childNode, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Enum representing the access type of single property on a parent object.
|
||||
* We distinguish on two independent axes:
|
||||
* Conditional / Unconditional:
|
||||
* - whether this property is accessed unconditionally (within the ReactiveBlock)
|
||||
* Access / Dependency:
|
||||
* - Access: this property is read on the path of a dependency. We do not
|
||||
* need to track change variables for accessed properties. Tracking accesses
|
||||
* helps Forget do more granular dependency tracking.
|
||||
* - Dependency: this property is read as a dependency and we must track changes
|
||||
* to it for correctness.
|
||||
*
|
||||
* ```javascript
|
||||
* // props.a is a dependency here and must be tracked
|
||||
* deps: {props.a, props.a.b} ---> minimalDeps: {props.a}
|
||||
* // props.a is just an access here and does not need to be tracked
|
||||
* deps: {props.a.b} ---> minimalDeps: {props.a.b}
|
||||
* ```
|
||||
*/
|
||||
enum PropertyAccessType {
|
||||
ConditionalAccess = 'ConditionalAccess',
|
||||
OptionalAccess = 'OptionalAccess',
|
||||
UnconditionalAccess = 'UnconditionalAccess',
|
||||
ConditionalDependency = 'ConditionalDependency',
|
||||
OptionalDependency = 'OptionalDependency',
|
||||
UnconditionalDependency = 'UnconditionalDependency',
|
||||
}
|
||||
|
||||
const MIN_ACCESS_TYPE = PropertyAccessType.ConditionalAccess;
|
||||
function isUnconditional(access: PropertyAccessType): boolean {
|
||||
return (
|
||||
access === PropertyAccessType.UnconditionalAccess ||
|
||||
access === PropertyAccessType.UnconditionalDependency
|
||||
);
|
||||
}
|
||||
function isDependency(access: PropertyAccessType): boolean {
|
||||
return (
|
||||
access === PropertyAccessType.ConditionalDependency ||
|
||||
access === PropertyAccessType.OptionalDependency ||
|
||||
access === PropertyAccessType.UnconditionalDependency
|
||||
);
|
||||
}
|
||||
function isOptional(access: PropertyAccessType): boolean {
|
||||
return (
|
||||
access === PropertyAccessType.OptionalAccess ||
|
||||
access === PropertyAccessType.OptionalDependency
|
||||
);
|
||||
}
|
||||
|
||||
function merge(
|
||||
access1: PropertyAccessType,
|
||||
access2: PropertyAccessType,
|
||||
): PropertyAccessType {
|
||||
const resultIsUnconditional =
|
||||
isUnconditional(access1) || isUnconditional(access2);
|
||||
const resultIsDependency = isDependency(access1) || isDependency(access2);
|
||||
const resultIsOptional = isOptional(access1) || isOptional(access2);
|
||||
|
||||
/*
|
||||
* Straightforward merge.
|
||||
* This can be represented as bitwise OR, but is written out for readability
|
||||
*
|
||||
* Observe that `UnconditionalAccess | ConditionalDependency` produces an
|
||||
* unconditionally accessed conditional dependency. We currently use these
|
||||
* as we use unconditional dependencies. (i.e. to codegen change variables)
|
||||
*/
|
||||
if (resultIsUnconditional) {
|
||||
if (resultIsDependency) {
|
||||
return PropertyAccessType.UnconditionalDependency;
|
||||
} else {
|
||||
return PropertyAccessType.UnconditionalAccess;
|
||||
}
|
||||
} else if (resultIsOptional) {
|
||||
if (resultIsDependency) {
|
||||
return PropertyAccessType.OptionalDependency;
|
||||
} else {
|
||||
return PropertyAccessType.OptionalAccess;
|
||||
}
|
||||
} else {
|
||||
if (resultIsDependency) {
|
||||
return PropertyAccessType.ConditionalDependency;
|
||||
} else {
|
||||
return PropertyAccessType.ConditionalAccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DependencyNode = {
|
||||
properties: Map<string, DependencyNode>;
|
||||
accessType: PropertyAccessType;
|
||||
};
|
||||
|
||||
type ReduceResultNode = {
|
||||
relativePath: DependencyPath;
|
||||
accessType: PropertyAccessType;
|
||||
};
|
||||
|
||||
function promoteResult(
|
||||
accessType: PropertyAccessType,
|
||||
path: {property: string; optional: boolean} | null,
|
||||
): Array<ReduceResultNode> {
|
||||
const result: ReduceResultNode = {
|
||||
relativePath: [],
|
||||
accessType,
|
||||
};
|
||||
if (path !== null) {
|
||||
result.relativePath.push(path);
|
||||
}
|
||||
return [result];
|
||||
}
|
||||
|
||||
function prependPath(
|
||||
results: Array<ReduceResultNode>,
|
||||
path: {property: string; optional: boolean} | null,
|
||||
): Array<ReduceResultNode> {
|
||||
if (path === null) {
|
||||
return results;
|
||||
}
|
||||
return results.map(result => {
|
||||
return {
|
||||
accessType: result.accessType,
|
||||
relativePath: [path, ...result.relativePath],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Recursively calculates minimal dependencies in a subtree.
|
||||
* @param dep DependencyNode representing a dependency subtree.
|
||||
* @returns a minimal list of dependencies in this subtree.
|
||||
*/
|
||||
function deriveMinimalDependenciesInSubtree(
|
||||
dep: DependencyNode,
|
||||
property: string | null,
|
||||
): Array<ReduceResultNode> {
|
||||
const results: Array<ReduceResultNode> = [];
|
||||
for (const [childName, childNode] of dep.properties) {
|
||||
const childResult = deriveMinimalDependenciesInSubtree(
|
||||
childNode,
|
||||
childName,
|
||||
);
|
||||
results.push(...childResult);
|
||||
}
|
||||
|
||||
switch (dep.accessType) {
|
||||
case PropertyAccessType.UnconditionalDependency: {
|
||||
return promoteResult(
|
||||
PropertyAccessType.UnconditionalDependency,
|
||||
property !== null ? {property, optional: false} : null,
|
||||
);
|
||||
}
|
||||
case PropertyAccessType.UnconditionalAccess: {
|
||||
if (
|
||||
results.every(
|
||||
({accessType}) =>
|
||||
accessType === PropertyAccessType.UnconditionalDependency ||
|
||||
accessType === PropertyAccessType.OptionalDependency,
|
||||
)
|
||||
) {
|
||||
// all children are unconditional dependencies, return them to preserve granularity
|
||||
return prependPath(
|
||||
results,
|
||||
property !== null ? {property, optional: false} : null,
|
||||
);
|
||||
} else {
|
||||
/*
|
||||
* at least one child is accessed conditionally, so this node needs to be promoted to
|
||||
* unconditional dependency
|
||||
*/
|
||||
return promoteResult(
|
||||
PropertyAccessType.UnconditionalDependency,
|
||||
property !== null ? {property, optional: false} : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
case PropertyAccessType.OptionalDependency: {
|
||||
return promoteResult(
|
||||
PropertyAccessType.OptionalDependency,
|
||||
property !== null ? {property, optional: true} : null,
|
||||
);
|
||||
}
|
||||
case PropertyAccessType.OptionalAccess: {
|
||||
if (
|
||||
results.every(
|
||||
({accessType}) =>
|
||||
accessType === PropertyAccessType.UnconditionalDependency ||
|
||||
accessType === PropertyAccessType.OptionalDependency,
|
||||
)
|
||||
) {
|
||||
// all children are unconditional dependencies, return them to preserve granularity
|
||||
return prependPath(
|
||||
results,
|
||||
property !== null ? {property, optional: true} : null,
|
||||
);
|
||||
} else {
|
||||
/*
|
||||
* at least one child is accessed conditionally, so this node needs to be promoted to
|
||||
* unconditional dependency
|
||||
*/
|
||||
return promoteResult(
|
||||
PropertyAccessType.OptionalDependency,
|
||||
property !== null ? {property, optional: true} : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
case PropertyAccessType.ConditionalAccess:
|
||||
case PropertyAccessType.ConditionalDependency: {
|
||||
if (
|
||||
results.every(
|
||||
({accessType}) =>
|
||||
accessType === PropertyAccessType.ConditionalDependency,
|
||||
)
|
||||
) {
|
||||
/*
|
||||
* No children are accessed unconditionally, so we cannot promote this node to
|
||||
* unconditional access.
|
||||
* Truncate results of child nodes here, since we shouldn't access them anyways
|
||||
*/
|
||||
return promoteResult(
|
||||
PropertyAccessType.ConditionalDependency,
|
||||
property !== null ? {property, optional: true} : null,
|
||||
);
|
||||
} else {
|
||||
/*
|
||||
* at least one child is accessed unconditionally, so this node can be promoted to
|
||||
* unconditional dependency
|
||||
*/
|
||||
return promoteResult(
|
||||
PropertyAccessType.UnconditionalDependency,
|
||||
property !== null ? {property, optional: true} : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
dep.accessType,
|
||||
'[PropgateScopeDependencies] Unhandled access type!',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 (!isUnconditional(accessType)) {
|
||||
// A conditionally accessed node should not have unconditional children
|
||||
continue;
|
||||
}
|
||||
node.accessType = isDependency(accessType)
|
||||
? PropertyAccessType.ConditionalDependency
|
||||
: PropertyAccessType.ConditionalAccess;
|
||||
|
||||
for (const childNode of properties.values()) {
|
||||
if (isUnconditional(accessType)) {
|
||||
/*
|
||||
* No conditional node can have an unconditional node as a child, so
|
||||
* we only process childNode if it is unconditional
|
||||
*/
|
||||
stack.push(childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculates currNode = union(currNode, otherNode), mutating currNode in place
|
||||
* If demoteOtherNode is specified, we demote the subtree represented by
|
||||
* otherNode to conditional access/deps before taking the union.
|
||||
*
|
||||
* This is a helper function used to join an inner scope to its parent scope.
|
||||
* @param currNode (mutable) return by argument
|
||||
* @param otherNode (move) {@link addSubtree} takes ownership of the subtree
|
||||
* represented by otherNode, which may be mutated or moved to currNode. It is
|
||||
* invalid to use otherNode after this call.
|
||||
*
|
||||
* Note that @param otherNode may contain both conditional and unconditional nodes,
|
||||
* due to inner control flow and conditional member expressions
|
||||
*
|
||||
* @param demoteOtherNode
|
||||
*/
|
||||
function addSubtree(
|
||||
currNode: DependencyNode,
|
||||
otherNode: DependencyNode,
|
||||
demoteOtherNode: boolean,
|
||||
): void {
|
||||
let otherType = otherNode.accessType;
|
||||
if (demoteOtherNode) {
|
||||
otherType = isDependency(otherType)
|
||||
? PropertyAccessType.ConditionalDependency
|
||||
: PropertyAccessType.ConditionalAccess;
|
||||
}
|
||||
currNode.accessType = merge(currNode.accessType, otherType);
|
||||
|
||||
for (const [propertyName, otherChild] of otherNode.properties) {
|
||||
const currChild = currNode.properties.get(propertyName);
|
||||
if (currChild) {
|
||||
// recursively calculate currChild = union(currChild, otherChild)
|
||||
addSubtree(currChild, otherChild, demoteOtherNode);
|
||||
} else {
|
||||
/*
|
||||
* if currChild doesn't exist, we can just move otherChild
|
||||
* currChild = otherChild.
|
||||
*/
|
||||
if (demoteOtherNode) {
|
||||
demoteSubtreeToConditional(otherChild);
|
||||
}
|
||||
currNode.properties.set(propertyName, otherChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Adds intersection(otherProperties) to currProperties, mutating
|
||||
* currProperties in place. i.e.
|
||||
* currProperties = union(currProperties, intersection(otherProperties))
|
||||
*
|
||||
* Used to merge unconditional accesses from exhaustive conditional branches
|
||||
* into the parent ReactiveDeps Tree.
|
||||
* intersection(currProperties) is determined as such:
|
||||
* - a node is present in the intersection iff it is present in all every
|
||||
* branch
|
||||
* - the type of an added node is `UnconditionalDependency` if it is a
|
||||
* dependency in at least one branch (otherwise `UnconditionalAccess`)
|
||||
*
|
||||
* @param otherProperties (read-only) an array of node properties containing
|
||||
* conditionally and unconditionally accessed nodes. Each element
|
||||
* represents asubtree of reactive dependencies from a single CFG
|
||||
* branch.
|
||||
* otherProperties must represent all reachable branches.
|
||||
* @param currProperties (mutable) return by argument properties of a node
|
||||
*
|
||||
* otherProperties and currProperties must be properties of disjoint nodes
|
||||
* that represent the same dependency (identifier + path).
|
||||
*/
|
||||
function addSubtreeIntersection(
|
||||
currProperties: Map<string, DependencyNode>,
|
||||
otherProperties: Array<Map<string, DependencyNode>>,
|
||||
): void {
|
||||
CompilerError.invariant(otherProperties.length > 1, {
|
||||
reason:
|
||||
'[DeriveMinimalDependencies] Expected otherProperties to be at least 2 elements long.',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
/*
|
||||
* otherProperties here may contain unconditional nodes as the result of
|
||||
* recursively merging exhaustively conditional children with unconditionally
|
||||
* accessed nodes (e.g. in the test condition itself)
|
||||
* See `reduce-reactive-cond-deps-cfg-nested-testifelse` fixture for example
|
||||
*/
|
||||
|
||||
for (const [propertyName, currNode] of currProperties) {
|
||||
const otherNodes = mapNonNull(otherProperties, properties => {
|
||||
const node = properties.get(propertyName);
|
||||
if (node != null && isUnconditional(node.accessType)) {
|
||||
return node;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* intersection(otherNodes[propertyName]) only exists if each element in
|
||||
* otherProperties accesses propertyName.
|
||||
*/
|
||||
if (otherNodes) {
|
||||
addSubtreeIntersection(
|
||||
currNode.properties,
|
||||
otherNodes.map(node => node.properties),
|
||||
);
|
||||
|
||||
const isDep = otherNodes.some(tree => isDependency(tree.accessType));
|
||||
const externalAccessType = isDep
|
||||
? PropertyAccessType.UnconditionalDependency
|
||||
: PropertyAccessType.UnconditionalAccess;
|
||||
currNode.accessType = merge(externalAccessType, currNode.accessType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user