mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-10-26 15:20:18 +03:00
Unify simplifyAbstractValue and AbstractValue.refineWithPathCondition.
Summary: Release notes: none This is a no new functionality refactor that gets rid of the duplication of logic between these two functions. Addresses #1019. I don't think I'm quite done with the issue, since the factory functions also contain simplification logic. Closes https://github.com/facebook/prepack/pull/1074 Differential Revision: D6035089 Pulled By: hermanventer fbshipit-source-id: 11f3d1a51927ae67beddfa46197088228b93cb9e
This commit is contained in:
parent
3d2b586bc4
commit
acf67a3362
@ -17,6 +17,7 @@ import * as evaluators from "./evaluators/index.js";
|
||||
import * as partialEvaluators from "./partial-evaluators/index.js";
|
||||
import { NewGlobalEnvironment } from "./methods/index.js";
|
||||
import { ObjectValue } from "./values/index.js";
|
||||
import simplifyAbstractValue from "./utils/simplifier.js";
|
||||
|
||||
export default function(opts: RealmOptions = {}): Realm {
|
||||
let r = new Realm(opts);
|
||||
@ -28,6 +29,7 @@ export default function(opts: RealmOptions = {}): Realm {
|
||||
initializeGlobal(r);
|
||||
for (let name in evaluators) r.evaluators[name] = evaluators[name];
|
||||
for (let name in partialEvaluators) r.partialEvaluators[name] = partialEvaluators[name];
|
||||
r.simplifyAbstractValue = simplifyAbstractValue.bind(null, r);
|
||||
r.$GlobalEnv = NewGlobalEnvironment(r, r.$GlobalObject, r.$GlobalObject);
|
||||
return r;
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ export function GetReferencedNamePartial(realm: Realm, V: Reference): AbstractVa
|
||||
// ECMA262 6.2.3.1
|
||||
export function GetValue(realm: Realm, V: Reference | Value): Value {
|
||||
let val = dereference(realm, V);
|
||||
if (val instanceof AbstractValue) return val.refineWithPathCondition();
|
||||
if (val instanceof AbstractValue) return realm.simplifyAbstractValue(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
|
@ -594,12 +594,7 @@ export function joinValuesAsConditional(
|
||||
v1: void | Value,
|
||||
v2: void | Value
|
||||
): Value {
|
||||
let result = AbstractValue.createFromConditionalOp(realm, condition, v1, v2);
|
||||
if (result instanceof AbstractValue) {
|
||||
if (v1) result.mightBeEmpty = v1.mightHaveBeenDeleted();
|
||||
if (v2 && !result.mightBeEmpty) result.mightBeEmpty = v2.mightHaveBeenDeleted();
|
||||
}
|
||||
return result;
|
||||
return AbstractValue.createFromConditionalOp(realm, condition, v1, v2);
|
||||
}
|
||||
|
||||
export function joinPropertyBindings(
|
||||
|
@ -232,6 +232,7 @@ export class Realm {
|
||||
metadata?: any
|
||||
) => [Completion | Reference | Value, BabelNode, Array<BabelNodeStatement>],
|
||||
};
|
||||
simplifyAbstractValue: AbstractValue => Value;
|
||||
|
||||
tracers: Array<Tracer>;
|
||||
|
||||
|
@ -118,6 +118,6 @@ function pushInversePathCondition(condition: Value) {
|
||||
|
||||
function pushRefinedConditions(unrefinedConditions: Array<AbstractValue>) {
|
||||
for (let unrefinedCond of unrefinedConditions) {
|
||||
pushPathCondition(unrefinedCond.refineWithPathCondition());
|
||||
pushPathCondition(unrefinedCond.$Realm.simplifyAbstractValue(unrefinedCond));
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
/* @flow */
|
||||
|
||||
import type { BabelNodeSourceLocation } from "babel-types";
|
||||
import { FatalError } from "../errors.js";
|
||||
import { ValuesDomain } from "../domains/index.js";
|
||||
import invariant from "../invariant.js";
|
||||
@ -33,29 +34,74 @@ export default function simplifyAbstractValue(realm: Realm, value: AbstractValue
|
||||
}
|
||||
}
|
||||
|
||||
function pathImplies(condition: AbstractValue): boolean {
|
||||
let path = condition.$Realm.pathConditions;
|
||||
for (let i = path.length - 1; i >= 0; i--) {
|
||||
let pathCondition = path[i];
|
||||
if (pathCondition.implies(condition)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function simplify(realm, value: Value): Value {
|
||||
if (value instanceof ConcreteValue) return value;
|
||||
invariant(value instanceof AbstractValue);
|
||||
switch (value.kind) {
|
||||
let op = value.kind;
|
||||
switch (op) {
|
||||
case "!":
|
||||
return negate(realm, value.args[0]);
|
||||
return negate(realm, value.args[0], value.expressionLocation);
|
||||
case "||":
|
||||
case "&&":
|
||||
case "&&": {
|
||||
let x = simplify(realm, value.args[0]);
|
||||
let y = simplify(realm, value.args[1]);
|
||||
if (x instanceof AbstractValue && x.equals(y)) return x;
|
||||
// todo: remove this check when there is an alternative way to indicate that an intrinsic object is nullable
|
||||
if (!(x instanceof AbstractObjectValue && x.isIntrinsic())) {
|
||||
// true && y <=> y
|
||||
// true || y <=> true
|
||||
if (!x.mightNotBeTrue()) return op === "&&" ? y : x;
|
||||
// (x == false) && y <=> x
|
||||
// false || y <=> y
|
||||
if (!x.mightNotBeFalse()) return op === "||" ? y : x;
|
||||
}
|
||||
if (x.getType() === BooleanValue && y.getType() === BooleanValue) {
|
||||
// (x: boolean) && true <=> x
|
||||
// x || true <=> true
|
||||
if (!y.mightNotBeTrue()) return op === "&&" ? x : realm.intrinsics.true;
|
||||
// (x: boolean) && false <=> false
|
||||
// (x: boolean) || false <=> x
|
||||
if (!y.mightNotBeFalse()) return op === "||" ? x : realm.intrinsics.false;
|
||||
}
|
||||
return AbstractValue.createFromLogicalOp(realm, (value.kind: any), x, y, value.expressionLocation);
|
||||
}
|
||||
case "==":
|
||||
case "!=":
|
||||
case "===":
|
||||
case "!==":
|
||||
return simplifyEquality(realm, value);
|
||||
case "conditional": {
|
||||
let c = simplify(realm, value.args[0]);
|
||||
let x = simplify(realm, value.args[1]);
|
||||
let y = simplify(realm, value.args[2]);
|
||||
if (!c.mightNotBeTrue()) return x;
|
||||
if (!c.mightNotBeFalse()) return y;
|
||||
invariant(c instanceof AbstractValue);
|
||||
if (pathImplies(c)) return x;
|
||||
let notc = AbstractValue.createFromUnaryOp(realm, "!", c);
|
||||
if (pathImplies(notc)) return y;
|
||||
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "===", value, x))) return x;
|
||||
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "!==", value, x))) return y;
|
||||
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "!==", value, y))) return x;
|
||||
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "===", value, y))) return y;
|
||||
return AbstractValue.createFromConditionalOp(realm, c, x, y, value.expressionLocation);
|
||||
}
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function simplifyEquality(realm: Realm, equality: AbstractValue): Value {
|
||||
let loc = equality.expressionLocation;
|
||||
let op = equality.kind;
|
||||
let [x, y] = equality.args;
|
||||
if (x instanceof ConcreteValue) [x, y] = [y, x];
|
||||
@ -66,38 +112,38 @@ function simplifyEquality(realm: Realm, equality: AbstractValue): Value {
|
||||
if (op === "===" || op === "!==") {
|
||||
// if xx === undefined && xy !== undefined then cond <=> x === undefined
|
||||
if (!y.mightNotBeUndefined() && !xx.mightNotBeUndefined() && !xy.mightBeUndefined())
|
||||
return op === "===" ? makeBoolean(realm, cond) : negate(realm, cond);
|
||||
return op === "===" ? makeBoolean(realm, cond, loc) : negate(realm, cond, loc);
|
||||
// if xx !== undefined && xy === undefined then !cond <=> x === undefined
|
||||
if (!y.mightNotBeUndefined() && !xx.mightBeUndefined() && !xy.mightNotBeUndefined())
|
||||
return op === "===" ? negate(realm, cond) : makeBoolean(realm, cond);
|
||||
return op === "===" ? negate(realm, cond, loc) : makeBoolean(realm, cond, loc);
|
||||
// if xx === null && xy !== null then cond <=> x === null
|
||||
if (!y.mightNotBeNull() && !xx.mightNotBeNull() && !xy.mightBeNull())
|
||||
return op === "===" ? makeBoolean(realm, cond) : negate(realm, cond);
|
||||
return op === "===" ? makeBoolean(realm, cond, loc) : negate(realm, cond, loc);
|
||||
// if xx !== null && xy === null then !cond <=> x === null
|
||||
if (!y.mightNotBeNull() && !xx.mightBeNull() && !xy.mightNotBeNull())
|
||||
return op === "===" ? negate(realm, cond) : makeBoolean(realm, cond);
|
||||
return op === "===" ? negate(realm, cond, loc) : makeBoolean(realm, cond, loc);
|
||||
} else {
|
||||
invariant(op === "==" || op === "!=");
|
||||
// if xx cannot be undefined/null and xy is undefined/null then !cond <=> x == undefined/null
|
||||
if (!xx.mightBeUndefined() && !xx.mightBeNull() && (!xy.mightNotBeUndefined() || !xy.mightNotBeNull()))
|
||||
return op === "==" ? negate(realm, cond) : makeBoolean(realm, cond);
|
||||
return op === "==" ? negate(realm, cond, loc) : makeBoolean(realm, cond, loc);
|
||||
// if xx is undefined/null and xy cannot be undefined/null then cond <=> x == undefined/null
|
||||
if ((!xx.mightNotBeUndefined() || !xx.mightNotBeNull()) && !xy.mightBeUndefined() && !xy.mightBeNull())
|
||||
return op === "==" ? makeBoolean(realm, cond) : negate(realm, cond);
|
||||
return op === "==" ? makeBoolean(realm, cond, loc) : negate(realm, cond, loc);
|
||||
}
|
||||
}
|
||||
return equality;
|
||||
}
|
||||
|
||||
function makeBoolean(realm: Realm, value: Value): Value {
|
||||
function makeBoolean(realm: Realm, value: Value, loc: ?BabelNodeSourceLocation = undefined): Value {
|
||||
if (value.getType() === BooleanValue) return value;
|
||||
if (value instanceof ConcreteValue) return new BooleanValue(realm, ToBoolean(realm, value));
|
||||
invariant(value instanceof AbstractValue);
|
||||
let v = AbstractValue.createFromUnaryOp(realm, "!", value, true, value.expressionLocation);
|
||||
return AbstractValue.createFromUnaryOp(realm, "!", v, true, value.expressionLocation);
|
||||
return AbstractValue.createFromUnaryOp(realm, "!", v, true, loc || value.expressionLocation);
|
||||
}
|
||||
|
||||
function negate(realm: Realm, value: Value): Value {
|
||||
function negate(realm: Realm, value: Value, loc: ?BabelNodeSourceLocation = undefined): Value {
|
||||
if (value instanceof ConcreteValue) return ValuesDomain.computeUnary(realm, "!", value);
|
||||
invariant(value instanceof AbstractValue);
|
||||
if (value.kind === "!") return makeBoolean(realm, value.args[0]);
|
||||
@ -140,7 +186,7 @@ function negate(realm: Realm, value: Value): Value {
|
||||
if (invertedComparison !== undefined) {
|
||||
let left = simplify(realm, value.args[0]);
|
||||
let right = simplify(realm, value.args[1]);
|
||||
return AbstractValue.createFromBinaryOp(realm, invertedComparison, left, right, value.expressionLocation);
|
||||
return AbstractValue.createFromBinaryOp(realm, invertedComparison, left, right, loc || value.expressionLocation);
|
||||
}
|
||||
let invertedLogicalOp;
|
||||
switch (value.kind) {
|
||||
@ -156,8 +202,8 @@ function negate(realm: Realm, value: Value): Value {
|
||||
if (invertedLogicalOp !== undefined) {
|
||||
let left = negate(realm, value.args[0]);
|
||||
let right = negate(realm, value.args[1]);
|
||||
return AbstractValue.createFromLogicalOp(realm, invertedLogicalOp, left, right, value.expressionLocation);
|
||||
return AbstractValue.createFromLogicalOp(realm, invertedLogicalOp, left, right, loc || value.expressionLocation);
|
||||
}
|
||||
}
|
||||
return AbstractValue.createFromUnaryOp(realm, "!", value, true, value.expressionLocation);
|
||||
return AbstractValue.createFromUnaryOp(realm, "!", value, true, loc || value.expressionLocation);
|
||||
}
|
||||
|
@ -294,83 +294,6 @@ export default class AbstractValue extends Value {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Simplify an already constructed abstract value in the light of the path conditions that apply in the
|
||||
// context where is this value is now being used.
|
||||
// TODO #1019: this logic largely duplicates the functionality in simplifyAbstractValue.
|
||||
// To fix this, two thing have to happen:
|
||||
// 1. Avoid injecting simplifier.js into the big Flow dependency cycle that includes AbstractValue. This can probably
|
||||
// be done by storing simplifyAbstractValue in the realm.
|
||||
// 2. Make simplifyAbstractValue path sensitive by checking for each condition if the current path conditions
|
||||
// imply them and then simplifying appropriately.
|
||||
refineWithPathCondition(): Value {
|
||||
function pathImplies(condition: AbstractValue): boolean {
|
||||
let path = condition.$Realm.pathConditions;
|
||||
for (let i = path.length - 1; i >= 0; i--) {
|
||||
let pathCondition = path[i];
|
||||
if (pathCondition.implies(condition)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
let realm = this.$Realm;
|
||||
if (realm.pathConditions.length === 0) return this;
|
||||
let op = this.kind;
|
||||
let result = (() => {
|
||||
if (op === "&&" || op === "||") {
|
||||
let [left, right] = this.args;
|
||||
invariant(left instanceof AbstractValue); // otherwise the factory would have simplified it.
|
||||
let refinedLeft = left.refineWithPathCondition();
|
||||
let refinedRight = right instanceof AbstractValue ? right.refineWithPathCondition() : right;
|
||||
// todo: remove this check when there is an alternative way to indicate that an intrinsic object is nullable
|
||||
if (!(refinedLeft instanceof AbstractObjectValue && refinedLeft.isIntrinsic())) {
|
||||
// true && y <=> y
|
||||
// true || y <=> true
|
||||
if (!refinedLeft.mightNotBeTrue()) return op === "&&" ? refinedRight : refinedLeft;
|
||||
// (x == false) && y <=> x
|
||||
// false || y <=> y
|
||||
if (!refinedLeft.mightNotBeFalse()) return op === "||" ? refinedRight : refinedLeft;
|
||||
}
|
||||
if (refinedLeft.getType() === BooleanValue && refinedRight.getType() === BooleanValue) {
|
||||
// (x: boolean) && true <=> x
|
||||
// x || true <=> true
|
||||
if (!refinedRight.mightNotBeTrue()) return op === "&&" ? refinedLeft : realm.intrinsics.true;
|
||||
// (x: boolean) && false <=> false
|
||||
// (x: boolean) || false <=> x
|
||||
if (!refinedRight.mightNotBeFalse()) return op === "||" ? refinedLeft : realm.intrinsics.false;
|
||||
}
|
||||
// return this if no refinements happened
|
||||
if (left === refinedLeft && right === refinedRight) return this;
|
||||
// recreate operation using refined operands
|
||||
return AbstractValue.createFromLogicalOp(realm, op, refinedLeft, refinedRight, this.expressionLocation);
|
||||
}
|
||||
if (op === "!") {
|
||||
let arg = this.args[0];
|
||||
invariant(arg instanceof AbstractValue); // otherwise this abstract value should have been folded to a constant
|
||||
let refinedArg = arg.refineWithPathCondition();
|
||||
if (arg === refinedArg) return this;
|
||||
if (!refinedArg.mightNotBeTrue()) return realm.intrinsics.false;
|
||||
if (!refinedArg.mightNotBeFalse()) return realm.intrinsics.true;
|
||||
invariant(refinedArg instanceof AbstractValue); // concrete values always make up their mind above
|
||||
return AbstractValue.createFromUnaryOp(realm, op, refinedArg);
|
||||
}
|
||||
if (op !== "conditional") return this;
|
||||
let [condition, trueVal, falseVal] = this.args;
|
||||
invariant(trueVal !== undefined);
|
||||
invariant(falseVal !== undefined);
|
||||
invariant(condition instanceof AbstractValue);
|
||||
let inverseCondition = AbstractValue.createFromUnaryOp(this.$Realm, "!", condition);
|
||||
if (pathImplies(condition)) return trueVal;
|
||||
if (pathImplies(inverseCondition)) return falseVal;
|
||||
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "===", this, trueVal))) return trueVal;
|
||||
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "!==", this, trueVal))) return falseVal;
|
||||
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "!==", this, falseVal))) return trueVal;
|
||||
if (pathImplies(AbstractValue.createFromBinaryOp(realm, "===", this, falseVal))) return falseVal;
|
||||
return this;
|
||||
})();
|
||||
if (result !== this && result instanceof AbstractValue) return result.refineWithPathCondition();
|
||||
return result;
|
||||
}
|
||||
|
||||
throwIfNotConcrete(): ConcreteValue {
|
||||
AbstractValue.reportIntrospectionError(this);
|
||||
throw new FatalError();
|
||||
@ -539,6 +462,8 @@ export default class AbstractValue extends Value {
|
||||
kind: "conditional",
|
||||
});
|
||||
result.expressionLocation = loc;
|
||||
if (left) result.mightBeEmpty = left.mightHaveBeenDeleted();
|
||||
if (right && !result.mightBeEmpty) result.mightBeEmpty = right.mightHaveBeenDeleted();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user