Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 10 additions & 22 deletions src/DependencyInjection/LazyContainerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,14 @@
use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser;
use Rector\PhpParser\Comparing\NodeComparator;
use Rector\PhpParser\Node\NodeFactory;
use Rector\PhpParser\NodeVisitor\ArgNodeVisitor;
use Rector\PhpParser\NodeVisitor\ArgNotAcceptingClosureNodeVisitor;
use Rector\PhpParser\NodeVisitor\AssignedToNodeVisitor;
use Rector\PhpParser\NodeVisitor\ByRefReturnNodeVisitor;
use Rector\PhpParser\NodeVisitor\ByRefVariableNodeVisitor;
use Rector\PhpParser\NodeVisitor\ClassConstFetchNodeVisitor;
use Rector\PhpParser\NodeVisitor\ClosureWithVariadicParametersNodeVisitor;
use Rector\PhpParser\NodeVisitor\ByRefNodeVisitor;
use Rector\PhpParser\NodeVisitor\CallLikeReflectionNodeVisitor;
use Rector\PhpParser\NodeVisitor\ContextNodeVisitor;
use Rector\PhpParser\NodeVisitor\GlobalVariableNodeVisitor;
use Rector\PhpParser\NodeVisitor\NameNodeVisitor;
use Rector\PhpParser\NodeVisitor\ParamDefaultNodeVisitor;
use Rector\PhpParser\NodeVisitor\DefaultValueNodeVisitor;
use Rector\PhpParser\NodeVisitor\LocalVariableScopeNodeVisitor;
use Rector\PhpParser\NodeVisitor\NameAndArgNodeVisitor;
use Rector\PhpParser\NodeVisitor\PhpVersionConditionNodeVisitor;
use Rector\PhpParser\NodeVisitor\PropertyOrClassConstDefaultNodeVisitor;
use Rector\PhpParser\NodeVisitor\StaticVariableNodeVisitor;
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
use Rector\PHPStanStaticTypeMapper\TypeMapper\ArrayTypeMapper;
use Rector\PHPStanStaticTypeMapper\TypeMapper\ConditionalTypeForParameterMapper;
Expand Down Expand Up @@ -103,20 +97,14 @@ final class LazyContainerFactory
* @var array<class-string<DecoratingNodeVisitorInterface>>
*/
private const array DECORATING_NODE_VISITOR_CLASSES = [
ArgNodeVisitor::class,
ClosureWithVariadicParametersNodeVisitor::class,
CallLikeReflectionNodeVisitor::class,
PhpVersionConditionNodeVisitor::class,
AssignedToNodeVisitor::class,
ByRefReturnNodeVisitor::class,
ByRefVariableNodeVisitor::class,
ByRefNodeVisitor::class,
ContextNodeVisitor::class,
GlobalVariableNodeVisitor::class,
NameNodeVisitor::class,
StaticVariableNodeVisitor::class,
PropertyOrClassConstDefaultNodeVisitor::class,
ParamDefaultNodeVisitor::class,
ClassConstFetchNodeVisitor::class,
ArgNotAcceptingClosureNodeVisitor::class,
LocalVariableScopeNodeVisitor::class,
NameAndArgNodeVisitor::class,
DefaultValueNodeVisitor::class,
];

/**
Expand Down
38 changes: 0 additions & 38 deletions src/PhpParser/NodeVisitor/ArgNodeVisitor.php

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,20 @@
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Return_;
use PhpParser\NodeVisitor;
use PhpParser\NodeVisitorAbstract;
use Rector\Contract\PhpParser\DecoratingNodeVisitorInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser;
use Rector\PhpParser\NodeTraverser\SimpleNodeTraverser;

final class ByRefVariableNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface
/**
* Marks by-ref returns (IS_BYREF_RETURN / IS_INSIDE_BYREF_FUNCTION_LIKE) and by-ref variables
* (IS_BYREF_VAR) from a single FunctionLike entry, sharing the one node subscription.
*/
final class ByRefNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface
{
public function __construct(
private readonly SimpleCallableNodeTraverser $simpleCallableNodeTraverser
Expand All @@ -33,14 +41,54 @@ public function enterNode(Node $node): ?Node
return null;
}

$byRefVariableNames = $this->resolveClosureUseIsByRefAttribute($node, []);
$byRefVariableNames = $this->resolveParamIsByRefAttribute($node, $byRefVariableNames);

$stmts = $node->getStmts();
if ($stmts === null) {
return null;
}

$this->decorateByRefReturn($node, $stmts);
$this->decorateByRefVariables($node, $stmts);

return null;
}

/**
* @param Node\Stmt[] $stmts
*/
private function decorateByRefReturn(FunctionLike $functionLike, array $stmts): void
{
if (! $functionLike->returnsByRef()) {
return;
}

SimpleNodeTraverser::decorateWithAttributeValue($stmts, AttributeKey::IS_INSIDE_BYREF_FUNCTION_LIKE, true);

$this->simpleCallableNodeTraverser->traverseNodesWithCallable(
$stmts,
static function (Node $node): int|null|Node {
// avoid nested functions or classes
if ($node instanceof Class_ || $node instanceof FunctionLike) {
return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
}

if (! $node instanceof Return_) {
return null;
}

$node->setAttribute(AttributeKey::IS_BYREF_RETURN, true);
return $node;
}
);
}

