Support logical operator unfolding in React reconcilation (#2091)

Summary:
Release notes: React compiler supports unfolding && and || logical expressions

This adds support for `&&` and `||` logical abstract value unfolding.
Closes https://github.com/facebook/prepack/pull/2091

Differential Revision: D8386493

Pulled By: trueadm

fbshipit-source-id: 3def85ac912906ada087a99c27f91ad80897c55e
This commit is contained in:
Dominic Gannaway 2018-06-12 18:25:36 -07:00 committed by Facebook Github Bot
parent 9c11ec9a5e
commit 3bd3ecc072
9 changed files with 767 additions and 0 deletions

View File

@ -1704,6 +1704,36 @@ ReactStatistics {
}
`;
exports[`Test React with JSX input, JSX output Functional component folding Component type change 11 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Foo",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Bar",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, JSX output Functional component folding Component type same 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -2788,6 +2818,107 @@ ReactStatistics {
}
`;
exports[`Test React with JSX input, JSX output Functional component folding Simple 18 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, JSX output Functional component folding Simple 19 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, JSX output Functional component folding Simple 20 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, JSX output Functional component folding Simple 21 1`] = `
ReactStatistics {
"componentsEvaluated": 2,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 1,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, JSX output Functional component folding Simple children 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -6434,6 +6565,36 @@ ReactStatistics {
}
`;
exports[`Test React with JSX input, create-element output Functional component folding Component type change 11 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Foo",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Bar",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, create-element output Functional component folding Component type same 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -7518,6 +7679,107 @@ ReactStatistics {
}
`;
exports[`Test React with JSX input, create-element output Functional component folding Simple 18 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, create-element output Functional component folding Simple 19 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, create-element output Functional component folding Simple 20 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, create-element output Functional component folding Simple 21 1`] = `
ReactStatistics {
"componentsEvaluated": 2,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 1,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, create-element output Functional component folding Simple children 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -11164,6 +11426,36 @@ ReactStatistics {
}
`;
exports[`Test React with create-element input, JSX output Functional component folding Component type change 11 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Foo",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Bar",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, JSX output Functional component folding Component type same 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -12248,6 +12540,107 @@ ReactStatistics {
}
`;
exports[`Test React with create-element input, JSX output Functional component folding Simple 18 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, JSX output Functional component folding Simple 19 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, JSX output Functional component folding Simple 20 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, JSX output Functional component folding Simple 21 1`] = `
ReactStatistics {
"componentsEvaluated": 2,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 1,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, JSX output Functional component folding Simple children 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -15909,6 +16302,36 @@ ReactStatistics {
}
`;
exports[`Test React with create-element input, create-element output Functional component folding Component type change 11 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Foo",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Bar",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, create-element output Functional component folding Component type same 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -16993,6 +17416,107 @@ ReactStatistics {
}
`;
exports[`Test React with create-element input, create-element output Functional component folding Simple 18 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, create-element output Functional component folding Simple 19 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, create-element output Functional component folding Simple 20 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, create-element output Functional component folding Simple 21 1`] = `
ReactStatistics {
"componentsEvaluated": 2,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Child",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 1,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, create-element output Functional component folding Simple children 1`] = `
ReactStatistics {
"componentsEvaluated": 3,

View File

@ -369,6 +369,22 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
await runTest(directory, "simple-17.js");
});
it("Simple 18", async () => {
await runTest(directory, "simple-18.js");
});
it("Simple 19", async () => {
await runTest(directory, "simple-19.js");
});
it("Simple 20", async () => {
await runTest(directory, "simple-20.js");
});
it("Simple 21", async () => {
await runTest(directory, "simple-21.js");
});
it("Havocing of ReactElements should not result in property assignments", async () => {
await runTest(directory, "react-element-havoc.js");
});
@ -622,6 +638,10 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
await runTest(directory, "type-change10.js");
});
it("Component type change 11", async () => {
await runTest(directory, "type-change11.js");
});
it("Component type same", async () => {
await runTest(directory, "type-same.js");
});

View File

@ -167,6 +167,8 @@ function applyBranchedLogicValue(realm: Realm, value: Value): Value {
);
}
);
} else if (value instanceof AbstractValue && (value.kind === "||" || value.kind === "&&")) {
invariant(false, "applyBranchedLogicValue encounterted a logical expression (|| or &&), this should never occur");
} else {
throw new ExpectedBailOut("Unsupported value encountered when applying branched logic to values");
}

View File

