Skip to content
Merged
2 changes: 1 addition & 1 deletion build/target-repository/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
],
"require": {
"php": "^7.4|^8.0",
"phpstan/phpstan": "^2.3.2"
"phpstan/phpstan": "^2.3.3"
},
"autoload": {
"files": [
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 8 additions & 0 deletions src/Application/ChangedNodeScopeRefresher.php
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
Expand All @@ -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;
Expand Down Expand Up @@ -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];
}
Expand Down
31 changes: 0 additions & 31 deletions src/BetterPhpDocParser/PhpDocParser/BetterPhpDocParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
}
15 changes: 4 additions & 11 deletions src/NodeTypeResolver/NodeTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
107 changes: 4 additions & 103 deletions src/NodeTypeResolver/PHPStan/Scope/PHPStanNodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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_;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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<array{Catch_, MutatingScope}> $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();
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Comment on lines -361 to -377

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section was so annoying, just came across it last week. Happy to see it gone 👏

}

foreach ($catches as [$catch, $catchScope]) {
$this->processCatch($catch, $filePath, $catchScope);
}
$this->nodeScopeResolverProcessNodes($stmts, $scope, $nodeCallback);
}

private function processYield(Yield_ $yield, MutatingScope $mutatingScope): void
Expand All @@ -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) {
Expand Down Expand Up @@ -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_) {
Expand Down
7 changes: 4 additions & 3 deletions src/NodeTypeResolver/PHPStan/Type/TypeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
Loading
Loading