Skip to content

Commit

Permalink
add full generic collection type
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Aug 15, 2024
1 parent b531e2b commit 696654e
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class VarAndAttribute
private $trainings = [];

/**
* @param Collection|\Rector\Doctrine\Tests\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Source\Training[] $trainings
* @param \Doctrine\Common\Collections\Collection<int, \Rector\Doctrine\Tests\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Source\Training> $trainings
*/
public function setTrainings($trainings)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\Type\Type;
use PHPStan\Type\Generic\GenericObjectType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
Expand Down Expand Up @@ -141,8 +141,11 @@ private function refactorClassMethod(Class_ $class): ?Class_
continue;
}

$collectionObjectType = $this->setterCollectionResolver->resolveAssignedType($class, $classMethod);
if (! $collectionObjectType instanceof Type) {
$collectionObjectType = $this->setterCollectionResolver->resolveAssignedGenericCollectionType(
$class,
$classMethod
);
if (! $collectionObjectType instanceof GenericObjectType) {
continue;
}

Expand All @@ -159,6 +162,7 @@ private function refactorClassMethod(Class_ $class): ?Class_

/** @var string $parameterName */
$parameterName = $this->getName($param);

$this->phpDocTypeChanger->changeParamType(
$classMethod,
$phpDocInfo,
Expand Down
54 changes: 52 additions & 2 deletions rules/CodeQuality/SetterCollectionResolver.php
Original file line number Diff line number Diff line change
@@ -1,31 +1,45 @@
<?php

declare(strict_types=1);

namespace Rector\Doctrine\CodeQuality;

use Rector\Doctrine\TypeAnalyzer\CollectionTypeFactory;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\Reflection\Php\PhpPropertyReflection;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\Doctrine\TypeAnalyzer\CollectionVarTagValueNodeResolver;
use Rector\NodeManipulator\AssignManipulator;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\Reflection\ReflectionResolver;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Rector\StaticTypeMapper\ValueObject\Type\ShortenedObjectType;

final readonly class SetterCollectionResolver
{
/**
* @var string
*/
private const COLLECTION_CLASS = 'Doctrine\Common\Collections\Collection';

public function __construct(
private AssignManipulator $assignManipulator,
private ReflectionResolver $reflectionResolver,
private NodeNameResolver $nodeNameResolver,
private CollectionVarTagValueNodeResolver $collectionVarTagValueNodeResolver,
private StaticTypeMapper $staticTypeMapper,
private CollectionTypeFactory $collectionTypeFactory
) {
}

public function resolveAssignedType(Class_ $class, ClassMethod $classMethod): ?Type
public function resolveAssignedGenericCollectionType(Class_ $class, ClassMethod $classMethod): ?GenericObjectType
{
$propertyFetches = $this->assignManipulator->resolveAssignsToLocalPropertyFetches($classMethod);
if (count($propertyFetches) !== 1) {
Expand All @@ -51,6 +65,42 @@ public function resolveAssignedType(Class_ $class, ClassMethod $classMethod): ?T
return null;
}

return $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($varTagValueNode->type, $property);
// remove collection union type, so this can be turned into generic type
$resolvedType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType(
$varTagValueNode->type,
$property
);

if ($resolvedType instanceof UnionType) {
$nonCollectionTypes = [];
foreach ($resolvedType->getTypes() as $unionedType) {
if (! $this->isCollectionType($unionedType)) {
continue;
}

$nonCollectionTypes[] = $unionedType;
}

if (count($nonCollectionTypes) === 1) {
$soleType = $nonCollectionTypes[0];
if ($soleType instanceof ArrayType && $soleType->getItemType() instanceof ObjectType) {
return $this->collectionTypeFactory->createType($soleType->getItemType());
}
}
}

if ($resolvedType instanceof GenericObjectType) {
return $resolvedType;
}

return null;
}

private function isCollectionType(Type $type): bool
{
if ($type instanceof ShortenedObjectType && $type->getFullyQualifiedName() === self::COLLECTION_CLASS) {
return true;
}
return $type instanceof ObjectType && $type->getClassName() === self::COLLECTION_CLASS;
}
}
6 changes: 3 additions & 3 deletions src/TypeAnalyzer/CollectionTypeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\IntegerType;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use PHPStan\Type\ObjectType;

final class CollectionTypeFactory
{
public function createType(FullyQualifiedObjectType $fullyQualifiedObjectType): GenericObjectType
public function createType(ObjectType $objectType): GenericObjectType
{
$genericTypes = [new IntegerType(), $fullyQualifiedObjectType];
$genericTypes = [new IntegerType(), $objectType];
return new GenericObjectType('Doctrine\Common\Collections\Collection', $genericTypes);
}
}

0 comments on commit 696654e

Please sign in to comment.