Skip to content

Commit

Permalink
[DowngradePhp74] Handle nested uses on ArrowFunctionToAnonymousFuncti…
Browse files Browse the repository at this point in the history
…onRector (#1040)

* [DowngradePhp74] add failing test cases for nested uses

* close #1039

* phpstan

* support more than 4 nested

* phpstan

* phpstan

* apply more than 4 nested

* [ci-review] Rector Rectify

* phpstan

* it works to keep sorted

* final touch: clean up

* [ci-review] Rector Rectify

* [ci-review] Rector Rectify

* real final touch: clean up

* fix duplicate ClosureUse definitino

* [ci-review] Rector Rectify

* cs

Co-authored-by: Lctrs <jerome@prmntr.me>
Co-authored-by: GitHub Action <action@github.com>
  • Loading branch information
3 people committed Oct 23, 2021
1 parent 39edeb3 commit f9fdebf
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace Rector\Tests\DowngradePhp74\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;

final class NestedStaticFnUses
{
public function __invoke()
{
return fn ($foo) => fn ($bar) => fn () => [$foo, $bar];
}

public function many()
{
return fn ($foo) => fn ($bar) => fn ($baz) => fn () => [$foo, $bar, $baz];
}

public function toomany()
{
return fn ($foo) => fn ($bar) => fn ($baz) => fn ($bat) => fn () => [$foo, $bar, $baz, $bat];
}

public function mixed()
{
return fn($foo) => fn ($bar) => function () use ($foo, $bar) {
return [$foo, $bar];
};
}
}
?>
-----
<?php

namespace Rector\Tests\DowngradePhp74\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;

final class NestedStaticFnUses
{
public function __invoke()
{
return function ($foo) {
return function ($bar) use ($foo) {
return function () use ($foo, $bar) {
return [$foo, $bar];
};
};
};
}

public function many()
{
return function ($foo) {
return function ($bar) use ($foo) {
return function ($baz) use ($foo, $bar) {
return function () use ($foo, $bar, $baz) {
return [$foo, $bar, $baz];
};
};
};
};
}

public function toomany()
{
return function ($foo) {
return function ($bar) use ($foo) {
return function ($baz) use ($foo, $bar) {
return function ($bat) use ($foo, $bar, $baz) {
return function () use ($foo, $bar, $baz, $bat) {
return [$foo, $bar, $baz, $bat];
};
};
};
};
};
}

public function mixed()
{
return function ($foo) {
return function ($bar) use ($foo) {
return function () use ($foo, $bar) {
return [$foo, $bar];
};
};
};
}
}
?>
64 changes: 63 additions & 1 deletion rules/Php72/NodeFactory/AnonymousFunctionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
use PHPStan\Type\MixedType;
use PHPStan\Type\VoidType;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Comparing\NodeComparator;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\PhpParser\Node\NodeFactory;
use Rector\NodeNameResolver\NodeNameResolver;
Expand All @@ -60,7 +61,8 @@ public function __construct(
private NodeFactory $nodeFactory,
private StaticTypeMapper $staticTypeMapper,
private SimpleCallableNodeTraverser $simpleCallableNodeTraverser,
private Parser $parser
private Parser $parser,
private NodeComparator $nodeComparator
) {
}

Expand All @@ -84,6 +86,7 @@ public function create(
}

foreach ($useVariables as $useVariable) {
$anonymousFunctionNode = $this->applyNestedUses($anonymousFunctionNode, $useVariable);
$anonymousFunctionNode->uses[] = new ClosureUse($useVariable);
}

Expand Down Expand Up @@ -172,6 +175,65 @@ public function createAnonymousFunctionFromString(Expr $expr): ?Closure
return $anonymousFunction;
}

/**
* @param ClosureUse[] $uses
* @return ClosureUse[]
*/
private function cleanClosureUses(array $uses): array
{
$variableNames = array_map(
fn ($use): string => (string) $this->nodeNameResolver->getName($use->var),
$uses,
[]
);
$variableNames = array_unique($variableNames);

return array_map(
fn ($variableName): ClosureUse => new ClosureUse(new Variable($variableName)),
$variableNames,
[]
);
}

private function applyNestedUses(Closure $anonymousFunctionNode, Variable $useVariable): Closure
{
$anonymousFunctionNode = clone $anonymousFunctionNode;
$parent = $this->betterNodeFinder->findParentType($useVariable, Closure::class);

while ($parent instanceof Closure) {
$parentOfParent = $this->betterNodeFinder->findParentType($parent, Closure::class);

$uses = [];
while ($parentOfParent instanceof Closure) {
$uses = $this->collectUsesEqual($parentOfParent, $uses, $useVariable);
$parentOfParent = $this->betterNodeFinder->findParentType($parentOfParent, Closure::class);
}

$uses = array_merge($parent->uses, $uses);
$uses = $this->cleanClosureUses($uses);
$parent->uses = $uses;

$parent = $this->betterNodeFinder->findParentType($parent, Closure::class);
}

return $anonymousFunctionNode;
}

/**
* @param ClosureUse[] $uses
* @return ClosureUse[]
*/
private function collectUsesEqual(Closure $closure, array $uses, Variable $useVariable): array
{
foreach ($closure->params as $param) {
if ($this->nodeComparator->areNodesEqual($param->var, $useVariable)) {
$uses[] = new ClosureUse($param->var);
}
}

return $uses;
}

/**
* @param Node[] $nodes
* @param Param[] $paramNodes
Expand Down
5 changes: 5 additions & 0 deletions src/Validation/InfiniteLoopValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Rector\Core\Exception\NodeTraverser\InfiniteLoopTraversingException;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\PhpParser\NodeVisitor\CreatedByRuleNodeVisitor;
use Rector\DowngradePhp74\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector;
use Rector\DowngradePhp80\Rector\NullsafeMethodCall\DowngradeNullsafeToTernaryOperatorRector;
use Rector\NodeTypeResolver\Node\AttributeKey;

Expand All @@ -29,6 +30,10 @@ public function process(Node $node, Node $originalNode, string $rectorClass): vo
return;
}

if ($rectorClass === ArrowFunctionToAnonymousFunctionRector::class) {
return;
}

$createdByRule = $originalNode->getAttribute(AttributeKey::CREATED_BY_RULE);

// special case
Expand Down

0 comments on commit f9fdebf

Please sign in to comment.