/**
  * 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;
 }