From 91f16507a601ebfd9502dc1900f4b15348d48ddf Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 4 Oct 2024 08:28:29 +0200 Subject: [PATCH 1/4] Fix issue with template of union --- src/Analyser/MutatingScope.php | 1 + src/Type/UnionType.php | 6 +++ .../Rules/Methods/ReturnTypeRuleTest.php | 5 +++ .../PHPStan/Rules/Methods/data/bug-11663.php | 41 +++++++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-11663.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index c24defb1f8..a8bc15d1c7 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -106,6 +106,7 @@ use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVarianceMap; +use PHPStan\Type\Generic\TemplateUnionType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index b7e794378a..f0bef45903 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -92,14 +92,20 @@ public function getTypes(): array public function filterTypes(callable $filterCb): Type { $newTypes = []; + $changed = false; foreach ($this->getTypes() as $innerType) { if (!$filterCb($innerType)) { + $changed = true; continue; } $newTypes[] = $innerType; } + if (!$changed) { + return $this; + } + return TypeCombinator::union(...$newTypes); } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 6d374a6f1c..fcbc0643c9 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1054,4 +1054,9 @@ public function testBug10715(): void $this->analyse([__DIR__ . '/data/bug-10715.php'], []); } + public function testBug11663(): void + { + $this->analyse([__DIR__ . '/data/bug-11663.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-11663.php b/tests/PHPStan/Rules/Methods/data/bug-11663.php new file mode 100644 index 0000000000..255a6733ce --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-11663.php @@ -0,0 +1,41 @@ +where('test'); + } +} From c5a4041aebaf0c99a26af150691a2be40f8d653b Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 4 Oct 2024 08:39:24 +0200 Subject: [PATCH 2/4] Fix test --- src/Analyser/MutatingScope.php | 1 - tests/PHPStan/Analyser/nsrt/bug-6609-83.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-6609.php | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index a8bc15d1c7..c24defb1f8 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -106,7 +106,6 @@ use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVarianceMap; -use PHPStan\Type\Generic\TemplateUnionType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; diff --git a/tests/PHPStan/Analyser/nsrt/bug-6609-83.php b/tests/PHPStan/Analyser/nsrt/bug-6609-83.php index 65d7f22f1d..4a5f5bb781 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6609-83.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6609-83.php @@ -50,7 +50,7 @@ function modify2(\DateTimeInterface $date) { */ function modify3(\DateTimeInterface $date, string $s) { $date = $date->modify($s); - assertType('DateTime|DateTimeImmutable', $date); + assertType('T of DateTime|DateTimeImmutable (method Bug6609Php83\Foo::modify3(), argument)', $date); return $date; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-6609.php b/tests/PHPStan/Analyser/nsrt/bug-6609.php index 046f9c8403..571f97d988 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6609.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6609.php @@ -50,7 +50,7 @@ function modify2(\DateTimeInterface $date) { */ function modify3(\DateTimeInterface $date, string $s) { $date = $date->modify($s); - assertType('(DateTime|DateTimeImmutable|false)', $date); + assertType('((T of DateTime|DateTimeImmutable (method Bug6609\Foo::modify3(), argument))|false)', $date); return $date; } From 9121f721a5919d0606c9ea491801dc773466a277 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 6 Nov 2024 09:58:06 +0100 Subject: [PATCH 3/4] Improve --- src/Type/UnionType.php | 18 ++++++++- .../PHPStan/Rules/Methods/data/bug-11663.php | 38 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index f0bef45903..6c2bc1f60c 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -22,6 +22,7 @@ use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; +use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Generic\TemplateUnionType; @@ -106,7 +107,22 @@ public function filterTypes(callable $filterCb): Type return $this; } - return TypeCombinator::union(...$newTypes); + $result = TypeCombinator::union(...$newTypes); + if ($this instanceof BenevolentUnionType) { + $result = TypeUtils::toBenevolentUnion($result); + } + if ($this instanceof TemplateType) { + $result = TemplateTypeFactory::create( + $this->getScope(), + $this->getName(), + $result, + $this->getVariance(), + $this->getStrategy(), + $this->getDefault(), + ); + } + + return $result; } public function isNormalized(): bool diff --git a/tests/PHPStan/Rules/Methods/data/bug-11663.php b/tests/PHPStan/Rules/Methods/data/bug-11663.php index 255a6733ce..ac2ed03a93 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-11663.php +++ b/tests/PHPStan/Rules/Methods/data/bug-11663.php @@ -26,6 +26,14 @@ public function where(string $test) } } +interface A +{ + public function doFoo(): static; +} + +interface B +{ +} class Test { @@ -38,4 +46,34 @@ public function test($template) { return $template->where('test'); } + + /** + * @param __benevolent $template + * @return __benevolent + */ + public function test2($template) + { + return $template->where('test'); + } + + + /** + * @template T of A|B + * @param T $ab + * @return T + */ + function foo(A|B $ab): A|B + { + return $ab->doFoo(); + } + + /** + * @template T of __benevolent + * @param T $ab + * @return T + */ + function foo2(A|B $ab): A|B + { + return $ab->doFoo(); + } } From 1c2520664de86ac259b5d742a1950517c4315d06 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 6 Nov 2024 11:52:13 +0100 Subject: [PATCH 4/4] Refacto --- src/Type/BenevolentUnionType.php | 10 ++++++++++ .../Generic/TemplateBenevolentUnionType.php | 17 +++++++++++++++++ src/Type/Generic/TemplateUnionType.php | 17 +++++++++++++++++ src/Type/UnionType.php | 18 +----------------- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/Type/BenevolentUnionType.php b/src/Type/BenevolentUnionType.php index bb4a47761b..d6b43fbd85 100644 --- a/src/Type/BenevolentUnionType.php +++ b/src/Type/BenevolentUnionType.php @@ -19,6 +19,16 @@ public function __construct(array $types, bool $normalized = false) parent::__construct($types, $normalized); } + public function filterTypes(callable $filterCb): Type + { + $result = parent::filterTypes($filterCb); + if (!$result instanceof self && $result instanceof UnionType) { + return TypeUtils::toBenevolentUnion($result); + } + + return $result; + } + public function describe(VerbosityLevel $level): string { return '(' . parent::describe($level) . ')'; diff --git a/src/Type/Generic/TemplateBenevolentUnionType.php b/src/Type/Generic/TemplateBenevolentUnionType.php index cc630fd0dd..aea8573131 100644 --- a/src/Type/Generic/TemplateBenevolentUnionType.php +++ b/src/Type/Generic/TemplateBenevolentUnionType.php @@ -47,4 +47,21 @@ public function withTypes(array $types): self ); } + public function filterTypes(callable $filterCb): Type + { + $result = parent::filterTypes($filterCb); + if (!$result instanceof TemplateType) { + return TemplateTypeFactory::create( + $this->getScope(), + $this->getName(), + $result, + $this->getVariance(), + $this->getStrategy(), + $this->getDefault(), + ); + } + + return $result; + } + } diff --git a/src/Type/Generic/TemplateUnionType.php b/src/Type/Generic/TemplateUnionType.php index cc196a07f4..dc58af565a 100644 --- a/src/Type/Generic/TemplateUnionType.php +++ b/src/Type/Generic/TemplateUnionType.php @@ -34,4 +34,21 @@ public function __construct( $this->default = $default; } + public function filterTypes(callable $filterCb): Type + { + $result = parent::filterTypes($filterCb); + if (!$result instanceof TemplateType) { + return TemplateTypeFactory::create( + $this->getScope(), + $this->getName(), + $result, + $this->getVariance(), + $this->getStrategy(), + $this->getDefault(), + ); + } + + return $result; + } + } diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 6c2bc1f60c..f0bef45903 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -22,7 +22,6 @@ use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; -use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Generic\TemplateUnionType; @@ -107,22 +106,7 @@ public function filterTypes(callable $filterCb): Type return $this; } - $result = TypeCombinator::union(...$newTypes); - if ($this instanceof BenevolentUnionType) { - $result = TypeUtils::toBenevolentUnion($result); - } - if ($this instanceof TemplateType) { - $result = TemplateTypeFactory::create( - $this->getScope(), - $this->getName(), - $result, - $this->getVariance(), - $this->getStrategy(), - $this->getDefault(), - ); - } - - return $result; + return TypeCombinator::union(...$newTypes); } public function isNormalized(): bool