From 47cb48b438e46d8c353e98618033fef7d9dca560 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 27 Sep 2018 00:32:21 -0700 Subject: [PATCH] Adds support for abstract length arrays in React reconcilation and serialization (#2571) Summary: Release notes: none This PR fixes issues with the React hoisting and equivalence system mechanics and serialization where previous, there was no support for abstract length arrays. This includes an optimization for when the React serializer outputs ReactElement children that are conditionals with one side being an empty value (in this case, we can use an empty string instead). Tests attached that focus on the areas covered in this PR. Pull Request resolved: https://github.com/facebook/prepack/pull/2571 Differential Revision: D10082133 Pulled By: trueadm fbshipit-source-id: d7de1834e10a5c4b3f35a90b9676ec72c6e797e2 --- src/react/ReactEquivalenceSet.js | 25 +- src/react/elements.js | 21 +- src/react/hoisting.js | 16 +- src/react/reconcilation.js | 2 +- src/react/utils.js | 156 ++++++++-- test/react/FunctionalComponents-test.js | 16 ++ test/react/FunctionalComponents/simple-26.js | 32 +++ test/react/FunctionalComponents/simple-27.js | 32 +++ test/react/FunctionalComponents/simple-28.js | 34 +++ test/react/FunctionalComponents/simple-29.js | 37 +++ .../FunctionalComponents-test.js.snap | 272 ++++++++++++++++++ 11 files changed, 592 insertions(+), 51 deletions(-) create mode 100644 test/react/FunctionalComponents/simple-26.js create mode 100644 test/react/FunctionalComponents/simple-27.js create mode 100644 test/react/FunctionalComponents/simple-28.js create mode 100644 test/react/FunctionalComponents/simple-29.js diff --git a/src/react/ReactEquivalenceSet.js b/src/react/ReactEquivalenceSet.js index c1df1496b..3d95fe4bc 100644 --- a/src/react/ReactEquivalenceSet.js +++ b/src/react/ReactEquivalenceSet.js @@ -216,21 +216,27 @@ export class ReactEquivalenceSet { return ((map.get(result): any): ReactSetNode); } - // for arrays: [0] -> [1] -> [2]... as nodes + // for arrays: [length] -> ([length] is numeric) -> [0] -> [1] -> [2]... as nodes _getArrayValue(array: ArrayValue, visitedValues: Set): ArrayValue { if (visitedValues.has(array)) return array; if (array.intrinsicName) return array; visitedValues.add(array); - let lengthValue = getProperty(this.realm, array, "length"); - invariant(lengthValue instanceof NumberValue); - let length = lengthValue.value; let currentMap = this.arrayRoot; - let result; + currentMap = this.getKey("length", currentMap, visitedValues); + let result = this.getEquivalentPropertyValue(array, "length", currentMap, visitedValues); + currentMap = result.map; - for (let i = 0; i < length; i++) { - currentMap = this.getKey(i, currentMap, visitedValues); - result = this.getEquivalentPropertyValue(array, "" + i, currentMap, visitedValues); - currentMap = result.map; + let lengthValue = getProperty(this.realm, array, "length"); + // If we have a numeric lenth that is not abstract, then also check all the array elements + if (lengthValue instanceof NumberValue) { + invariant(lengthValue instanceof NumberValue); + let length = lengthValue.value; + + for (let i = 0; i < length; i++) { + currentMap = this.getKey(i, currentMap, visitedValues); + result = this.getEquivalentPropertyValue(array, "" + i, currentMap, visitedValues); + currentMap = result.map; + } } if (result === undefined) { if (this.realm.react.emptyArray !== undefined) { @@ -241,6 +247,7 @@ export class ReactEquivalenceSet { if (result.value === null) { result.value = array; } + invariant(result.value instanceof ArrayValue); return result.value; } diff --git a/src/react/elements.js b/src/react/elements.js index 586a8c68e..599088435 100644 --- a/src/react/elements.js +++ b/src/react/elements.js @@ -28,6 +28,7 @@ import { createInternalReactElement, flagPropsWithNoPartialKeyOrRef, flattenChildren, + getMaxLength, hardModifyReactObjectPropertyBinding, getProperty, hasNoPartialKeyOrRef, @@ -418,7 +419,7 @@ export function wrapReactElementWithKeyedFragment(realm: Realm, keyValue: Value, Create.CreateDataPropertyOrThrow(realm, fragmentConfigValue, "key", keyValue); let fragmentChildrenValue = Create.ArrayCreate(realm, 1); Create.CreateDataPropertyOrThrow(realm, fragmentChildrenValue, "0", reactElement); - fragmentChildrenValue = flattenChildren(realm, fragmentChildrenValue); + fragmentChildrenValue = flattenChildren(realm, fragmentChildrenValue, true); return createReactElement(realm, reactFragment, fragmentConfigValue, fragmentChildrenValue); } @@ -449,6 +450,13 @@ export function traverseReactElement( traversalVisitor.visitRef(refValue); } + const loopArrayElements = (childrenValue: ArrayValue, length: number): void => { + for (let i = 0; i < length; i++) { + let child = getProperty(realm, childrenValue, "" + i); + traversalVisitor.visitChildNode(child); + } + }; + const handleChildren = () => { // handle children invariant(propsValue instanceof ObjectValue); @@ -457,13 +465,12 @@ export function traverseReactElement( if (childrenValue !== realm.intrinsics.undefined && childrenValue !== realm.intrinsics.null) { if (childrenValue instanceof ArrayValue && !childrenValue.intrinsicName) { let childrenLength = getProperty(realm, childrenValue, "length"); - let childrenLengthValue = 0; if (childrenLength instanceof NumberValue) { - childrenLengthValue = childrenLength.value; - for (let i = 0; i < childrenLengthValue; i++) { - let child = getProperty(realm, childrenValue, "" + i); - traversalVisitor.visitChildNode(child); - } + loopArrayElements(childrenValue, childrenLength.value); + } else if (childrenLength instanceof AbstractValue && childrenLength.kind === "conditional") { + loopArrayElements(childrenValue, getMaxLength(childrenLength, 0)); + } else { + invariant(false, "TODO: support other types of array length value"); } } else { traversalVisitor.visitChildNode(childrenValue); diff --git a/src/react/hoisting.js b/src/react/hoisting.js index 424180237..9fd5a874d 100644 --- a/src/react/hoisting.js +++ b/src/react/hoisting.js @@ -61,13 +61,17 @@ function canHoistArray( ): boolean { if (array.intrinsicName) return false; let lengthValue = Get(realm, array, "length"); - invariant(lengthValue instanceof NumberValue); - let length = lengthValue.value; - for (let i = 0; i < length; i++) { - let element = Get(realm, array, "" + i); + if (!canHoistValue(realm, lengthValue, residualHeapVisitor, visitedValues)) { + return false; + } + if (lengthValue instanceof NumberValue) { + let length = lengthValue.value; + for (let i = 0; i < length; i++) { + let element = Get(realm, array, "" + i); - if (!canHoistValue(realm, element, residualHeapVisitor, visitedValues)) { - return false; + if (!canHoistValue(realm, element, residualHeapVisitor, visitedValues)) { + return false; + } } } return true; diff --git a/src/react/reconcilation.js b/src/react/reconcilation.js index 8b9f26360..287ae88ea 100644 --- a/src/react/reconcilation.js +++ b/src/react/reconcilation.js @@ -1033,7 +1033,7 @@ export class Reconciler { ); // we can optimize further and flatten arrays on non-composite components if (resolvedChildren instanceof ArrayValue && !resolvedChildren.intrinsicName) { - resolvedChildren = flattenChildren(this.realm, resolvedChildren); + resolvedChildren = flattenChildren(this.realm, resolvedChildren, true); } if (resolvedChildren !== childrenValue) { let newProps = cloneProps(this.realm, propsValue, resolvedChildren); diff --git a/src/react/utils.js b/src/react/utils.js index f6a87d9d8..9baa33b79 100644 --- a/src/react/utils.js +++ b/src/react/utils.js @@ -21,6 +21,7 @@ import { BoundFunctionValue, ECMAScriptFunctionValue, ECMAScriptSourceFunctionValue, + EmptyValue, FunctionValue, NumberValue, ObjectValue, @@ -239,17 +240,31 @@ export function forEachArrayValue( mapFunc: (element: Value, index: number) => void ): void { let lengthValue = Get(realm, array, "length"); - invariant(lengthValue instanceof NumberValue, "TODO: support non-numeric length on forEachArrayValue"); - let length = lengthValue.value; + let isConditionalLength = lengthValue instanceof AbstractValue && lengthValue.kind === "conditional"; + let length; + if (isConditionalLength) { + length = getMaxLength(lengthValue, 0); + } else { + invariant(lengthValue instanceof NumberValue, "TODO: support other types of array length value"); + length = lengthValue.value; + } for (let i = 0; i < length; i++) { let elementProperty = array.properties.get("" + i); let elementPropertyDescriptor = elementProperty && elementProperty.descriptor; if (elementPropertyDescriptor) { invariant(elementPropertyDescriptor instanceof PropertyDescriptor); let elementValue = elementPropertyDescriptor.value; - if (elementValue instanceof Value) { - mapFunc(elementValue, i); + // If we are in an array with conditional length, the element might be a conditional join + // of the same type as the length of the array + if (isConditionalLength && elementValue instanceof AbstractValue && elementValue.kind === "conditional") { + invariant(lengthValue instanceof AbstractValue); + let lengthCondition = lengthValue.args[0]; + let elementCondition = elementValue.args[0]; + // If they are the same condition + invariant(lengthCondition.equals(elementCondition), "TODO: support cases where the condition is not the same"); } + invariant(elementValue instanceof Value); + mapFunc(elementValue, i); } } } @@ -259,28 +274,66 @@ export function mapArrayValue( array: ArrayValue, mapFunc: (element: Value, descriptor: Descriptor) => Value ): ArrayValue { - let lengthValue = Get(realm, array, "length"); - invariant(lengthValue instanceof NumberValue, "TODO: support non-numeric length on mapArrayValue"); - let length = lengthValue.value; - let newArray = Create.ArrayCreate(realm, length); let returnTheNewArray = false; + let newArray; - for (let i = 0; i < length; i++) { - let elementProperty = array.properties.get("" + i); - let elementPropertyDescriptor = elementProperty && elementProperty.descriptor; - if (elementPropertyDescriptor) { - invariant(elementPropertyDescriptor instanceof PropertyDescriptor); - let elementValue = elementPropertyDescriptor.value; - if (elementValue instanceof Value) { - let newElement = mapFunc(elementValue, elementPropertyDescriptor); - if (newElement !== elementValue) { - returnTheNewArray = true; + const mapArray = (lengthValue: NumberValue): void => { + let length = lengthValue.value; + + for (let i = 0; i < length; i++) { + let elementProperty = array.properties.get("" + i); + let elementPropertyDescriptor = elementProperty && elementProperty.descriptor; + if (elementPropertyDescriptor) { + invariant(elementPropertyDescriptor instanceof PropertyDescriptor); + let elementValue = elementPropertyDescriptor.value; + if (elementValue instanceof Value) { + let newElement = mapFunc(elementValue, elementPropertyDescriptor); + if (newElement !== elementValue) { + returnTheNewArray = true; + } + Create.CreateDataPropertyOrThrow(realm, newArray, "" + i, newElement); + continue; } - Create.CreateDataPropertyOrThrow(realm, newArray, "" + i, newElement); - continue; } + Create.CreateDataPropertyOrThrow(realm, newArray, "" + i, realm.intrinsics.undefined); } - Create.CreateDataPropertyOrThrow(realm, newArray, "" + i, realm.intrinsics.undefined); + }; + + let lengthValue = Get(realm, array, "length"); + if (lengthValue instanceof AbstractValue && lengthValue.kind === "conditional") { + returnTheNewArray = true; + let [condValue, consequentVal, alternateVal] = lengthValue.args; + newArray = Create.ArrayCreate(realm, 0); + realm.evaluateWithAbstractConditional( + condValue, + () => { + return realm.evaluateForEffects( + () => { + invariant(consequentVal instanceof NumberValue); + mapArray(consequentVal); + return realm.intrinsics.undefined; + }, + null, + "mapArrayValue consequent" + ); + }, + () => { + return realm.evaluateForEffects( + () => { + invariant(alternateVal instanceof NumberValue); + mapArray(alternateVal); + return realm.intrinsics.undefined; + }, + null, + "mapArrayValue alternate" + ); + } + ); + } else if (lengthValue instanceof NumberValue) { + newArray = Create.ArrayCreate(realm, lengthValue.value); + mapArray(lengthValue); + } else { + invariant(false, "TODO: support other types of array length value"); } return returnTheNewArray ? newArray : array; } @@ -602,21 +655,68 @@ export function hasNoPartialKeyOrRef(realm: Realm, props: ObjectValue | Abstract return false; } -function recursivelyFlattenArray(realm: Realm, array, targetArray): void { - forEachArrayValue(realm, array, item => { - if (item instanceof ArrayValue && !item.intrinsicName) { - recursivelyFlattenArray(realm, item, targetArray); +export function getMaxLength(value: Value, maxLength: number): number { + if (value instanceof NumberValue) { + if (value.value > maxLength) { + return value.value; + } else { + return maxLength; + } + } else if (value instanceof AbstractValue && value.kind === "conditional") { + let [, consequentVal, alternateVal] = value.args; + let consequentMaxVal = getMaxLength(consequentVal, maxLength); + let alternateMaxVal = getMaxLength(alternateVal, maxLength); + if (consequentMaxVal > maxLength && consequentMaxVal >= alternateMaxVal) { + return consequentMaxVal; + } else if (alternateMaxVal > maxLength && alternateMaxVal >= consequentMaxVal) { + return alternateMaxVal; + } + return maxLength; + } + invariant(false, "TODO: support other types of array length value"); +} + +function recursivelyFlattenArray(realm: Realm, array, targetArray: ArrayValue, noHoles: boolean): void { + forEachArrayValue(realm, array, _item => { + let element = _item; + if (element instanceof ArrayValue && !element.intrinsicName) { + recursivelyFlattenArray(realm, element, targetArray, noHoles); } else { let lengthValue = Get(realm, targetArray, "length"); invariant(lengthValue instanceof NumberValue); - Properties.Set(realm, targetArray, "" + lengthValue.value, item, true); + if (noHoles && element instanceof EmptyValue) { + // We skip holely elements + return; + } else if (noHoles && element instanceof AbstractValue && element.kind === "conditional") { + let [condValue, consequentVal, alternateVal] = element.args; + invariant(condValue instanceof AbstractValue); + let consquentIsHolely = consequentVal instanceof EmptyValue; + let alternateIsHolely = alternateVal instanceof EmptyValue; + + if (consquentIsHolely && alternateIsHolely) { + // We skip holely elements + return; + } + if (consquentIsHolely) { + element = AbstractValue.createFromLogicalOp( + realm, + "&&", + AbstractValue.createFromUnaryOp(realm, "!", condValue), + alternateVal + ); + } + if (alternateIsHolely) { + element = AbstractValue.createFromLogicalOp(realm, "&&", condValue, consequentVal); + } + } + Properties.Set(realm, targetArray, "" + lengthValue.value, element, true); } }); } -export function flattenChildren(realm: Realm, array: ArrayValue): ArrayValue { +export function flattenChildren(realm: Realm, array: ArrayValue, noHoles: boolean): ArrayValue { let flattenedChildren = Create.ArrayCreate(realm, 0); - recursivelyFlattenArray(realm, array, flattenedChildren); + recursivelyFlattenArray(realm, array, flattenedChildren, noHoles); flattenedChildren.makeFinal(); return flattenedChildren; } diff --git a/test/react/FunctionalComponents-test.js b/test/react/FunctionalComponents-test.js index 29b1298e5..fa0b92290 100644 --- a/test/react/FunctionalComponents-test.js +++ b/test/react/FunctionalComponents-test.js @@ -132,6 +132,22 @@ it("Simple 25", () => { runTest(__dirname + "/FunctionalComponents/simple-25.js"); }); +it("Simple 26", () => { + runTest(__dirname + "/FunctionalComponents/simple-26.js"); +}); + +it("Simple 27", () => { + runTest(__dirname + "/FunctionalComponents/simple-27.js"); +}); + +it("Simple 28", () => { + runTest(__dirname + "/FunctionalComponents/simple-28.js"); +}); + +it("Simple 29", () => { + runTest(__dirname + "/FunctionalComponents/simple-29.js"); +}); + it("Bound type", () => { runTest(__dirname + "/FunctionalComponents/bound-type.js"); }); diff --git a/test/react/FunctionalComponents/simple-26.js b/test/react/FunctionalComponents/simple-26.js new file mode 100644 index 000000000..e7929ff16 --- /dev/null +++ b/test/react/FunctionalComponents/simple-26.js @@ -0,0 +1,32 @@ +var React = require("react"); + +function App(props) { + var arr = [1, 2, 3]; + + if (props.cond) { + arr.push(4); + } else { + arr.pop(); + } + return ( +
+ + +
+ ); +} + +App.getTrials = function(renderer, Root) { + let results = []; + renderer.update(); + results.push(["abstract array length on prop (cond: true)", renderer.toJSON()]); + renderer.update(); + results.push(["abstract array length on prop (cond: false)", renderer.toJSON()]); + return results; +}; + +if (this.__optimizeReactComponentTree) { + __optimizeReactComponentTree(App); +} + +module.exports = App; diff --git a/test/react/FunctionalComponents/simple-27.js b/test/react/FunctionalComponents/simple-27.js new file mode 100644 index 000000000..9d38843ee --- /dev/null +++ b/test/react/FunctionalComponents/simple-27.js @@ -0,0 +1,32 @@ +var React = require("react"); + +function App(props) { + var arr = [1, 2, 3]; + + if (props.cond) { + arr.push(4); + } else { + arr.pop(); + } + return ( +
+ {arr} + {arr} +
+ ); +} + +App.getTrials = function(renderer, Root) { + let results = []; + renderer.update(); + results.push(["abstract array length on prop (cond: true)", renderer.toJSON()]); + renderer.update(); + results.push(["abstract array length on prop (cond: false)", renderer.toJSON()]); + return results; +}; + +if (this.__optimizeReactComponentTree) { + __optimizeReactComponentTree(App); +} + +module.exports = App; diff --git a/test/react/FunctionalComponents/simple-28.js b/test/react/FunctionalComponents/simple-28.js new file mode 100644 index 000000000..c2e19bcbf --- /dev/null +++ b/test/react/FunctionalComponents/simple-28.js @@ -0,0 +1,34 @@ +var React = require("react"); + +function App(props) { + var arr = [1, 2, 3]; + + if (props.cond) { + arr.push(4); + } else { + arr.pop(); + } + arr.reverse(); + return ( +
+ {arr[0]} + {arr[1]} + {arr[2]} +
+ ); +} + +App.getTrials = function(renderer, Root) { + let results = []; + renderer.update(); + results.push(["abstract array length on prop (cond: true)", renderer.toJSON()]); + renderer.update(); + results.push(["abstract array length on prop (cond: false)", renderer.toJSON()]); + return results; +}; + +if (this.__optimizeReactComponentTree) { + __optimizeReactComponentTree(App); +} + +module.exports = App; diff --git a/test/react/FunctionalComponents/simple-29.js b/test/react/FunctionalComponents/simple-29.js new file mode 100644 index 000000000..a1bde0284 --- /dev/null +++ b/test/react/FunctionalComponents/simple-29.js @@ -0,0 +1,37 @@ +var React = require("react"); + +function App(props) { + var arr = [1, 2, 3]; + + if (props.cond) { + arr.push(4); + } else { + arr.pop(); + } + var arr1 = Array.from(arr); + arr1.reverse(); + var arr2 = Array.from(arr).join(", "); + var arr3 = Array.from(arr).map(x => x); + return ( +
+ {arr1} + {arr2} + {arr3} +
+ ); +} + +App.getTrials = function(renderer, Root) { + let results = []; + renderer.update(); + results.push(["abstract array length on prop (cond: true)", renderer.toJSON()]); + renderer.update(); + results.push(["abstract array length on prop (cond: false)", renderer.toJSON()]); + return results; +}; + +if (this.__optimizeReactComponentTree) { + __optimizeReactComponentTree(App); +} + +module.exports = App; diff --git a/test/react/__snapshots__/FunctionalComponents-test.js.snap b/test/react/__snapshots__/FunctionalComponents-test.js.snap index 8ec8e463d..31c78e364 100644 --- a/test/react/__snapshots__/FunctionalComponents-test.js.snap +++ b/test/react/__snapshots__/FunctionalComponents-test.js.snap @@ -11568,6 +11568,278 @@ ReactStatistics { } `; +exports[`Simple 26: (JSX => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 26: (JSX => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 26: (createElement => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 26: (createElement => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 27: (JSX => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 27: (JSX => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 27: (createElement => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 27: (createElement => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 28: (JSX => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 28: (JSX => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 28: (createElement => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 28: (createElement => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 29: (JSX => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 29: (JSX => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 29: (createElement => JSX) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + +exports[`Simple 29: (createElement => createElement) 1`] = ` +ReactStatistics { + "componentsEvaluated": 1, + "evaluatedRootNodes": Array [ + Object { + "children": Array [], + "message": "", + "name": "App", + "status": "ROOT", + }, + ], + "inlinedComponents": 0, + "optimizedNestedClosures": 0, + "optimizedTrees": 1, +} +`; + exports[`Simple children: (JSX => JSX) 1`] = ` ReactStatistics { "componentsEvaluated": 3,