diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index 35536317dd..1e8e73b043 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -177,6 +177,15 @@ public function isValidVariance(TemplateType $templateType, Type $a, Type $b): I if ($this->invariant()) { $result = $a->equals($b); + if ( + !$result + && $a instanceof TemplateType + && $b instanceof TemplateType + && $a->getScope()->equals($b->getScope()) + && $a->getName() === $b->getName() + ) { + $result = true; + } $reasons = []; if (!$result) { if ( diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 511bf6136d..a1db3f755a 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -20,6 +20,8 @@ class InstantiationRuleTest extends RuleTestCase { + private bool $checkExplicitMixed = false; + protected function getRule(): Rule { $reflectionProvider = self::createReflectionProvider(); @@ -27,7 +29,7 @@ protected function getRule(): Rule return new InstantiationRule( $container, $reflectionProvider, - new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, false, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), $reflectionProvider, true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($reflectionProvider, true, false, true, $this->checkExplicitMixed, false, false, true), new NullsafeCheck(), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), $reflectionProvider, true, true, true, true), new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck($container), @@ -570,6 +572,13 @@ public function testBug14097(): void $this->analyse([__DIR__ . '/data/bug-14097.php'], []); } + #[RequiresPhp('>= 8.0')] + public function testBug13440(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-13440.php'], []); + } + public function testNewStaticWithConsistentConstructor(): void { $this->analyse([__DIR__ . '/data/instantiation-new-static-consistent-constructor.php'], [ diff --git a/tests/PHPStan/Rules/Classes/data/bug-13440.php b/tests/PHPStan/Rules/Classes/data/bug-13440.php new file mode 100644 index 0000000000..bfc5fddacd --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-13440.php @@ -0,0 +1,37 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug13440; + +use Closure; + +/** @template T */ +interface Foo {} + +/** + * @template TVal + * @template TReturn + */ +class Box +{ + /** + * @param TVal $val + * @param Closure(Foo): TReturn $cb + */ + public function __construct( + private mixed $val, + private Closure $cb, + ) { + } + + /** + * @template TNewReturn + * @param Closure(Foo): TNewReturn $cb + * @return self + */ + public function test(Closure $cb): self + { + return new self($this->val, $cb); + } +}