/** * Helper Constructor. * * This is called when we join two flow scope chains. For example, when we combine the outcomes of the ON_TRUE * and the ON_FALSE branch of an IF clause. * * @param \Scrutinizer\PhpAnalyzer\DataFlow\TypeInference\LinkedFlowScope $joinedScopeA * @param \Scrutinizer\PhpAnalyzer\DataFlow\TypeInference\LinkedFlowScope $joinedScopeB * * @return FlatFlowScopeCache */ public static function createFromLinkedScopes(LinkedFlowScope $joinedScopeA, LinkedFlowScope $joinedScopeB) { $cache = new self(); // Always prefer the "real" scope to the faked-out bottom scope. $cache->functionScope = $joinedScopeA->flowsFromBottom() ? $joinedScopeB->getFunctionScope() : $joinedScopeA->getFunctionScope(); $slotsA = $cache->symbols = $joinedScopeA->allFlowSlots(); $slotsB = $joinedScopeB->allFlowSlots(); // There are 5 different join cases: // 1) The type is declared in joinedScopeA, not in joinedScopeB, // and not in functionScope. Just use the one in A. // 2) The type is declared in joinedScopeB, not in joinedScopeA, // and not in functionScope. Just use the one in B. // 3) The type is declared in functionScope and joinedScopeA, but // not in joinedScopeB. Join the two types. // 4) The type is declared in functionScope and joinedScopeB, but // not in joinedScopeA. Join the two types. // 5) The type is declared in joinedScopeA and joinedScopeB. Join // the two types. $symbolNames = array_unique(array_merge(array_keys($slotsA), array_keys($slotsB))); foreach ($symbolNames as $name) { $slotA = isset($slotsA[$name]) ? $slotsA[$name] : null; $slotB = isset($slotsB[$name]) ? $slotsB[$name] : null; $joinedType = null; if (null === $slotB || $slotB->getType() === null) { $fnSlot = $joinedScopeB->getFunctionScope()->getVar($name); $fnSlotType = null === $fnSlot ? null : $fnSlot->getType(); if (null === $fnSlotType) { // Case #1 -- The symbol was already inserted from A slots. } else { // Case #3 $joinedType = $slotA->getType()->getLeastSuperType($fnSlotType); } } else { if (null === $slotA || $slotA->getType() === null) { $fnSlot = $joinedScopeA->getFunctionScope()->getVar($name); $fnSlotType = null === $fnSlot ? null : $fnSlot->getType(); if (null === $fnSlotType) { // Case #2 $cache->symbols[$name] = $slotB; } else { // Case #4 $joinedType = $slotB->getType()->getLeastSuperType($fnSlotType); } } else { // Case #5 $joinedType = $slotA->getType()->getLeastSuperType($slotB->getType()); } } if (null !== $joinedType) { $cache->symbols[$name] = new SimpleSlot($name, $joinedType, true); } } return $cache; }