/** * On the first run of the type inference engine, arrays always have * the generic array type at this point. This is assigned by the * {@see AbstractScopeBuilder::attachLiteralTypes}. * * We try to make this more specific by introspecting the values of * the items which are assigned. * * @param \PHPParser_Node_Expr_Array $node * @param LinkedFlowScope $scope * * @return LinkedFlowScope */ private function traverseArray(\PHPParser_Node_Expr_Array $node, LinkedFlowScope $scope) { $keyTypes = $elementTypes = $itemTypes = array(); $lastNumericKey = -1; $containsDynamicKey = false; foreach ($node->items as $item) { assert($item instanceof \PHPParser_Node_Expr_ArrayItem); $scope = $this->traverse($item, $scope); if (null === $item->key) { $keyTypes[] = 'integer'; } else { if (null !== ($type = $item->key->getAttribute('type'))) { $keyTypes[] = $type; } else { // If the key type is not available, then we will just assume both // possible types. It's not that bad after all. $keyTypes[] = 'string'; $keyTypes[] = 'integer'; } } $itemName = null; $keyValue = NodeUtil::getValue($item->key); if (null === $item->key && !$containsDynamicKey) { $itemName = ++$lastNumericKey; } else { if ($keyValue->isDefined()) { // We cast everything to a string (even integers), and then process // both cases: 1) numeric key, and 2) anything else $keyValue = (string) $keyValue->get(); if (ctype_digit($keyValue)) { // Case 1) $itemName = (int) $keyValue; // PHP will always use the next highest number starting from the // greatest numeric value that the array contains when no explicit // key has been defined; so, we need to keep track of this. if ($itemName > $lastNumericKey) { $lastNumericKey = $itemName; } } else { // Case 2) $itemName = $keyValue; } } else { $containsDynamicKey = true; } } if (null !== ($type = $item->value->getAttribute('type'))) { if (null !== $itemName) { $itemTypes[$itemName] = $type; } $elementTypes[] = $type; } } $node->setAttribute('type', $this->typeRegistry->getArrayType($elementTypes ? $this->typeRegistry->createUnionType($elementTypes) : null, $keyTypes ? $this->typeRegistry->createUnionType($keyTypes) : null, $itemTypes)); return $scope; }
/** * @group non-refinable */ public function testArrayExprNonRefinable() { $n = new \PHPParser_Node_Expr_Array(); $n->setAttribute('type', $this->registry->getNativeType('array')); $this->doTestTypeFunction($this->newScope(), 'is_callable', $n, true, array(), array()); }