diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveUselessUnionReturnDocblockRector/Fixture/skip_nullable_array_native.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveUselessUnionReturnDocblockRector/Fixture/skip_nullable_array_native.php.inc new file mode 100644 index 00000000000..bf53ea289de --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveUselessUnionReturnDocblockRector/Fixture/skip_nullable_array_native.php.inc @@ -0,0 +1,13 @@ + +----- + diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/Fixture/narrow_bool_to_true.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/Fixture/narrow_bool_to_true.php.inc new file mode 100644 index 00000000000..35760c376f6 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/Fixture/narrow_bool_to_true.php.inc @@ -0,0 +1,33 @@ + +----- + diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/Fixture/narrow_function.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/Fixture/narrow_function.php.inc new file mode 100644 index 00000000000..a600e879cf6 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/Fixture/narrow_function.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/Fixture/skip_already_has_constant.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/Fixture/skip_already_has_constant.php.inc new file mode 100644 index 00000000000..bffeaae0f8a --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/Fixture/skip_already_has_constant.php.inc @@ -0,0 +1,14 @@ +|int + */ + private function parseValue(string $value): string|array|int + { + return $value; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/NarrowBoolDocblockReturnTypeRectorTest.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/NarrowBoolDocblockReturnTypeRectorTest.php new file mode 100644 index 00000000000..daf7eae25a5 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/NarrowBoolDocblockReturnTypeRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/config/configured_rule.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/config/configured_rule.php new file mode 100644 index 00000000000..b736f9f0624 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(NarrowBoolDocblockReturnTypeRector::class); +}; diff --git a/rules/DeadCode/Rector/ClassMethod/RemoveUselessUnionReturnDocblockRector.php b/rules/DeadCode/Rector/ClassMethod/RemoveUselessUnionReturnDocblockRector.php index d6d713e751a..561a2689a75 100644 --- a/rules/DeadCode/Rector/ClassMethod/RemoveUselessUnionReturnDocblockRector.php +++ b/rules/DeadCode/Rector/ClassMethod/RemoveUselessUnionReturnDocblockRector.php @@ -106,8 +106,8 @@ public function refactor(Node $node): ?Node $nativeReturnType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($node->returnType); - // keep array native type, the docblock may carry element types - if ($nativeReturnType->isArray()->yes()) { + // keep array-involving native type (array, ?array, union with array), the docblock may carry element types + if (! $nativeReturnType->isArray()->no()) { return null; } diff --git a/rules/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector.php new file mode 100644 index 00000000000..ad9a39a75ba --- /dev/null +++ b/rules/TypeDeclaration/Rector/ClassMethod/NarrowBoolDocblockReturnTypeRector.php @@ -0,0 +1,171 @@ +matchNativeConstantBoolName($node); + if ($constantBoolName === null) { + return null; + } + + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); + if (! $phpDocInfo instanceof PhpDocInfo) { + return null; + } + + $returnTagValueNode = $phpDocInfo->getReturnTagValue(); + if (! $returnTagValueNode instanceof ReturnTagValueNode) { + return null; + } + + if (! $returnTagValueNode->type instanceof UnionTypeNode) { + return null; + } + + if (! $this->narrowBoolInUnion($returnTagValueNode->type, $constantBoolName)) { + return null; + } + + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + + return $node; + } + + /** + * Returns "false" or "true" when the native return type allows exactly one of them, null otherwise. + */ + private function matchNativeConstantBoolName(ClassMethod|Function_ $node): ?string + { + $returnType = $node->returnType; + if (! $returnType instanceof UnionType) { + return null; + } + + $hasFalse = false; + $hasTrue = false; + + foreach ($returnType->types as $type) { + if (! $type instanceof Identifier) { + continue; + } + + $lowerName = $type->toLowerString(); + if ($lowerName === 'bool') { + // ambiguous, both values possible + return null; + } + + if ($lowerName === 'false') { + $hasFalse = true; + } + + if ($lowerName === 'true') { + $hasTrue = true; + } + } + + if ($hasFalse && ! $hasTrue) { + return 'false'; + } + + if ($hasTrue && ! $hasFalse) { + return 'true'; + } + + return null; + } + + private function narrowBoolInUnion(UnionTypeNode $unionTypeNode, string $constantBoolName): bool + { + // already contains the constant? replacing "bool" would create a duplicate + foreach ($unionTypeNode->types as $typeNode) { + if ($typeNode instanceof IdentifierTypeNode && $typeNode->name === $constantBoolName) { + return false; + } + } + + $hasChanged = false; + + foreach ($unionTypeNode->types as $key => $typeNode) { + if ($typeNode instanceof IdentifierTypeNode && $typeNode->name === 'bool') { + $unionTypeNode->types[$key] = new IdentifierTypeNode($constantBoolName); + $hasChanged = true; + } + } + + return $hasChanged; + } +} diff --git a/src/Caching/ValueObject/Storage/MemoryCacheStorage.php b/src/Caching/ValueObject/Storage/MemoryCacheStorage.php index 65e24c37f13..c3e4ecf1a31 100644 --- a/src/Caching/ValueObject/Storage/MemoryCacheStorage.php +++ b/src/Caching/ValueObject/Storage/MemoryCacheStorage.php @@ -17,9 +17,6 @@ final class MemoryCacheStorage implements CacheStorageInterface */ private array $storage = []; - /** - * @return null|mixed - */ public function load(string $key, string $variableKey): mixed { if (! isset($this->storage[$key])) { diff --git a/src/PhpAttribute/AnnotationToAttributeMapper.php b/src/PhpAttribute/AnnotationToAttributeMapper.php index ac8cbad06e1..26825226509 100644 --- a/src/PhpAttribute/AnnotationToAttributeMapper.php +++ b/src/PhpAttribute/AnnotationToAttributeMapper.php @@ -29,9 +29,6 @@ public function __construct( Assert::notEmpty($annotationToAttributeMappers); } - /** - * @return mixed|DocTagNodeState::REMOVE_ARRAY - */ public function map(mixed $value): mixed { foreach ($this->annotationToAttributeMappers as $annotationToAttributeMapper) {