From 3402a90c3df869ac422384e6cca3d4d88cc3656b Mon Sep 17 00:00:00 2001 From: Maarten Paauw Date: Mon, 13 Jul 2026 15:02:56 +0200 Subject: [PATCH] fix(dead-code): preserve generic `@return` tags in `RemoveReturnTagIncompatibleWithNativeTypeRector` The rule incorrectly removed `@return self` and `@return ClassName` annotations when the native return type was `self`. These annotations are not incompatible, because they carry template parameters that the native type cannot express. PHPStan and Psalm rely on them for generic type inference. Add an early bail-out when the `@return` type node is a `GenericTypeNode`, which covers all parameterized return types (`self`, `static`, `Foo`). --- .../skip_generic_fluent_builder.php.inc | 26 ++++++++++++++++ .../Fixture/skip_generic_self_return.php.inc | 31 +++++++++++++++++++ ...urnTagIncompatibleWithNativeTypeRector.php | 6 ++++ 3 files changed, 63 insertions(+) create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/skip_generic_fluent_builder.php.inc create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/skip_generic_self_return.php.inc diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/skip_generic_fluent_builder.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/skip_generic_fluent_builder.php.inc new file mode 100644 index 00000000000..f7f616c4967 --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/skip_generic_fluent_builder.php.inc @@ -0,0 +1,26 @@ + + */ + public function withFoo(): self + { + return $this; + } + + /** + * @return SkipGenericFluentBuilder + */ + public function withBar(): self + { + return $this; + } +} diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/skip_generic_self_return.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/skip_generic_self_return.php.inc new file mode 100644 index 00000000000..d3e496abf63 --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/skip_generic_self_return.php.inc @@ -0,0 +1,31 @@ + $added + * @param list $removed + */ + private function __construct( + public array $added, + public array $removed, + ) { + } + + /** + * @param list $added + * @param list $removed + * + * @return self + */ + public static function create(array $added, array $removed): self + { + return new self($added, $removed); + } +} diff --git a/rules/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector.php b/rules/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector.php index c412683ffcb..ccc93cd78bc 100644 --- a/rules/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector.php +++ b/rules/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector.php @@ -10,6 +10,7 @@ use PHPStan\Analyser\Scope; use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; +use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; @@ -103,6 +104,11 @@ public function refactor(Node $node): ?Node return null; } + // keep generic return types, because they carry template info the native type cannot express + if ($returnTagValueNode->type instanceof GenericTypeNode) { + return null; + } + if ($this->isClassTypeAlias($node, $returnTagValueNode)) { return null; }