diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 43a17e853e1..a6edd1ace17 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1473,12 +1473,6 @@ parameters: count: 1 path: src/Type/ObjectShapeType.php - - - rawMessage: 'Doing instanceof PHPStan\Type\Enum\EnumCaseObjectType is error-prone and deprecated. Use Type::getEnumCases() instead.' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Type/ObjectType.php - - rawMessage: Doing instanceof PHPStan\Type\Generic\GenericObjectType is error-prone and deprecated. identifier: phpstanApi.instanceofType diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index 35536317dd6..2d4ed549f97 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -176,7 +176,7 @@ public function isValidVariance(TemplateType $templateType, Type $a, Type $b): I } if ($this->invariant()) { - $result = $a->equals($b); + $result = $a->equals($b) || ($b instanceof TemplateType && $a->equals($b->getBound())); $reasons = []; if (!$result) { if ( diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index f90a09d6908..3d81d774103 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -56,6 +56,7 @@ use function array_map; use function array_values; use function count; +use function get_class; use function implode; use function in_array; use function sprintf; @@ -624,11 +625,7 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult public function equals(Type $type): bool { - if (!$type instanceof self) { - return false; - } - - if ($type instanceof EnumCaseObjectType) { + if (get_class($this) !== get_class($type)) { return false; } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 69e992139d8..5b04c59f08b 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1207,4 +1207,11 @@ public function testBug13799(): void ]); } + #[RequiresPhp('>= 8.1')] + public function testBug14429(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/bug-14429.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-14429.php b/tests/PHPStan/Rules/Comparison/data/bug-14429.php new file mode 100644 index 00000000000..173d62c02fd --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-14429.php @@ -0,0 +1,43 @@ += 8.1 + +declare(strict_types = 1); + +namespace Bug14429; + +function throw_if(bool $condition, string $message): void +{ + if ($condition) { throw new \Exception($message); } +} + +class Foo +{ + /** + * @param list $tags + * @param list $scores + * @param \ArrayObject $stringMap + * @param \ArrayObject $intKeyMap + */ + public function __construct( + public array $tags, + public array $scores, + public ?\ArrayObject $stringMap = null, + public ?\ArrayObject $intKeyMap = null, + ) { + foreach ($tags as $tagsItem) { + throw_if(!is_string($tagsItem), 'tags item must be string'); + } + foreach ($scores as $scoresItem) { + throw_if(!is_int($scoresItem) && !is_float($scoresItem), 'scores item must be number'); + } + if ($stringMap !== null) { + foreach ($stringMap as $stringMapValue) { + throw_if(!is_string($stringMapValue), 'stringMap value must be string'); + } + } + if ($intKeyMap !== null) { + foreach ($intKeyMap as $intKeyMapValue) { + throw_if(!is_int($intKeyMapValue), 'intKeyMap value must be int'); + } + } + } +} diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 838211838ef..599428ee863 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -586,6 +586,14 @@ public function testBug12457(): void ]); } + public function testBug14429(): void + { + $this->checkTypeAgainstPhpDocType = true; + $this->strictWideningCheck = true; + $this->analyse([__DIR__ . '/data/bug-14429.php'], []); + } + + public function testNewIsAlwaysFinalClass(): void { $this->checkTypeAgainstPhpDocType = true; diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-14429.php b/tests/PHPStan/Rules/PhpDoc/data/bug-14429.php new file mode 100644 index 00000000000..aeef50ca3c7 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-14429.php @@ -0,0 +1,87 @@ + + */ + 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->targetRepository = $targetRepository; + } + + return $this->targetRepository; + } +}