public function equals(LatticeElementInterface $that)
 {
     if (!$that instanceof LinkedFlowScope) {
         return false;
     }
     if ($this->optimize() === $that->optimize()) {
         return true;
     }
     // If two flow scopes are in the same function, then they could have two possible function scopes:
     //   1) the real function scope, or
     //   2) the BOTTOM scope.
     // If they have different function scopes, we theoretically should iterate through all the variable in each
     // scope and compare. However, 99.9% of the time, they're not equal. The other 0.1% of the time, we can just
     // assume that they are equal. Eventually, it just means that data flow analysis has to propagate the entry
     // lattice a bit further than it really needs to, but the end result is not affected.
     if ($this->getFunctionScope() !== $that->getFunctionScope()) {
         return false;
     }
     if ($this->cache === $that->cache) {
         // If the two flow scopes have the same cache, then we can check equality a lot faster: by just looking at
         // the "dirty" elements in the cache, and comparing them in both scopes.
         foreach ($this->cache->dirtySymbols as $name) {
             if ($this->diffSlots($this->getSlot($name), $that->getSlot($name))) {
                 return false;
             }
         }
         return true;
     }
     $myFlowSlots = $this->allFlowSlots();
     $otherFlowSlots = $that->allFlowSlots();
     foreach ($myFlowSlots as $name => $slot) {
         if ($this->diffSlots($slot, isset($otherFlowSlots[$name]) ? $otherFlowSlots[$name] : null)) {
             return false;
         }
         unset($otherFlowSlots[$name]);
     }
     foreach ($otherFlowSlots as $name => $slot) {
         if ($this->diffSlots($slot, isset($myFlowSlots[$name]) ? $myFlowSlots[$name] : null)) {
             return false;
         }
     }
     return true;
 }
 /**
  * Calculates the out lattices for the different branches.
  *
  * Right now, we just treat ON_EX edges like UNCOND edges. If we want to be perfect, we would have to actually join
  * all the out lattices of this flow with the in lattice, and then make that the out lattice for the ON_EX edge.
  * However, that would add some extra computation for an edge case. So, the current behavior looks like a "good enough"
  * approximation.
  *
  * @param \PHPParser_Node $source
  * @param LatticeElementInterface $input
  *
  * @return array<LatticeElementInterface>
  */
 protected function branchedFlowThrough($source, LatticeElementInterface $input)
 {
     assert($source instanceof \PHPParser_Node);
     assert($input instanceof LinkedFlowScope);
     // If we have not walked a path from the entry node to this node, we cannot infer anything
     // about this scope. So, just skip it for now, we will come back later.
     if ($input === $this->bottomScope) {
         $output = $input;
     } else {
         $output = $input->createChildFlowScope();
         $output = $this->traverse($source, $output);
     }
     $condition = $conditionFlowScope = $conditionOutcomes = null;
     $result = array();
     foreach ($this->cfg->getOutEdges($source) as $branchEdge) {
         $branch = $branchEdge->getType();
         $newScope = $output;
         switch ($branch) {
             case GraphEdge::TYPE_ON_TRUE:
                 if ($source instanceof \PHPParser_Node_Stmt_Foreach) {
                     $informed = $this->traverse($source->expr, $output->createChildFlowScope());
                     $exprType = $this->getType($source->expr);
                     if (null !== $source->keyVar && $source->keyVar instanceof \PHPParser_Node_Expr_Variable) {
                         $keyType = null;
                         if ($exprType->isArrayType()) {
                             $keyType = $exprType->toMaybeArrayType()->getKeyType();
                         }
                         // If we do not have the precise key type available, we can always
                         // assume it to be either a string, or an integer.
                         $this->redeclareSimpleVar($informed, $source->keyVar, $keyType ?: $this->typeRegistry->getNativeType('generic_array_key'));
                     }
                     $valueType = $this->typeRegistry->getNativeType('unknown');
                     if ($exprType->isArrayType()) {
                         $valueType = $exprType->toMaybeArrayType()->getElementType();
                     } else {
                         if ($exprType->toMaybeObjectType()) {
                             $valueType = $exprType->toMaybeObjectType()->getTraversableElementType();
                         }
                     }
                     $source->valueVar->setAttribute('type', $valueType);
                     $this->redeclareSimpleVar($informed, $source->valueVar, $valueType);
                     $newScope = $informed;
                     break;
                 }
                 // FALL THROUGH
             // FALL THROUGH
             case GraphEdge::TYPE_ON_FALSE:
                 if (null === $condition) {
                     $condition = NodeUtil::getConditionExpression($source);
                     if (null === $condition && $source instanceof \PHPParser_Node_Stmt_Case && null !== $source->cond) {
                         $condition = $source;
                         // conditionFlowScope is cached from previous iterations of the loop
                         if (null === $conditionFlowScope) {
                             $conditionFlowScope = $this->traverse($source->cond, $output->createChildFlowScope());
                         }
                     }
                 }
                 if (null !== $condition) {
                     if ($condition instanceof \PHPParser_Node_Expr_BooleanAnd || $condition instanceof \PHPParser_Node_Expr_LogicalAnd || $condition instanceof \PHPParser_Node_Expr_BooleanOr || $condition instanceof \PHPParser_Node_Expr_LogicalOr) {
                         // When handling the short-circuiting binary operators, the outcome scope on true can be
                         // different than the outcome scope on false.
                         //
                         // TODO:
                         // The "right" way to do this is to carry the known outcome all the way through the
                         // recursive traversal, so that we can construct a different flow scope based on the outcome.
                         // However, this would require a bunch of code and a bunch of extra computation for an edge
                         // case. This seems to be a "good enough" approximation.
                         // conditionOutcomes is cached from previous iterations of the loop.
                         if (null === $conditionOutcomes) {
                             $conditionOutcomes = $condition instanceof \PHPParser_Node_Expr_BooleanAnd || $condition instanceof \PHPParser_Node_Expr_LogicalAnd ? $this->traverseAnd($condition, $output->createChildFlowScope()) : $this->traverseOr($condition, $output->createChildFlowScope());
                         }
                         $newScope = $this->reverseInterpreter->getPreciserScopeKnowingConditionOutcome($condition, $conditionOutcomes->getOutcomeFlowScope($condition, $branch === GraphEdge::TYPE_ON_TRUE), $branch === GraphEdge::TYPE_ON_TRUE);
                     } else {
                         // conditionFlowScope is cached from previous iterations of the loop.
                         if (null === $conditionFlowScope) {
                             // In case of a FOR loop, $condition might be an array of expressions. PHP will execute
                             // all expressions, but only the last one is used to determine the expression outcome.
                             if (is_array($condition)) {
                                 $conditionFlowScope = $output->createChildFlowScope();
                                 foreach ($condition as $cond) {
                                     $conditionFlowScope = $this->traverse($cond, $conditionFlowScope);
                                 }
                             } else {
                                 $conditionFlowScope = $this->traverse($condition, $output->createChildFlowScope());
                             }
                         }
                         // See above comment regarding the handling of FOR loops.
                         $relevantCondition = $condition;
                         if (is_array($condition)) {
                             $relevantCondition = end($condition);
                         }
                         $newScope = $this->reverseInterpreter->getPreciserScopeKnowingConditionOutcome($relevantCondition, $conditionFlowScope, $branch === GraphEdge::TYPE_ON_TRUE);
                     }
                 }
                 break;
         }
         if (null === $newScope) {
             throw new \LogicException('$newScope must not be null for source ' . get_class($source) . ' and branch ' . GraphEdge::getLiteral($branch) . '.');
         }
         $result[] = $newScope->optimize();
     }
     return $result;
 }
 /**
  * @dataProvider getEqualsTests
  */
 public function testEquals(LatticeElementInterface $a, LatticeElementInterface $b, $expectedOutcome)
 {
     $this->assertSame($expectedOutcome, $a->equals($b));
 }