diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index d756ae0039a..9481e3f1592 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -24,3 +24,4 @@ parameters: nullContextForVoidReturningFunctions: true unescapeStrings: true duplicateStubs: true + invarianceComposition: true diff --git a/conf/config.neon b/conf/config.neon index 56b79657630..46736e4c5a4 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -54,6 +54,7 @@ parameters: nullContextForVoidReturningFunctions: false unescapeStrings: false duplicateStubs: false + invarianceComposition: false fileExtensions: - php checkAdvancedIsset: false @@ -271,6 +272,7 @@ parametersSchema: nullContextForVoidReturningFunctions: bool() unescapeStrings: bool() duplicateStubs: bool() + invarianceComposition: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 573949f02b2..37f2bf7bb7b 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -21,6 +21,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Generic\TemplateTypeVariance; use Symfony\Component\Finder\Finder; use function array_diff_key; use function array_map; @@ -174,6 +175,7 @@ public static function postInitializeContainer(Container $container): void BleedingEdgeToggle::setBleedingEdge($container->getParameter('featureToggles')['bleedingEdge']); AccessoryArrayListType::setListTypeEnabled($container->getParameter('featureToggles')['listType']); + TemplateTypeVariance::setInvarianceCompositionEnabled($container->getParameter('featureToggles')['invarianceComposition']); } public function clearOldContainers(string $tempDirectory): void diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index 38488c75937..1de2d9b1dd0 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -21,6 +21,8 @@ class TemplateTypeVariance /** @var self[] */ private static array $registry; + private static bool $invarianceCompositionEnabled = false; + private function __construct(private int $value) { } @@ -93,7 +95,7 @@ public function compose(self $other): self return self::createInvariant(); } - if ($this->invariant()) { + if (self::$invarianceCompositionEnabled && $this->invariant()) { return self::createInvariant(); } @@ -177,4 +179,9 @@ public static function __set_state(array $properties): self return new self($properties['value']); } + public static function setInvarianceCompositionEnabled(bool $enabled): void + { + self::$invarianceCompositionEnabled = $enabled; + } + } diff --git a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php index 67ccd18d208..75c9c0d7fe6 100644 --- a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php @@ -355,7 +355,7 @@ public function testResolveTemplateTypes(Type $received, Type $template, array $ ); } - /** @return array}> */ + /** @return array}> */ public function dataGetReferencedTypeArguments(): array { $templateType = static fn (string $name, ?Type $bound = null): TemplateType => TemplateTypeFactory::create( @@ -371,6 +371,7 @@ public function dataGetReferencedTypeArguments(): array new GenericObjectType(D\Invariant::class, [ $templateType('T'), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -383,6 +384,7 @@ public function dataGetReferencedTypeArguments(): array new GenericObjectType(D\Out::class, [ $templateType('T'), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -397,6 +399,7 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -413,6 +416,7 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -425,6 +429,7 @@ public function dataGetReferencedTypeArguments(): array new GenericObjectType(D\In::class, [ $templateType('T'), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -439,6 +444,7 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -455,6 +461,7 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -469,6 +476,7 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -483,6 +491,7 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -497,6 +506,7 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -511,6 +521,7 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -525,10 +536,11 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), - TemplateTypeVariance::createInvariant(), + TemplateTypeVariance::createCovariant(), ), ], ], @@ -539,10 +551,11 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), - TemplateTypeVariance::createInvariant(), + TemplateTypeVariance::createContravariant(), ), ], ], @@ -555,10 +568,11 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), - TemplateTypeVariance::createInvariant(), + TemplateTypeVariance::createCovariant(), ), ], ], @@ -571,10 +585,11 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), - TemplateTypeVariance::createInvariant(), + TemplateTypeVariance::createContravariant(), ), ], ], @@ -583,6 +598,7 @@ public function dataGetReferencedTypeArguments(): array new GenericObjectType(D\Invariant::class, [ $templateType('T'), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -595,6 +611,7 @@ public function dataGetReferencedTypeArguments(): array new GenericObjectType(D\Out::class, [ $templateType('T'), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -609,6 +626,7 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -625,6 +643,7 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -637,6 +656,7 @@ public function dataGetReferencedTypeArguments(): array new GenericObjectType(D\In::class, [ $templateType('T'), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -651,6 +671,7 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -667,6 +688,7 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -681,6 +703,7 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -695,6 +718,7 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -709,6 +733,7 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -723,6 +748,7 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), @@ -737,10 +763,11 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), - TemplateTypeVariance::createInvariant(), + TemplateTypeVariance::createCovariant(), ), ], ], @@ -751,10 +778,11 @@ public function dataGetReferencedTypeArguments(): array $templateType('T'), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), - TemplateTypeVariance::createInvariant(), + TemplateTypeVariance::createContravariant(), ), ], ], @@ -767,10 +795,11 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), + false, [ new TemplateTypeReference( $templateType('T'), - TemplateTypeVariance::createInvariant(), + TemplateTypeVariance::createCovariant(), ), ], ], @@ -783,6 +812,195 @@ public function dataGetReferencedTypeArguments(): array ]), ]), ]), + false, + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createContravariant(), + ), + ], + ], + 'param: Out> (with invariance composition)' => [ + TemplateTypeVariance::createContravariant(), + new GenericObjectType(D\Out::class, [ + new GenericObjectType(D\Invariant::class, [ + $templateType('T'), + ]), + ]), + true, + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant(), + ), + ], + ], + 'param: In> (with invariance composition)' => [ + TemplateTypeVariance::createContravariant(), + new GenericObjectType(D\In::class, [ + new GenericObjectType(D\Invariant::class, [ + $templateType('T'), + ]), + ]), + true, + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant(), + ), + ], + ], + 'param: Invariant> (with invariance composition)' => [ + TemplateTypeVariance::createContravariant(), + new GenericObjectType(D\Invariant::class, [ + new GenericObjectType(D\Out::class, [ + $templateType('T'), + ]), + ]), + true, + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant(), + ), + ], + ], + 'param: Invariant> (with invariance composition)' => [ + TemplateTypeVariance::createContravariant(), + new GenericObjectType(D\Invariant::class, [ + new GenericObjectType(D\In::class, [ + $templateType('T'), + ]), + ]), + true, + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant(), + ), + ], + ], + 'param: In>> (with invariance composition)' => [ + TemplateTypeVariance::createContravariant(), + new GenericObjectType(D\In::class, [ + new GenericObjectType(D\Invariant::class, [ + new GenericObjectType(D\Out::class, [ + $templateType('T'), + ]), + ]), + ]), + true, + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant(), + ), + ], + ], + 'param: Out>> (with invariance composition)' => [ + TemplateTypeVariance::createContravariant(), + new GenericObjectType(D\Out::class, [ + new GenericObjectType(D\Invariant::class, [ + new GenericObjectType(D\In::class, [ + $templateType('T'), + ]), + ]), + ]), + true, + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant(), + ), + ], + ], + 'return: Out> (with invariance composition)' => [ + TemplateTypeVariance::createCovariant(), + new GenericObjectType(D\Out::class, [ + new GenericObjectType(D\Invariant::class, [ + $templateType('T'), + ]), + ]), + true, + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant(), + ), + ], + ], + 'return: In> (with invariance composition)' => [ + TemplateTypeVariance::createCovariant(), + new GenericObjectType(D\In::class, [ + new GenericObjectType(D\Invariant::class, [ + $templateType('T'), + ]), + ]), + true, + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant(), + ), + ], + ], + 'return: Invariant> (with invariance composition)' => [ + TemplateTypeVariance::createCovariant(), + new GenericObjectType(D\Invariant::class, [ + new GenericObjectType(D\Out::class, [ + $templateType('T'), + ]), + ]), + true, + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant(), + ), + ], + ], + 'return: Invariant> (with invariance composition)' => [ + TemplateTypeVariance::createCovariant(), + new GenericObjectType(D\Invariant::class, [ + new GenericObjectType(D\In::class, [ + $templateType('T'), + ]), + ]), + true, + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant(), + ), + ], + ], + 'return: In>> (with invariance composition)' => [ + TemplateTypeVariance::createCovariant(), + new GenericObjectType(D\In::class, [ + new GenericObjectType(D\Invariant::class, [ + new GenericObjectType(D\Out::class, [ + $templateType('T'), + ]), + ]), + ]), + true, + [ + new TemplateTypeReference( + $templateType('T'), + TemplateTypeVariance::createInvariant(), + ), + ], + ], + 'return: Out>> (with invariance composition)' => [ + TemplateTypeVariance::createCovariant(), + new GenericObjectType(D\Out::class, [ + new GenericObjectType(D\Invariant::class, [ + new GenericObjectType(D\In::class, [ + $templateType('T'), + ]), + ]), + ]), + true, [ new TemplateTypeReference( $templateType('T'), @@ -798,8 +1016,10 @@ public function dataGetReferencedTypeArguments(): array * * @param array $expectedReferences */ - public function testGetReferencedTypeArguments(TemplateTypeVariance $positionVariance, Type $type, array $expectedReferences): void + public function testGetReferencedTypeArguments(TemplateTypeVariance $positionVariance, Type $type, bool $invarianceComposition, array $expectedReferences): void { + TemplateTypeVariance::setInvarianceCompositionEnabled($invarianceComposition); + $result = []; foreach ($type->getReferencedTemplateTypes($positionVariance) as $r) { $result[] = $r;