|
48 | 48 | use PHPStan\Type\Generic\TemplateTypeVariance; |
49 | 49 | use PHPStan\Type\Generic\TemplateTypeVarianceMap; |
50 | 50 | use PHPStan\Type\Generic\TypeProjectionHelper; |
| 51 | +use PHPStan\Type\MixedType; |
51 | 52 | use PHPStan\Type\ObjectType; |
52 | 53 | use PHPStan\Type\Type; |
53 | 54 | use PHPStan\Type\TypeAlias; |
@@ -237,7 +238,7 @@ public function getParentClass(): ?ClassReflection |
237 | 238 | if ($this->isGeneric()) { |
238 | 239 | $extendedType = TemplateTypeHelper::resolveTemplateTypes( |
239 | 240 | $extendedType, |
240 | | - $this->getPossiblyIncompleteActiveTemplateTypeMap(), |
| 241 | + $this->getActiveTemplateTypeMapForAncestorResolution(), |
241 | 242 | $this->getCallSiteVarianceMap(), |
242 | 243 | TemplateTypeVariance::createStatic(), |
243 | 244 | ); |
@@ -1164,7 +1165,7 @@ public function getImmediateInterfaces(): array |
1164 | 1165 | if ($this->isGeneric()) { |
1165 | 1166 | $implementedType = TemplateTypeHelper::resolveTemplateTypes( |
1166 | 1167 | $implementedType, |
1167 | | - $this->getPossiblyIncompleteActiveTemplateTypeMap(), |
| 1168 | + $this->getActiveTemplateTypeMapForAncestorResolution(), |
1168 | 1169 | $this->getCallSiteVarianceMap(), |
1169 | 1170 | TemplateTypeVariance::createStatic(), |
1170 | 1171 | true, |
@@ -1686,6 +1687,37 @@ public function getPossiblyIncompleteActiveTemplateTypeMap(): TemplateTypeMap |
1686 | 1687 | return $this->resolvedTemplateTypeMap ?? $this->getTemplateTypeMap(); |
1687 | 1688 | } |
1688 | 1689 |
|
| 1690 | + /** |
| 1691 | + * Returns a template type map for resolving ancestor type declarations (@extends, @implements). |
| 1692 | + * Like getPossiblyIncompleteActiveTemplateTypeMap(), but resolves ErrorType entries |
| 1693 | + * to their template bounds when the bound is not mixed. This ensures that when a child |
| 1694 | + * class narrows a template bound (e.g. `@template T of SpecificType`), the narrowed bound |
| 1695 | + * is propagated to ancestor declarations instead of being lost as ErrorType. |
| 1696 | + */ |
| 1697 | + private function getActiveTemplateTypeMapForAncestorResolution(): TemplateTypeMap |
| 1698 | + { |
| 1699 | + $map = $this->getPossiblyIncompleteActiveTemplateTypeMap(); |
| 1700 | + $templateTypeMap = $this->getTemplateTypeMap(); |
| 1701 | + |
| 1702 | + return $map->map(static function (string $name, Type $type) use ($templateTypeMap): Type { |
| 1703 | + if (!$type instanceof ErrorType) { |
| 1704 | + return $type; |
| 1705 | + } |
| 1706 | + |
| 1707 | + $templateType = $templateTypeMap->getType($name); |
| 1708 | + if (!$templateType instanceof TemplateType) { |
| 1709 | + return $type; |
| 1710 | + } |
| 1711 | + |
| 1712 | + $bound = $templateType->getBound(); |
| 1713 | + if ($bound instanceof MixedType) { |
| 1714 | + return $type; |
| 1715 | + } |
| 1716 | + |
| 1717 | + return TemplateTypeHelper::resolveToDefaults($templateType); |
| 1718 | + }); |
| 1719 | + } |
| 1720 | + |
1689 | 1721 | private function getDefaultCallSiteVarianceMap(): TemplateTypeVarianceMap |
1690 | 1722 | { |
1691 | 1723 | if ($this->defaultCallSiteVarianceMap !== null) { |
|
0 commit comments