Clean up of React nested optimized logic (#2348)

Summary:
Release notes: none

Follow up to https://github.com/facebook/prepack/pull/2346. This PR cleans up all the React specific nested optimized function logic, removing most of the old legacy code in favor of the simpler/cleaner approach.

This also introduces a `bubbleSideEffectReports` parameter to `evaluatePure` for specific case where we don't want side-effects to be reported to parent `evaluatePure` calls; necessary for the React reconciler to not throw a `UnsupportedSideEffect` which will cause the reconciliation process to hard-fail (instead nested optimize functions bail-out to be inline abstract function calls like before, and we do not try and optimize the nested function).

Lastly, we now throw an `SideEffect` object when evaluating a nested optimized function and detecting a side-effect. There's no point in continuing evaluation of a given side-effectful function, so this speeds things up a bit by terminating execution.
Pull Request resolved: https://github.com/facebook/prepack/pull/2348

Differential Revision: D9138290

Pulled By: trueadm

fbshipit-source-id: 6d468ea0430be0c0190ff9817b2872df368c325d
This commit is contained in:
Dominic Gannaway 2018-08-02 13:53:35 -07:00 committed by Facebook Github Bot
parent 1b4f70658f
commit 7cb3129cd0
11 changed files with 336 additions and 421 deletions

View File

@ -70,3 +70,10 @@ export class InvariantError extends Error {
}
export type ErrorHandler = (error: CompilerDiagnostic, suppressDiagnostics: boolean) => ErrorHandlerResult;
// When a side-effect occurs when evaluating a pure nested optimized function, we stop execution of that function
// and catch the error to properly handle the according logic (either bail-out or report the error).
// Ideally this should extend FatalError, but that will mean re-working every call-site that catches FatalError
// and make it treat NestedOptimizedFunctionSideEffect errors differently, which isn't ideal so maybe a better
// FatalError catching/handling process is needed throughout the codebase at some point.
export class NestedOptimizedFunctionSideEffect extends Error {}

View File

@ -227,7 +227,11 @@ export default function(realm: Realm): void {
invariant(functionValue instanceof ECMAScriptSourceFunctionValue);
invariant(typeof functionValue.$Call === "function");
let functionCall: Function = functionValue.$Call;
return realm.evaluatePure(() => functionCall(realm.intrinsics.undefined, []), /*reportSideEffectFunc*/ null);
return realm.evaluatePure(
() => functionCall(realm.intrinsics.undefined, []),
/*bubbles*/ true,
/*reportSideEffectFunc*/ null
);
}
),
writable: true,

View File

@ -306,6 +306,7 @@ export function evaluateClassConstructor(
/*state*/ null,
`react component constructor: ${constructorFunc.getName()}`
),
/*bubbles*/ true,
/*reportSideEffectFunc*/ null
);

View File

