Skip to content

Commit

Permalink
Improve closure node construction to just-in-time (#2529)
Browse files Browse the repository at this point in the history
* [CodeQuality] Privatize public method of array callable

* [ci-review] Rector Rectify

Co-authored-by: GitHub Action <action@github.com>
  • Loading branch information
TomasVotruba and actions-user authored Jun 17, 2022
1 parent 7129067 commit c80d7e1
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Rector\Tests\CodeQuality\Rector\Array_\CallableThisArrayToAnonymousFun

use Rector\Tests\CodeQuality\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\Source\SortingClass;

class AnotherClass
final class AnotherClass
{
/**
* @var SortingClass
Expand Down Expand Up @@ -34,7 +34,7 @@ namespace Rector\Tests\CodeQuality\Rector\Array_\CallableThisArrayToAnonymousFun

use Rector\Tests\CodeQuality\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\Source\SortingClass;

class AnotherClass
final class AnotherClass
{
/**
* @var SortingClass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@

use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Analyser\Scope;
use PHPStan\Node\ClassMethod;
use PHPStan\Reflection\Php\PhpMethodReflection;
use PHPStan\Type\ThisType;
use Rector\Core\Rector\AbstractScopeAwareRector;
use Rector\Core\Reflection\ReflectionResolver;
use Rector\NodeCollector\NodeAnalyzer\ArrayCallableMethodMatcher;
use Rector\NodeCollector\ValueObject\ArrayCallable;
use Rector\Php72\NodeFactory\AnonymousFunctionFactory;
use Rector\Privatization\NodeManipulator\VisibilityManipulator;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

Expand All @@ -28,7 +32,8 @@ final class CallableThisArrayToAnonymousFunctionRector extends AbstractScopeAwar
public function __construct(
private readonly AnonymousFunctionFactory $anonymousFunctionFactory,
private readonly ReflectionResolver $reflectionResolver,
private readonly ArrayCallableMethodMatcher $arrayCallableMethodMatcher
private readonly ArrayCallableMethodMatcher $arrayCallableMethodMatcher,
private readonly VisibilityManipulator $visibilityManipulator,
) {
}

Expand Down Expand Up @@ -98,6 +103,8 @@ public function refactorWithScope(Node $node, Scope $scope): ?Node
return null;
}

$this->privatizeLocalClassMethod($arrayCallable, $node);

$phpMethodReflection = $this->reflectionResolver->resolveMethodReflection(
$arrayCallable->getClass(),
$arrayCallable->getMethod(),
Expand All @@ -113,4 +120,27 @@ public function refactorWithScope(Node $node, Scope $scope): ?Node
$arrayCallable->getCallerExpr()
);
}

private function privatizeLocalClassMethod(ArrayCallable $arrayCallable, Array_ $array): void
{
$callerExpr = $arrayCallable->getCallerExpr();
$callerType = $this->getType($callerExpr);

// local method, lets make it private
if (! $callerType instanceof ThisType) {
return;
}

$methodName = $arrayCallable->getMethod();

$class = $this->betterNodeFinder->findParentType($array, Class_::class);
if (! $class instanceof Class_) {
return;
}

$currentClassMethod = $class->getMethod($methodName);
if ($currentClassMethod instanceof ClassMethod) {
$this->visibilityManipulator->makePrivate($currentClassMethod);
}
}
}
71 changes: 42 additions & 29 deletions rules/Php72/NodeFactory/AnonymousFunctionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,34 +114,34 @@ public function createFromPhpMethodReflection(PhpMethodReflection $phpMethodRefl
$functionVariantWithPhpDoc = ParametersAcceptorSelector::selectSingle($phpMethodReflection->getVariants());

$newParams = $this->createParams($phpMethodReflection, $functionVariantWithPhpDoc->getParameters());
$anonymousFunction = new Closure([
'params' => $newParams,
]);

$innerMethodCall = $this->createInnerMethodCall($phpMethodReflection, $expr, $newParams);
if ($innerMethodCall === null) {
return null;
}

$returnTypeNode = null;
if (! $functionVariantWithPhpDoc->getReturnType() instanceof MixedType) {
$returnType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
$returnTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
$functionVariantWithPhpDoc->getReturnType(),
TypeKind::RETURN
);

$anonymousFunction->returnType = $returnType;
}

// does method return something?
$anonymousFunction->stmts[] = $functionVariantWithPhpDoc->getReturnType() instanceof VoidType
? new Expression($innerMethodCall)
: new Return_($innerMethodCall);

$uses = [];
if ($expr instanceof Variable && ! $this->nodeNameResolver->isName($expr, 'this')) {
$anonymousFunction->uses[] = new ClosureUse($expr);
$uses[] = new ClosureUse($expr);
}

return $anonymousFunction;
// does method return something?
$stmts = $this->resolveStmts($functionVariantWithPhpDoc, $innerMethodCall);

return new Closure([
'params' => $newParams,
'returnType' => $returnTypeNode,
'uses' => $uses,
'stmts' => $stmts,
]);
}

public function createAnonymousFunctionFromExpr(Expr $expr): ?Closure
Expand Down Expand Up @@ -266,49 +266,48 @@ private function createParams(PhpMethodReflection $phpMethodReflection, array $p

$params = [];
foreach ($parameterReflections as $key => $parameterReflection) {
$param = new Param(new Variable($parameterReflection->getName()));
$this->applyParamType($param, $parameterReflection);
$this->applyParamDefaultValue($param, $parameterReflection, $key, $classMethod);
$this->applyParamByReference($param, $parameterReflection);
$variable = new Variable($parameterReflection->getName());
$defaultExpr = $this->resolveParamDefaultExpr($parameterReflection, $key, $classMethod);
$type = $this->resolveParamType($parameterReflection);
$byRef = $this->isParamByReference($parameterReflection);

$params[] = $param;
$params[] = new Param($variable, $defaultExpr, $type, $byRef);
}

return $params;
}

private function applyParamType(Param $param, ParameterReflection $parameterReflection): void
private function resolveParamType(ParameterReflection $parameterReflection): Name|ComplexType|null
{
if ($parameterReflection->getType() instanceof MixedType) {
return;
return null;
}

$param->type = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
$parameterReflection->getType(),
TypeKind::PARAM
);
}

private function applyParamByReference(Param $param, ParameterReflection $parameterReflection): void
private function isParamByReference(ParameterReflection $parameterReflection): bool
{
/** @var ReflectionParameter $reflection */
$reflection = $this->privatesAccessor->getPrivateProperty($parameterReflection, 'reflection');
$param->byRef = $reflection->isPassedByReference();
return $reflection->isPassedByReference();
}

private function applyParamDefaultValue(
Param $param,
private function resolveParamDefaultExpr(
ParameterReflection $parameterReflection,
int $key,
ClassMethod $classMethod
): void {
): ?Expr {
if (! $parameterReflection->getDefaultValue() instanceof Type) {
return;
return null;
}

$paramDefaultExpr = $classMethod->params[$key]->default;
if (! $paramDefaultExpr instanceof Expr) {
return;
return null;
}

// reset original node, to allow the printer to re-use the expr
Expand All @@ -321,7 +320,7 @@ function (Node $node): Node {
}
);

$param->default = $paramDefaultExpr;
return $paramDefaultExpr;
}

/**
Expand Down Expand Up @@ -393,4 +392,18 @@ private function resolveExpr(Expr $expr): New_ | Expr | null
? null
: new New_(new FullyQualified($className));
}

/**
* @return Stmt[]
*/
private function resolveStmts(
FunctionVariantWithPhpDocs $functionVariantWithPhpDocs,
StaticCall|MethodCall $innerMethodCall
): array {
if ($functionVariantWithPhpDocs->getReturnType() instanceof VoidType) {
return [new Expression($innerMethodCall)];
}

return [new Return_($innerMethodCall)];
}
}

0 comments on commit c80d7e1

Please sign in to comment.