From ae1c6b90f7bb47f500c1deb045658cccb89cdfad Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 21 Mar 2021 22:31:18 +0100 Subject: [PATCH] Remove dynamic type checks #2 (#5941) --- .../PhpDocParser/BetterPhpDocParser.php | 7 +- .../Caching/FileSystem/DependencyResolver.php | 8 +- .../Scope/PHPStanNodeScopeResolver.php | 4 +- .../PHPStan/Scope/ScopeFactory.php | 7 +- .../NodeAnalyzer/DocBlockNameImporter.php | 2 +- ...orBetterReflectionSourceLocatorFactory.php | 3 +- .../TypeAnalyzer/CountableTypeAnalyzer.php | 57 ++-------- .../TypeMapper/UnionTypeMapper.php | 19 ++-- .../Utils/TypeUnwrapper.php | 5 +- .../PhpDocParser/IdentifierTypeMapper.php | 3 +- .../ClassMethodReturnTypeOverrideGuard.php | 70 +++--------- phpstan.neon | 7 +- .../Rector/Concat/JoinStringConcatRector.php | 3 +- rules/CodingStyle/Node/ConcatManipulator.php | 4 +- .../InstanceOfUniqueKeyResolver.php | 40 +++++++ .../RemoveDuplicatedInstanceOfRector.php | 106 +++++------------- .../RemoveEmptyMethodCallRector.php | 3 +- .../DeadCode/ValueObject/VariableNodeUse.php | 2 +- .../NodeAnalyzer/EntityObjectTypeResolver.php | 10 +- .../PhpDoc/CollectionTypeFactory.php | 3 +- .../Array_/DowngradeArraySpreadRector.php | 2 +- .../AssertManipulator.php | 3 +- .../Php70/ValueObject/VariableAssignPair.php | 5 +- .../StrStartsWithFuncCallFactory.php | 4 +- .../NodeManipulator/TokenManipulator.php | 2 +- .../FunctionToStaticMethodRector.php | 3 +- .../ArgumentFuncCallToMethodCallRector.php | 3 +- .../PHPStan/Type/ObjectTypeSpecifier.php | 3 +- .../TypeDeclaration/PhpParserTypeAnalyzer.php | 58 ++-------- .../AddArrayReturnDocTypeRector.php | 1 + .../ReturnTypeFromStrictTypedCallRector.php | 4 +- .../ReturnTypeDeclarationRector.php | 2 +- .../RectorContainerFactory.php | 3 +- src/NodeManipulator/IfManipulator.php | 2 +- .../Parser/PhpParserLexerFactory.php | 3 +- .../Rule/NoInstanceOfStaticReflectionRule.php | 1 + .../Fixture/SkipTypesArray.php | 48 ++++++++ .../NoInstanceOfStaticReflectionRuleTest.php | 1 + 38 files changed, 221 insertions(+), 290 deletions(-) create mode 100644 rules/DeadCode/NodeAnalyzer/InstanceOfUniqueKeyResolver.php create mode 100644 utils/phpstan-extensions/tests/Rule/NoInstanceOfStaticReflectionRule/Fixture/SkipTypesArray.php diff --git a/packages/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php b/packages/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php index 136e008b223f..62f0a75edd3d 100644 --- a/packages/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php +++ b/packages/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php @@ -6,6 +6,7 @@ use Nette\Utils\Strings; use PHPStan\PhpDocParser\Ast\Node; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocChildNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; @@ -127,7 +128,7 @@ public function parse(TokenIterator $tokenIterator): PhpDocNode // might be in the middle of annotations $tokenIterator->tryConsumeTokenType(Lexer::TOKEN_CLOSE_PHPDOC); - $phpDocNode = new PhpDocNode(array_values($children)); + $phpDocNode = new PhpDocNode($children); $docContent = $this->annotationContentResolver->resolveFromTokenIterator($originalTokenIterator); return $this->phpDocNodeMapper->transform($phpDocNode, $docContent); @@ -199,14 +200,14 @@ private function setPhpDocNodeFactories(array $phpDocNodeFactories): void } } - private function parseChildAndStoreItsPositions(TokenIterator $tokenIterator): Node + private function parseChildAndStoreItsPositions(TokenIterator $tokenIterator): PhpDocChildNode { $originalTokenIterator = clone $tokenIterator; $docContent = $this->annotationContentResolver->resolveFromTokenIterator($originalTokenIterator); $tokenStart = $this->getTokenIteratorIndex($tokenIterator); - /** @var PhpDocNode $phpDocNode */ + /** @var PhpDocChildNode $phpDocNode */ $phpDocNode = $this->privatesCaller->callPrivateMethod($this, 'parseChild', [$tokenIterator]); $tokenEnd = $this->resolveTokenEnd($tokenIterator); diff --git a/packages/Caching/FileSystem/DependencyResolver.php b/packages/Caching/FileSystem/DependencyResolver.php index 768fbf3cc2e7..0fe72cdcf8b6 100644 --- a/packages/Caching/FileSystem/DependencyResolver.php +++ b/packages/Caching/FileSystem/DependencyResolver.php @@ -5,7 +5,7 @@ namespace Rector\Caching\FileSystem; use PhpParser\Node; -use PHPStan\Analyser\Scope; +use PHPStan\Analyser\MutatingScope; use PHPStan\Dependency\DependencyResolver as PHPStanDependencyResolver; use PHPStan\File\FileHelper; use Rector\Core\Configuration\Configuration; @@ -40,7 +40,7 @@ public function __construct( /** * @return string[] */ - public function resolveDependencies(Node $node, Scope $scope): array + public function resolveDependencies(Node $node, MutatingScope $mutatingScope): array { $fileInfos = $this->configuration->getFileInfos(); @@ -51,7 +51,7 @@ public function resolveDependencies(Node $node, Scope $scope): array $dependencyFiles = []; - $nodeDependencies = $this->phpStanDependencyResolver->resolveDependencies($node, $scope); + $nodeDependencies = $this->phpStanDependencyResolver->resolveDependencies($node, $mutatingScope); foreach ($nodeDependencies as $nodeDependency) { $dependencyFile = $nodeDependency->getFileName(); if (! $dependencyFile) { @@ -59,7 +59,7 @@ public function resolveDependencies(Node $node, Scope $scope): array } $dependencyFile = $this->fileHelper->normalizePath($dependencyFile); - if ($scope->getFile() === $dependencyFile) { + if ($mutatingScope->getFile() === $dependencyFile) { continue; } diff --git a/packages/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php b/packages/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php index 3c6536f2f3e0..bf1472914cc4 100644 --- a/packages/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php +++ b/packages/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php @@ -195,14 +195,14 @@ private function resolveClassOrInterfaceScope(ClassLike $classLike, Scope $scope return $scope->enterClass($classReflection); } - private function resolveDependentFiles(Node $node, Scope $scope): void + private function resolveDependentFiles(Node $node, MutatingScope $mutatingScope): void { if (! $this->configuration->isCacheEnabled()) { return; } try { - $dependentFiles = $this->dependencyResolver->resolveDependencies($node, $scope); + $dependentFiles = $this->dependencyResolver->resolveDependencies($node, $mutatingScope); foreach ($dependentFiles as $dependentFile) { $this->dependentFiles[] = $dependentFile; } diff --git a/packages/NodeTypeResolver/PHPStan/Scope/ScopeFactory.php b/packages/NodeTypeResolver/PHPStan/Scope/ScopeFactory.php index cf13f067d4fb..436b441f58e8 100644 --- a/packages/NodeTypeResolver/PHPStan/Scope/ScopeFactory.php +++ b/packages/NodeTypeResolver/PHPStan/Scope/ScopeFactory.php @@ -4,7 +4,7 @@ namespace Rector\NodeTypeResolver\PHPStan\Scope; -use PHPStan\Analyser\Scope; +use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\ScopeContext; use PHPStan\Analyser\ScopeFactory as PHPStanScopeFactory; use Symplify\SmartFileSystem\SmartFileInfo; @@ -21,8 +21,9 @@ public function __construct(PHPStanScopeFactory $phpStanScopeFactory) $this->phpStanScopeFactory = $phpStanScopeFactory; } - public function createFromFile(SmartFileInfo $fileInfo): Scope + public function createFromFile(SmartFileInfo $fileInfo): MutatingScope { - return $this->phpStanScopeFactory->create(ScopeContext::create($fileInfo->getRealPath())); + $scopeContext = ScopeContext::create($fileInfo->getRealPath()); + return $this->phpStanScopeFactory->create($scopeContext); } } diff --git a/packages/NodeTypeResolver/PhpDoc/NodeAnalyzer/DocBlockNameImporter.php b/packages/NodeTypeResolver/PhpDoc/NodeAnalyzer/DocBlockNameImporter.php index 44b0eb9c55c4..50d27b8ed5c4 100644 --- a/packages/NodeTypeResolver/PhpDoc/NodeAnalyzer/DocBlockNameImporter.php +++ b/packages/NodeTypeResolver/PhpDoc/NodeAnalyzer/DocBlockNameImporter.php @@ -89,7 +89,7 @@ private function processFqnNameImport( Node $node, IdentifierTypeNode $identifierTypeNode, FullyQualifiedObjectType $fullyQualifiedObjectType - ): PhpDocParserNode { + ): IdentifierTypeNode { if ($this->classNameImportSkipper->shouldSkipNameForFullyQualifiedObjectType( $node, $fullyQualifiedObjectType diff --git a/packages/NodeTypeResolver/Reflection/BetterReflection/RectorBetterReflectionSourceLocatorFactory.php b/packages/NodeTypeResolver/Reflection/BetterReflection/RectorBetterReflectionSourceLocatorFactory.php index 54df651988d7..44403158df87 100644 --- a/packages/NodeTypeResolver/Reflection/BetterReflection/RectorBetterReflectionSourceLocatorFactory.php +++ b/packages/NodeTypeResolver/Reflection/BetterReflection/RectorBetterReflectionSourceLocatorFactory.php @@ -6,7 +6,6 @@ use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator; use PHPStan\BetterReflection\SourceLocator\Type\MemoizingSourceLocator; -use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; use PHPStan\Reflection\BetterReflection\BetterReflectionSourceLocatorFactory; use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocator\IntermediateSourceLocator; @@ -30,7 +29,7 @@ public function __construct( $this->intermediateSourceLocator = $intermediateSourceLocator; } - public function create(): SourceLocator + public function create(): MemoizingSourceLocator { $phpStanSourceLocator = $this->betterReflectionSourceLocatorFactory->create(); diff --git a/packages/NodeTypeResolver/TypeAnalyzer/CountableTypeAnalyzer.php b/packages/NodeTypeResolver/TypeAnalyzer/CountableTypeAnalyzer.php index 0717d2d95b1e..aaf99fef8584 100644 --- a/packages/NodeTypeResolver/TypeAnalyzer/CountableTypeAnalyzer.php +++ b/packages/NodeTypeResolver/TypeAnalyzer/CountableTypeAnalyzer.php @@ -5,10 +5,7 @@ namespace Rector\NodeTypeResolver\TypeAnalyzer; use PhpParser\Node; -use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; -use PHPStan\Type\Type; -use PHPStan\Type\UnionType; use Rector\NodeTypeResolver\NodeTypeCorrector\PregMatchTypeCorrector; use Rector\NodeTypeResolver\NodeTypeResolver; @@ -29,6 +26,11 @@ final class CountableTypeAnalyzer */ private $nodeTypeResolver; + /** + * @var ObjectType[] + */ + private $countableObjectTypes = []; + public function __construct( ArrayTypeAnalyzer $arrayTypeAnalyzer, NodeTypeResolver $nodeTypeResolver, @@ -37,60 +39,25 @@ public function __construct( $this->arrayTypeAnalyzer = $arrayTypeAnalyzer; $this->pregMatchTypeCorrector = $pregMatchTypeCorrector; $this->nodeTypeResolver = $nodeTypeResolver; - } - public function isCountableType(Node $node): bool - { - $nodeType = $this->nodeTypeResolver->resolve($node); - $nodeType = $this->pregMatchTypeCorrector->correct($node, $nodeType); - - if ($this->isCountableObjectType($nodeType)) { - return true; - } - - return $this->arrayTypeAnalyzer->isArrayType($node); - } - - private function isCountableObjectType(Type $type): bool - { - $countableObjectTypes = [ + $this->countableObjectTypes = [ new ObjectType('Countable'), new ObjectType('SimpleXMLElement'), new ObjectType('ResourceBundle'), ]; - - if ($type instanceof UnionType) { - return $this->isCountableUnionType($type, $countableObjectTypes); - } - - if ($type instanceof ObjectType) { - foreach ($countableObjectTypes as $countableObjectType) { - if (! is_a($type->getClassName(), $countableObjectType->getClassName(), true)) { - continue; - } - - return true; - } - } - - return false; } - /** - * @param ObjectType[] $countableObjectTypes - */ - private function isCountableUnionType(UnionType $unionType, array $countableObjectTypes): bool + public function isCountableType(Node $node): bool { - if ($unionType->isSubTypeOf(new NullType())->yes()) { - return false; - } + $nodeType = $this->nodeTypeResolver->resolve($node); + $nodeType = $this->pregMatchTypeCorrector->correct($node, $nodeType); - foreach ($countableObjectTypes as $countableObjectType) { - if ($unionType->isSuperTypeOf($countableObjectType)->yes()) { + foreach ($this->countableObjectTypes as $countableObjectType) { + if ($countableObjectType->isSuperTypeOf($nodeType)->yes()) { return true; } } - return false; + return $this->arrayTypeAnalyzer->isArrayType($node); } } diff --git a/packages/PHPStanStaticTypeMapper/TypeMapper/UnionTypeMapper.php b/packages/PHPStanStaticTypeMapper/TypeMapper/UnionTypeMapper.php index 09c9788455f9..7203df64e55f 100644 --- a/packages/PHPStanStaticTypeMapper/TypeMapper/UnionTypeMapper.php +++ b/packages/PHPStanStaticTypeMapper/TypeMapper/UnionTypeMapper.php @@ -309,15 +309,6 @@ private function resolveCompatibleObjectCandidate(UnionType $unionType): ?TypeWi return null; } - private function areTypeWithClassNamesRelated(TypeWithClassName $firstType, TypeWithClassName $secondType): bool - { - if (is_a($firstType->getClassName(), $secondType->getClassName(), true)) { - return true; - } - - return is_a($secondType->getClassName(), $firstType->getClassName(), true); - } - private function matchTwoObjectTypes(UnionType $unionType): ?TypeWithClassName { /** @var TypeWithClassName $unionedType */ @@ -335,6 +326,16 @@ private function matchTwoObjectTypes(UnionType $unionType): ?TypeWithClassName return null; } + private function areTypeWithClassNamesRelated(TypeWithClassName $firstType, TypeWithClassName $secondType): bool + { + if ($firstType->accepts($secondType, false)->yes()) { + return true; + } + + return $secondType->accepts($firstType, false) + ->yes(); + } + private function correctObjectType(TypeWithClassName $typeWithClassName): TypeWithClassName { if ($typeWithClassName->getClassName() === NodeAbstract::class) { diff --git a/packages/PHPStanStaticTypeMapper/Utils/TypeUnwrapper.php b/packages/PHPStanStaticTypeMapper/Utils/TypeUnwrapper.php index dc94c3cbe1e7..ff501be7dabe 100644 --- a/packages/PHPStanStaticTypeMapper/Utils/TypeUnwrapper.php +++ b/packages/PHPStanStaticTypeMapper/Utils/TypeUnwrapper.php @@ -47,10 +47,7 @@ public function unwrapFirstObjectTypeFromUnionType(Type $type): Type return $type; } - /** - * @return Type|UnionType - */ - public function removeNullTypeFromUnionType(UnionType $unionType): Type + public function removeNullTypeFromUnionType(UnionType $unionType): UnionType { $unionedTypesWithoutNullType = []; diff --git a/packages/StaticTypeMapper/PhpDocParser/IdentifierTypeMapper.php b/packages/StaticTypeMapper/PhpDocParser/IdentifierTypeMapper.php index 8db3b8c6de8d..1d9ef26f7275 100644 --- a/packages/StaticTypeMapper/PhpDocParser/IdentifierTypeMapper.php +++ b/packages/StaticTypeMapper/PhpDocParser/IdentifierTypeMapper.php @@ -13,6 +13,7 @@ use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\StaticType; +use PHPStan\Type\SubtractableType; use PHPStan\Type\Type; use Rector\NodeCollector\ScopeResolver\ParentClassScopeResolver; use Rector\NodeTypeResolver\Node\AttributeKey; @@ -97,7 +98,7 @@ public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $name return $this->objectTypeSpecifier->narrowToFullyQualifiedOrAliasedObjectType($node, $objectType); } - private function mapSelf(Node $node): Type + private function mapSelf(Node $node): SubtractableType { /** @var string|null $className */ $className = $node->getAttribute(AttributeKey::CLASS_NAME); diff --git a/packages/VendorLocker/NodeVendorLocker/ClassMethodReturnTypeOverrideGuard.php b/packages/VendorLocker/NodeVendorLocker/ClassMethodReturnTypeOverrideGuard.php index e6dacd2d2733..7a4fcce9f997 100644 --- a/packages/VendorLocker/NodeVendorLocker/ClassMethodReturnTypeOverrideGuard.php +++ b/packages/VendorLocker/NodeVendorLocker/ClassMethodReturnTypeOverrideGuard.php @@ -17,14 +17,11 @@ use PHPStan\Type\MixedType; use PHPStan\Type\StringType; use PHPStan\Type\Type; -use PHPStan\Type\TypeWithClassName; -use PHPStan\Type\UnionType; use Rector\Core\Exception\ShouldNotHappenException; use Rector\Core\PhpParser\Node\BetterNodeFinder; use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; -use Rector\NodeTypeResolver\NodeTypeResolver; final class ClassMethodReturnTypeOverrideGuard { @@ -40,11 +37,6 @@ final class ClassMethodReturnTypeOverrideGuard */ private $nodeNameResolver; - /** - * @var NodeTypeResolver - */ - private $nodeTypeResolver; - /** * @var ReflectionProvider */ @@ -62,13 +54,11 @@ final class ClassMethodReturnTypeOverrideGuard public function __construct( NodeNameResolver $nodeNameResolver, - NodeTypeResolver $nodeTypeResolver, ReflectionProvider $reflectionProvider, FamilyRelationsAnalyzer $familyRelationsAnalyzer, BetterNodeFinder $betterNodeFinder ) { $this->nodeNameResolver = $nodeNameResolver; - $this->nodeTypeResolver = $nodeTypeResolver; $this->reflectionProvider = $reflectionProvider; $this->familyRelationsAnalyzer = $familyRelationsAnalyzer; $this->betterNodeFinder = $betterNodeFinder; @@ -115,15 +105,12 @@ public function shouldSkipClassMethodOldTypeWithNewType(Type $oldType, Type $new } // new generic string type is more advanced than old array type - if ($oldType instanceof ArrayType && $newType instanceof ArrayType && ($oldType->getItemType() instanceof StringType && $newType->getItemType() instanceof GenericClassStringType)) { + if ($this->isFirstArrayTypeMoreAdvanced($oldType, $newType)) { return false; } - if ($oldType->isSuperTypeOf($newType)->yes()) { - return true; - } - - return $this->isArrayMutualType($newType, $oldType); + return $oldType->isSuperTypeOf($newType) + ->yes(); } private function shouldSkipChaoticClassMethods(ClassMethod $classMethod): bool @@ -160,54 +147,31 @@ private function shouldSkipChaoticClassMethods(ClassMethod $classMethod): bool return false; } - private function isArrayMutualType(Type $newType, Type $oldType): bool + private function hasClassMethodExprReturn(ClassMethod $classMethod): bool { - if (! $newType instanceof ArrayType) { - return false; - } + return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node): bool { + if (! $node instanceof Return_) { + return false; + } + + return $node->expr instanceof Expr; + }); + } + private function isFirstArrayTypeMoreAdvanced(Type $oldType, Type $newType): bool + { if (! $oldType instanceof ArrayType) { return false; } - $oldTypeWithClassName = $oldType->getItemType(); - if (! $oldTypeWithClassName instanceof TypeWithClassName) { + if (! $newType instanceof ArrayType) { return false; } - $arrayItemType = $newType->getItemType(); - if (! $arrayItemType instanceof UnionType) { + if (! $oldType->getItemType() instanceof StringType) { return false; } - $isMatchingClassTypes = false; - - foreach ($arrayItemType->getTypes() as $newUnionedType) { - if (! $newUnionedType instanceof TypeWithClassName) { - return false; - } - - $oldClass = $this->nodeTypeResolver->getFullyQualifiedClassName($oldTypeWithClassName); - $newClass = $this->nodeTypeResolver->getFullyQualifiedClassName($newUnionedType); - - if (is_a($oldClass, $newClass, true) || is_a($newClass, $oldClass, true)) { - $isMatchingClassTypes = true; - } else { - return false; - } - } - - return $isMatchingClassTypes; - } - - private function hasClassMethodExprReturn(ClassMethod $classMethod): bool - { - return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node): bool { - if (! $node instanceof Return_) { - return false; - } - - return $node->expr instanceof Expr; - }); + return $newType->getItemType() instanceof GenericClassStringType; } } diff --git a/phpstan.neon b/phpstan.neon index 82086425f08b..832d4a8c13d6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -336,10 +336,6 @@ parameters: - '#Method (.*?) should return (.*?)\|null but returns PhpParser\\Node\|null#' - '#Method (.*?) should return array but returns array#' -# - '#Parameter \#1 \$nodes of method Rector\\Core\\PhpParser\\Node\\BetterNodeFinder\:\:findFirst\(\) expects array\|PhpParser\\Node, array\|null given#' - - '#Parameter \#2 \$type of method Rector\\Core\\PhpParser\\Node\\BetterNodeFinder\:\:findInstanceOfName\(\) expects class\-string, string given#' -# - '#Method Rector\\Core\\PhpParser\\Node\\BetterNodeFinder\:\:findVariableOfName\(\) should return PhpParser\\Node\\Expr\\Variable\|null but returns T of PhpParser\\Node\|null#' - # fixed in php-parser master - '#Parameter \#4 \$classWithConstants of class Rector\\Privatization\\ValueObject\\ReplaceStringWithClassConstant constructor expects class\-string, string given#' @@ -431,7 +427,6 @@ parameters: - packages/NodeNameResolver/NodeNameResolver/ClassNameResolver.php - packages/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php - packages/BetterPhpDocParser/Printer/PhpDocInfoPrinter.php - - packages/BetterPhpDocParser/Printer/MultilineSpaceFormatPreserver.php - message: '#Instead of "ReflectionClass" class/interface use "PHPStan\\Reflection\\ClassReflection"#' @@ -473,7 +468,6 @@ parameters: paths: - src/Console/Command/ProcessCommand.php - - '#Cognitive complexity for "Rector\\EarlyReturn\\Rector\\If_\\ChangeAndIfToEarlyReturnRector\:\:refactor\(\)" is 10, keep it under 9#' - '#Parameter \#2 \$returnedStrictTypeNode of method Rector\\TypeDeclaration\\Rector\\ClassMethod\\ReturnTypeFromStrictTypedCallRector\:\:refactorSingleReturnType\(\) expects PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType, PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PHPStan\\Type\\UnionType given#' - '#Method Rector\\DowngradePhp80\\Rector\\ClassMethod\\DowngradeTrailingCommasInParamUseRector\:\:processUses\(\) should return PhpParser\\Node\\Expr\\Closure but returns PhpParser\\Node#' - '#Cognitive complexity for "Rector\\NodeTypeResolver\\NodeTypeResolver\:\:getStaticType\(\)" is 11, keep it under 9#' @@ -519,6 +513,7 @@ parameters: # known types - '#Parameter \#1 \$node of method Rector\\Naming\\Naming\\VariableNaming\:\:resolveFromMethodCall\(\) expects PhpParser\\Node\\Expr\\MethodCall\|PhpParser\\Node\\Expr\\NullsafeMethodCall\|PhpParser\\Node\\Expr\\StaticCall, PhpParser\\Node given#' + - '#Class with base "DependencyResolver" name is already used in "PHPStan\\Dependency\\DependencyResolver", "Rector\\Caching\\FileSystem\\DependencyResolver"\. Use unique name to make classes easy to recognize#' - message: '#Do not inherit from abstract class, better use composition#' diff --git a/rules/CodeQuality/Rector/Concat/JoinStringConcatRector.php b/rules/CodeQuality/Rector/Concat/JoinStringConcatRector.php index ede66a440197..f322eb906863 100644 --- a/rules/CodeQuality/Rector/Concat/JoinStringConcatRector.php +++ b/rules/CodeQuality/Rector/Concat/JoinStringConcatRector.php @@ -6,6 +6,7 @@ use Nette\Utils\Strings; use PhpParser\Node; +use PhpParser\Node\Expr; use PhpParser\Node\Expr\BinaryOp\Concat; use PhpParser\Node\Scalar\String_; use Rector\Core\Rector\AbstractRector; @@ -97,7 +98,7 @@ private function isTopMostConcatNode(Concat $concat): bool /** * @return Concat|String_ */ - private function joinConcatIfStrings(Concat $node): Node + private function joinConcatIfStrings(Concat $node): Expr { $concat = clone $node; diff --git a/rules/CodingStyle/Node/ConcatManipulator.php b/rules/CodingStyle/Node/ConcatManipulator.php index cefcf0213a1d..128e43dad39b 100644 --- a/rules/CodingStyle/Node/ConcatManipulator.php +++ b/rules/CodingStyle/Node/ConcatManipulator.php @@ -30,7 +30,7 @@ public function __construct( $this->nodeComparator = $nodeComparator; } - public function getFirstConcatItem(Concat $concat): Node + public function getFirstConcatItem(Concat $concat): Expr { // go to the deep, until there is no concat while ($concat->left instanceof Concat) { @@ -40,7 +40,7 @@ public function getFirstConcatItem(Concat $concat): Node return $concat->left; } - public function removeFirstItemFromConcat(Concat $concat): Node + public function removeFirstItemFromConcat(Concat $concat): Expr { // just 2 items, return right one if (! $concat->left instanceof Concat) { diff --git a/rules/DeadCode/NodeAnalyzer/InstanceOfUniqueKeyResolver.php b/rules/DeadCode/NodeAnalyzer/InstanceOfUniqueKeyResolver.php new file mode 100644 index 000000000000..5835068fcba0 --- /dev/null +++ b/rules/DeadCode/NodeAnalyzer/InstanceOfUniqueKeyResolver.php @@ -0,0 +1,40 @@ +nodeNameResolver = $nodeNameResolver; + } + + public function resolve(Instanceof_ $instanceof): ?string + { + if (! $instanceof->expr instanceof Variable) { + return null; + } + $variableName = $this->nodeNameResolver->getName($instanceof->expr); + if ($variableName === null) { + return null; + } + + $className = $this->nodeNameResolver->getName($instanceof->class); + if ($className === null) { + return null; + } + + return $variableName . '_' . $className; + } +} diff --git a/rules/DeadCode/Rector/BinaryOp/RemoveDuplicatedInstanceOfRector.php b/rules/DeadCode/Rector/BinaryOp/RemoveDuplicatedInstanceOfRector.php index f53088c59784..56a2371d7ab7 100644 --- a/rules/DeadCode/Rector/BinaryOp/RemoveDuplicatedInstanceOfRector.php +++ b/rules/DeadCode/Rector/BinaryOp/RemoveDuplicatedInstanceOfRector.php @@ -5,11 +5,10 @@ namespace Rector\DeadCode\Rector\BinaryOp; use PhpParser\Node; -use PhpParser\Node\Expr; use PhpParser\Node\Expr\BinaryOp; use PhpParser\Node\Expr\Instanceof_; -use PhpParser\Node\Expr\Variable; use Rector\Core\Rector\AbstractRector; +use Rector\DeadCode\NodeAnalyzer\InstanceOfUniqueKeyResolver; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -19,9 +18,14 @@ final class RemoveDuplicatedInstanceOfRector extends AbstractRector { /** - * @var string[] + * @var InstanceOfUniqueKeyResolver */ - private $duplicatedInstanceOfs = []; + private $instanceOfUniqueKeyResolver; + + public function __construct(InstanceOfUniqueKeyResolver $instanceOfUniqueKeyResolver) + { + $this->instanceOfUniqueKeyResolver = $instanceOfUniqueKeyResolver; + } public function getRuleDefinition(): RuleDefinition { @@ -65,97 +69,41 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $this->resolveDuplicatedInstancesOf($node); - if ($this->duplicatedInstanceOfs === []) { + $duplicatedInstanceOfs = $this->resolveDuplicatedInstancesOf($node); + if ($duplicatedInstanceOfs === []) { return null; } - return $this->traverseBinaryOpAndRemoveDuplicatedInstanceOfs($node); + $this->removeNodes($duplicatedInstanceOfs); + + return $node; } - private function resolveDuplicatedInstancesOf(BinaryOp $binaryOp): void + /** + * @return Instanceof_[] + */ + private function resolveDuplicatedInstancesOf(BinaryOp $binaryOp): array { - $this->duplicatedInstanceOfs = []; + $duplicatedInstanceOfs = []; /** @var Instanceof_[] $instanceOfs */ - $instanceOfs = $this->betterNodeFinder->findInstanceOf([$binaryOp], Instanceof_::class); + $instanceOfs = $this->betterNodeFinder->findInstanceOf($binaryOp, Instanceof_::class); - $instanceOfsByClass = []; + $uniqueInstanceOfKeys = []; foreach ($instanceOfs as $instanceOf) { - $variableClassKey = $this->createUniqueKeyForInstanceOf($instanceOf); - if ($variableClassKey === null) { + $uniqueKey = $this->instanceOfUniqueKeyResolver->resolve($instanceOf); + if ($uniqueKey === null) { continue; } - $instanceOfsByClass[$variableClassKey][] = $instanceOf; - } - - foreach ($instanceOfsByClass as $variableClassKey => $instanceOfs) { - if (count($instanceOfs) < 2) { - unset($instanceOfsByClass[$variableClassKey]); - } - } - - $this->duplicatedInstanceOfs = array_keys($instanceOfsByClass); - } - - private function traverseBinaryOpAndRemoveDuplicatedInstanceOfs(BinaryOp $binaryOp): Node - { - $this->traverseNodesWithCallable([&$binaryOp], function (Node $node): ?Node { - if (! $node instanceof BinaryOp) { - return null; - } - - if ($node->left instanceof Instanceof_) { - return $this->processBinaryWithFirstInstaneOf($node->left, $node->right); - } - - if ($node->right instanceof Instanceof_) { - return $this->processBinaryWithFirstInstaneOf($node->right, $node->left); + // already present before → duplicated + if (in_array($uniqueKey, $uniqueInstanceOfKeys, true)) { + $duplicatedInstanceOfs[] = $instanceOf; } - return null; - }); - - return $binaryOp; - } - - private function createUniqueKeyForInstanceOf(Instanceof_ $instanceof): ?string - { - if (! $instanceof->expr instanceof Variable) { - return null; - } - $variableName = $this->getName($instanceof->expr); - if ($variableName === null) { - return null; - } - - $className = $this->getName($instanceof->class); - if ($className === null) { - return null; - } - - return $variableName . '_' . $className; - } - - private function processBinaryWithFirstInstaneOf(Instanceof_ $instanceof, Expr $otherExpr): ?Expr - { - $variableClassKey = $this->createUniqueKeyForInstanceOf($instanceof); - - if (! in_array($variableClassKey, $this->duplicatedInstanceOfs, true)) { - return null; + $uniqueInstanceOfKeys[] = $uniqueKey; } - // remove just once - $this->removeClassFromDuplicatedInstanceOfs($variableClassKey); - - // remove left instanceof - return $otherExpr; - } - - private function removeClassFromDuplicatedInstanceOfs(string $variableClassKey): void - { - // remove just once - unset($this->duplicatedInstanceOfs[array_search($variableClassKey, $this->duplicatedInstanceOfs, true)]); + return $duplicatedInstanceOfs; } } diff --git a/rules/DeadCode/Rector/MethodCall/RemoveEmptyMethodCallRector.php b/rules/DeadCode/Rector/MethodCall/RemoveEmptyMethodCallRector.php index 82c27dbb9141..2b7a496ae0cd 100644 --- a/rules/DeadCode/Rector/MethodCall/RemoveEmptyMethodCallRector.php +++ b/rules/DeadCode/Rector/MethodCall/RemoveEmptyMethodCallRector.php @@ -5,6 +5,7 @@ namespace Rector\DeadCode\Rector\MethodCall; use PhpParser\Node; +use PhpParser\Node\Expr; use PhpParser\Node\Expr\ArrowFunction; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\MethodCall; @@ -140,7 +141,7 @@ private function shouldSkipClassMethod(?Class_ $class, MethodCall $methodCall): return count((array) $classMethod->stmts) !== 0; } - private function processArrowFunction(ArrowFunction $arrowFunction, MethodCall $methodCall): Node + private function processArrowFunction(ArrowFunction $arrowFunction, MethodCall $methodCall): Expr { $parentOfParent = $arrowFunction->getAttribute(AttributeKey::PARENT_NODE); if ($parentOfParent instanceof Expression) { diff --git a/rules/DeadCode/ValueObject/VariableNodeUse.php b/rules/DeadCode/ValueObject/VariableNodeUse.php index 4aaa01fb5aaf..cf6ab95fe2f9 100644 --- a/rules/DeadCode/ValueObject/VariableNodeUse.php +++ b/rules/DeadCode/ValueObject/VariableNodeUse.php @@ -75,7 +75,7 @@ public function isType(string $type): bool return $this->type === $type; } - public function getVariableNode(): Node + public function getVariableNode(): Variable { return $this->variable; } diff --git a/rules/DoctrineCodeQuality/NodeAnalyzer/EntityObjectTypeResolver.php b/rules/DoctrineCodeQuality/NodeAnalyzer/EntityObjectTypeResolver.php index 99e26ecd77d7..1217a3a0b7d9 100644 --- a/rules/DoctrineCodeQuality/NodeAnalyzer/EntityObjectTypeResolver.php +++ b/rules/DoctrineCodeQuality/NodeAnalyzer/EntityObjectTypeResolver.php @@ -13,7 +13,7 @@ use PhpParser\Node\Stmt\Expression; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; -use PHPStan\Type\Type; +use PHPStan\Type\SubtractableType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\ValueObject\PhpDocNode\Doctrine\Class_\EntityTagValueNode; use Rector\Core\Exception\ShouldNotHappenException; @@ -57,7 +57,7 @@ public function __construct( $this->nodeNameResolver = $nodeNameResolver; } - public function resolveFromRepositoryClass(Class_ $repositoryClass): Type + public function resolveFromRepositoryClass(Class_ $repositoryClass): SubtractableType { $entityType = $this->resolveFromParentConstruct($repositoryClass); if (! $entityType instanceof MixedType) { @@ -77,7 +77,7 @@ public function resolveFromRepositoryClass(Class_ $repositoryClass): Type return new MixedType(); } - private function resolveFromGetterReturnType(Class_ $repositoryClass): Type + private function resolveFromGetterReturnType(Class_ $repositoryClass): SubtractableType { foreach ($repositoryClass->getMethods() as $classMethod) { if (! $classMethod->isPublic()) { @@ -98,7 +98,7 @@ private function resolveFromGetterReturnType(Class_ $repositoryClass): Type return new MixedType(); } - private function resolveFromMatchingEntityAnnotation(Class_ $repositoryClass): Type + private function resolveFromMatchingEntityAnnotation(Class_ $repositoryClass): SubtractableType { $repositoryClassName = $repositoryClass->getAttribute(AttributeKey::CLASS_NAME); @@ -133,7 +133,7 @@ private function resolveFromMatchingEntityAnnotation(Class_ $repositoryClass): T return new MixedType(); } - private function resolveFromParentConstruct(Class_ $class): Type + private function resolveFromParentConstruct(Class_ $class): SubtractableType { $constructorClassMethod = $class->getMethod(MethodName::CONSTRUCT); if (! $constructorClassMethod instanceof ClassMethod) { diff --git a/rules/DoctrineCodeQuality/PhpDoc/CollectionTypeFactory.php b/rules/DoctrineCodeQuality/PhpDoc/CollectionTypeFactory.php index 80a5166ecf79..7b5d37e421ab 100644 --- a/rules/DoctrineCodeQuality/PhpDoc/CollectionTypeFactory.php +++ b/rules/DoctrineCodeQuality/PhpDoc/CollectionTypeFactory.php @@ -8,7 +8,6 @@ use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; -use PHPStan\Type\Type; use PHPStan\Type\UnionType; use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType; @@ -22,7 +21,7 @@ public function createType(FullyQualifiedObjectType $fullyQualifiedObjectType): return new UnionType([$genericType, $arrayType]); } - private function createGenericObjectType(FullyQualifiedObjectType $fullyQualifiedObjectType): Type + private function createGenericObjectType(FullyQualifiedObjectType $fullyQualifiedObjectType): GenericObjectType { $genericTypes = [new IntegerType(), $fullyQualifiedObjectType]; diff --git a/rules/DowngradePhp74/Rector/Array_/DowngradeArraySpreadRector.php b/rules/DowngradePhp74/Rector/Array_/DowngradeArraySpreadRector.php index 4c9677892bb5..0564e555e85d 100644 --- a/rules/DowngradePhp74/Rector/Array_/DowngradeArraySpreadRector.php +++ b/rules/DowngradePhp74/Rector/Array_/DowngradeArraySpreadRector.php @@ -118,7 +118,7 @@ private function shouldRefactor(Array_ $array): bool return false; } - private function refactorNode(Array_ $array): Node + private function refactorNode(Array_ $array): FuncCall { $newItems = $this->createArrayItems($array); // Replace this array node with an `array_merge` diff --git a/rules/NetteTesterToPHPUnit/AssertManipulator.php b/rules/NetteTesterToPHPUnit/AssertManipulator.php index 3c5b19075304..e4c20d050fb5 100644 --- a/rules/NetteTesterToPHPUnit/AssertManipulator.php +++ b/rules/NetteTesterToPHPUnit/AssertManipulator.php @@ -4,7 +4,6 @@ namespace Rector\NetteTesterToPHPUnit; -use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Cast\Bool_; use PhpParser\Node\Expr\Closure; @@ -138,7 +137,7 @@ public function __construct( /** * @return StaticCall|MethodCall */ - public function processStaticCall(StaticCall $staticCall): Node + public function processStaticCall(StaticCall $staticCall): Expr { if ($this->nodeNameResolver->isNames($staticCall->name, ['truthy', 'falsey'])) { return $this->processTruthyOrFalseyCall($staticCall); diff --git a/rules/Php70/ValueObject/VariableAssignPair.php b/rules/Php70/ValueObject/VariableAssignPair.php index 6c448d57e394..7cb868ed058a 100644 --- a/rules/Php70/ValueObject/VariableAssignPair.php +++ b/rules/Php70/ValueObject/VariableAssignPair.php @@ -5,6 +5,7 @@ namespace Rector\Php70\ValueObject; use PhpParser\Node; +use PhpParser\Node\Expr; use PhpParser\Node\Expr\ArrayDimFetch; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\AssignOp; @@ -38,7 +39,7 @@ public function __construct(Node $variable, Node $node) /** * @return Variable|ArrayDimFetch|PropertyFetch|StaticPropertyFetch */ - public function getVariable(): Node + public function getVariable(): Expr { return $this->variable; } @@ -46,7 +47,7 @@ public function getVariable(): Node /** * @return Assign|AssignOp|AssignRef */ - public function getAssign(): Node + public function getAssign(): Expr { return $this->assign; } diff --git a/rules/Php80/NodeFactory/StrStartsWithFuncCallFactory.php b/rules/Php80/NodeFactory/StrStartsWithFuncCallFactory.php index a83f3c16e8dd..f9c1d79dc6f0 100644 --- a/rules/Php80/NodeFactory/StrStartsWithFuncCallFactory.php +++ b/rules/Php80/NodeFactory/StrStartsWithFuncCallFactory.php @@ -4,8 +4,8 @@ namespace Rector\Php80\NodeFactory; -use PhpParser\Node; use PhpParser\Node\Arg; +use PhpParser\Node\Expr; use PhpParser\Node\Expr\BooleanNot; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; @@ -16,7 +16,7 @@ final class StrStartsWithFuncCallFactory /** * @return FuncCall|BooleanNot */ - public function createStrStartsWith(StrStartsWith $strStartsWith): Node + public function createStrStartsWith(StrStartsWith $strStartsWith): Expr { $args = [new Arg($strStartsWith->getHaystackExpr()), new Arg($strStartsWith->getNeedleExpr())]; diff --git a/rules/Php80/NodeManipulator/TokenManipulator.php b/rules/Php80/NodeManipulator/TokenManipulator.php index 6265e7bbd7b0..71b9ab928e91 100644 --- a/rules/Php80/NodeManipulator/TokenManipulator.php +++ b/rules/Php80/NodeManipulator/TokenManipulator.php @@ -336,7 +336,7 @@ private function shouldSkipNodeRemovalForPartOfIf(FuncCall $funcCall): bool return false; } - private function matchParentNodeInCaseOfIdenticalTrue(FuncCall $funcCall): Node + private function matchParentNodeInCaseOfIdenticalTrue(FuncCall $funcCall): Expr { $parentNode = $funcCall->getAttribute(AttributeKey::PARENT_NODE); if ($parentNode instanceof Identical) { diff --git a/rules/Transform/Rector/FileWithoutNamespace/FunctionToStaticMethodRector.php b/rules/Transform/Rector/FileWithoutNamespace/FunctionToStaticMethodRector.php index 45ccf8453f17..4f21ee892682 100644 --- a/rules/Transform/Rector/FileWithoutNamespace/FunctionToStaticMethodRector.php +++ b/rules/Transform/Rector/FileWithoutNamespace/FunctionToStaticMethodRector.php @@ -7,6 +7,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\StaticCall; +use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Namespace_; @@ -194,7 +195,7 @@ private function printStaticMethodClass( * @param Namespace_|FileWithoutNamespace $node * @return Namespace_|Class_ */ - private function resolveNodeToPrint(Node $node, Class_ $class): Node + private function resolveNodeToPrint(Node $node, Class_ $class): Stmt { if ($node instanceof Namespace_) { return new Namespace_($node->name, [$class]); diff --git a/rules/Transform/Rector/FuncCall/ArgumentFuncCallToMethodCallRector.php b/rules/Transform/Rector/FuncCall/ArgumentFuncCallToMethodCallRector.php index 712b73d2a051..d8f71df8396c 100644 --- a/rules/Transform/Rector/FuncCall/ArgumentFuncCallToMethodCallRector.php +++ b/rules/Transform/Rector/FuncCall/ArgumentFuncCallToMethodCallRector.php @@ -5,6 +5,7 @@ namespace Rector\Transform\Rector\FuncCall; use PhpParser\Node; +use PhpParser\Node\Expr; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; @@ -239,7 +240,7 @@ private function refactorArrayFunctionToMethodCall( private function refactorEmptyFuncCallArgs( ArgumentFuncCallToMethodCall $argumentFuncCallToMethodCall, PropertyFetch $propertyFetch - ): Node { + ): Expr { if ($argumentFuncCallToMethodCall->getMethodIfNoArgs()) { $methodName = $argumentFuncCallToMethodCall->getMethodIfNoArgs(); if (! is_string($methodName)) { diff --git a/rules/TypeDeclaration/PHPStan/Type/ObjectTypeSpecifier.php b/rules/TypeDeclaration/PHPStan/Type/ObjectTypeSpecifier.php index c59282e0aea5..986cdeeef7e1 100644 --- a/rules/TypeDeclaration/PHPStan/Type/ObjectTypeSpecifier.php +++ b/rules/TypeDeclaration/PHPStan/Type/ObjectTypeSpecifier.php @@ -14,6 +14,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; +use PHPStan\Type\SubtractableType; use PHPStan\Type\Type; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType; @@ -35,7 +36,7 @@ public function __construct(ReflectionProvider $reflectionProvider) /** * @return AliasedObjectType|FullyQualifiedObjectType|ObjectType|MixedType */ - public function narrowToFullyQualifiedOrAliasedObjectType(Node $node, ObjectType $objectType): Type + public function narrowToFullyQualifiedOrAliasedObjectType(Node $node, ObjectType $objectType): SubtractableType { /** @var Use_[]|null $uses */ $uses = $node->getAttribute(AttributeKey::USE_NODES); diff --git a/rules/TypeDeclaration/PhpParserTypeAnalyzer.php b/rules/TypeDeclaration/PhpParserTypeAnalyzer.php index f34fbe60effd..17a1e9de07d1 100644 --- a/rules/TypeDeclaration/PhpParserTypeAnalyzer.php +++ b/rules/TypeDeclaration/PhpParserTypeAnalyzer.php @@ -9,25 +9,25 @@ use PhpParser\Node\Name; use PhpParser\Node\NullableType; use PhpParser\Node\UnionType; -use Rector\NodeNameResolver\NodeNameResolver; +use Rector\StaticTypeMapper\StaticTypeMapper; final class PhpParserTypeAnalyzer { /** - * @var NodeNameResolver + * @var StaticTypeMapper */ - private $nodeNameResolver; + private $staticTypeMapper; - public function __construct(NodeNameResolver $nodeNameResolver) + public function __construct(StaticTypeMapper $staticTypeMapper) { - $this->nodeNameResolver = $nodeNameResolver; + $this->staticTypeMapper = $staticTypeMapper; } /** * @param Name|NullableType|UnionType|Identifier $possibleSubtype * @param Name|NullableType|UnionType|Identifier $possibleParentType */ - public function isSubtypeOf(Node $possibleSubtype, Node $possibleParentType): bool + public function isCovariantSubtypeOf(Node $possibleSubtype, Node $possibleParentType): bool { // skip until PHP 8 is out if ($this->isUnionType($possibleSubtype, $possibleParentType)) { @@ -36,7 +36,7 @@ public function isSubtypeOf(Node $possibleSubtype, Node $possibleParentType): bo // possible - https://3v4l.org/ZuJCh if ($possibleSubtype instanceof NullableType && ! $possibleParentType instanceof NullableType) { - return $this->isSubtypeOf($possibleSubtype->type, $possibleParentType); + return $this->isCovariantSubtypeOf($possibleSubtype->type, $possibleParentType); } // not possible - https://3v4l.org/iNDTc @@ -44,25 +44,11 @@ public function isSubtypeOf(Node $possibleSubtype, Node $possibleParentType): bo return false; } - // unwrap nullable types - $possibleParentType = $this->unwrapNullableAndToString($possibleParentType); - $possibleSubtype = $this->unwrapNullableAndToString($possibleSubtype); + $subtypeType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($possibleParentType); + $parentType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($possibleSubtype); - if (is_a($possibleSubtype, $possibleParentType, true)) { - return true; - } - - if ($this->isTraversableOrIterableSubtype($possibleSubtype, $possibleParentType)) { - return true; - } - - if ($possibleParentType === $possibleSubtype) { - return true; - } - if (! ctype_upper($possibleSubtype[0])) { - return false; - } - return $possibleParentType === 'object'; + return $parentType->isSuperTypeOf($subtypeType) + ->yes(); } private function isUnionType(Node $possibleSubtype, Node $possibleParentType): bool @@ -73,26 +59,4 @@ private function isUnionType(Node $possibleSubtype, Node $possibleParentType): b return $possibleParentType instanceof UnionType; } - - private function unwrapNullableAndToString(Node $node): string - { - if (! $node instanceof NullableType) { - return $this->nodeNameResolver->getName($node); - } - - return $this->nodeNameResolver->getName($node->type); - } - - private function isTraversableOrIterableSubtype(string $possibleSubtype, string $possibleParentType): bool - { - if (in_array($possibleSubtype, ['array', 'Traversable'], true) && $possibleParentType === 'iterable') { - return true; - } - - if (! in_array($possibleSubtype, ['array', 'ArrayIterator'], true)) { - return false; - } - - return $possibleParentType === 'countable'; - } } diff --git a/rules/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector.php index e19a6a5b960c..210f650226bb 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector.php @@ -163,6 +163,7 @@ public function refactor(Node $node): ?Node } $currentReturnType = $phpDocInfo->getReturnType(); + if ($this->classMethodReturnTypeOverrideGuard->shouldSkipClassMethodOldTypeWithNewType( $currentReturnType, $inferredReturnType diff --git a/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector.php b/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector.php index 2877545d6c69..977b2e235f22 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector.php @@ -132,7 +132,7 @@ public function refactor(Node $node): ?Node /** * @param ClassMethod|Function_|Closure $node */ - private function processSingleUnionType(Node $node, UnionType $unionType, NullableType $nullableType): Node + private function processSingleUnionType(Node $node, UnionType $unionType, NullableType $nullableType): FunctionLike { $types = $unionType->getTypes(); $returnType = $types[0] instanceof ObjectType && $types[1] instanceof NullType @@ -250,7 +250,7 @@ private function refactorSingleReturnType( Return_ $return, Node $returnedStrictTypeNode, FunctionLike $functionLike - ): Node { + ): FunctionLike { $resolvedType = $this->nodeTypeResolver->resolve($return); if ($resolvedType instanceof UnionType) { diff --git a/rules/TypeDeclaration/Rector/FunctionLike/ReturnTypeDeclarationRector.php b/rules/TypeDeclaration/Rector/FunctionLike/ReturnTypeDeclarationRector.php index e8a099e3edbb..38c387263a3a 100644 --- a/rules/TypeDeclaration/Rector/FunctionLike/ReturnTypeDeclarationRector.php +++ b/rules/TypeDeclaration/Rector/FunctionLike/ReturnTypeDeclarationRector.php @@ -265,7 +265,7 @@ private function addReturnType(FunctionLike $functionLike, Node $inferredReturnN return; } - $isSubtype = $this->phpParserTypeAnalyzer->isSubtypeOf($inferredReturnNode, $functionLike->returnType); + $isSubtype = $this->phpParserTypeAnalyzer->isCovariantSubtypeOf($inferredReturnNode, $functionLike->returnType); if ($this->isAtLeastPhpVersion(PhpVersionFeature::COVARIANT_RETURN) && $isSubtype) { $functionLike->returnType = $inferredReturnNode; diff --git a/src/DependencyInjection/RectorContainerFactory.php b/src/DependencyInjection/RectorContainerFactory.php index 6846efcf9c1f..fe5c53e56831 100644 --- a/src/DependencyInjection/RectorContainerFactory.php +++ b/src/DependencyInjection/RectorContainerFactory.php @@ -4,14 +4,13 @@ namespace Rector\Core\DependencyInjection; -use Psr\Container\ContainerInterface; use Rector\Caching\Detector\ChangedFilesDetector; use Rector\Core\Configuration\Configuration; use Rector\Core\HttpKernel\RectorKernel; use Rector\Core\Stubs\PHPStanStubLoader; use Rector\Core\ValueObject\Bootstrap\BootstrapConfigs; use Rector\Testing\PHPUnit\StaticPHPUnitEnvironment; -use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symplify\PackageBuilder\Console\Input\StaticInputDetector; use Symplify\SmartFileSystem\SmartFileInfo; diff --git a/src/NodeManipulator/IfManipulator.php b/src/NodeManipulator/IfManipulator.php index 856d90c24ab2..aa8093279df1 100644 --- a/src/NodeManipulator/IfManipulator.php +++ b/src/NodeManipulator/IfManipulator.php @@ -422,7 +422,7 @@ private function hasOnlyStmtOfType(If_ $if, string $desiredType): bool return is_a($stmts[0], $desiredType); } - private function getIfCondVar(If_ $if): Node + private function getIfCondVar(If_ $if): Expr { /** @var Identical|NotIdentical $ifCond */ $ifCond = $if->cond; diff --git a/src/PhpParser/Parser/PhpParserLexerFactory.php b/src/PhpParser/Parser/PhpParserLexerFactory.php index 3a928ed4670d..bc92c4610f72 100644 --- a/src/PhpParser/Parser/PhpParserLexerFactory.php +++ b/src/PhpParser/Parser/PhpParserLexerFactory.php @@ -4,7 +4,6 @@ namespace Rector\Core\PhpParser\Parser; -use PhpParser\Lexer; use PhpParser\Lexer\Emulative; /** @@ -13,7 +12,7 @@ */ final class PhpParserLexerFactory { - public function create(): Lexer + public function create(): Emulative { return new Emulative([ 'usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos'], diff --git a/utils/phpstan-extensions/src/Rule/NoInstanceOfStaticReflectionRule.php b/utils/phpstan-extensions/src/Rule/NoInstanceOfStaticReflectionRule.php index 36e40291ba1d..350614ae1a30 100644 --- a/utils/phpstan-extensions/src/Rule/NoInstanceOfStaticReflectionRule.php +++ b/utils/phpstan-extensions/src/Rule/NoInstanceOfStaticReflectionRule.php @@ -110,6 +110,7 @@ private function resolveExprStaticType(Node $node, Scope $scope): ?Type } $typeArgValue = $node->args[1]->value; + return $scope->getType($typeArgValue); } } diff --git a/utils/phpstan-extensions/tests/Rule/NoInstanceOfStaticReflectionRule/Fixture/SkipTypesArray.php b/utils/phpstan-extensions/tests/Rule/NoInstanceOfStaticReflectionRule/Fixture/SkipTypesArray.php new file mode 100644 index 000000000000..d627fbecb3c0 --- /dev/null +++ b/utils/phpstan-extensions/tests/Rule/NoInstanceOfStaticReflectionRule/Fixture/SkipTypesArray.php @@ -0,0 +1,48 @@ +> + */ + private const COLLECTABLE_NODE_TYPES = [ + Class_::class, + Interface_::class, + ClassConst::class, + ClassConstFetch::class, + New_::class, + StaticCall::class, + MethodCall::class, + Array_::class, + Param::class, + ]; + + public function isCollectableNode(Node $node): bool + { + foreach (self::COLLECTABLE_NODE_TYPES as $collectableNodeType) { + /** @var class-string $collectableNodeType */ + if (is_a($node, $collectableNodeType, true)) { + return true; + } + } + + return false; + } +} diff --git a/utils/phpstan-extensions/tests/Rule/NoInstanceOfStaticReflectionRule/NoInstanceOfStaticReflectionRuleTest.php b/utils/phpstan-extensions/tests/Rule/NoInstanceOfStaticReflectionRule/NoInstanceOfStaticReflectionRuleTest.php index 71029a630ba6..4fcbb332dd57 100644 --- a/utils/phpstan-extensions/tests/Rule/NoInstanceOfStaticReflectionRule/NoInstanceOfStaticReflectionRuleTest.php +++ b/utils/phpstan-extensions/tests/Rule/NoInstanceOfStaticReflectionRule/NoInstanceOfStaticReflectionRuleTest.php @@ -36,6 +36,7 @@ public function provideData(): Iterator yield [__DIR__ . '/Fixture/SkipArrayClassString.php', []]; yield [__DIR__ . '/Fixture/SkipReflection.php', []]; yield [__DIR__ . '/Fixture/SkipDateTime.php', []]; + yield [__DIR__ . '/Fixture/SkipTypesArray.php', []]; } protected function getRule(): Rule