/**
* @param Node\Stmt[] $stmts
*/
private function decorateByRefVariables(FunctionLike $functionLike, array $stmts): void
{
$byRefVariableNames = $this->resolveClosureUseIsByRefAttribute($functionLike, []);
$byRefVariableNames = $this->resolveParamIsByRefAttribute($functionLike, $byRefVariableNames);

$this->simpleCallableNodeTraverser->traverseNodesWithCallable(
$stmts,
function (Node $subNode) use (&$byRefVariableNames): null|Variable {
Expand All @@ -61,8 +109,6 @@ function (Node $subNode) use (&$byRefVariableNames): null|Variable {
return $subNode;
}
);

return null;
}

/**
Expand Down
61 changes: 0 additions & 61 deletions src/PhpParser/NodeVisitor/ByRefReturnNodeVisitor.php

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\CallLike;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Identifier;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\Native\NativeFunctionReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\CallableType;
use PHPStan\Type\ObjectType;
use Rector\Contract\PhpParser\DecoratingNodeVisitorInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
Expand All @@ -21,12 +26,12 @@
use Rector\Reflection\ReflectionResolver;

/**
* Mark array arguments passed to a parameter that cannot hold a Closure,
* e.g. "array" or "string|array|null", so an array callable is kept as is
*
* @see https://github.com/rectorphp/rector/issues/9563
* Decorates call args using the resolved function-like reflection, resolved once per call:
* - Closure/arrow-fn args passed to a variadic callable parameter (HAS_CLOSURE_WITH_VARIADIC_ARGS)
* - array args passed to a parameter that cannot hold a Closure, so an array callable is kept as is
* (IS_ARG_NOT_ACCEPTING_CLOSURE), see https://github.com/rectorphp/rector/issues/9563
*/
final class ArgNotAcceptingClosureNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface
final class CallLikeReflectionNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface
{
public function __construct(
private readonly ReflectionResolver $reflectionResolver
Expand All @@ -44,19 +49,68 @@ public function enterNode(Node $node): ?Node
}

$args = $node->getArgs();
if (! array_any($args, static fn (Arg $arg): bool => $arg->value instanceof Array_)) {
if ($args === []) {
return null;
}

$functionLikeReflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($node);

$this->decorateClosureWithVariadicArgs($args, $functionLikeReflection);
$this->decorateArgNotAcceptingClosure($node, $args, $functionLikeReflection);

return null;
}

/**
* @param Arg[] $args
*/
private function decorateClosureWithVariadicArgs(
array $args,
MethodReflection|FunctionReflection|null $functionLikeReflection
): void {
foreach ($args as $arg) {
if (! $arg->value instanceof Closure && ! $arg->value instanceof ArrowFunction) {
continue;
}

if ($functionLikeReflection instanceof NativeFunctionReflection) {
$parametersAcceptors = ParametersAcceptorSelector::combineAcceptors(
$functionLikeReflection->getVariants()
);

foreach ($parametersAcceptors->getParameters() as $extendedParameterReflection) {
if ($extendedParameterReflection->getType() instanceof CallableType && $extendedParameterReflection->getType()->isVariadic()) {
$arg->value->setAttribute(AttributeKey::HAS_CLOSURE_WITH_VARIADIC_ARGS, true);
}
}

return;
}

$arg->value->setAttribute(AttributeKey::HAS_CLOSURE_WITH_VARIADIC_ARGS, true);
}
}

/**
* @param Arg[] $args
*/
private function decorateArgNotAcceptingClosure(
CallLike $callLike,
array $args,
MethodReflection|FunctionReflection|null $functionLikeReflection
): void {
if (! $functionLikeReflection instanceof FunctionReflection && ! $functionLikeReflection instanceof MethodReflection) {
return null;
return;
}

if (! array_any($args, static fn (Arg $arg): bool => $arg->value instanceof Array_)) {
return;
}

$parameterReflections = ParametersAcceptorSelectorVariantsWrapper::select(
$functionLikeReflection,
$node,
ScopeFetcher::fetch($node)
$callLike,
ScopeFetcher::fetch($callLike)
)->getParameters();

$closureObjectType = new ObjectType('Closure');
Expand All @@ -77,8 +131,6 @@ public function enterNode(Node $node): ?Node

$arg->value->setAttribute(AttributeKey::IS_ARG_NOT_ACCEPTING_CLOSURE, true);
}

return $node;
}

/**
Expand Down
31 changes: 0 additions & 31 deletions src/PhpParser/NodeVisitor/ClassConstFetchNodeVisitor.php

This file was deleted.

Loading
Loading