/** * Refines an array type with the information about the used key, and assigned element type. * * This also defines the type for the item key if available and non-dynamic. * * @param PhpType $varType * @param PhpType $resultType * @param \PHPParser_Node_Expr_ArrayDimFetch $node * * @return ArrayType */ private function getRefinedArrayType(PhpType $varType, PhpType $resultType, \PHPParser_Node_Expr_ArrayDimFetch $node) { $usedKeyType = $this->getKeyTypeForDimFetch($node); $keyType = $varType->getKeyType(); if ($keyType === $this->typeRegistry->getNativeType('generic_array_key')) { $refinedKeyType = $usedKeyType; } else { $refinedKeyType = $keyType->getLeastSupertype($usedKeyType); } $elementType = $varType->getElementType(); if ($elementType === $this->typeRegistry->getNativeType('generic_array_value')) { $refinedElementType = $resultType; } else { $refinedElementType = $elementType->getLeastSupertype($resultType); } $refinedType = $this->typeRegistry->getArrayType($refinedElementType, $refinedKeyType); // Infer the type of the key if a concrete value is available. NodeUtil::getValue($node->dim)->map(function ($keyName) use(&$refinedType, $resultType) { $refinedType = $refinedType->inferItemType($keyName, $resultType); }); return $refinedType; }
private function parseTypeName() { $type = null; if ('\\' === $this->token[0][0]) { $type = $this->typeRegistry->getClassOrCreate(substr($this->token[0], 1)); } else { // First, we check whether the name is a class that we have already scanned $parts = explode("\\", $this->token[0]); if (isset($this->importedNamespaces[$parts[0]])) { $className = $this->importedNamespaces[$parts[0]]; if (count($parts) > 1) { $className .= '\\' . implode("\\", array_slice($parts, 1)); } $type = $this->typeRegistry->getClassOrCreate($className); } else { switch (strtolower($this->token[0])) { case '(': $type = $this->parseGroup(); break; case 'false': $type = $this->typeRegistry->getNativeType('false'); break; case 'bool': case 'boolean': $type = $this->typeRegistry->getNativeType('boolean'); break; case 'int': case 'integer': $type = $this->typeRegistry->getNativeType('integer'); break; case 'float': case 'double': $type = $this->typeRegistry->getNativeType('double'); break; case 'scalar': $type = $this->typeRegistry->getNativeType('scalar'); break; case 'string': $type = $this->typeRegistry->getNativeType('string'); break; case 'array': if ($this->isNextToken('<')) { $this->match('<'); $this->moveNext(); $firstType = $this->parse(); if ($this->isNextToken(',')) { $this->match(','); $this->moveNext(); $arrayType = $this->typeRegistry->getArrayType($this->parse(), $firstType); $this->match('>'); return $arrayType; } $this->match('>'); return $this->typeRegistry->getArrayType($firstType, $this->typeRegistry->getNativeType('integer')); } $type = $this->typeRegistry->getNativeType('array'); break; case 'resource': $type = $this->typeRegistry->getNativeType('resource'); break; case '*': case 'mixed': $type = $this->typeRegistry->getNativeType('all'); break; case 'number': $type = $this->typeRegistry->getNativeType('number'); break; case 'object': $type = $this->typeRegistry->getNativeType('object'); break; case 'void': case 'null': $type = $this->typeRegistry->getNativeType('null'); break; case 'callable': case 'callback': $type = $this->typeRegistry->getNativeType('callable'); break; default: if ($this->isThisReference($this->token[0])) { if (null === $this->currentClassName) { throw new \RuntimeException(sprintf('"%s" is only available from within classes.', $this->token[0])); } $type = $this->typeRegistry->getClassOrCreate($this->currentClassName); } else { if (preg_match('/^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*(?:\\\\[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)*$/', $this->token[0])) { $className = !empty($this->importedNamespaces['']) ? $this->importedNamespaces[''] . '\\' : ''; $className .= $this->token[0]; $type = $this->typeRegistry->getClassOrCreate($className); } else { throw new \RuntimeException(sprintf('Unknown type name "%s" at position %d.', $this->token[0], $this->token[1])); } } } } } if (!$type) { throw new \RuntimeException(sprintf('Internal error for token "%s" at position %d.', $this->token[0], $this->token[1])); } if ($this->isNextToken('[')) { $this->moveNext(); // In the php stubs, there often is an [optional] appended to types. // We just ignore this suffix. if ($this->isNextToken('optional')) { $this->moveNext(); $this->match(']'); } else { $this->match(']'); return $this->typeRegistry->getArrayType($type, $this->typeRegistry->getNativeType('integer')); } } return $type; }