From f2d468d5b0fbbdd2ebc97cd4fbfc83ea6cb71716 Mon Sep 17 00:00:00 2001 From: Philosoft Date: Fri, 7 Aug 2026 15:21:57 -0400 Subject: [PATCH] [DeadCode] Skip RemoveReturnTagIncompatibleWithNativeTypeRector on a type alias nested in a union A @phpstan-type alias name is not resolved to the type it stands for, it becomes a NonExistingObjectType. So "@return ConfigArray|CustomConfig" over a native "array" looked like a contradiction and the tag was removed. The alias guard only matched when the whole @return type was a single IdentifierTypeNode. Look the name up in every identifier of the type node instead, which covers unions, nullables and intersections as well. Co-Authored-By: Claude Opus 5 (1M context) --- ...e_object_return_next_to_type_alias.php.inc | 36 ++++++++++++++++++ ...skip_union_of_phpstan_type_aliases.php.inc | 13 +++++++ ...urnTagIncompatibleWithNativeTypeRector.php | 37 ++++++++++++++++++- 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/remove_object_return_next_to_type_alias.php.inc create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/skip_union_of_phpstan_type_aliases.php.inc diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/remove_object_return_next_to_type_alias.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/remove_object_return_next_to_type_alias.php.inc new file mode 100644 index 00000000000..0648711a0bb --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/remove_object_return_next_to_type_alias.php.inc @@ -0,0 +1,36 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/skip_union_of_phpstan_type_aliases.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/skip_union_of_phpstan_type_aliases.php.inc new file mode 100644 index 00000000000..34e8a924967 --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveReturnTagIncompatibleWithNativeTypeRector/Fixture/skip_union_of_phpstan_type_aliases.php.inc @@ -0,0 +1,13 @@ +type instanceof IdentifierTypeNode - && isset($typeAliases[$returnTagValueNode->type->name]); + return $this->containsTypeAliasName($returnTagValueNode->type, $typeAliases); + } + + /** + * The alias can be nested in a composed type as well, e.g. "ConfigArray|CustomConfig" or "?ConfigArray" + * + * @param array $typeAliases + */ + private function containsTypeAliasName(TypeNode $typeNode, array $typeAliases): bool + { + if ($typeNode instanceof IdentifierTypeNode) { + return isset($typeAliases[$typeNode->name]); + } + + $hasTypeAliasName = false; + + // the traverser visits sub-nodes only, that is why the type node itself is checked above + $phpDocNodeTraverser = new PhpDocNodeTraverser(); + $phpDocNodeTraverser->traverseWithCallable($typeNode, '', static function (AstNode $astNode) use ( + $typeAliases, + &$hasTypeAliasName + ): ?int { + if ($astNode instanceof IdentifierTypeNode && isset($typeAliases[$astNode->name])) { + $hasTypeAliasName = true; + } + + return null; + }); + + return $hasTypeAliasName; } private function isReturnTemplate(PhpDocInfo $phpDocInfo, ReturnTagValueNode $returnTagValueNode): bool