@ -926,6 +926,37 @@ export class Reconciler {
return value;
}
_resolveAbstractLogicalValue(
componentType: Value,
value: AbstractValue,
context: ObjectValue | AbstractObjectValue,
evaluatedNode: ReactEvaluatedNode
) {
let [leftValue, rightValue] = value.args;
let operator = value.kind;
invariant(leftValue instanceof AbstractValue);
if (operator === "||") {
return this._resolveAbstractConditionalValue(
componentType,
leftValue,
leftValue,
rightValue,
context,
evaluatedNode
);
} else {
return this._resolveAbstractConditionalValue(
componentType,
leftValue,
rightValue,
leftValue,
context,
evaluatedNode
);
}
}
_resolveAbstractValue(
componentType: Value,
value: AbstractValue,
@ -946,6 +977,8 @@ export class Reconciler {
context,
evaluatedNode
);
} else if (value.kind === "||" || value.kind === "&&") {
return this._resolveAbstractLogicalValue(componentType, value, context, evaluatedNode);
} else {
if (value instanceof AbstractValue && this.realm.react.abstractHints.has(value)) {
let reactHint = this.realm.react.abstractHints.get(value);

View File

@ -0,0 +1,29 @@
var React = require('React');
// the JSX transform converts to React, so we need to add it back in
this['React'] = React;
function App(props) {
return (
<div>
{props.x || <div />}
{props.y && <span />}
</div>
);
}
App.getTrials = function(renderer, Root) {
let results = [];
renderer.update(<Root x={<span />} y={true} />);
results.push(['deals with logical expression 1', renderer.toJSON()]);
renderer.update(<Root x={false} y={true} />);
results.push(['deals with logical expression 2', renderer.toJSON()]);
renderer.update(<Root x={false} y={<div />} />);
results.push(['deals with logical expression 3', renderer.toJSON()]);
return results;
};
if (this.__optimizeReactComponentTree) {
__optimizeReactComponentTree(App);
}
module.exports = App;

View File

@ -0,0 +1,37 @@
var React = require('React');
// the JSX transform converts to React, so we need to add it back in
this['React'] = React;
function Child(props) {
var x = Object.assign({}, props, {
a: 1,
b: 2,
});
return <span>{x.a}{x.b}{x.c}</span>
}
function App(props) {
return (
<div>
{props.x || <Child {...props} />}
{props.y && <Child {...props} />}
</div>
);
}
App.getTrials = function(renderer, Root) {
let results = [];
renderer.update(<Root x={<span />} y={true} c={3} />);
results.push(['deals with logical expression 1', renderer.toJSON()]);
renderer.update(<Root x={false} y={true} c={4} />);
results.push(['deals with logical expression 2', renderer.toJSON()]);
renderer.update(<Root x={false} y={<div />} c={5} />);
results.push(['deals with logical expression 3', renderer.toJSON()]);
return results;
};
if (this.__optimizeReactComponentTree) {
__optimizeReactComponentTree(App);
}
module.exports = App;

View File

@ -0,0 +1,40 @@
var React = require('React');
// the JSX transform converts to React, so we need to add it back in
this['React'] = React;
function Child(props) {
var x = Object.assign({}, props, {
a: 1,
b: 2,
});
return <span>{x.a}{x.b}{x.c}</span>
}
function App(props) {
var foo = props.x ? props.y : props.z;
return (
<div>
{foo || <Child {...props} />}
{foo && <Child {...props} />}
</div>
);
}
App.getTrials = function(renderer, Root) {
let results = [];
renderer.update(<Root x={true} y={true} z={true} c={3} />);
results.push(['deals with logical expression 1', renderer.toJSON()]);
renderer.update(<Root x={true} y={true} z={false} c={3} />);
results.push(['deals with logical expression 2', renderer.toJSON()]);
renderer.update(<Root x={true} y={false} z={true} c={3} />);
results.push(['deals with logical expression 3', renderer.toJSON()]);
renderer.update(<Root x={false} y={true} z={true} c={3} />);
results.push(['deals with logical expression 4', renderer.toJSON()]);
return results;
};
if (this.__optimizeReactComponentTree) {
__optimizeReactComponentTree(App);
}
module.exports = App;

View File

@ -0,0 +1,32 @@
var React = require('React');
// the JSX transform converts to React, so we need to add it back in
this['React'] = React;
function Child(props) {
return <span>{props.x.toString()}</span>;
}
function App(props) {
return (
<div>
{props.x !== null && <Child {...props} />}
</div>
);
}
App.getTrials = function(renderer, Root) {
let results = [];
renderer.update(<Root x={null} />);
results.push(['deals with logical expression 1', renderer.toJSON()]);
renderer.update(<Root x={5} />);
results.push(['deals with logical expression 2', renderer.toJSON()]);
renderer.update(<Root x={"hello world"} />);
results.push(['deals with logical expression 3', renderer.toJSON()]);
return results;
};
if (this.__optimizeReactComponentTree) {
__optimizeReactComponentTree(App);
}
module.exports = App;

View File

@ -0,0 +1,50 @@
var React = require('react');
// the JSX transform converts to React, so we need to add it back in
this['React'] = React;
function App(props) {
if (props.foo) {
return (
<div>
<Foo callback={props.callback} x={props.x} />
</div>
);
}
return (
<div>
<Bar callback={props.callback} x={props.x} />
</div>
);
}
function Foo(props) {
return props.x || <input ref={props.callback} />;
}
function Bar(props) {
return props.x && <input ref={props.callback} />;
}
if (this.__optimizeReactComponentTree) {
__optimizeReactComponentTree(App);
}
App.getTrials = function(renderer, Root) {
let counter = 0;
let nodes = [];
function callback(node) {
nodes.push(node);
counter++;
}
renderer.update(<Root callback={callback} foo={true} x={true} />);
renderer.update(<Root callback={callback} foo={true} x={false} />);
renderer.update(<Root callback={callback} foo={false} x={false} />);
renderer.update(<Root callback={callback} foo={false} x={true} />);
let results = [];
results.push(['ensure refs was called 3 times', counter]);
results.push(['ensure refs at 0 is not null', nodes[0] !== null]);
return results;
};
module.exports = App;