diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 838211838ef..90fd45df71e 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -586,6 +586,18 @@ public function testBug12457(): void ]); } + public function testGenericSubtype(): void + { + $this->checkTypeAgainstPhpDocType = true; + $this->strictWideningCheck = true; + $this->analyse([__DIR__ . '/data/generic-subtype.php'], [ + [ + 'PHPDoc tag @var with type GenericSubtype\IRepository is not subtype of type GenericSubtype\IRepository.', + 131, + ], + ]); + } + public function testNewIsAlwaysFinalClass(): void { $this->checkTypeAgainstPhpDocType = true; diff --git a/tests/PHPStan/Rules/PhpDoc/data/generic-subtype.php b/tests/PHPStan/Rules/PhpDoc/data/generic-subtype.php new file mode 100644 index 00000000000..adc29e95b15 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/generic-subtype.php @@ -0,0 +1,148 @@ += 8.0 + +namespace GenericSubtype; + +interface IEntity { + /** + * @return IRepository + */ + public function getRepository(): IRepository; +} + +interface IProperty {} + +interface IPropertyContainer extends IProperty {} + +/** + * @template E of IEntity + */ +interface IEntityAwareProperty extends IProperty {} + +/** + * @template E of IEntity + * @extends IEntityAwareProperty + */ +interface IRelationshipContainer extends IPropertyContainer, IEntityAwareProperty {} + +interface IModel { + /** + * @template E of IEntity + * @template T of IRepository + * @param class-string $className + * @return T + */ + public function getRepository(string $className): IRepository; +} + +/** + * @template E of IEntity + */ +interface IRepository { + public function getModel(): IModel; +} + +class PropertyRelationshipMetadata { + /** @var class-string> */ + public string $repository; +} + +/** + * @template E of IEntity + * @implements IRelationshipContainer + */ +class HasOne implements IRelationshipContainer +{ + /** @var E|null */ + protected ?IEntity $parent = null; + + /** @var IRepository|null */ + protected ?IRepository $targetRepository = null; + + protected PropertyRelationshipMetadata $metadataRelationship; + + /** + * @return E + */ + protected function getParentEntity(): IEntity + { + return $this->parent ?? throw new \InvalidArgumentException('Relationship is not attached to a parent entity.'); + } + + /** + * @return IRepository + */ + protected function getTargetRepository(): IRepository + { + if ($this->targetRepository === null) { + /** @var IRepository $targetRepository */ + $targetRepository = $this->getParentEntity() + ->getRepository() + ->getModel() + ->getRepository($this->metadataRelationship->repository); + + $this->test($targetRepository); + + $this->targetRepository = $targetRepository; + } + + return $this->targetRepository; + } + + /** + * @param IRepository + */ + protected function test(): void {} +} + +class Foo implements IEntity { + public function getRepository(): IRepository { + throw new \BadMethodCallException(); + } +} + +/** + * @implements IRelationshipContainer + */ +class HasOne2 implements IRelationshipContainer +{ + /** @var Foo|null */ + protected ?IEntity $parent = null; + + /** @var IRepository|null */ + protected ?IRepository $targetRepository = null; + + protected PropertyRelationshipMetadata $metadataRelationship; + + /** + * @return Foo + */ + protected function getParentEntity(): IEntity + { + return $this->parent ?? throw new \InvalidArgumentException('Relationship is not attached to a parent entity.'); + } + + /** + * @return IRepository + */ + protected function getTargetRepository(): IRepository + { + if ($this->targetRepository === null) { + /** @var IRepository $targetRepository */ + $targetRepository = $this->getParentEntity() + ->getRepository() + ->getModel() + ->getRepository($this->metadataRelationship->repository); + + $this->test($targetRepository); + + $this->targetRepository = $targetRepository; + } + + return $this->targetRepository; + } + + /** + * @param IRepository $repository + */ + protected function test($repository): void {} +}