@ -344,20 +344,32 @@ class ReactDOMServerRenderer {
if (nestedOptimizedFunctionEffects !== undefined) {
for (let [func, effects] of nestedOptimizedFunctionEffects) {
let resolvedEffects = this.realm.evaluateForEffects(
() => {
let result = effects.result;
this.realm.applyEffects(effects);
let funcCall = () => {
let result = effects.result;
this.realm.applyEffects(effects);
if (result instanceof SimpleNormalCompletion) {
result = result.value;
}
invariant(result instanceof Value);
return this.render(result, namespace, depth);
};
let pureFuncCall = () =>
this.realm.evaluatePure(funcCall, /*bubbles*/ true, () => {
invariant(false, "SSR _renderArrayValue side-effect should have been caught in main React reconciler");
});
if (result instanceof SimpleNormalCompletion) {
result = result.value;
}
invariant(result instanceof Value);
return this.render(result, namespace, depth);
},
/*state*/ null,
`react nested optimized closure`
);
let resolvedEffects;
let saved_pathConditions = this.realm.pathConditions;
this.realm.pathConditions = [];
try {
resolvedEffects = this.realm.evaluateForEffects(
pureFuncCall,
/*state*/ null,
`react SSR resolve nested optimized closure`
);
} finally {
this.realm.pathConditions = saved_pathConditions;
}
nestedOptimizedFunctionEffects.set(func, resolvedEffects);
this.realm.collectedNestedOptimizedFunctionEffects.set(func, resolvedEffects);
}
@ -468,42 +480,17 @@ class ReactDOMServerRenderer {
}
}
function handleNestedOptimizedFunctions(realm: Realm, reconciler: Reconciler, staticMarkup: boolean): void {
for (let { func, evaluatedNode, componentType, context } of reconciler.nestedOptimizedClosures) {
if (reconciler.hasEvaluatedNestedClosure(func)) {
continue;
}
if (func instanceof ECMAScriptSourceFunctionValue && reconciler.hasEvaluatedRootNode(func, evaluatedNode)) {
continue;
}
let closureEffects = reconciler.resolveNestedOptimizedClosure(func, [], componentType, context, evaluatedNode);
let closureEffectsRenderedToString = realm.evaluateForEffectsWithPriorEffects(
[closureEffects],
() => {
let serverRenderer = new ReactDOMServerRenderer(realm, staticMarkup);
invariant(closureEffects.result instanceof SimpleNormalCompletion);
return serverRenderer.render(closureEffects.result.value);
},
"handleNestedOptimizedFunctions"
);
realm.react.optimizedNestedClosuresToWrite.push({
effects: closureEffectsRenderedToString,
func,
});
}
}
export function renderToString(
realm: Realm,
reactElement: ObjectValue,
staticMarkup: boolean
): StringValue | AbstractValue {
let reactStatistics = new ReactStatistics();
let alreadyEvaluated = new Map();
let reconciler = new Reconciler(
realm,
{ firstRenderOnly: true, isRoot: true, modelString: undefined },
alreadyEvaluated,
reactStatistics
);
let typeValue = getProperty(realm, reactElement, "type");
@ -527,6 +514,5 @@ export function renderToString(
invariant(effects.result instanceof SimpleNormalCompletion);
let serverRenderer = new ReactDOMServerRenderer(realm, staticMarkup);
let renderValue = serverRenderer.render(effects.result.value);
handleNestedOptimizedFunctions(realm, reconciler, staticMarkup);
return renderValue;
}

View File

@ -10,7 +10,7 @@
/* @flow strict-local */
import { type Effects, Realm } from "../realm.js";
import { AbstractValue, ECMAScriptSourceFunctionValue, BoundFunctionValue, ObjectValue } from "../values/index.js";
import { AbstractValue, ECMAScriptSourceFunctionValue, ObjectValue } from "../values/index.js";
import { createAdditionalEffects } from "../serializer/utils.js";
import {
convertFunctionalComponentToComplexClassComponent,
@ -110,7 +110,8 @@ function optimizeReactComponentTreeBranches(
reconciler: Reconciler,
writeEffects: WriteEffects,
environmentRecordIdAfterGlobalCode: number,
logger: Logger
logger: Logger,
alreadyEvaluated: Map<ECMAScriptSourceFunctionValue, ReactEvaluatedNode>
): void {
if (realm.react.verbose && reconciler.branchedComponentTrees.length > 0) {
logger.logInformation(` Evaluating React component tree branches...`);
@ -123,10 +124,10 @@ function optimizeReactComponentTreeBranches(
evaluatedNode.status = "UNKNOWN_TYPE";
continue;
}
// so we don't process the same component multiple times (we might change this logic later)
if (reconciler.hasEvaluatedRootNode(branchComponentType, evaluatedNode)) {
continue;
if (alreadyEvaluated.has(branchComponentType)) {
return;
}
alreadyEvaluated.set(branchComponentType, evaluatedNode);
reconciler.clearComponentTreeState();
if (realm.react.verbose) {
logger.logInformation(` Evaluating ${evaluatedNode.name} (branch)`);
@ -148,53 +149,6 @@ function optimizeReactComponentTreeBranches(
}
}
function optimizeReactNestedClosures(
realm: Realm,
reconciler: Reconciler,
writeEffects: WriteEffects,
environmentRecordIdAfterGlobalCode: number,
logger: Logger
): void {
if (realm.react.verbose && reconciler.nestedOptimizedClosures.length > 0) {
logger.logInformation(` Evaluating nested closures...`);
}
for (let { func, evaluatedNode, nestedEffects, componentType, context } of reconciler.nestedOptimizedClosures) {
if (reconciler.hasEvaluatedNestedClosure(func)) {
continue;
}
if (func instanceof ECMAScriptSourceFunctionValue && reconciler.hasEvaluatedRootNode(func, evaluatedNode)) {
continue;
}
if (realm.react.verbose) {
logger.logInformation(` Evaluating function "${getComponentName(realm, func)}"`);
}
let closureEffects = reconciler.resolveNestedOptimizedClosure(
func,
nestedEffects,
componentType,
context,
evaluatedNode
);
if (realm.react.verbose) {
logger.logInformation(` ✔ function "${getComponentName(realm, func)}"`);
}
let additionalFunctionEffects = createAdditionalEffects(
realm,
closureEffects,
true,
"ReactNestedAdditionalFunctionEffects",
environmentRecordIdAfterGlobalCode
);
invariant(additionalFunctionEffects);
if (func instanceof BoundFunctionValue) {
invariant(func.$BoundTargetFunction instanceof ECMAScriptSourceFunctionValue);
writeEffects.set(func.$BoundTargetFunction, additionalFunctionEffects);
} else {
writeEffects.set(func, additionalFunctionEffects);
}
}
}
export function optimizeReactComponentTreeRoot(
realm: Realm,
componentRoot: ECMAScriptSourceFunctionValue | AbstractValue,
@ -202,18 +156,20 @@ export function optimizeReactComponentTreeRoot(
writeEffects: WriteEffects,
environmentRecordIdAfterGlobalCode: number,
logger: Logger,
statistics: ReactStatistics
statistics: ReactStatistics,
alreadyEvaluated: Map<ECMAScriptSourceFunctionValue, ReactEvaluatedNode>
): void {
let reconciler = new Reconciler(realm, config, statistics, logger);
let reconciler = new Reconciler(realm, config, alreadyEvaluated, statistics, logger);
let componentType = getComponentTypeFromRootValue(realm, componentRoot);
if (componentType === null) {
return;
}
let evaluatedRootNode = createReactEvaluatedNode("ROOT", getComponentName(realm, componentType));
statistics.evaluatedRootNodes.push(evaluatedRootNode);
if (reconciler.hasEvaluatedRootNode(componentType, evaluatedRootNode)) {
if (alreadyEvaluated.has(componentType)) {
return;
}
let evaluatedRootNode = createReactEvaluatedNode("ROOT", getComponentName(realm, componentType));
statistics.evaluatedRootNodes.push(evaluatedRootNode);
alreadyEvaluated.set(componentType, evaluatedRootNode);
if (realm.react.verbose) {
logger.logInformation(` Evaluating ${evaluatedRootNode.name} (root)`);
}
@ -231,36 +187,16 @@ export function optimizeReactComponentTreeRoot(
writeEffects,
environmentRecordIdAfterGlobalCode
);
let startingComponentTreeBranches = 0;
do {
startingComponentTreeBranches = reconciler.branchedComponentTrees.length;
optimizeReactComponentTreeBranches(realm, reconciler, writeEffects, environmentRecordIdAfterGlobalCode, logger);
if (realm.react.optimizeNestedFunctions) {
optimizeReactNestedClosures(realm, reconciler, writeEffects, environmentRecordIdAfterGlobalCode, logger);
}
optimizeReactComponentTreeBranches(
realm,
reconciler,
writeEffects,
environmentRecordIdAfterGlobalCode,
logger,
alreadyEvaluated
);
} while (startingComponentTreeBranches !== reconciler.branchedComponentTrees.length);
}
export function applyOptimizedReactComponents(
realm: Realm,
writeEffects: WriteEffects,
environmentRecordIdAfterGlobalCode: number
): void {
for (let { effects, func } of realm.react.optimizedNestedClosuresToWrite) {
let additionalFunctionEffects = createAdditionalEffects(
realm,
effects,
true,
"ReactNestedAdditionalFunctionEffects",
environmentRecordIdAfterGlobalCode
);
invariant(additionalFunctionEffects);
if (func instanceof BoundFunctionValue) {
invariant(func.$BoundTargetFunction instanceof ECMAScriptSourceFunctionValue);
writeEffects.set(func.$BoundTargetFunction, additionalFunctionEffects);
} else {
writeEffects.set(func, additionalFunctionEffects);
}
}
}

View File

@ -17,7 +17,6 @@ import {
BooleanValue,
BoundFunctionValue,
ECMAScriptSourceFunctionValue,
FunctionValue,
NullValue,
NumberValue,
ObjectValue,
@ -32,7 +31,6 @@ import {
cloneProps,
createReactEvaluatedNode,
doNotOptimizeComponent,
evaluateWithNestedParentEffects,
flattenChildren,
hardModifyReactObjectPropertyBinding,
getComponentName,
@ -50,8 +48,8 @@ import {
} from "./utils.js";
import { Get } from "../methods/index.js";
import invariant from "../invariant.js";
import { Properties } from "../singletons.js";
import { FatalError, CompilerDiagnostic } from "../errors.js";
import { Havoc, Properties, Utils } from "../singletons.js";
import { FatalError, NestedOptimizedFunctionSideEffect } from "../errors.js";
import {
type BranchStatusEnum,
getValueWithBranchingLogicApplied,
@ -80,7 +78,6 @@ import { Logger } from "../utils/logger.js";
import type { ClassComponentMetadata, ReactComponentTreeConfig, ReactHint } from "../types.js";
import { handleReportedSideEffect } from "../serializer/utils.js";
import { createOperationDescriptor } from "../utils/generator.js";
import * as t from "@babel/types";
type ComponentResolutionStrategy =
| "NORMAL"
@ -137,10 +134,15 @@ function setContextCurrentValue(contextObject: ObjectValue | AbstractObjectValue
}
}
function throwUnsupportedSideEffectError(msg: string) {
throw new UnsupportedSideEffect(msg);
}
export class Reconciler {
constructor(
realm: Realm,
componentTreeConfig: ReactComponentTreeConfig,
alreadyEvaluated: Map<ECMAScriptSourceFunctionValue, ReactEvaluatedNode>,
statistics: ReactStatistics,
logger?: Logger
) {
@ -149,9 +151,7 @@ export class Reconciler {
this.logger = logger;
this.componentTreeConfig = componentTreeConfig;
this.componentTreeState = this._createComponentTreeState();
this.alreadyEvaluatedRootNodes = new Map();
this.alreadyEvaluatedNestedClosures = new Set();
this.nestedOptimizedClosures = [];
this.alreadyEvaluated = alreadyEvaluated;
this.branchedComponentTrees = [];
}
@ -159,11 +159,9 @@ export class Reconciler {
statistics: ReactStatistics;
logger: void | Logger;
componentTreeState: ComponentTreeState;
alreadyEvaluatedRootNodes: Map<ECMAScriptSourceFunctionValue, ReactEvaluatedNode>;
alreadyEvaluatedNestedClosures: Set<FunctionValue>;
alreadyEvaluated: Map<ECMAScriptSourceFunctionValue, ReactEvaluatedNode>;
componentTreeConfig: ReactComponentTreeConfig;
currentEffectsStack: Array<Effects>;
nestedOptimizedClosures: Array<OptimizedClosure>;
branchedComponentTrees: Array<BranchReactComponentTree>;
resolveReactComponentTree(
@ -176,7 +174,6 @@ export class Reconciler {
try {
let initialProps = props || getInitialProps(this.realm, componentType, this.componentTreeConfig);
let initialContext = context || getInitialContext(this.realm, componentType);
this.alreadyEvaluatedRootNodes.set(componentType, evaluatedRootNode);
let { result } = this._resolveComponent(componentType, initialProps, initialContext, "ROOT", evaluatedRootNode);
this.statistics.optimizedTrees++;
return result;
@ -189,10 +186,7 @@ export class Reconciler {
try {
this.realm.react.activeReconciler = this;
let throwUnsupportedSideEffectError = (msg: string) => {
throw new UnsupportedSideEffect(msg);
};
let effects = this.realm.wrapInGlobalEnv(() =>
return this.realm.wrapInGlobalEnv(() =>
this.realm.evaluatePure(
() =>
this.realm.evaluateForEffects(
@ -200,111 +194,11 @@ export class Reconciler {
/*state*/ null,
`react component: ${getComponentName(this.realm, componentType)}`
),
/*bubbles*/ true,
(sideEffectType, binding, expressionLocation) =>
handleReportedSideEffect(throwUnsupportedSideEffectError, sideEffectType, binding, expressionLocation)
)
);
this._handleNestedOptimizedClosuresFromEffects(effects, evaluatedRootNode);
return effects;
} finally {
this.realm.react.activeReconciler = undefined;
}
}
_handleNestedOptimizedClosuresFromEffects(effects: Effects, evaluatedNode: ReactEvaluatedNode) {
for (let { nestedEffects } of this.nestedOptimizedClosures) {
if (nestedEffects.length === 0) {
nestedEffects.push(...nestedEffects, effects);
}
}
}
resolveNestedOptimizedClosure(
func: ECMAScriptSourceFunctionValue | BoundFunctionValue,
nestedEffects: Array<Effects>,
componentType: Value | null,
context: ObjectValue | AbstractObjectValue | null,
evaluatedNode: ReactEvaluatedNode
): Effects {
const resolveOptimizedClosure = () => {
let baseObject = this.realm.$GlobalEnv.environmentRecord.WithBaseObject();
// we want to optimize the function that is bound
if (func instanceof BoundFunctionValue) {
// we want to set the "this" to be the bound object
// for firstRender this will optimize the function
// for updates, "this" will be intrinsic, so either way
// they should both work
baseObject = func.$BoundThis;
invariant(func.$BoundTargetFunction instanceof ECMAScriptSourceFunctionValue);
func = func.$BoundTargetFunction;
}
let numArgs = func.getLength();
let args = [];
let targetFunc = func;
this.alreadyEvaluatedNestedClosures.add(func);
invariant(targetFunc instanceof ECMAScriptSourceFunctionValue);
let params = targetFunc.$FormalParameters;
if (numArgs && numArgs > 0 && params) {
for (let parameterId of params) {
if (t.isIdentifier(parameterId)) {
// Create an AbstractValue similar to __abstract being called
args.push(
AbstractValue.createAbstractArgument(
this.realm,
((parameterId: any): BabelNodeIdentifier).name,
targetFunc.expressionLocation
)
);
} else {
this.realm.handleError(
new CompilerDiagnostic(
"Non-identifier args to additional functions unsupported",
targetFunc.expressionLocation,
"PP1005",
"FatalError"
)
);
throw new FatalError("Non-identifier args to additional functions unsupported");
}
}
}
try {
invariant(
baseObject instanceof ObjectValue ||
baseObject instanceof AbstractObjectValue ||
baseObject instanceof UndefinedValue
);
let value = getValueFromFunctionCall(this.realm, func, baseObject, args);
invariant(componentType instanceof Value);
invariant(context instanceof ObjectValue || context instanceof AbstractObjectValue);
let result = this._resolveDeeply(componentType, value, context, "NEW_BRANCH", evaluatedNode);
this.statistics.optimizedNestedClosures++;
return result;
} catch (error) {
this._handleComponentTreeRootFailure(error, evaluatedNode);
// flow belives we can get here, when it should never be possible
invariant(false, "resolveNestedOptimizedClosure error not handled correctly");
}
};
try {
this.realm.react.activeReconciler = this;
let throwUnsupportedSideEffectError = (msg: string) => {
throw new UnsupportedSideEffect(msg);
};
let effects = this.realm.wrapInGlobalEnv(() =>
this.realm.evaluatePure(
() =>
evaluateWithNestedParentEffects(this.realm, nestedEffects, () =>
this.realm.evaluateForEffects(resolveOptimizedClosure, /*state*/ null, `react nested optimized closure`)
),
(sideEffectType, binding, expressionLocation) =>
handleReportedSideEffect(throwUnsupportedSideEffectError, sideEffectType, binding, expressionLocation)
)
);
this._handleNestedOptimizedClosuresFromEffects(effects, evaluatedNode);
return effects;
} finally {
this.realm.react.activeReconciler = undefined;
}
@ -314,23 +208,6 @@ export class Reconciler {
this.componentTreeState = this._createComponentTreeState();
}
_queueOptimizedClosure(
func: ECMAScriptSourceFunctionValue | BoundFunctionValue,
evaluatedNode: ReactEvaluatedNode,
componentType: Value | null,
context: ObjectValue | AbstractObjectValue | null
): void {
if (this.realm.react.optimizeNestedFunctions) {
this.nestedOptimizedClosures.push({
evaluatedNode,
func,
nestedEffects: [],
componentType,
context,
});
}
}
_queueNewComponentTree(
rootValue: Value,
evaluatedNode: ReactEvaluatedNode,
@ -343,7 +220,7 @@ export class Reconciler {
invariant(rootValue instanceof ECMAScriptSourceFunctionValue || rootValue instanceof AbstractValue);
this.componentTreeState.deadEnds++;
let componentType = getComponentTypeFromRootValue(this.realm, rootValue);
if (componentType !== null && !this.hasEvaluatedRootNode(componentType, evaluatedNode)) {
if (componentType !== null && !this.alreadyEvaluated.has(componentType)) {
this.branchedComponentTrees.push({
context,
evaluatedNode,
@ -364,7 +241,7 @@ export class Reconciler {
if (branchStatus !== "ROOT") {
// if the tree is simple and we're not in a branch, we can make this tree complex
// and make this complex component the root
let evaluatedComplexNode = this.alreadyEvaluatedRootNodes.get(componentType);
let evaluatedComplexNode = this.alreadyEvaluated.get(componentType);
if (
branchStatus === "NO_BRANCH" &&
this.componentTreeState.status === "SIMPLE" &&
@ -543,7 +420,14 @@ export class Reconciler {
if (propsValue instanceof ObjectValue && propsValue.properties.has("children")) {
let renderProp = getProperty(this.realm, propsValue, "children");
this._findReactComponentTrees(propsValue, evaluatedChildNode, "NORMAL_FUNCTIONS");
this._findReactComponentTrees(
propsValue,
evaluatedChildNode,
"NORMAL_FUNCTIONS",
componentType,
context,
branchStatus
);
if (renderProp instanceof ECMAScriptSourceFunctionValue) {
if (typeValue instanceof ObjectValue || typeValue instanceof AbstractObjectValue) {
// make sure this context is in our tree
@ -562,10 +446,23 @@ export class Reconciler {
}
}
}
this._queueOptimizedClosure(renderProp, evaluatedChildNode, componentType, context);
this._evaluateNestedOptimizedFunctionAndStoreEffects(
componentType,
context,
branchStatus,
evaluatedChildNode,
renderProp
);
return;
} else {
this._findReactComponentTrees(renderProp, evaluatedChildNode, "NESTED_CLOSURES");
this._findReactComponentTrees(
renderProp,
evaluatedChildNode,
"NESTED_CLOSURES",
componentType,
context,
branchStatus
);
}
}
}
@ -605,6 +502,7 @@ export class Reconciler {
componentType: Value,
reactElement: ObjectValue,
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
evaluatedNode: ReactEvaluatedNode
): Value | void {
let typeValue = getProperty(this.realm, reactElement, "type");
@ -619,12 +517,32 @@ export class Reconciler {
let renderProp = getProperty(this.realm, propsValue, "render");
if (renderProp instanceof ECMAScriptSourceFunctionValue) {
this._queueOptimizedClosure(renderProp, evaluatedChildNode, componentType, context);
this._evaluateNestedOptimizedFunctionAndStoreEffects(
componentType,
context,
branchStatus,
evaluatedChildNode,
renderProp
);
} else if (renderProp instanceof AbstractValue) {
this._findReactComponentTrees(renderProp, evaluatedChildNode, "NESTED_CLOSURES", componentType, context);
this._findReactComponentTrees(
renderProp,
evaluatedChildNode,
"NESTED_CLOSURES",
componentType,
context,
branchStatus
);
}
}
this._findReactComponentTrees(propsValue, evaluatedChildNode, "NORMAL_FUNCTIONS");
this._findReactComponentTrees(
propsValue,
evaluatedChildNode,
"NORMAL_FUNCTIONS",
componentType,
context,
branchStatus
);
return;
}
// this is the worst case, we were unable to find the render prop function
@ -1022,13 +940,26 @@ export class Reconciler {
return value;
}
_resolveUnknownComponentType(reactElement: ObjectValue, evaluatedNode: ReactEvaluatedNode) {
_resolveUnknownComponentType(
componentType: Value,
reactElement: ObjectValue,
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
evaluatedNode: ReactEvaluatedNode
): ObjectValue {
let typeValue = getProperty(this.realm, reactElement, "type");
let propsValue = getProperty(this.realm, reactElement, "props");
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS", componentType, context, branchStatus);
if (typeValue instanceof AbstractValue) {
this._findReactComponentTrees(typeValue, evaluatedNode, "FUNCTIONAL_COMPONENTS");
this._findReactComponentTrees(
typeValue,
evaluatedNode,
"FUNCTIONAL_COMPONENTS",
componentType,
context,
branchStatus
);
return reactElement;
} else {
let evaluatedChildNode = createReactEvaluatedNode("BAIL-OUT", getComponentName(this.realm, typeValue));
@ -1041,7 +972,13 @@ export class Reconciler {
}
}
_resolveReactElementBadRef(reactElement: ObjectValue, evaluatedNode: ReactEvaluatedNode) {
_resolveReactElementBadRef(
componentType: Value,
reactElement: ObjectValue,
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
evaluatedNode: ReactEvaluatedNode
): ObjectValue {
let typeValue = getProperty(this.realm, reactElement, "type");
let propsValue = getProperty(this.realm, reactElement, "props");
@ -1051,16 +988,18 @@ export class Reconciler {
evaluatedChildNode.message = bailOutMessage;
this._queueNewComponentTree(typeValue, evaluatedChildNode);
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS", componentType, context, branchStatus);
this._assignBailOutMessage(reactElement, bailOutMessage);
return reactElement;
}
_resolveReactElementUndefinedRender(
componentType: Value,
reactElement: ObjectValue,
evaluatedNode: ReactEvaluatedNode,
branchStatus: BranchStatusEnum
) {
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
evaluatedNode: ReactEvaluatedNode
): ObjectValue {
let typeValue = getProperty(this.realm, reactElement, "type");
let propsValue = getProperty(this.realm, reactElement, "props");
@ -1070,7 +1009,7 @@ export class Reconciler {
evaluatedChildNode.message = bailOutMessage;
this._assignBailOutMessage(reactElement, bailOutMessage);
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS", componentType, context, branchStatus);
return reactElement;
}
@ -1080,7 +1019,7 @@ export class Reconciler {
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
evaluatedNode: ReactEvaluatedNode
) {
): ObjectValue {
let propsValue = getProperty(this.realm, reactElement, "props");
// terminal host component. Start evaluating its children.
if (propsValue instanceof ObjectValue && propsValue.properties.has("children")) {
@ -1119,7 +1058,7 @@ export class Reconciler {
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
evaluatedNode: ReactEvaluatedNode
) {
): ObjectValue {
this.statistics.componentsEvaluated++;
if (this.componentTreeConfig.firstRenderOnly) {
let evaluatedChildNode = createReactEvaluatedNode("INLINED", "React.Fragment");
@ -1198,7 +1137,7 @@ export class Reconciler {
// point, so if we're here, then the FatalError has been recovered explicitly
!(refValue instanceof AbstractValue)
) {
this._resolveReactElementBadRef(reactElement, evaluatedNode);
this._resolveReactElementBadRef(componentType, reactElement, context, branchStatus, evaluatedNode);
}
try {
@ -1209,7 +1148,7 @@ export class Reconciler {
if (
!(typeValue instanceof ECMAScriptSourceFunctionValue || valueIsKnownReactAbstraction(this.realm, typeValue))
) {
return this._resolveUnknownComponentType(reactElement, evaluatedNode);
return this._resolveUnknownComponentType(componentType, reactElement, context, branchStatus, evaluatedNode);
}
let evaluatedChildNode = createReactEvaluatedNode("INLINED", getComponentName(this.realm, typeValue));
let render = this._resolveComponent(
@ -1233,7 +1172,13 @@ export class Reconciler {
}
case "RELAY_QUERY_RENDERER": {
invariant(typeValue instanceof AbstractObjectValue);
result = this._resolveRelayQueryRendererComponent(componentType, reactElement, context, evaluatedNode);
result = this._resolveRelayQueryRendererComponent(
componentType,
reactElement,
context,
branchStatus,
evaluatedNode
);
break;
}
case "CONTEXT_PROVIDER": {
@ -1269,7 +1214,13 @@ export class Reconciler {
}
if (result instanceof UndefinedValue) {
return this._resolveReactElementUndefinedRender(reactElement, evaluatedNode, branchStatus);
return this._resolveReactElementUndefinedRender(
componentType,
reactElement,
context,
branchStatus,
evaluatedNode
);
}
// If we have a new result and we might have a key value then wrap our inlined result in a
@ -1280,7 +1231,14 @@ export class Reconciler {
return result;
} catch (error) {
return this._resolveComponentResolutionFailure(error, reactElement, evaluatedNode, branchStatus);
return this._resolveComponentResolutionFailure(
componentType,
error,
reactElement,
context,
evaluatedNode,
branchStatus
);
}
}
@ -1325,8 +1283,10 @@ export class Reconciler {
}
_resolveComponentResolutionFailure(
componentType: Value,
error: Error | Completion,
reactElement: ObjectValue,
context: ObjectValue | AbstractObjectValue,
evaluatedNode: ReactEvaluatedNode,
branchStatus: BranchStatusEnum
): Value {
@ -1357,7 +1317,14 @@ export class Reconciler {
let propsValue = getProperty(this.realm, reactElement, "props");
// assign a bail out message
if (error instanceof NewComponentTreeBranch) {
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
this._findReactComponentTrees(
propsValue,
evaluatedNode,
"NORMAL_FUNCTIONS",
componentType,
context,
branchStatus
);
evaluatedNode.children.push(error.evaluatedNode);
// NO-OP (we don't queue a newComponentTree as this was already done)
} else {
@ -1367,7 +1334,14 @@ export class Reconciler {
}
evaluatedNode.children.push(evaluatedChildNode);
this._queueNewComponentTree(typeValue, evaluatedChildNode);
this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS");
this._findReactComponentTrees(
propsValue,
evaluatedNode,
"NORMAL_FUNCTIONS",
componentType,
context,
branchStatus
);
if (error instanceof ExpectedBailOut) {
evaluatedChildNode.message = error.message;
this._assignBailOutMessage(reactElement, error.message);
@ -1444,20 +1418,32 @@ export class Reconciler {
if (nestedOptimizedFunctionEffects !== undefined) {
for (let [func, effects] of nestedOptimizedFunctionEffects) {
let resolvedEffects = this.realm.evaluateForEffects(
() => {
let result = effects.result;
this.realm.applyEffects(effects);
let funcCall = () => {
let result = effects.result;
this.realm.applyEffects(effects);
if (result instanceof SimpleNormalCompletion) {
result = result.value;
}
invariant(result instanceof Value);
return this._resolveDeeply(componentType, result, context, branchStatus, evaluatedNode, needsKey);
};
let pureFuncCall = () =>
this.realm.evaluatePure(funcCall, /*bubbles*/ true, (sideEffectType, binding, expressionLocation) =>
handleReportedSideEffect(throwUnsupportedSideEffectError, sideEffectType, binding, expressionLocation)
);
if (result instanceof SimpleNormalCompletion) {
result = result.value;
}
invariant(result instanceof Value);
return this._resolveDeeply(componentType, result, context, branchStatus, evaluatedNode, needsKey);
},
/*state*/ null,
`react nested optimized closure`
);
let resolvedEffects;
let saved_pathConditions = this.realm.pathConditions;
this.realm.pathConditions = [];
try {
resolvedEffects = this.realm.evaluateForEffects(
pureFuncCall,
/*state*/ null,
`react resolve nested optimized closure`
);
} finally {
this.realm.pathConditions = saved_pathConditions;
}
this.statistics.optimizedNestedClosures++;
nestedOptimizedFunctionEffects.set(func, resolvedEffects);
this.realm.collectedNestedOptimizedFunctionEffects.set(func, resolvedEffects);
@ -1473,33 +1459,18 @@ export class Reconciler {
return children;
}
hasEvaluatedRootNode(componentType: ECMAScriptSourceFunctionValue, evaluateNode: ReactEvaluatedNode): boolean {
if (this.alreadyEvaluatedRootNodes.has(componentType)) {
let alreadyEvaluatedNode = this.alreadyEvaluatedRootNodes.get(componentType);
invariant(alreadyEvaluatedNode);
evaluateNode.children = alreadyEvaluatedNode.children;
evaluateNode.status = alreadyEvaluatedNode.status;
evaluateNode.name = alreadyEvaluatedNode.name;
return true;
}
return false;
}
hasEvaluatedNestedClosure(func: ECMAScriptSourceFunctionValue | BoundFunctionValue): boolean {
return this.alreadyEvaluatedNestedClosures.has(func);
}
_findReactComponentTrees(
value: Value,
evaluatedNode: ReactEvaluatedNode,
treatFunctionsAs: "NORMAL_FUNCTIONS" | "NESTED_CLOSURES" | "FUNCTIONAL_COMPONENTS",
componentType?: Value,
context?: ObjectValue | AbstractObjectValue
context?: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum
): void {
if (value instanceof AbstractValue) {
if (value.args.length > 0) {
for (let arg of value.args) {
this._findReactComponentTrees(arg, evaluatedNode, treatFunctionsAs, componentType, context);
this._findReactComponentTrees(arg, evaluatedNode, treatFunctionsAs, componentType, context, branchStatus);
}
} else {
this.componentTreeState.deadEnds++;
@ -1515,7 +1486,14 @@ export class Reconciler {
this._queueNewComponentTree(value, evaluatedChildNode);
} else if (treatFunctionsAs === "NESTED_CLOSURES") {
invariant(componentType && context);
this._queueOptimizedClosure(value, evaluatedNode, componentType, context);
let evaluatedChildNode = createReactEvaluatedNode("RENDER_PROPS", getComponentName(this.realm, value));
this._evaluateNestedOptimizedFunctionAndStoreEffects(
componentType,
context,
branchStatus,
evaluatedChildNode,
value
);
}
} else if (value instanceof ObjectValue) {
if (isReactElement(value)) {
@ -1528,8 +1506,8 @@ export class Reconciler {
evaluatedNode.children.push(evaluatedChildNode);
this._queueNewComponentTree(typeValue, evaluatedChildNode);
}
this._findReactComponentTrees(ref, evaluatedNode, treatFunctionsAs, componentType, context);
this._findReactComponentTrees(props, evaluatedNode, treatFunctionsAs, componentType, context);
this._findReactComponentTrees(ref, evaluatedNode, treatFunctionsAs, componentType, context, branchStatus);
this._findReactComponentTrees(props, evaluatedNode, treatFunctionsAs, componentType, context, branchStatus);
} else {
for (let [propName, binding] of value.properties) {
if (binding && binding.descriptor && binding.descriptor.enumerable) {
@ -1538,11 +1516,65 @@ export class Reconciler {
evaluatedNode,
treatFunctionsAs,
componentType,
context
context,
branchStatus
);
}
}
}
}
}
_evaluateNestedOptimizedFunctionAndStoreEffects(
componentType: Value,
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
evaluatedNode: ReactEvaluatedNode,
func: ECMAScriptSourceFunctionValue | BoundFunctionValue,
thisValue?: Value = this.realm.intrinsics.undefined
): void {
if (!this.realm.react.optimizeNestedFunctions) {
return;
}
let funcToModel = func;
if (func instanceof BoundFunctionValue) {
funcToModel = func.$BoundTargetFunction;
thisValue = func.$BoundThis;
}
invariant(funcToModel instanceof ECMAScriptSourceFunctionValue);
let funcCall = Utils.createModelledFunctionCall(this.realm, funcToModel, undefined, thisValue);
// We take the modelled function and wrap it in a pure evaluation so we can check for
// side-effects that occur when evaluating the function. If there are side-effects, then
// we don't try and optimize the nested function.
let pureFuncCall = () =>
this.realm.evaluatePure(funcCall, /*bubbles*/ false, () => {
throw new NestedOptimizedFunctionSideEffect();
});
let effects;
let saved_pathConditions = this.realm.pathConditions;
this.realm.pathConditions = [];
try {
effects = this.realm.evaluateForEffects(
() => {
let result = pureFuncCall();
return this._resolveDeeply(componentType, result, context, branchStatus, evaluatedNode, false);
},
null,
"React nestedOptimizedFunction"
);
} catch (e) {
// If the nested optimized function had side-effects, we need to fallback to
// the default behaviour and havoc the nested functions so any bindings
// within the function properly leak and materialize.
if (e instanceof NestedOptimizedFunctionSideEffect) {
Havoc.value(this.realm, func);
return;
}
throw e;
} finally {
this.realm.pathConditions = saved_pathConditions;
}
this.statistics.optimizedNestedClosures++;
this.realm.collectedNestedOptimizedFunctionEffects.set(funcToModel, effects);
}
}

View File

@ -9,8 +9,8 @@
/* @flow */
import { Realm, Effects } from "../realm.js";
import { AbruptCompletion, Completion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js";
import { Realm } from "../realm.js";
import { AbruptCompletion, PossiblyNormalCompletion, SimpleNormalCompletion } from "../completions.js";
import type { BabelNode, BabelNodeJSXIdentifier } from "@babel/types";
import { parseExpression } from "@babel/parser";
import {
@ -29,7 +29,7 @@ import {
UndefinedValue,
Value,
} from "../values/index.js";
import { Generator, TemporalObjectAssignEntry } from "../utils/generator.js";
import { TemporalObjectAssignEntry } from "../utils/generator.js";
import type { Descriptor, ReactComponentTreeConfig, ReactHint, PropertyBinding } from "../types.js";
import { Get, cloneDescriptor } from "../methods/index.js";
import { computeBinary } from "../evaluators/BinaryExpression.js";
@ -603,48 +603,6 @@ export function flattenChildren(realm: Realm, array: ArrayValue): ArrayValue {
return flattenedChildren;
}
export function evaluateWithNestedParentEffects(
realm: Realm,
nestedEffects: Array<Effects>,
f: () => Effects
): Effects {
let nextEffects = nestedEffects.slice();
let modifiedBindings;
let modifiedProperties;
let createdObjects;
let value;
if (nextEffects.length !== 0) {
let effects = nextEffects.shift();
value = effects.result;
if (value instanceof Completion) value = value.shallowCloneWithoutEffects();
createdObjects = effects.createdObjects;
modifiedBindings = effects.modifiedBindings;
modifiedProperties = effects.modifiedProperties;
realm.applyEffects(
new Effects(
value,
new Generator(realm, "evaluateWithNestedEffects", effects.generator.pathConditions),
modifiedBindings,
modifiedProperties,
createdObjects
)
);
}
try {
if (nextEffects.length === 0) {
return f();
} else {
return evaluateWithNestedParentEffects(realm, nextEffects, f);
}
} finally {
if (modifiedBindings && modifiedProperties) {
realm.undoBindings(modifiedBindings);
realm.restoreProperties(modifiedProperties);
}
}
}
// This function is mainly use to get internal properties
// on objects that we know are safe to access internally
// such as ReactElements. Getting properties here does

View File

@ -33,7 +33,6 @@ import {
AbstractObjectValue,
AbstractValue,
ArrayValue,
BoundFunctionValue,
ConcreteValue,
ECMAScriptSourceFunctionValue,
FunctionValue,
@ -305,7 +304,6 @@ export class Realm {
hoistableFunctions: new WeakMap(),
hoistableReactElements: new WeakMap(),
noopFunction: undefined,
optimizedNestedClosuresToWrite: [],
optimizeNestedFunctions: opts.reactOptimizeNestedFunctions || false,
output: opts.reactOutput || "create-element",
propsWithNoPartialKeyOrRef: new WeakSet(),
@ -408,10 +406,6 @@ export class Realm {
hoistableFunctions: WeakMap<FunctionValue, boolean>,
hoistableReactElements: WeakMap<ObjectValue, boolean>,
noopFunction: void | ECMAScriptSourceFunctionValue,
optimizedNestedClosuresToWrite: Array<{
effects: Effects,
func: ECMAScriptSourceFunctionValue | BoundFunctionValue,
}>,
optimizeNestedFunctions: boolean,
output?: ReactOutputTypes,
propsWithNoPartialKeyOrRef: WeakSet<ObjectValue | AbstractObjectValue>,
@ -734,17 +728,23 @@ export class Realm {
// call.
evaluatePure<T>(
f: () => T,
bubbleSideEffectReports: boolean,
reportSideEffectFunc:
| null
| ((sideEffectType: SideEffectType, binding: void | Binding | PropertyBinding, value: void | Value) => void)
): T {
let saved_createdObjectsTrackedForLeaks = this.createdObjectsTrackedForLeaks;
let saved_reportSideEffectCallbacks;
// Track all objects (including function closures) created during
// this call. This will be used to make the assumption that every
// *other* object is unchanged (pure). These objects are marked
// as leaked if they're passed to abstract functions.
this.createdObjectsTrackedForLeaks = new Set();
if (reportSideEffectFunc !== null) {
if (!bubbleSideEffectReports) {
saved_reportSideEffectCallbacks = this.reportSideEffectCallbacks;
this.reportSideEffectCallbacks = new Set();
}
this.reportSideEffectCallbacks.add(reportSideEffectFunc);
}
try {
@ -752,6 +752,9 @@ export class Realm {
} finally {
this.createdObjectsTrackedForLeaks = saved_createdObjectsTrackedForLeaks;
if (reportSideEffectFunc !== null) {
if (!bubbleSideEffectReports && saved_reportSideEffectCallbacks !== undefined) {
this.reportSideEffectCallbacks = saved_reportSideEffectCallbacks;
}
this.reportSideEffectCallbacks.delete(reportSideEffectFunc);
}
}
@ -1603,7 +1606,8 @@ export class Realm {
createdObjectsTrackedForLeaks !== undefined &&
!createdObjectsTrackedForLeaks.has(object) &&
// __markPropertyAsChecked__ is set by realm.markPropertyAsChecked
(typeof binding.key !== "string" || !binding.key.includes("__propertyHasBeenChecked__"))
(typeof binding.key !== "string" || !binding.key.includes("__propertyHasBeenChecked__")) &&
binding.key !== "_temporalAlias"
) {
if (binding.object === this.$GlobalObject) {
for (let callback of this.reportSideEffectCallbacks) {

View File

@ -32,7 +32,7 @@ import { createAdditionalEffects } from "./utils.js";
import { ReactStatistics } from "./types";
import type { AdditionalFunctionEffects, WriteEffects } from "./types";
import { convertConfigObjectToReactComponentTreeConfig, valueIsKnownReactAbstraction } from "../react/utils.js";
import { applyOptimizedReactComponents, optimizeReactComponentTreeRoot } from "../react/optimizing.js";
import { optimizeReactComponentTreeRoot } from "../react/optimizing.js";
import { handleReportedSideEffect } from "./utils.js";
import type { ArgModel } from "../types.js";
import { stringOfLocation } from "../utils/babelhelpers";
@ -161,6 +161,7 @@ export class Functions {
if (this.realm.react.verbose) {
logger.logInformation(`Evaluating ${recordedReactRootValues.length} React component tree roots...`);
}
let alreadyEvaluated = new Map();
for (let { value: componentRoot, config } of recordedReactRootValues) {
invariant(config);
optimizeReactComponentTreeRoot(
@ -170,10 +171,10 @@ export class Functions {
this.writeEffects,
environmentRecordIdAfterGlobalCode,
logger,
statistics
statistics,
alreadyEvaluated
);
}
applyOptimizedReactComponents(this.realm, this.writeEffects, environmentRecordIdAfterGlobalCode);
}
getDeclaringOptimizedFunction(functionValue: ECMAScriptSourceFunctionValue) {
@ -235,6 +236,7 @@ export class Functions {
};
let effects: Effects = realm.evaluatePure(
() => realm.evaluateForEffectsInGlobalEnv(call, undefined, "additional function"),
/*bubbles*/ true,
(sideEffectType, binding, expressionLocation) =>
handleReportedSideEffect(logCompilerDiagnostic, sideEffectType, binding, expressionLocation)
);

View File

@ -24,6 +24,7 @@ import { IsAccessorDescriptor, IsPropertyKey, IsArrayIndex } from "../methods/is
import { Havoc, Properties, To, Utils } from "../singletons.js";
import { type OperationDescriptor } from "../utils/generator.js";
import invariant from "../invariant.js";
import { NestedOptimizedFunctionSideEffect } from "../errors.js";
type PossibleNestedOptimizedFunctions = [
{ func: BoundFunctionValue | ECMAScriptSourceFunctionValue, thisValue: Value },
@ -42,28 +43,36 @@ function evaluatePossibleNestedOptimizedFunctionsAndStoreEffects(
}
invariant(funcToModel instanceof ECMAScriptSourceFunctionValue);
let funcCall = Utils.createModelledFunctionCall(realm, funcToModel, undefined, thisValue);
let hadSideEffects = false;
// We take the modelled function and wrap it in a pure evaluation so we can check for
// side-effects that occur when evaluating the function. If there are side-effects, then
// we don't try and optimize the nested function.
let pureFuncCall = () =>
realm.evaluatePure(funcCall, () => {
hadSideEffects = true;
realm.evaluatePure(funcCall, /*bubbles*/ false, () => {
throw new NestedOptimizedFunctionSideEffect();
});
let effects = realm.evaluateForEffects(pureFuncCall, null, "temporalArray nestedOptimizedFunction");
if (hadSideEffects) {
let effects;
let saved_pathConditions = realm.pathConditions;
realm.pathConditions = [];
try {
effects = realm.evaluateForEffects(pureFuncCall, null, "temporalArray nestedOptimizedFunction");
} catch (e) {
// If the nested optimized function had side-effects, we need to fallback to
// the default behaviour and havoc the nested functions so any bindings
// within the function properly leak and materialize.
Havoc.value(realm, func);
} else {
// Check if effects were pure then add them
if (abstractArrayValue.nestedOptimizedFunctionEffects === undefined) {
abstractArrayValue.nestedOptimizedFunctionEffects = new Map();
if (e instanceof NestedOptimizedFunctionSideEffect) {
Havoc.value(realm, func);
return;
}
abstractArrayValue.nestedOptimizedFunctionEffects.set(funcToModel, effects);
realm.collectedNestedOptimizedFunctionEffects.set(funcToModel, effects);
throw e;
} finally {
realm.pathConditions = saved_pathConditions;
}
// Check if effects were pure then add them
if (abstractArrayValue.nestedOptimizedFunctionEffects === undefined) {
abstractArrayValue.nestedOptimizedFunctionEffects = new Map();
}
abstractArrayValue.nestedOptimizedFunctionEffects.set(funcToModel, effects);
realm.collectedNestedOptimizedFunctionEffects.set(funcToModel, effects);
}
}

View File

@ -9626,7 +9626,7 @@ ReactStatistics {
exports[`Simple 5: (JSX => JSX) 1`] = `
ReactStatistics {
"componentsEvaluated": 4,
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
@ -9641,22 +9641,16 @@ ReactStatistics {
"name": "App",
"status": "ROOT",
},
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 3,
"optimizedTrees": 2,
}
`;
exports[`Simple 5: (JSX => createElement) 1`] = `
ReactStatistics {
"componentsEvaluated": 4,
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
@ -9671,22 +9665,16 @@ ReactStatistics {
"name": "App",
"status": "ROOT",
},
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 3,
"optimizedTrees": 2,
}
`;
exports[`Simple 5: (createElement => JSX) 1`] = `
ReactStatistics {
"componentsEvaluated": 4,
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
@ -9701,22 +9689,16 @@ ReactStatistics {
"name": "App",
"status": "ROOT",
},
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 3,
"optimizedTrees": 2,
}
`;
exports[`Simple 5: (createElement => createElement) 1`] = `
ReactStatistics {
"componentsEvaluated": 4,
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
@ -9731,16 +9713,10 @@ ReactStatistics {
"name": "App",
"status": "ROOT",
},
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 3,
"optimizedTrees": 2,
}
`;