diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/arrow_function.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/arrow_function.php.inc new file mode 100644 index 00000000000..1e02f85474c --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/arrow_function.php.inc @@ -0,0 +1,25 @@ + $x > 5 ? 'high' : 10; + +$ternary = fn($value): string|int|float|null => + $value === null ? null : ($value > 0 ? 'positive' : -1); + +$cast = fn($input): int|string|array => (int) $input; + +?> +----- + $x > 5 ? 'high' : 10; + +$ternary = fn($value): string|int|null => + $value === null ? null : ($value > 0 ? 'positive' : -1); + +$cast = fn($input): int => (int) $input; + +?> diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/closure.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/closure.php.inc new file mode 100644 index 00000000000..1380d360f1e --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/closure.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/edge_cases.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/edge_cases.php.inc new file mode 100644 index 00000000000..d0ba4dad7c7 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/edge_cases.php.inc @@ -0,0 +1,59 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/final_class.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/final_class.php.inc new file mode 100644 index 00000000000..f74634b803f --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/final_class.php.inc @@ -0,0 +1,87 @@ + 'value']; + } +} + +?> +----- + 'value']; + } +} + +?> diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/final_inheritance.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/final_inheritance.php.inc new file mode 100644 index 00000000000..e1f81c5f4e2 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/final_inheritance.php.inc @@ -0,0 +1,59 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/final_methods.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/final_methods.php.inc new file mode 100644 index 00000000000..938c6f8429a --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/final_methods.php.inc @@ -0,0 +1,57 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/function.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/function.php.inc new file mode 100644 index 00000000000..a2caaafb334 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/function.php.inc @@ -0,0 +1,37 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/phpdocs.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/phpdocs.php.inc new file mode 100644 index 00000000000..40d92c06dd3 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/phpdocs.php.inc @@ -0,0 +1,129 @@ + $class + * @return class-string|int + */ + public function bar(string $class): string|int + { + return $class; + } + + /** @return class-string|int */ + public function baz(string $class): string|int + { + return SomeInterface::class; + } + + /** @return \Iterator|string */ + function qux(): \Iterator|string + { + return new \ArrayIterator([1]); + } + + /** @return \Iterator|string */ + function qax(): \Iterator|string + { + return 'text'; + } + + /** + * @param int $a + */ + function quux(int $a): int|string + { + return $a; + } + + /** + * @param int $a + * @return int|string + */ + function mixedReturn(int $a): int|string + { + return $a; + } +} + +?> +----- + $class + * @return class-string + */ + public function bar(string $class): string + { + return $class; + } + + /** @return class-string */ + public function baz(string $class): string + { + return SomeInterface::class; + } + + /** @return \Iterator */ + function qux(): \Iterator + { + return new \ArrayIterator([1]); + } + + /** @return string */ + function qax(): string + { + return 'text'; + } + + /** + * @param int $a + */ + function quux(int $a): int + { + return $a; + } + + /** + * @param int $a + * @return int + */ + function mixedReturn(int $a): int + { + return $a; + } +} + +?> diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_abstracts.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_abstracts.php.inc new file mode 100644 index 00000000000..4c218f4c0cc --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_abstracts.php.inc @@ -0,0 +1,28 @@ + diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_constant_wildcard.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_constant_wildcard.php.inc new file mode 100644 index 00000000000..48816681ff8 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_constant_wildcard.php.inc @@ -0,0 +1,24 @@ + $this->execute(); + } + + /** + * @return string + */ + private function execute() + { + return 1; + } +} + +?> diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_exact_types.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_exact_types.php.inc new file mode 100644 index 00000000000..348f112ecf2 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_exact_types.php.inc @@ -0,0 +1,26 @@ + yield 1; + +fn(string $a): \Iterator|array|null => match ($a) { + 'a' => yield 1, + 'b' => yield 2, + default => 'test', +}; + +fn(): \Iterator|array|null => yield from [1, 2, 3]; + +?> diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_native_mixed_returns.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_native_mixed_returns.php.inc new file mode 100644 index 00000000000..f26e65d9907 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_native_mixed_returns.php.inc @@ -0,0 +1,29 @@ + $class + * @return class-string|int + */ + public function bar($class): string|int + { + return $class; + } + + /** + * @return class-string<\stdClass>|int + */ + public function skipMixedByDoc(): string|int + { + /** + * @var class-string<\stdClass> $class + */ + $class = get(); + return $class; + } +} + +?> diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_non_final_classes.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_non_final_classes.php.inc new file mode 100644 index 00000000000..df43fc5a948 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_non_final_classes.php.inc @@ -0,0 +1,18 @@ + diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_non_final_inheritance.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_non_final_inheritance.php.inc new file mode 100644 index 00000000000..d0c024cd6bb --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_non_final_inheritance.php.inc @@ -0,0 +1,29 @@ + diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_null.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_null.php.inc new file mode 100644 index 00000000000..112b658ac46 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_null.php.inc @@ -0,0 +1,11 @@ + diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_standalone_null.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_standalone_null.php.inc new file mode 100644 index 00000000000..572d0a93992 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_standalone_null.php.inc @@ -0,0 +1,18 @@ + diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_true_false_to_become_bool.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_true_false_to_become_bool.php.inc new file mode 100644 index 00000000000..82bdf954a29 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/skip_true_false_to_become_bool.php.inc @@ -0,0 +1,16 @@ + diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/terminating_methods.php.inc b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/terminating_methods.php.inc new file mode 100644 index 00000000000..4a24bc1dacd --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Fixture/terminating_methods.php.inc @@ -0,0 +1,123 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/NarrowTooWideReturnTypeRectorTest.php b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/NarrowTooWideReturnTypeRectorTest.php new file mode 100644 index 00000000000..02823bb43a2 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/NarrowTooWideReturnTypeRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Source/SomeAbstractClass.php b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Source/SomeAbstractClass.php new file mode 100644 index 00000000000..82b85203a95 --- /dev/null +++ b/rules-tests/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector/Source/SomeAbstractClass.php @@ -0,0 +1,8 @@ +rule(NarrowTooWideReturnTypeRector::class); + $rectorConfig->phpVersion(PhpVersionFeature::UNION_TYPES); +}; diff --git a/rules/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector.php b/rules/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector.php new file mode 100644 index 00000000000..852a02859f3 --- /dev/null +++ b/rules/DeadCode/Rector/FunctionLike/NarrowTooWideReturnTypeRector.php @@ -0,0 +1,308 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class, Function_::class, Closure::class, ArrowFunction::class]; + } + + /** + * @param ClassMethod|Function_|Closure|ArrowFunction $node + */ + public function refactor(Node $node): ?Node + { + if ($this->shouldSkipNode($node)) { + return null; + } + + if ($this->shouldSkipByDocblock($node)) { + return null; + } + + $returnStatements = $this->betterNodeFinder->findReturnsScoped($node); + + if ($returnStatements === []) { + return null; + } + + $hasImplicitNullReturn = $this->silentVoidResolver->hasSilentVoid($node) + || $this->hasImplicitNullReturn($returnStatements); + + $returnType = $node->returnType; + Assert::isInstanceOfAny($returnType, [UnionType::class, NullableType::class]); + + $returnType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($returnType); + $actualReturnTypes = $this->collectActualReturnTypes($returnStatements); + + if ($hasImplicitNullReturn) { + $actualReturnTypes[] = new NullType(); + } + + $unusedTypes = $this->getUnusedType($returnType, $actualReturnTypes); + + if ($unusedTypes === []) { + return null; + } + + $newReturnType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode( + TypeCombinator::remove($returnType, TypeCombinator::union(...$unusedTypes)), + TypeKind::RETURN + ); + + if (! $newReturnType instanceof Node) { + return null; + } + + // mostly placeholder + if ($this->isName($newReturnType, 'null')) { + return null; + } + + $node->returnType = $newReturnType; + + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); + + if ($phpDocInfo?->hasByName('@return') === true) { + $this->changePhpDocReturnType($node, $phpDocInfo, $unusedTypes); + } + + return $node; + } + + private function shouldSkipByDocblock(ClassMethod|Function_|Closure|ArrowFunction $node): bool + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); + + if (! $phpDocInfo instanceof PhpDocInfo) { + return false; + } + + $returnTag = $phpDocInfo->getReturnTagValue(); + + if (! $returnTag instanceof ReturnTagValueNode) { + return false; + } + + $returnType = $phpDocInfo->getReturnType(); + if (! $returnType instanceof \PHPStan\Type\UnionType) { + return false; + } + + $type = $this->typeFactory->createMixedPassedOrUnionType($returnType->getTypes()); + return ! $type->equals($returnType); + } + + private function shouldSkipNode(ClassMethod|Function_|Closure|ArrowFunction $node): bool + { + $returnType = $node->returnType; + + if (! $returnType instanceof UnionType && ! $returnType instanceof NullableType) { + return true; + } + + $types = $returnType instanceof UnionType + ? $returnType->types + : [new ConstFetch(new Name('null')), $returnType->type]; + + foreach ($types as $type) { + if ($this->isNames($type, ['true', 'false'])) { + return true; + } + } + + if (! $node instanceof ClassMethod) { + return false; + } + + if ($node->isPrivate() || $node->isFinal()) { + return false; + } + + if ($node->isAbstract()) { + return true; + } + + $classReflection = $this->reflectionResolver->resolveClassReflection($node); + + if (! $classReflection instanceof ClassReflection) { + return true; + } + + if (! $classReflection->isClass()) { + return true; + } + + return ! $classReflection->isFinalByKeyword(); + } + + /** + * @param Return_[] $returnStatements + */ + private function hasImplicitNullReturn(array $returnStatements): bool + { + foreach ($returnStatements as $returnStatement) { + if ($returnStatement->expr === null) { + return true; + } + } + + return false; + } + + /** + * @param Return_[] $returnStatements + * @return Type[] + */ + private function collectActualReturnTypes(array $returnStatements): array + { + $returnTypes = []; + + foreach ($returnStatements as $returnStatement) { + if ($returnStatement->expr === null) { + continue; + } + + $returnTypes[] = $this->nodeTypeResolver->getNativeType($returnStatement->expr); + } + + return $returnTypes; + } + + /** + * @param Type[] $actualReturnTypes + * @return Type[] + */ + private function getUnusedType(Type $returnType, array $actualReturnTypes): array + { + $types = $returnType instanceof PHPStanUnionType ? $returnType->getTypes() : [$returnType]; + $unusedTypes = []; + + foreach ($types as $type) { + foreach ($actualReturnTypes as $actualReturnType) { + if (! $type->isSuperTypeOf($actualReturnType)->no()) { + continue 2; + } + } + + $unusedTypes[] = $type; + } + + return $unusedTypes; + } + + /** + * @param Type[] $unusedTypes + */ + private function changePhpDocReturnType( + FunctionLike $functionLike, + PhpDocInfo $phpDocInfo, + array $unusedTypes, + ): void { + $returnTagValueNode = $phpDocInfo->getReturnTagValue(); + + if (! $returnTagValueNode instanceof ReturnTagValueNode) { + return; + } + + $newReturnType = TypeCombinator::remove($phpDocInfo->getReturnType(), TypeCombinator::union(...$unusedTypes)); + $this->phpDocTypeChanger->changeReturnType($functionLike, $phpDocInfo, $newReturnType); + } +} diff --git a/rules/Php81/Rector/MethodCall/MyCLabsMethodCallToEnumConstRector.php b/rules/Php81/Rector/MethodCall/MyCLabsMethodCallToEnumConstRector.php index 3d84aa194bb..40d7320af27 100644 --- a/rules/Php81/Rector/MethodCall/MyCLabsMethodCallToEnumConstRector.php +++ b/rules/Php81/Rector/MethodCall/MyCLabsMethodCallToEnumConstRector.php @@ -201,7 +201,7 @@ private function isCallerClassEnum(StaticCall|MethodCall $node): bool return $this->isObjectType($node->var, new ObjectType('MyCLabs\Enum\Enum')); } - private function getNonEnumReturnTypeExpr(Node $node): null|ClassConstFetch|Expr + private function getNonEnumReturnTypeExpr(Node $node): ?Expr { if (! $node instanceof StaticCall && ! $node instanceof MethodCall) { return null; diff --git a/src/Config/Level/DeadCodeLevel.php b/src/Config/Level/DeadCodeLevel.php index a67918d0597..b8dfcea0b28 100644 --- a/src/Config/Level/DeadCodeLevel.php +++ b/src/Config/Level/DeadCodeLevel.php @@ -35,6 +35,7 @@ use Rector\DeadCode\Rector\For_\RemoveDeadLoopRector; use Rector\DeadCode\Rector\Foreach_\RemoveUnusedForeachKeyRector; use Rector\DeadCode\Rector\FuncCall\RemoveFilterVarOnExactTypeRector; +use Rector\DeadCode\Rector\FunctionLike\NarrowTooWideReturnTypeRector; use Rector\DeadCode\Rector\FunctionLike\RemoveDeadReturnRector; use Rector\DeadCode\Rector\If_\ReduceAlwaysFalseIfOrRector; use Rector\DeadCode\Rector\If_\RemoveAlwaysTrueIfConditionRector; @@ -141,5 +142,6 @@ final class DeadCodeLevel RemoveDeadReturnRector::class, RemoveArgumentFromDefaultParentCallRector::class, + NarrowTooWideReturnTypeRector::class, ]; } diff --git a/src/PhpParser/Node/BetterNodeFinder.php b/src/PhpParser/Node/BetterNodeFinder.php index 7979194ce70..86ec1521b04 100644 --- a/src/PhpParser/Node/BetterNodeFinder.php +++ b/src/PhpParser/Node/BetterNodeFinder.php @@ -5,15 +5,12 @@ namespace Rector\PhpParser\Node; use PhpParser\Node; -use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Expr\Yield_; use PhpParser\Node\Expr\YieldFrom; use PhpParser\Node\FunctionLike; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Return_; use PhpParser\NodeFinder; use PhpParser\NodeVisitor; @@ -158,17 +155,15 @@ public function findFirst(Node | array $nodes, callable $filter): ?Node * @template T of Node * @param array>|class-string $types */ - public function hasInstancesOfInFunctionLikeScoped( - ClassMethod | Function_ | Closure $functionLike, - string|array $types - ): bool { + public function hasInstancesOfInFunctionLikeScoped(FunctionLike $functionLike, string|array $types): bool + { if (is_string($types)) { $types = [$types]; } $isFoundNode = false; $this->simpleCallableNodeTraverser->traverseNodesWithCallable( - (array) $functionLike->stmts, + (array) $functionLike->getStmts(), static function (Node $subNode) use ($types, &$isFoundNode): ?int { if ($subNode instanceof Class_ || $subNode instanceof FunctionLike) { return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; @@ -191,12 +186,12 @@ static function (Node $subNode) use ($types, &$isFoundNode): ?int { /** * @return Return_[] */ - public function findReturnsScoped(ClassMethod | Function_ | Closure $functionLike): array + public function findReturnsScoped(FunctionLike $functionLike): array { $returns = []; $this->simpleCallableNodeTraverser->traverseNodesWithCallable( - (array) $functionLike->stmts, + (array) $functionLike->getStmts(), function (Node $subNode) use (&$returns): ?int { if ($subNode instanceof Class_ || $subNode instanceof FunctionLike) { return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; @@ -267,24 +262,20 @@ static function (Node $subNode) use ($types, &$foundNodes): ?int { * @param array>|class-string $types * @return array */ - public function findInstancesOfInFunctionLikeScoped( - ClassMethod | Function_ | Closure $functionLike, - string|array $types - ): array { + public function findInstancesOfInFunctionLikeScoped(FunctionLike $functionLike, string|array $types): array + { return $this->findInstancesOfScoped([$functionLike], $types); } /** * @param callable(Node $node): bool $filter */ - public function findFirstInFunctionLikeScoped( - ClassMethod | Function_ | Closure $functionLike, - callable $filter - ): ?Node { + public function findFirstInFunctionLikeScoped(FunctionLike $functionLike, callable $filter): ?Node + { $scopedNode = null; $this->simpleCallableNodeTraverser->traverseNodesWithCallable( - (array) $functionLike->stmts, + (array) $functionLike->getStmts(), function (Node $subNode) use (&$scopedNode, $filter): ?int { if (! $filter($subNode)) { if ($subNode instanceof Class_ || $subNode instanceof FunctionLike) {