case GREATER_THAN:
if (fact.isTruthy()) {
// If (a < b) the a != b, and !(a > b).
Expression left = operands.get(0);
Expression right = operands.get(1);
Operator included = op.getOperator() == Operator.LESS_THAN
? Operator.LESS_EQUALS : Operator.GREATER_EQUALS;
addFactInt(Operation.create(UNK, included, left, right), Fact.TRUE);
addFactInt(
Operation.create(UNK, Operator.STRICTLY_NOT_EQUAL, left, right),
Fact.TRUE);
// Incomparable values like NaN means we can conclude nothing if
// !(a < b).
}
break;
case LESS_EQUALS:
case GREATER_EQUALS:
if (fact.isFalsey()) {
// if !(a <= b) then a !== b.
Expression left = operands.get(0);
Expression right = operands.get(1);
addFactInt(
Operation.create(UNK, Operator.STRICTLY_NOT_EQUAL, left, right),
Fact.TRUE);
}
break;
case INSTANCE_OF:
// if (x instanceof y) does not throw, then y must be a function.
// if it's true, then x must be an object.
// Note: primitives are not instances of their wrapper class.
addFactInt(
Operation.create(UNK, Operator.TYPEOF, operands.get(1)),
Fact.is(StringLiteral.valueOf(UNK, "function")));
if (fact.isTruthy()) {
addFactInt(operands.get(0), Fact.TRUTHY);
}
break;
case EQUAL:
addFactInt(
Operation.create(
UNK, Operator.NOT_EQUAL, operands.get(0), operands.get(1)),
fact.isTruthy() ? Fact.FALSE : Fact.TRUE);
break;
case NOT_EQUAL:
addFactInt(
Operation.create(
UNK, Operator.EQUAL, operands.get(0), operands.get(1)),
fact.isTruthy() ? Fact.FALSE : Fact.TRUE);
break;
case STRICTLY_EQUAL:
if (fact.isTruthy()) {
Expression lhs = operands.get(0);
Expression rhs = operands.get(1);
addFactInt(Operation.create(UNK, Operator.EQUAL, lhs, rhs),
Fact.TRUE);
if (rhs instanceof Literal) {
// TODO(mikesamuel): what do we do about the fact that (0 === -0)?
// Instead of inferring that the value IS 0, we could infer that
// it's falsey, and the typeof is number.
addFactInt(lhs, Fact.is((Literal) rhs));
// (this.global === this) -> global aliases the global object.
} else if (isThis(rhs) && lhs instanceof Reference) {
addFactInt(lhs, Fact.GLOBAL);
} else {
String typeOf = rhs.typeOf();
if (typeOf != null && lhs.typeOf() == null) {
addFactInt(
Operation.create(UNK, Operator.TYPEOF, lhs),
Fact.is(StringLiteral.valueOf(UNK, typeOf)));
}
// TODO(mikesamuel): Is this useful? When, in a comparison,
// do we know that something is truthy or falsey, but not what
// literal value it is. The expressions:
// x === function () {}
// y === [1,2,3]
// z === {}
// are never true, so control wouldn't reach here.
Boolean truthiness = rhs.conditionResult();
if (truthiness != null) {
addFuzzyFact(lhs, truthiness);
}
}
} else {
Expression lhs = operands.get(0);
Expression rhs = operands.get(1);
if (ParseTreeNodes.deepEquals(lhs, rhs)) {
addFactInt(lhs, Fact.is(new RealLiteral(UNK, Double.NaN)));
}
}
addFactInt(
Operation.create(
UNK, Operator.STRICTLY_NOT_EQUAL,
operands.get(0), operands.get(1)),
fact.isTruthy() ? Fact.FALSE : Fact.TRUE);
break;
case STRICTLY_NOT_EQUAL:
addFactInt(Operation.create(
UNK, Operator.STRICTLY_EQUAL, operands.get(0), operands.get(1)),
fact.isTruthy() ? Fact.FALSE : Fact.TRUE);
break;
case LOGICAL_AND:
case LOGICAL_OR:
boolean isAnd = op.getOperator() == Operator.LOGICAL_AND;
if (fact.isTruthy() == isAnd) {
addFuzzyFact(operands.get(0), isAnd);
addFactInt(operands.get(1), fact); // Second value is result
}
break;
case MEMBER_ACCESS:
case SQUARE_BRACKET:
// If foo.bar is truthy, then so is foo.
if (fact.isTruthy()) {
addFuzzyFact(operands.get(0), true);
}
break;
case TYPEOF:
if (fact.type == Fact.Type.IS
&& fact.value instanceof StringLiteral) {
String s = ((StringLiteral) fact.value).getUnquotedValue();
Expression op0 = operands.get(0);
if ("undefined".equals(s)) {
addFactInt(op0, Fact.UNDEFINED);
} else {
if ("function".equals(s)) { addFactInt(op0, Fact.TRUTHY); }
// undefined is a commonly tested value, so infer its absence
// for other types.
addFactInt(
Operation.create(
UNK, Operator.STRICTLY_EQUAL, op0, Fact.UNDEFINED.value),
Fact.FALSE);
}
}
break;
default:
break;
}
// (a < b) -> (b > a) since we know (a) and (b) are pure
if (operands.size() == 2) {
Operator swapped = WITH_REVERSE_ORDER.get(op.getOperator());
if (swapped != null) {
addFactInt(
Operation.create(
op.getFilePosition(), swapped,
operands.get(1), operands.get(0)),