diff --git a/build/target-repository/composer.json b/build/target-repository/composer.json index ffe6958ddc5..0ab4cf0d464 100644 --- a/build/target-repository/composer.json +++ b/build/target-repository/composer.json @@ -9,7 +9,7 @@ ], "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.3.2" + "phpstan/phpstan": "^2.3.3" }, "autoload": { "files": [ diff --git a/composer.json b/composer.json index a99f7c8ccc7..063fdd3cdf3 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,8 @@ "nette/utils": "^4.1.5", "nikic/php-parser": "^5.9", "ondram/ci-detector": "^4.2", - "phpstan/phpdoc-parser": "^2.3.3", - "phpstan/phpstan": "^2.3.2", + "phpstan/phpdoc-parser": "^2.3.8", + "phpstan/phpstan": "^2.3.3", "react/child-process": "^0.6.5", "react/event-loop": "^1.6", "react/socket": "^1.17", diff --git a/src/Application/ChangedNodeScopeRefresher.php b/src/Application/ChangedNodeScopeRefresher.php index ddd1b1852ad..43a8746a07d 100644 --- a/src/Application/ChangedNodeScopeRefresher.php +++ b/src/Application/ChangedNodeScopeRefresher.php @@ -22,6 +22,7 @@ use PhpParser\Node\PropertyItem; use PhpParser\Node\StaticVar; use PhpParser\Node\Stmt; +use PhpParser\Node\Stmt\Catch_; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Declare_; @@ -33,6 +34,7 @@ use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\Static_; +use PhpParser\Node\Stmt\TryCatch; use PhpParser\Node\Stmt\Use_; use PhpParser\Node\UseItem; use PHPStan\Analyser\MutatingScope; @@ -158,6 +160,12 @@ private function resolveStmts(Node $node): array ])]; } + // Catch_ is not processed standalone by PHPStan, only as part of a TryCatch; an empty try + // makes it a catch no throw point reaches, which PHPStan still walks with the catch scope + if ($node instanceof Catch_) { + return [new TryCatch([], [$node])]; + } + if ($node instanceof Stmt) { return [$node]; } diff --git a/src/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php b/src/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php index 420e9fc7921..34fca2be541 100644 --- a/src/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php +++ b/src/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php @@ -106,14 +106,6 @@ public function parseWithNode(TokenIterator $tokenIterator, Node $node): PhpDocN return $phpDocNode; } - public function parseTag(TokenIterator $tokenIterator): PhpDocTagNode - { - $tag = $this->resolveTag($tokenIterator); - $phpDocTagValueNode = $this->parseTagValue($tokenIterator, $tag); - - return new PhpDocTagNode($tag, $phpDocTagValueNode); - } - public function parseTagValue(TokenIterator $tokenIterator, string $tag): PhpDocTagValueNode { $isPrecededByHorizontalWhitespace = $tokenIterator->isPrecededByHorizontalWhitespace(); @@ -166,27 +158,4 @@ private function parseChildAndStoreItsPositions(TokenIterator $tokenIterator): P return $phpDocNode; } - - private function resolveTag(TokenIterator $tokenIterator): string - { - $tag = $tokenIterator->currentTokenValue(); - $tokenIterator->next(); - - // there is a space → stop - if ($tokenIterator->isPrecededByHorizontalWhitespace()) { - return $tag; - } - - // is not e.g "@var " - // join tags like "@ORM\Column" etc. - if (! $tokenIterator->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) { - return $tag; - } - - // @todo use joinUntil("(")? - $tag .= $tokenIterator->currentTokenValue(); - $tokenIterator->next(); - - return $tag; - } } diff --git a/src/NodeTypeResolver/NodeTypeResolver.php b/src/NodeTypeResolver/NodeTypeResolver.php index a516fe7a2b9..569676419b4 100644 --- a/src/NodeTypeResolver/NodeTypeResolver.php +++ b/src/NodeTypeResolver/NodeTypeResolver.php @@ -260,7 +260,7 @@ public function getNativeType(Expr $expr): Type } } - $type = $this->resolveNativeTypeWithBuiltinMethodCallFallback($expr, $scope); + $type = $this->resolveNativeTypeWithBuiltinFuncCallFallback($expr, $scope); if ($expr instanceof ArrayDimFetch) { $type = $this->resolveArrayDimFetchType($expr, $scope, $type); } @@ -575,18 +575,11 @@ private function resolveTernaryType(Ternary $ternary): MixedType|UnionType } /** - * Method calls on native PHP classes report mixed, - * even on strict known type; this fallbacks to getType() that provides correct type + * Calls of native PHP functions report their signature-map return type as native; + * this fallbacks to getType() that provides correct type */ - private function resolveNativeTypeWithBuiltinMethodCallFallback(Expr $expr, Scope $scope): Type + private function resolveNativeTypeWithBuiltinFuncCallFallback(Expr $expr, Scope $scope): Type { - if ($expr instanceof MethodCall) { - $callerType = $scope->getType($expr->var); - if ($callerType instanceof ObjectType && $callerType->getClassReflection() instanceof ClassReflection && $callerType->getClassReflection()->isBuiltin()) { - return $scope->getType($expr); - } - } - if ($expr instanceof FuncCall) { if (! $expr->name instanceof Name) { return $scope->getNativeType($expr); diff --git a/src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php b/src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php index 384f6c6cbeb..dc88d0c2281 100644 --- a/src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php +++ b/src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php @@ -4,13 +4,11 @@ namespace Rector\NodeTypeResolver\PHPStan\Scope; -use Error; use PhpParser\Node; use PhpParser\Node\ArrayItem; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Expr\ArrayDimFetch; -use PhpParser\Node\Expr\ArrowFunction; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\AssignOp; use PhpParser\Node\Expr\AssignRef; @@ -21,7 +19,6 @@ use PhpParser\Node\Expr\Cast; use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\Clone_; -use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\Empty_; use PhpParser\Node\Expr\ErrorSuppress; @@ -44,14 +41,12 @@ use PhpParser\Node\Expr\Throw_; use PhpParser\Node\Expr\UnaryMinus; use PhpParser\Node\Expr\UnaryPlus; -use PhpParser\Node\Expr\Variable; use PhpParser\Node\Expr\Yield_; use PhpParser\Node\Expr\YieldFrom; use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\NullableType; use PhpParser\Node\Stmt; -use PhpParser\Node\Stmt\Catch_; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Do_; use PhpParser\Node\Stmt\Echo_; @@ -79,7 +74,6 @@ use PHPStan\Node\FunctionCallableNode; use PHPStan\Node\InstantiationCallableNode; use PHPStan\Node\MethodCallableNode; -use PHPStan\Node\Printer\Printer; use PHPStan\Node\StaticMethodCallableNode; use PHPStan\Node\UnreachableStatementNode; use PHPStan\Node\VirtualNode; @@ -88,11 +82,8 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; -use PHPStan\Type\ObjectType; -use PHPStan\Type\TypeCombinator; use Rector\Contract\PhpParser\DecoratingNodeVisitorInterface; use Rector\NodeAnalyzer\ClassAnalyzer; -use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpParser\Node\FileNode; use Rector\Util\Reflection\PrivatesAccessor; @@ -116,7 +107,6 @@ public function __construct( array $decoratingNodeVisitors, private ScopeFactory $scopeFactory, private PrivatesAccessor $privatesAccessor, - private NodeNameResolver $nodeNameResolver, private ClassAnalyzer $classAnalyzer ) { // @todo make use of immutable, to avoid tedious traversing @@ -134,7 +124,7 @@ public function processNodes( ): array { $scope = $formerMutatingScope ?? $this->scopeFactory->createFromFile($filePath); - $this->fillScope($stmts, $filePath, $scope); + $this->fillScope($stmts, $scope); // use after scope filling so DecoratingNodeVisitorInterface instance can fetch the scope of target node // @see https://github.com/rectorphp/rector-src/pull/7721#discussion_r2595932460 @@ -146,15 +136,9 @@ public function processNodes( /** * @param Stmt[] $stmts */ - private function fillScope(array $stmts, string $filePath, MutatingScope $scope): void + private function fillScope(array $stmts, MutatingScope $scope): void { - /** @var array $catches */ - $catches = []; - - $nodeCallback = function (Node $node, MutatingScope $mutatingScope) use ( - &$nodeCallback, - &$catches, - ): void { + $nodeCallback = function (Node $node, MutatingScope $mutatingScope) use (&$nodeCallback): void { if ($mutatingScope instanceof NodeCallbackScope) { $mutatingScope = $mutatingScope->toWalkScope(); } @@ -231,14 +215,6 @@ private function fillScope(array $stmts, string $filePath, MutatingScope $scope) if ($node instanceof Assign || $node instanceof AssignOp || $node instanceof AssignRef) { $this->processAssign($node, $mutatingScope); - if ($node->var instanceof Variable && $node->var->name instanceof Expr) { - $this->nodeScopeResolverProcessNodes( - [new Expression($node->var), new Expression($node->expr)], - $mutatingScope, - $nodeCallback - ); - } - return; } @@ -287,17 +263,6 @@ private function fillScope(array $stmts, string $filePath, MutatingScope $scope) return; } - if ($node instanceof Catch_) { - // PHPStan fills the catch stmts itself right after this callback, unless no throw point matches; - // reset the first stmt scope, so the skipped ones can be detected after processing - if ($node->stmts !== []) { - $node->stmts[0]->setAttribute(AttributeKey::SCOPE, null); - $catches[] = [$node, $mutatingScope]; - } - - return; - } - if ($node instanceof NullableType) { $node->type->setAttribute(AttributeKey::SCOPE, $mutatingScope); return; @@ -357,29 +322,9 @@ private function fillScope(array $stmts, string $filePath, MutatingScope $scope) return; } - - // PHPStan does not process attributes of closures and arrow functions - if (($node instanceof Closure || $node instanceof ArrowFunction) && $node->attrGroups !== []) { - $this->processClosureAttributeGroups($node, $mutatingScope, $nodeCallback); - } }; - try { - $this->nodeScopeResolverProcessNodes($stmts, $scope, $nodeCallback); - } catch (Error $error) { - if (! str_starts_with($error->getMessage(), 'Call to undefined method ' . Printer::class . '::pPHPStan_')) { - throw $error; - } - - // nothing we can do more precise here as error printing from deep internal PHPStan Printer service with service injection we cannot reset - // in the middle of process - // fallback to fill by found scope - RectorNodeScopeResolver::processNodes($stmts, $scope); - } - - foreach ($catches as [$catch, $catchScope]) { - $this->processCatch($catch, $filePath, $catchScope); - } + $this->nodeScopeResolverProcessNodes($stmts, $scope, $nodeCallback); } private function processYield(Yield_ $yield, MutatingScope $mutatingScope): void @@ -400,32 +345,6 @@ private function processIssetOrUnset(Isset_|Unset_ $node, MutatingScope $mutatin } } - /** - * @param callable(Node $node, MutatingScope $scope): void $nodeCallback - */ - private function processClosureAttributeGroups( - Closure|ArrowFunction $node, - MutatingScope $mutatingScope, - callable $nodeCallback - ): void { - $argExpressions = []; - foreach ($node->attrGroups as $attrGroup) { - $attrGroup->setAttribute(AttributeKey::SCOPE, $mutatingScope); - - foreach ($attrGroup->attrs as $attr) { - $attr->setAttribute(AttributeKey::SCOPE, $mutatingScope); - $attr->name->setAttribute(AttributeKey::SCOPE, $mutatingScope); - - foreach ($attr->args as $arg) { - $arg->setAttribute(AttributeKey::SCOPE, $mutatingScope); - $argExpressions[] = new Expression($arg->value); - } - } - } - - $this->nodeScopeResolverProcessNodes($argExpressions, $mutatingScope, $nodeCallback); - } - private function processEcho(Echo_ $echo, MutatingScope $mutatingScope): void { foreach ($echo->exprs as $expr) { @@ -531,24 +450,6 @@ private function processSwitch(Switch_ $switch, MutatingScope $mutatingScope): v } } - private function processCatch(Catch_ $catch, string $filePath, MutatingScope $mutatingScope): void - { - if ($catch->stmts[0]->getAttribute(AttributeKey::SCOPE) instanceof MutatingScope) { - return; - } - - $varName = $catch->var instanceof Variable - ? $this->nodeNameResolver->getName($catch->var) - : null; - - $type = TypeCombinator::union( - ...array_map(static fn (Name $name): ObjectType => new ObjectType((string) $name), $catch->types) - ); - - $catchMutatingScope = $mutatingScope->enterCatchType($type, $varName); - $this->fillScope($catch->stmts, $filePath, $catchMutatingScope); - } - private function processTryCatch(TryCatch $tryCatch, MutatingScope $mutatingScope): void { if ($tryCatch->finally instanceof Finally_) { diff --git a/src/NodeTypeResolver/PHPStan/Type/TypeFactory.php b/src/NodeTypeResolver/PHPStan/Type/TypeFactory.php index 2572eeb7694..49d431e8d37 100644 --- a/src/NodeTypeResolver/PHPStan/Type/TypeFactory.php +++ b/src/NodeTypeResolver/PHPStan/Type/TypeFactory.php @@ -16,6 +16,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; use Rector\NodeTypeResolver\PHPStan\ObjectWithoutClassTypeWithParentTypes; use Rector\NodeTypeResolver\PHPStan\TypeHasher; @@ -126,7 +127,7 @@ private function unwrapUnionedTypes(array $types): array // unwrap union types $unwrappedTypes = []; foreach ($types as $type) { - $flattenTypes = TypeFlattener::flatten($type); + $flattenTypes = TypeUtils::flattenTypes($type); foreach ($flattenTypes as $flattenType) { if ($flattenType instanceof ConstantArrayType) { @@ -201,8 +202,8 @@ private function unwrapConstantArrayTypes(ConstantArrayType $constantArrayType): { $unwrappedTypes = []; - $flattenKeyTypes = TypeFlattener::flatten($constantArrayType->getIterableKeyType()); - $flattenItemTypes = TypeFlattener::flatten($constantArrayType->getIterableValueType()); + $flattenKeyTypes = TypeUtils::flattenTypes($constantArrayType->getIterableKeyType()); + $flattenItemTypes = TypeUtils::flattenTypes($constantArrayType->getIterableValueType()); foreach ($flattenItemTypes as $position => $nestedFlattenItemType) { $nestedFlattenKeyType = $flattenKeyTypes[$position] ?? null; diff --git a/src/NodeTypeResolver/PHPStan/Type/TypeFlattener.php b/src/NodeTypeResolver/PHPStan/Type/TypeFlattener.php deleted file mode 100644 index f3d41aad187..00000000000 --- a/src/NodeTypeResolver/PHPStan/Type/TypeFlattener.php +++ /dev/null @@ -1,95 +0,0 @@ -getTypes() as $innerType) { - foreach (self::flatten($innerType) as $flattenType) { - $types[] = $flattenType; - } - } - - return $types; - } - - $constantArrays = $type->getConstantArrays(); - if ($constantArrays === []) { - return [$type]; - } - - // each ConstantArrayType with N optional keys expands to 2^N variants via getAllArrays(), - // and the cartesian product across arrays multiplies those counts - bail out before it explodes - $estimatedCount = 1; - foreach ($constantArrays as $constantArray) { - $optionalCount = count($constantArray->getOptionalKeys()); - $arrayCount = $optionalCount <= 20 ? 1 << $optionalCount : PHP_INT_MAX; - - if ($arrayCount > 16384 || $estimatedCount > 16384 / max($arrayCount, 1)) { - return [$type]; - } - - $estimatedCount *= $arrayCount; - } - - $allArrays = []; - foreach ($constantArrays as $constantArray) { - $allArrays[] = $constantArray->getAllArrays(); - } - - $result = []; - foreach (self::combinations($allArrays) as $combination) { - $intersected = $combination[0]; - for ($i = 1, $count = count($combination); $i < $count; ++$i) { - $intersected = TypeCombinator::intersect($intersected, $combination[$i]); - } - - if ($intersected instanceof NeverType) { - continue; - } - - $result[] = $intersected; - } - - return $result; - } - - /** - * @param array> $arrays - * @return iterable> - */ - private static function combinations(array $arrays): iterable - { - if ($arrays === []) { - yield []; - return; - } - - $last = array_pop($arrays); - foreach (self::combinations($arrays) as $combination) { - foreach ($last as $value) { - $next = $combination; - $next[] = $value; - - yield $next; - } - } - } -}