/**
  * 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;
     }
 }
 /**
  * @param string $type
  *
  * @return null|PhpType
  */
 private function tryGettingMoreSpecificType(PhpType $docType = null, PhpType $actualType, AbstractFunction $function, array $importedNamespaces, MethodContainer $container = null, $type)
 {
     if (!$docType) {
         if (!$actualType->isUnknownType() && !$actualType->isAllType()) {
             return null;
         }
         return $this->{'infer' . $type . 'TypeForFunction'}($function, $container);
     }
     // If the type defined by the comment is an object (and not the NoObjectType),
     // and a super type of the actual type, we keep it.
     if (null !== $docType->toMaybeObjectType() && $actualType->isSubtypeOf($docType)) {
         return $docType;
     }
     // If the type defined by the comment is a nullable object type (excluding the
     // NoObject type), we keep the comment that is currently in place.
     if ($this->isNullableObjectType($docType) && $actualType->isSubtypeOf($docType)) {
         return $docType;
     }
     if ($docType->getDocType($importedNamespaces) !== $actualType->getDocType($importedNamespaces)) {
         return $actualType;
     }
     if ($actualType === $this->registry->getNativeType('array')) {
         $inferredType = $this->{'infer' . $type . 'TypeForFunction'}($function, $container);
         if ($inferredType && $inferredType->isArrayType()) {
             return $inferredType;
         }
     }
     return null;
 }
 private function isMoreSpecificType(PhpType $actualType, PhpType $annotatedType)
 {
     if ($actualType->equals($annotatedType)) {
         return false;
     }
     if ($actualType->isUnknownType()) {
         return false;
     }
     return $actualType->isSubtypeOf($annotatedType);
 }