From 8f9e1a7b103593bf696f8df9740b10ab616ee2a6 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 6 Nov 2019 11:31:32 +0100 Subject: [PATCH] Generic variance --- src/PhpDoc/PhpDocNodeResolver.php | 14 ++- src/PhpDoc/Tag/TemplateTag.php | 15 ++- src/Type/Generic/GenericObjectType.php | 29 +++-- src/Type/Generic/TemplateMixedType.php | 15 +++ src/Type/Generic/TemplateObjectType.php | 34 ++---- src/Type/Generic/TemplateType.php | 2 + src/Type/Generic/TemplateTypeFactory.php | 8 +- src/Type/Generic/TemplateTypeVariance.php | 101 ++++++++++++++++++ src/Type/ObjectType.php | 4 +- .../Generics/GenericsIntegrationTest.php | 1 + .../Generics/TemplateTypeFactoryTest.php | 11 +- tests/PHPStan/Generics/data/variance-5.json | 7 ++ tests/PHPStan/Generics/data/variance.php | 50 +++++++++ .../GenericParametersAcceptorResolverTest.php | 4 +- .../ParametersAcceptorSelectorTest.php | 4 +- tests/PHPStan/Type/ArrayTypeTest.php | 4 +- tests/PHPStan/Type/CallableTypeTest.php | 4 +- .../Type/Constant/ConstantArrayTypeTest.php | 4 +- .../Type/Generic/GenericObjectTypeTest.php | 34 +++++- .../Type/Generic/TemplateTypeHelperTest.php | 3 +- .../Type/Generic/data/generic-classes-c.php | 11 ++ tests/PHPStan/Type/IterableTypeTest.php | 7 +- tests/PHPStan/Type/TemplateTypeTest.php | 10 +- tests/PHPStan/Type/TypeCombinatorTest.php | 92 +++++++++++++--- 24 files changed, 398 insertions(+), 70 deletions(-) create mode 100644 src/Type/Generic/TemplateTypeVariance.php create mode 100644 tests/PHPStan/Generics/data/variance-5.json create mode 100644 tests/PHPStan/Generics/data/variance.php create mode 100644 tests/PHPStan/Type/Generic/data/generic-classes-c.php diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index c417c10099..568b3a07bc 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -22,6 +22,7 @@ use PHPStan\Type\ArrayType; use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeMap; +use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; use PHPStan\Type\Type; @@ -263,7 +264,18 @@ private function resolveTemplateTags(PhpDocNode $phpDocNode, NameScope $nameScop foreach ($phpDocNode->getTemplateTagValues($tagName) as $tagValue) { $resolved[$tagValue->name] = new TemplateTag( $tagValue->name, - $tagValue->bound !== null ? $this->typeNodeResolver->resolve($tagValue->bound, $nameScope) : new MixedType() + $tagValue->bound !== null ? $this->typeNodeResolver->resolve($tagValue->bound, $nameScope) : new MixedType(), + TemplateTypeVariance::createInvariant() + ); + } + } + + foreach (['@template-covariant', '@psalm-template-covariant', '@phpstan-template-covariant'] as $tagName) { + foreach ($phpDocNode->getTemplateTagValues($tagName) as $tagValue) { + $resolved[$tagValue->name] = new TemplateTag( + $tagValue->name, + $tagValue->bound !== null ? $this->typeNodeResolver->resolve($tagValue->bound, $nameScope) : new MixedType(), + TemplateTypeVariance::createCovariant() ); } } diff --git a/src/PhpDoc/Tag/TemplateTag.php b/src/PhpDoc/Tag/TemplateTag.php index dbd4b84fb5..62d4852053 100644 --- a/src/PhpDoc/Tag/TemplateTag.php +++ b/src/PhpDoc/Tag/TemplateTag.php @@ -2,6 +2,7 @@ namespace PHPStan\PhpDoc\Tag; +use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Type; class TemplateTag @@ -13,10 +14,14 @@ class TemplateTag /** @var \PHPStan\Type\Type */ private $bound; - public function __construct(string $name, Type $bound) + /** @var TemplateTypeVariance */ + private $variance; + + public function __construct(string $name, Type $bound, TemplateTypeVariance $variance) { $this->name = $name; $this->bound = $bound; + $this->variance = $variance; } public function getName(): string @@ -29,6 +34,11 @@ public function getBound(): Type return $this->bound; } + public function getVariance(): TemplateTypeVariance + { + return $this->variance; + } + /** * @param mixed[] $properties * @return self @@ -37,7 +47,8 @@ public static function __set_state(array $properties): self { return new self( $properties['name'], - $properties['bound'] + $properties['bound'], + $properties['variance'] ); } diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index 4512a81b06..cafebda41e 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -13,7 +13,6 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\ErrorType; use PHPStan\Type\IntersectionType; -use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\TypeWithClassName; @@ -116,17 +115,24 @@ private function isSuperTypeOfInternal(Type $type, bool $acceptsContext): Trinar return TrinaryLogic::createNo(); } - foreach ($this->types as $i => $t) { + $classReflection = $this->getClassReflection(); + if ($classReflection === null) { + return $nakedSuperTypeOf; + } + + $typeList = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()); + + foreach ($typeList as $i => $templateType) { if (!isset($ancestor->types[$i])) { throw new \PHPStan\ShouldNotHappenException(); } - if (!$t->equals($ancestor->types[$i])) { - if ($t instanceof MixedType) { - continue; - } - if ($ancestor->types[$i] instanceof MixedType) { - continue; - } + if (!isset($this->types[$i])) { + throw new \PHPStan\ShouldNotHappenException(); + } + if (!$templateType instanceof TemplateType) { + throw new \PHPStan\ShouldNotHappenException(); + } + if (!$templateType->isValidVariance($this->types[$i], $ancestor->types[$i])) { return TrinaryLogic::createNo(); } } @@ -218,6 +224,11 @@ public function traverse(callable $cb): Type return $this; } + public function changeSubtractedType(?Type $subtractedType): Type + { + return new self($this->getClassName(), $this->types, $subtractedType); + } + /** * @param mixed[] $properties * @return Type diff --git a/src/Type/Generic/TemplateMixedType.php b/src/Type/Generic/TemplateMixedType.php index f8f63cbfc4..1b0ae8df85 100644 --- a/src/Type/Generic/TemplateMixedType.php +++ b/src/Type/Generic/TemplateMixedType.php @@ -25,9 +25,13 @@ final class TemplateMixedType extends MixedType implements TemplateType /** @var TemplateTypeStrategy */ private $strategy; + /** @var TemplateTypeVariance */ + private $variance; + public function __construct( TemplateTypeScope $scope, TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, string $name, ?Type $subtractedType = null ) @@ -36,6 +40,7 @@ public function __construct( $this->scope = $scope; $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; $this->name = $name; } @@ -143,11 +148,17 @@ public function toArgument(): TemplateType return new self( $this->scope, new TemplateTypeArgumentStrategy(), + $this->variance, $this->name, $this->getSubtractedType() ); } + public function isValidVariance(Type $a, Type $b): bool + { + return $this->variance->isValidVariance($a, $b); + } + public function subtract(Type $type): Type { if ($type instanceof self) { @@ -160,6 +171,7 @@ public function subtract(Type $type): Type return new self( $this->scope, $this->strategy, + $this->variance, $this->name, $type ); @@ -170,6 +182,7 @@ public function getTypeWithoutSubtractedType(): Type return new self( $this->scope, $this->strategy, + $this->variance, $this->name, null ); @@ -180,6 +193,7 @@ public function changeSubtractedType(?Type $subtractedType): Type return new self( $this->scope, $this->strategy, + $this->variance, $this->name, $subtractedType ); @@ -194,6 +208,7 @@ public static function __set_state(array $properties): Type return new self( $properties['scope'], $properties['strategy'], + $properties['variance'], $properties['name'], $properties['subtractedType'] ); diff --git a/src/Type/Generic/TemplateObjectType.php b/src/Type/Generic/TemplateObjectType.php index c3676e1595..a68656c4f4 100644 --- a/src/Type/Generic/TemplateObjectType.php +++ b/src/Type/Generic/TemplateObjectType.php @@ -8,7 +8,6 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Type; -use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -28,9 +27,13 @@ final class TemplateObjectType extends ObjectType implements TemplateType /** @var ObjectType */ private $bound; + /** @var TemplateTypeVariance */ + private $variance; + public function __construct( TemplateTypeScope $scope, TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, string $name, string $class, ?Type $subtractedType = null @@ -40,6 +43,7 @@ public function __construct( $this->scope = $scope; $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; $this->name = $name; $this->bound = new ObjectType($class, $subtractedType); } @@ -163,36 +167,16 @@ public function toArgument(): TemplateType return new self( $this->scope, new TemplateTypeArgumentStrategy(), + TemplateTypeVariance::createInvariant(), $this->name, $this->getClassName(), $this->getSubtractedType() ); } - public function subtract(Type $type): Type - { - if ($this->getSubtractedType() !== null) { - $type = TypeCombinator::union($this->getSubtractedType(), $type); - } - - return new self( - $this->scope, - $this->strategy, - $this->name, - $this->getClassName(), - $type - ); - } - - public function getTypeWithoutSubtractedType(): Type + public function isValidVariance(Type $a, Type $b): bool { - return new self( - $this->scope, - $this->strategy, - $this->name, - $this->getClassName(), - null - ); + return $this->variance->isValidVariance($a, $b); } public function changeSubtractedType(?Type $subtractedType): Type @@ -200,6 +184,7 @@ public function changeSubtractedType(?Type $subtractedType): Type return new self( $this->scope, $this->strategy, + $this->variance, $this->name, $this->getClassName(), $subtractedType @@ -215,6 +200,7 @@ public static function __set_state(array $properties): Type return new self( $properties['scope'], $properties['strategy'], + $properties['variance'], $properties['name'], $properties['className'], $properties['subtractedType'] diff --git a/src/Type/Generic/TemplateType.php b/src/Type/Generic/TemplateType.php index f214fa3f3e..8b86d6e78f 100644 --- a/src/Type/Generic/TemplateType.php +++ b/src/Type/Generic/TemplateType.php @@ -18,4 +18,6 @@ public function toArgument(): TemplateType; public function isArgument(): bool; + public function isValidVariance(Type $a, Type $b): bool; + } diff --git a/src/Type/Generic/TemplateTypeFactory.php b/src/Type/Generic/TemplateTypeFactory.php index a6b12de390..3066d35172 100644 --- a/src/Type/Generic/TemplateTypeFactory.php +++ b/src/Type/Generic/TemplateTypeFactory.php @@ -11,16 +11,16 @@ final class TemplateTypeFactory { - public static function create(TemplateTypeScope $scope, string $name, ?Type $bound): Type + public static function create(TemplateTypeScope $scope, string $name, ?Type $bound, TemplateTypeVariance $variance): Type { $strategy = new TemplateTypeParameterStrategy(); if ($bound instanceof ObjectType) { - return new TemplateObjectType($scope, $strategy, $name, $bound->getClassName()); + return new TemplateObjectType($scope, $strategy, $variance, $name, $bound->getClassName()); } if ($bound === null || get_class($bound) === MixedType::class) { - return new TemplateMixedType($scope, $strategy, $name); + return new TemplateMixedType($scope, $strategy, $variance, $name); } return new ErrorType(); @@ -28,7 +28,7 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou public static function fromTemplateTag(TemplateTypeScope $scope, TemplateTag $tag): Type { - return self::create($scope, $tag->getName(), $tag->getBound()); + return self::create($scope, $tag->getName(), $tag->getBound(), $tag->getVariance()); } } diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php new file mode 100644 index 0000000000..c505646c5b --- /dev/null +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -0,0 +1,101 @@ +value = $value; + } + + private static function create(int $value): self + { + self::$registry[$value] = self::$registry[$value] ?? new self($value); + return self::$registry[$value]; + } + + public static function createInvariant(): self + { + return self::create(self::INVARIANT); + } + + public static function createCovariant(): self + { + return self::create(self::COVARIANT); + } + + public static function createContravariant(): self + { + return self::create(self::CONTRAVARIANT); + } + + public function invariant(): bool + { + return $this->value === self::INVARIANT; + } + + public function covariant(): bool + { + return $this->value === self::COVARIANT; + } + + public function contravariant(): bool + { + return $this->value === self::CONTRAVARIANT; + } + + public function isValidVariance(Type $a, Type $b): bool + { + if ($a instanceof MixedType && !$a instanceof TemplateType) { + return true; + } + + if ($b instanceof MixedType && !$b instanceof TemplateType) { + return true; + } + + if ($this->invariant()) { + return $a->equals($b); + } + + if ($this->covariant()) { + return $a->isSuperTypeOf($b)->yes(); + } + + if ($this->contravariant()) { + return $b->isSuperTypeOf($a)->yes(); + } + + throw new \PHPStan\ShouldNotHappenException(); + } + + public function equals(self $other): bool + { + return $other->value === $this->value; + } + + /** + * @param array{value: int} $properties + * @return self + */ + public static function __set_state(array $properties): self + { + return new self($properties['value']); + } + +} diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index bb12384404..fc3c968994 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -771,12 +771,12 @@ public function subtract(Type $type): Type $type = TypeCombinator::union($this->subtractedType, $type); } - return new self($this->className, $type); + return $this->changeSubtractedType($type); } public function getTypeWithoutSubtractedType(): Type { - return new self($this->className); + return $this->changeSubtractedType(null); } public function changeSubtractedType(?Type $subtractedType): Type diff --git a/tests/PHPStan/Generics/GenericsIntegrationTest.php b/tests/PHPStan/Generics/GenericsIntegrationTest.php index 56bdad2eb9..2b4aa25cba 100644 --- a/tests/PHPStan/Generics/GenericsIntegrationTest.php +++ b/tests/PHPStan/Generics/GenericsIntegrationTest.php @@ -13,6 +13,7 @@ public function dataTopics(): array ['pick'], ['varyingAcceptor'], ['classes'], + ['variance'], ]; } diff --git a/tests/PHPStan/Generics/TemplateTypeFactoryTest.php b/tests/PHPStan/Generics/TemplateTypeFactoryTest.php index 30bfdeeb1a..0ef256a6bc 100644 --- a/tests/PHPStan/Generics/TemplateTypeFactoryTest.php +++ b/tests/PHPStan/Generics/TemplateTypeFactoryTest.php @@ -6,6 +6,7 @@ use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeScope; +use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; @@ -44,7 +45,8 @@ public function dataCreate(): array TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), 'U', - null + null, + TemplateTypeVariance::createInvariant() ), false, ], @@ -64,7 +66,12 @@ public function dataCreate(): array public function testCreate(?Type $bound, bool $expectSuccess): void { $scope = TemplateTypeScope::createWithFunction('a'); - $templateType = TemplateTypeFactory::create($scope, 'T', $bound); + $templateType = TemplateTypeFactory::create( + $scope, + 'T', + $bound, + TemplateTypeVariance::createInvariant() + ); if ($expectSuccess) { $this->assertInstanceOf(TemplateType::class, $templateType); diff --git a/tests/PHPStan/Generics/data/variance-5.json b/tests/PHPStan/Generics/data/variance-5.json new file mode 100644 index 0000000000..e851dc805e --- /dev/null +++ b/tests/PHPStan/Generics/data/variance-5.json @@ -0,0 +1,7 @@ +[ + { + "message": "Parameter #1 $it of function PHPStan\\Generics\\Variance\\acceptInvariantIterOfDateTimeInterface expects PHPStan\\Generics\\Variance\\InvariantIter, PHPStan\\Generics\\Variance\\InvariantIter given.", + "line": 46, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Generics/data/variance.php b/tests/PHPStan/Generics/data/variance.php new file mode 100644 index 0000000000..81a953e75f --- /dev/null +++ b/tests/PHPStan/Generics/data/variance.php @@ -0,0 +1,50 @@ + $it */ +function acceptInvariantIterOfDateTime($it): void { +} + +/** @param InvariantIter<\DateTimeInterface> $it */ +function acceptInvariantIterOfDateTimeInterface($it): void { +} + +/** @param Iter<\DateTime> $it */ +function acceptIterOfDateTime($it): void { +} + +/** @param Iter<\DateTimeInterface> $it */ +function acceptIterOfDateTimeInterface($it): void { +} + +/** + * @param Iter<\DateTime> $itOfDateTime + * @param InvariantIter<\DateTime> $invariantItOfDateTime + */ +function test($itOfDateTime, $invariantItOfDateTime): void { + acceptInvariantIterOfDateTime($invariantItOfDateTime); + acceptInvariantIterOfDateTimeInterface($invariantItOfDateTime); + + acceptIterOfDateTime($itOfDateTime); + acceptIterOfDateTimeInterface($itOfDateTime); +} diff --git a/tests/PHPStan/Reflection/GenericParametersAcceptorResolverTest.php b/tests/PHPStan/Reflection/GenericParametersAcceptorResolverTest.php index 0c0ba22e6b..1cbb5979f8 100644 --- a/tests/PHPStan/Reflection/GenericParametersAcceptorResolverTest.php +++ b/tests/PHPStan/Reflection/GenericParametersAcceptorResolverTest.php @@ -9,6 +9,7 @@ use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeScope; +use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; @@ -30,7 +31,8 @@ public function dataResolve(): array return TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), $name, - $type + $type, + TemplateTypeVariance::createInvariant() ); }; diff --git a/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php b/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php index 59a2cb0180..0dba7bf75f 100644 --- a/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php +++ b/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php @@ -12,6 +12,7 @@ use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeScope; +use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; @@ -397,7 +398,8 @@ public function dataSelectFromTypes(): \Generator TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), 'T', - null + null, + TemplateTypeVariance::createInvariant() ), false, PassedByReference::createNo(), diff --git a/tests/PHPStan/Type/ArrayTypeTest.php b/tests/PHPStan/Type/ArrayTypeTest.php index 1db613a39f..842a75c9a8 100644 --- a/tests/PHPStan/Type/ArrayTypeTest.php +++ b/tests/PHPStan/Type/ArrayTypeTest.php @@ -7,6 +7,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeScope; +use PHPStan\Type\Generic\TemplateTypeVariance; class ArrayTypeTest extends \PHPStan\Testing\TestCase { @@ -144,7 +145,8 @@ public function dataInferTemplateTypes(): array return TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), $name, - new MixedType() + new MixedType(), + TemplateTypeVariance::createInvariant() ); }; diff --git a/tests/PHPStan/Type/CallableTypeTest.php b/tests/PHPStan/Type/CallableTypeTest.php index 7ce57b81ea..100ddbe415 100644 --- a/tests/PHPStan/Type/CallableTypeTest.php +++ b/tests/PHPStan/Type/CallableTypeTest.php @@ -8,6 +8,7 @@ use PHPStan\Type\Accessory\HasMethodType; use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeScope; +use PHPStan\Type\Generic\TemplateTypeVariance; class CallableTypeTest extends \PHPStan\Testing\TestCase { @@ -179,7 +180,8 @@ public function dataInferTemplateTypes(): array return TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), $name, - new MixedType() + new MixedType(), + TemplateTypeVariance::createInvariant() ); }; diff --git a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php index 661822d644..27e716495a 100644 --- a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php @@ -7,6 +7,7 @@ use PHPStan\Type\CallableType; use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeScope; +use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\IntegerType; use PHPStan\Type\IterableType; use PHPStan\Type\MixedType; @@ -205,7 +206,8 @@ public function dataInferTemplateTypes(): array return TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), $name, - new MixedType() + new MixedType(), + TemplateTypeVariance::createInvariant() ); }; diff --git a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php index a1dca3e69e..b0f254bf6a 100644 --- a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php @@ -8,6 +8,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\Test\A; use PHPStan\Type\Test\B; +use PHPStan\Type\Test\C; use PHPStan\Type\Type; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -53,6 +54,36 @@ public function dataIsSuperTypeOf(): array new GenericObjectType(B\IImpl::class, [new ObjectType('DateTime')]), TrinaryLogic::createNo(), ], + 'invariant with equals types' => [ + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')]), + TrinaryLogic::createYes(), + ], + 'invariant with sub type' => [ + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')]), + TrinaryLogic::createNo(), + ], + 'invariant with super type' => [ + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTime')]), + new GenericObjectType(C\Invariant::class, [new ObjectType('DateTimeInterface')]), + TrinaryLogic::createNo(), + ], + 'covariant with equals types' => [ + new GenericObjectType(C\Covariant::class, [new ObjectType('DateTime')]), + new GenericObjectType(C\Covariant::class, [new ObjectType('DateTime')]), + TrinaryLogic::createYes(), + ], + 'covariant with sub type' => [ + new GenericObjectType(C\Covariant::class, [new ObjectType('DateTimeInterface')]), + new GenericObjectType(C\Covariant::class, [new ObjectType('DateTime')]), + TrinaryLogic::createYes(), + ], + 'covariant with super type' => [ + new GenericObjectType(C\Covariant::class, [new ObjectType('DateTime')]), + new GenericObjectType(C\Covariant::class, [new ObjectType('DateTimeInterface')]), + TrinaryLogic::createNo(), + ], ]; } @@ -139,7 +170,8 @@ public function dataInferTemplateTypes(): array return TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), $name, - $bound ?? new MixedType() + $bound ?? new MixedType(), + TemplateTypeVariance::createInvariant() ); }; diff --git a/tests/PHPStan/Type/Generic/TemplateTypeHelperTest.php b/tests/PHPStan/Type/Generic/TemplateTypeHelperTest.php index 356fa5c58f..279617324e 100644 --- a/tests/PHPStan/Type/Generic/TemplateTypeHelperTest.php +++ b/tests/PHPStan/Type/Generic/TemplateTypeHelperTest.php @@ -14,7 +14,8 @@ public function testIssue2512(): void $templateType = TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), 'T', - null + null, + TemplateTypeVariance::createInvariant() ); $type = TemplateTypeHelper::resolveTemplateTypes( diff --git a/tests/PHPStan/Type/Generic/data/generic-classes-c.php b/tests/PHPStan/Type/Generic/data/generic-classes-c.php new file mode 100644 index 0000000000..cd17cc9261 --- /dev/null +++ b/tests/PHPStan/Type/Generic/data/generic-classes-c.php @@ -0,0 +1,11 @@ +', + ], + [ + [ + new GenericObjectType(Variance\Invariant::class, [ + new ObjectType(\DateTimeInterface::class), + ]), + new GenericObjectType(Variance\Invariant::class, [ + new ObjectType(\DateTime::class), + ]), + ], + UnionType::class, + 'PHPStan\Type\Variance\Invariant|PHPStan\Type\Variance\Invariant', + ], + [ + [ + new GenericObjectType(Variance\Covariant::class, [ + new ObjectType(\DateTimeInterface::class), + ]), + new GenericObjectType(Variance\Covariant::class, [ + new ObjectType(\DateTime::class), + ]), + ], + GenericObjectType::class, + 'PHPStan\Type\Variance\Covariant', + ], ]; } @@ -1519,7 +1563,8 @@ public function dataIntersect(): array TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('_'), 'T', - null + null, + TemplateTypeVariance::createInvariant() ) ), ], @@ -2052,7 +2097,8 @@ public function dataIntersect(): array TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), 'T', - null + null, + TemplateTypeVariance::createInvariant() ), new ObjectType('DateTime'), ], @@ -2064,7 +2110,8 @@ public function dataIntersect(): array TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), 'T', - new ObjectType('DateTime') + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() ), new ObjectType('DateTime'), ], @@ -2076,12 +2123,14 @@ public function dataIntersect(): array TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), 'T', - new ObjectType('DateTime') + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() ), TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), 'T', - new ObjectType('DateTime') + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() ), ], TemplateType::class, @@ -2092,12 +2141,14 @@ public function dataIntersect(): array TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), 'T', - new ObjectType('DateTime') + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() ), TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), 'U', - new ObjectType('DateTime') + new ObjectType('DateTime'), + TemplateTypeVariance::createInvariant() ), ], IntersectionType::class, @@ -2108,7 +2159,8 @@ public function dataIntersect(): array TemplateTypeFactory::create( TemplateTypeScope::createWithFunction('a'), 'T', - null + null, + TemplateTypeVariance::createInvariant() ), new MixedType(), ], @@ -2347,6 +2399,18 @@ public function dataIntersect(): array MixedType::class, 'mixed', ], + [ + [ + new GenericObjectType(Variance\Covariant::class, [ + new ObjectType(\DateTimeInterface::class), + ]), + new GenericObjectType(Variance\Covariant::class, [ + new ObjectType(\DateTime::class), + ]), + ], + GenericObjectType::class, + 'PHPStan\Type\Variance\Covariant', + ], ]; }