/**
  * Returns the array type contained in the given type, or null if no array
  * type is available.
  *
  * @param PhpType|null $type
  *
  * @return ArrayType|null
  */
 private function getContainedArrayType(PhpType $type = null)
 {
     if (null === $type) {
         return null;
     }
     if ($type->isArrayType()) {
         return $type;
     }
     if (!$type->isUnionType()) {
         return null;
     }
     foreach ($type->getAlternates() as $alt) {
         if ($alt->isArrayType()) {
             return $alt;
         }
     }
     return null;
 }
 private function restrictByCallable(PhpType $type = null, $outcome)
 {
     if (null === $type) {
         return $outcome ? $this->typeRegistry->getNativeType('callable') : null;
     }
     if ($outcome) {
         if ($type->isUnknownType() || $type->isAllType()) {
             return $this->typeRegistry->getNativeType('callable');
         }
         if ($type->isUnionType()) {
             $types = array();
             foreach ($type->getAlternates() as $altType) {
                 if ($altType->canBeCalled()) {
                     $types[] = $altType;
                 }
             }
             return $this->typeRegistry->createUnionType($types);
         }
         return $type->canBeCalled() ? $type : $this->typeRegistry->getNativeType('none');
     }
     if ($type->isCallableType()) {
         return $this->typeRegistry->getNativeType('none');
     }
     if ($type->isUnionType()) {
         $types = array();
         foreach ($type->getAlternates() as $altType) {
             if ($altType->isCallableType()) {
                 continue;
             }
             $types[] = $altType;
         }
         return $this->typeRegistry->createUnionType($types);
     }
     return $type;
 }
 /**
  * Updates the context of returned ThisType types.
  *
  * ```php
  *     class A {
  *         public function returnsThis() {
  *             return $this; // this<A>
  *         }
  *     }
  *
  *     class B extends A {
  *         public function returnsSomething() {
  *             $rs = $this->returnsThis();
  *
  *             return $rs; // this<B>
  *         }
  *
  *         public function returnsSomethingElse()
  *         {
  *             $rs = parent::returnsThis();
  *
  *             return $rs; // this<B>
  *         }
  *     }
  *
  *     class C extends B { }
  *
  *     $c = new C();
  *     $c->returnsThis(); // object<C>
  *     $c->returnsSomething(); // object<C>
  * ```
  *
  * We have two basic cases:
  *
  *     1. The called node refers to the current scope ($this->, self::,
  *        parent::, static::, or SuperTypeName::).
  *     2. The node was called from the "outside" $foo->...().
  *
  * In the first case, we need to preserve the wrapping with ThisType with
  * an updated creation context. In the second case, we have to unwrap the
  * ThisType.
  *
  * @param \PHPParser_Node $calledNode
  * @param PhpType $thisType Type of the current context
  * @param PhpType $calledType Type of the $calledNode
  * @param PhpType $returnType
  *
  * @return PhpType
  */
 private function updateThisReference(\PHPParser_Node $calledNode, PhpType $thisType = null, PhpType $calledType, PhpType $returnType = null)
 {
     // Delays execution until necessary.
     $needsWrapping = function () use($calledNode, $thisType, $calledType) {
         if (null === $thisType) {
             return false;
         }
         switch (true) {
             case $calledNode instanceof \PHPParser_Node_Expr_Variable:
                 return 'this' === $calledNode->name;
             case $calledNode instanceof \PHPParser_Node_Name:
                 if (in_array(strtolower(implode("\\", $calledNode->parts)), array('self', 'static', 'parent'), true)) {
                     return true;
                 }
                 // This handles the following case:
                 //
                 //       class A { public function foo() { return $this; } }
                 //       class B extends A { public function bar() { return A::foo(); } }
                 //       $x = (new B())->bar();
                 //
                 // $x is resolved to object<B>.
                 if (null !== $thisType && $thisType->isSubtypeOf($calledType)) {
                     return true;
                 }
                 return false;
             default:
                 return false;
         }
     };
     switch (true) {
         case $returnType instanceof ThisType:
             return $needsWrapping() ? $this->typeRegistry->getThisType($thisType) : $calledType;
         case $returnType instanceof UnionType:
             $types = array();
             foreach ($returnType->getAlternates() as $alt) {
                 if ($alt instanceof ThisType) {
                     $types[] = $needsWrapping() ? $this->typeRegistry->getThisType($thisType) : $calledType;
                     continue;
                 }
                 $types[] = $alt;
             }
             return $this->typeRegistry->createUnionType($types);
         default:
             return $returnType;
     }
 }
 private function isNullableObjectType(PhpType $type)
 {
     if (!$type->isUnionType()) {
         return false;
     }
     foreach ($type->getAlternates() as $alt) {
         if (null === $alt->toMaybeObjectType() && !$alt->isNullType()) {
             return false;
         }
     }
     return true;
 }
 private function getStringRepr(PhpType $type)
 {
     switch (true) {
         case $type instanceof AllType:
             return TypeRegistry::NATIVE_ALL;
             // This handles the generic array type specially.
         // This handles the generic array type specially.
         case $type === self::$typeRegistry->getNativeType('array'):
             return 'array';
         case $type instanceof ArrayType:
             $itemTypes = $type->getItemTypes();
             if (empty($itemTypes)) {
                 return TypeRegistry::NATIVE_ARRAY . '<' . $this->getStringRepr($type->getKeyType()) . ',' . $this->getStringRepr($type->getElementType()) . '>';
             }
             return sprintf('array<%s,%s,%s>', $this->getStringRepr($type->getKeyType()), $this->getStringRepr($type->getElementType()), $this->dumpJsonLike($itemTypes, true));
         case $type instanceof FalseType:
             return TypeRegistry::NATIVE_BOOLEAN_FALSE;
         case $type instanceof BooleanType:
             return TypeRegistry::NATIVE_BOOLEAN;
         case $type instanceof CallableType:
             return TypeRegistry::NATIVE_CALLABLE;
         case $type instanceof ResourceType:
             return TypeRegistry::NATIVE_RESOURCE;
         case $type instanceof DoubleType:
             return TypeRegistry::NATIVE_DOUBLE;
         case $type instanceof IntegerType:
             return TypeRegistry::NATIVE_INTEGER;
         case $type instanceof ThisType:
             return 'this<' . $type->getReferenceName() . '>';
         case $type instanceof NamedType:
             // If this type has been resolved, we can get the representation
             // of the resolved type instead of using the reference name.
             if (!$type->isNoResolvedType()) {
                 return $this->getStringRepr($type->getReferencedType());
             }
             return 'object<' . $type->getReferenceName() . '>';
         case $type instanceof NoObjectType:
             return TypeRegistry::NATIVE_OBJECT;
         case $type instanceof NoType:
             return TypeRegistry::NATIVE_NONE;
         case $type instanceof NullType:
             return TypeRegistry::NATIVE_NULL;
         case $type instanceof ObjectType:
             return 'object<' . $type->getName() . '>';
         case $type instanceof StringType:
             return TypeRegistry::NATIVE_STRING;
         case $type instanceof UnionType:
             $alt = array();
             foreach ($type->getAlternates() as $t) {
                 $alt[] = $this->getStringRepr($t);
             }
             return implode('|', $alt);
         case $type instanceof UnknownType:
             return $type->isChecked() ? TypeRegistry::NATIVE_UNKNOWN_CHECKED : TypeRegistry::NATIVE_UNKNOWN;
     }
     throw new \InvalidArgumentException(sprintf('The SWITCH statement is exhaustive, but got "%s".', get_class($type)));
 }