diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index 7ee37d6d413..0245c2e333c 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -18,6 +18,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\IntersectionType; use PHPStan\Type\IsSuperTypeOfResult; +use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\RecursionGuard; use PHPStan\Type\Type; @@ -161,6 +162,10 @@ private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): IsSupe return $nakedSuperTypeOf->and(IsSuperTypeOfResult::createMaybe()); } + if (!$acceptsContext && $this->ancestorHasAllUnresolvedCovariantArgs($type, $ancestor)) { + return $nakedSuperTypeOf->and(IsSuperTypeOfResult::createMaybe()); + } + if (count($this->types) !== count($ancestor->types)) { return IsSuperTypeOfResult::createNo(); } @@ -209,6 +214,48 @@ private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): IsSupe return $result; } + private function ancestorHasAllUnresolvedCovariantArgs(ObjectType $type, self $ancestor): bool + { + $typeClassReflection = $type->getClassReflection(); + if ($typeClassReflection !== null && $typeClassReflection->isGeneric()) { + return false; + } + + $classReflection = $this->getClassReflection(); + if ($classReflection === null) { + return false; + } + + $typeList = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()); + $hasCovariantWithMixed = false; + + foreach ($typeList as $i => $templateType) { + if (!isset($ancestor->types[$i]) || !isset($this->types[$i])) { + continue; + } + if (!$templateType instanceof TemplateType) { + continue; + } + + $thisVariance = $this->variances[$i] ?? TemplateTypeVariance::createInvariant(); + $effectiveVariance = $thisVariance->invariant() ? $templateType->getVariance() : $thisVariance; + + if (!$effectiveVariance->covariant()) { + continue; + } + + if (!$ancestor->types[$i] instanceof MixedType || $ancestor->types[$i] instanceof TemplateType) { + return false; + } + + if (!$this->types[$i] instanceof MixedType || $this->types[$i] instanceof TemplateType) { + $hasCovariantWithMixed = true; + } + } + + return $hasCovariantWithMixed; + } + public function getClassReflection(): ?ClassReflection { if ($this->classReflection !== null) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-10008.php b/tests/PHPStan/Analyser/nsrt/bug-10008.php new file mode 100644 index 00000000000..8400be0ddc7 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10008.php @@ -0,0 +1,36 @@ +}> $t + */ + public function sayHello($r, $t): void + { + $x = $r; + if (rand(0,1)) { + $x = $t; + } + assertType('mysqli_result|Traversable}>', $x); + } + + /** + * @param \Iterator $a + * @param \Traversable $b + */ + public function testDifferentValueTypes($a, $b): void + { + $x = $a; + if (rand(0,1)) { + $x = $b; + } + assertType('Iterator|Traversable', $x); + } +}