/** * 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; }
/** * print an associative array */ public function pExpr_Array(\PHPParser_Node_Expr_Array $node) { $multiLine = FALSE; $startLine = $node->getAttribute('startLine'); $endLine = $node->getAttribute('endLine'); if ($startLine != $endLine) { $multiLine = TRUE; } $printedNodes = ''; foreach ($node->items as $itemNode) { $glueToken = ", "; if ($itemNode->getAttribute('startLine') != $startLine) { $glueToken = ',' . LF; $startLine = $itemNode->getAttribute('startLine'); } if (!empty($printedNodes)) { $printedNodes .= $glueToken . $this->p($itemNode); } else { $printedNodes .= $this->p($itemNode); } } if ($multiLine) { $multiLinedItems = $this->indentToken . preg_replace('~\\n(?!$|' . $this->noIndentToken . ')~', LF . $this->indentToken, $printedNodes); return 'array(' . LF . $multiLinedItems . LF . ')'; } else { return parent::pExpr_Array($node); } }
/** * @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()); }