diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/StrictArrayParamDimFetchRector/Fixture/skip_private_method_called_with_array_access_arg.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/StrictArrayParamDimFetchRector/Fixture/skip_private_method_called_with_array_access_arg.php.inc new file mode 100644 index 00000000000..7c341dee1ae --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/StrictArrayParamDimFetchRector/Fixture/skip_private_method_called_with_array_access_arg.php.inc @@ -0,0 +1,19 @@ +isTrue($arrayAccess); + $this->isTrue($data); + } + + private function isTrue($value): bool + { + return $value['asdf'] === true; + } +} diff --git a/rules/TypeDeclaration/Rector/ClassMethod/StrictArrayParamDimFetchRector.php b/rules/TypeDeclaration/Rector/ClassMethod/StrictArrayParamDimFetchRector.php index 696e72cc064..e44dcc82d16 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/StrictArrayParamDimFetchRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/StrictArrayParamDimFetchRector.php @@ -19,6 +19,7 @@ use PhpParser\Node\Expr\Instanceof_; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; +use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\StaticPropertyFetch; use PhpParser\Node\Expr\Variable; use PhpParser\Node\FunctionLike; @@ -35,6 +36,7 @@ use PHPStan\Type\UnionType; use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory; use Rector\NodeTypeResolver\TypeComparator\TypeComparator; +use Rector\PhpParser\Node\BetterNodeFinder; use Rector\Rector\AbstractRector; use Rector\RuleDoc\CodeSample\CodeSample; use Rector\RuleDoc\RuleDefinition; @@ -48,7 +50,8 @@ final class StrictArrayParamDimFetchRector extends AbstractRector public function __construct( private readonly ParentClassMethodTypeOverrideGuard $parentClassMethodTypeOverrideGuard, private readonly TypeComparator $typeComparator, - private readonly TypeFactory $typeFactory + private readonly TypeFactory $typeFactory, + private readonly BetterNodeFinder $betterNodeFinder ) { } @@ -111,7 +114,7 @@ public function refactor(Node $node): ?Node return null; } - foreach ($node->getParams() as $param) { + foreach ($node->getParams() as $position => $param) { if ($param->type instanceof Node) { continue; } @@ -128,6 +131,15 @@ public function refactor(Node $node): ?Node continue; } + // a private method is only reachable from inside the class, so all its callers are visible here; + // skip if any of them passes an ArrayAccess object, which also supports dim fetch + if ($node instanceof ClassMethod && $node->isPrivate() && $this->isCalledWithArrayAccessArg( + $node, + $position + )) { + continue; + } + $param->type = new Identifier('array'); $hasChanged = true; } @@ -339,6 +351,51 @@ private function isMethodCall(string $paramName, ?Node $node): bool return false; } + private function isCalledWithArrayAccessArg(ClassMethod $classMethod, int $position): bool + { + $class = $this->findClassOfMethod($classMethod); + if (! $class instanceof Class_) { + return false; + } + + $methodName = $this->getName($classMethod); + + $calls = $this->betterNodeFinder->findInstancesOf($class->getMethods(), [MethodCall::class, StaticCall::class]); + + return array_any( + $calls, + function (MethodCall|StaticCall $call) use ($methodName, $position): bool { + if ($call->isFirstClassCallable()) { + return false; + } + + if (! $this->isName($call->name, $methodName)) { + return false; + } + + $args = $call->getArgs(); + if (! isset($args[$position])) { + return false; + } + + return $this->isArrayAccess($this->getType($args[$position]->value)); + } + ); + } + + private function findClassOfMethod(ClassMethod $classMethod): ?Class_ + { + $classes = $this->betterNodeFinder->findInstanceOf($this->getFile()->getNewStmts(), Class_::class); + + foreach ($classes as $class) { + if (in_array($classMethod, $class->getMethods(), true)) { + return $class; + } + } + + return null; + } + private function isArrayAccess(Type $type): bool { if (! $type instanceof ObjectType) {