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
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddParamArrayDocblockFromDataProviderRector\Fixture;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

final class MultipleOptions extends TestCase
{
#[DataProvider('provideData')]
public function test(array $names): void
{
}

public static function provideData()
{
yield [['Tom', 'John']];
yield [[]];
yield [['John', 'Doe']];
}
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\AddParamArrayDocblockFromDataProviderRector\Fixture;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

final class MultipleOptions extends TestCase
{
/**
* @param string[] $names
*/
#[DataProvider('provideData')]
public function test(array $names): void
{
}

public static function provideData()
{
yield [['Tom', 'John']];
yield [[]];
yield [['John', 'Doe']];
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\AddReturnDocblockDataProviderRector\Fixture;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

final class YieldProvider extends TestCase
{
#[DataProvider('provideData')]
public function testSomething()
{
}

public static function provideData()
{
yield ['data1', 'data2'];
yield ['item4', 'item5'];
}
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\AddReturnDocblockDataProviderRector\Fixture;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

final class YieldProvider extends TestCase
{
#[DataProvider('provideData')]
public function testSomething()
{
}

/**
* @return \Iterator<array<int, string>>
*/
public static function provideData()
{
yield ['data1', 'data2'];
yield ['item4', 'item5'];
}
}

?>
2 changes: 1 addition & 1 deletion rules/CodeQuality/NodeFactory/PropertyTypeDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public function __construct(
public function decorateProperty(Property $property, Type $propertyType): void
{
// generalize false/true type to bool, as mostly default value but accepts both
$propertyType = $this->typeNormalizer->generalizeConstantBoolTypes($propertyType);
$propertyType = $this->typeNormalizer->generalizeConstantTypes($propertyType);

$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property);
$phpDocInfo->makeMultiLined();
Expand Down
13 changes: 11 additions & 2 deletions rules/Privatization/TypeManipulator/TypeNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,22 @@

final class TypeNormalizer
{
/**
* @deprecated This method is deprecated and will be removed in the next major release.
* Use @see generalizeConstantTypes() instead.
*/
public function generalizeConstantBoolTypes(\PHPStan\Type\Type $type): Type
{
return $this->generalizeConstantTypes($type);
}

/**
* Generalize false/true constantArrayType to bool,
* as mostly default value but accepts both
*/
public function generalizeConstantBoolTypes(Type $type): Type
public function generalizeConstantTypes(Type $type): Type
{
return TypeTraverser::map($type, function (Type $type, callable $traverseCallback): BooleanType|Type {
return TypeTraverser::map($type, function (Type $type, callable $traverseCallback): Type {
if ($type instanceof ConstantBooleanType) {
return new BooleanType();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,14 @@
namespace Rector\TypeDeclaration\Rector\FunctionLike;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\Yield_;
use PhpParser\Node\Expr\YieldFrom;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Function_;
use PhpParser\NodeVisitor;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser;
use Rector\PHPStan\ScopeFetcher;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\Rector\AbstractRector;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedGenericObjectType;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use Rector\TypeDeclarationDocblocks\NodeFinder\YieldNodeFinder;
use Rector\TypeDeclarationDocblocks\TypeResolver\YieldTypeResolver;
use Rector\ValueObject\PhpVersionFeature;
use Rector\VendorLocker\NodeVendorLocker\ClassMethodReturnTypeOverrideGuard;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
Expand All @@ -40,10 +25,10 @@
final class AddReturnTypeDeclarationFromYieldsRector extends AbstractRector implements MinPhpVersionInterface
{
public function __construct(
private readonly TypeFactory $typeFactory,
private readonly SimpleCallableNodeTraverser $simpleCallableNodeTraverser,
private readonly StaticTypeMapper $staticTypeMapper,
private readonly ClassMethodReturnTypeOverrideGuard $classMethodReturnTypeOverrideGuard
private readonly ClassMethodReturnTypeOverrideGuard $classMethodReturnTypeOverrideGuard,
private readonly YieldNodeFinder $yieldNodeFinder,
private readonly YieldTypeResolver $yieldTypeResolver,
) {
}

Expand Down Expand Up @@ -91,7 +76,8 @@ public function getNodeTypes(): array
public function refactor(Node $node): ?Node
{
$scope = ScopeFetcher::fetch($node);
$yieldNodes = $this->findCurrentScopeYieldNodes($node);

$yieldNodes = $this->yieldNodeFinder->find($node);
if ($yieldNodes === []) {
return null;
}
Expand All @@ -111,7 +97,7 @@ public function refactor(Node $node): ?Node
return null;
}

$yieldType = $this->resolveYieldType($yieldNodes, $node);
$yieldType = $this->yieldTypeResolver->resolveFromYieldNodes($yieldNodes, $node);
$returnTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($yieldType, TypeKind::RETURN);
if (! $returnTypeNode instanceof Node) {
return null;
Expand All @@ -125,109 +111,4 @@ public function provideMinPhpVersion(): int
{
return PhpVersionFeature::SCALAR_TYPES;
}

/**
* @return Yield_[]|YieldFrom[]
*/
private function findCurrentScopeYieldNodes(FunctionLike $functionLike): array
{
$yieldNodes = [];

$this->simpleCallableNodeTraverser->traverseNodesWithCallable((array) $functionLike->getStmts(), static function (
Node $node
) use (&$yieldNodes): ?int {
// skip anonymous class and inner function
if ($node instanceof Class_) {
return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
}

// skip nested scope
if ($node instanceof FunctionLike) {
return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
}

if ($node instanceof Stmt && ! $node instanceof Expression) {
$yieldNodes = [];
return NodeVisitor::STOP_TRAVERSAL;
}

if (! $node instanceof Yield_ && ! $node instanceof YieldFrom) {
return null;
}

$yieldNodes[] = $node;
return null;
});

return $yieldNodes;
}

private function resolveYieldValue(Yield_ | YieldFrom $yield): ?Expr
{
if ($yield instanceof Yield_) {
return $yield->value;
}

return $yield->expr;
}

/**
* @param array<Yield_|YieldFrom> $yieldNodes
* @return Type[]
*/
private function resolveYieldedTypes(array $yieldNodes): array
{
$yieldedTypes = [];

foreach ($yieldNodes as $yieldNode) {
$value = $this->resolveYieldValue($yieldNode);
if (! $value instanceof Expr) {
// one of the yields is empty
return [];
}

$resolvedType = $this->nodeTypeResolver->getType($value);
if ($resolvedType instanceof MixedType) {
continue;
}

$yieldedTypes[] = $resolvedType;
}

return $yieldedTypes;
}

/**
* @param array<Yield_|YieldFrom> $yieldNodes
*/
private function resolveYieldType(
array $yieldNodes,
ClassMethod|Function_ $functionLike
): FullyQualifiedObjectType|FullyQualifiedGenericObjectType {
$yieldedTypes = $this->resolveYieldedTypes($yieldNodes);

$className = $this->resolveClassName($functionLike);

if ($yieldedTypes === []) {
return new FullyQualifiedObjectType($className);
}

$yieldedTypes = $this->typeFactory->createMixedPassedOrUnionType($yieldedTypes);
return new FullyQualifiedGenericObjectType($className, [$yieldedTypes]);
}

private function resolveClassName(Function_|ClassMethod|Closure $functionLike): string
{
$returnTypeNode = $functionLike->getReturnType();

if ($returnTypeNode instanceof Identifier && $returnTypeNode->name === 'iterable') {
return 'Iterator';
}

if ($returnTypeNode instanceof Name && ! $this->isName($returnTypeNode, 'Generator')) {
return $this->getName($returnTypeNode);
}

return 'Generator';
}
}
59 changes: 59 additions & 0 deletions rules/TypeDeclarationDocblocks/NodeFinder/YieldNodeFinder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace Rector\TypeDeclarationDocblocks\NodeFinder;

use PhpParser\Node;
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\Expression;
use PhpParser\NodeVisitor;
use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser;

final readonly class YieldNodeFinder
{
public function __construct(
private SimpleCallableNodeTraverser $simpleCallableNodeTraverser
) {
}

/**
* @return Yield_[]|YieldFrom[]
*/
public function find(FunctionLike $functionLike): array
{
$yieldNodes = [];

$this->simpleCallableNodeTraverser->traverseNodesWithCallable((array) $functionLike->getStmts(), static function (
Node $node
) use (&$yieldNodes): ?int {
// skip anonymous class and inner function
if ($node instanceof Class_) {
return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
}

// skip nested scope
if ($node instanceof FunctionLike) {
return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
}

if ($node instanceof Stmt && ! $node instanceof Expression) {
$yieldNodes = [];
return NodeVisitor::STOP_TRAVERSAL;
}

if (! $node instanceof Yield_ && ! $node instanceof YieldFrom) {
return null;
}

$yieldNodes[] = $node;
return null;
});

return $yieldNodes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public function refactor(Node $node): ?Node
$dataProviderNodes->getClassMethods()
);

$generalizedParameterType = $this->typeNormalizer->generalizeConstantBoolTypes($parameterType);
$generalizedParameterType = $this->typeNormalizer->generalizeConstantTypes($parameterType);

$parameterTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode(
$generalizedParameterType
Expand Down
Loading
Loading