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
49 changes: 18 additions & 31 deletions packages/NodeTypeResolver/src/StaticTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,6 @@ public function mapPHPStanPhpDocTypeNodeToPHPStanType(TypeNode $typeNode, Node $
}

// @todo improve - making many false positives now

$objectType = new ObjectType($typeNode->name);

return $this->objectTypeSpecifier->narrowToFullyQualifiedOrAlaisedObjectType($node, $objectType);
Expand Down Expand Up @@ -644,45 +643,33 @@ public function mapPHPStanPhpDocTypeNodeToPHPStanType(TypeNode $typeNode, Node $
}

if ($typeNode instanceof GenericTypeNode) {
if ($typeNode->type instanceof IdentifierTypeNode) {
$typeName = $typeNode->type->name;

// remove extra prefix
$typeName = ltrim($typeName, '\\');
$genericMainType = $this->mapPHPStanPhpDocTypeNodeToPHPStanType($typeNode->type, $node);

if (in_array($typeName, ['array', 'iterable', 'Traversable'], true)) {
$genericTypes = [];
foreach ($typeNode->genericTypes as $genericTypeNode) {
$genericTypes[] = $this->mapPHPStanPhpDocTypeNodeToPHPStanType($genericTypeNode, $node);
}
if ($genericMainType instanceof TypeWithClassName) {
$mainTypeAsString = $genericMainType->getClassName();
} else {
$mainTypeAsString = $typeNode->type->name;
}

$genericType = $this->typeFactory->createMixedPassedOrUnionType($genericTypes);
$genericTypes = [];
foreach ($typeNode->genericTypes as $genericTypeNode) {
$genericTypes[] = $this->mapPHPStanPhpDocTypeNodeToPHPStanType($genericTypeNode, $node);
}

if ($typeName === 'array') {
return new ArrayType(new MixedType(), $genericType);
}
// special use case for array
if (in_array($mainTypeAsString, ['array', 'iterable'], true)) {
$genericType = $this->typeFactory->createMixedPassedOrUnionType($genericTypes);

if ($typeName === 'Traversable') {
return new ObjectType('Traversable');
}
if ($mainTypeAsString === 'array') {
return new ArrayType(new MixedType(), $genericType);
}

if ($mainTypeAsString === 'iterable') {
return new IterableType(new MixedType(), $genericType);
}
}

$mainType = $this->mapPHPStanPhpDocTypeNodeToPHPStanType($typeNode->type, $node);
if ($mainType instanceof TypeWithClassName) {
$className = $mainType->getClassName();
} else {
throw new NotImplementedException();
}

$genericTypes = [];
foreach ($typeNode->genericTypes as $genericType) {
$genericTypes[] = $this->mapPHPStanPhpDocTypeNodeToPHPStanType($genericType, $node);
}

return new GenericObjectType($className, $genericTypes);
return new GenericObjectType($mainTypeAsString, $genericTypes);
}

throw new NotImplementedException(__METHOD__ . ' for ' . get_class($typeNode));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Rector\NodeTypeResolver\Tests\StaticTypeMapper;

use Iterator;
use PhpParser\Node\Scalar\String_;
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\IterableType;
use Rector\HttpKernel\RectorKernel;
use Rector\NodeTypeResolver\StaticTypeMapper;
use Symplify\PackageBuilder\Tests\AbstractKernelTestCase;

final class StaticTypeMapperTest extends AbstractKernelTestCase
{
/**
* @var StaticTypeMapper
*/
private $staticTypeMapper;

protected function setUp(): void
{
$this->bootKernel(RectorKernel::class);

$this->staticTypeMapper = self::$container->get(StaticTypeMapper::class);
}

/**
* @dataProvider provideDataForMapPHPStanPhpDocTypeNodeToPHPStanType()
*/
public function testMapPHPStanPhpDocTypeNodeToPHPStanType(TypeNode $typeNode, string $expectedType): void
{
$node = new String_('hey');

$phpStanType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($typeNode, $node);

$this->assertInstanceOf($expectedType, $phpStanType);
}

public function provideDataForMapPHPStanPhpDocTypeNodeToPHPStanType(): Iterator
{
$genericTypeNode = new GenericTypeNode(new IdentifierTypeNode('Traversable'), []);
yield [$genericTypeNode, GenericObjectType::class];

$genericTypeNode = new GenericTypeNode(new IdentifierTypeNode('iterable'), []);
yield [$genericTypeNode, IterableType::class];
}
}
4 changes: 2 additions & 2 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ parameters:

- '#Method Rector\\BetterPhpDocParser\\PhpDocNodeFactory\\Gedmo\\(.*?)\:\:createFromNodeAndTokens\(\) should return Rector\\BetterPhpDocParser\\PhpDocNode\\Gedmo\\(.*?)\|null but returns PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocTagValueNode\|null#'



- '#Access to an undefined property PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\:\:\$type#'
- '#Call to an undefined method PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\:\:toString\(\)#'
- '#Parameter \#1 \$expected of method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) expects class\-string<object\>, string given#'
- '#Unable to resolve the template type ExpectedType in call to method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\)#'