decouple UnionTypeAnalyzer and UnionTypeAnalysis

This commit is contained in:
TomasVotruba 2020-01-14 21:14:35 +01:00
parent 49b3ca04b8
commit ba72bc847d
7 changed files with 151 additions and 106 deletions

View File

@ -6,3 +6,4 @@ services:
Rector\PHPStanStaticTypeMapper\:
resource: '../src'
exclude:
- '../src/ValueObject/*'

View File

@ -158,14 +158,6 @@ final class PHPStanStaticTypeMapper
return new Identifier('callable');
}
// if ($phpStanType instanceof ShortenedObjectType) {
// return new FullyQualified($phpStanType->getFullyQualifiedName());
// }
//
// if ($phpStanType instanceof AliasedObjectType) {
// return new Name($phpStanType->getClassName());
// }
if ($phpStanType instanceof TypeWithClassName) {
$lowerCasedClassName = strtolower($phpStanType->getClassName());
if ($lowerCasedClassName === 'callable') {

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Rector\PHPStanStaticTypeMapper\TypeAnalyzer;
use PHPStan\Type\ArrayType;
use PHPStan\Type\IterableType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\UnionType;
use Rector\PHPStanStaticTypeMapper\ValueObject\UnionTypeAnalysis;
use Traversable;
final class UnionTypeAnalyzer
{
public function analyseForNullableAndIterable(UnionType $unionType): ?UnionTypeAnalysis
{
$isNullableType = false;
$hasIterable = false;
foreach ($unionType->getTypes() as $unionedType) {
if ($unionedType instanceof IterableType) {
$hasIterable = true;
continue;
}
if ($unionedType instanceof ArrayType) {
continue;
}
if ($unionedType instanceof NullType) {
$isNullableType = true;
continue;
}
if ($unionedType instanceof ObjectType) {
if ($unionedType->getClassName() === Traversable::class) {
$hasIterable = true;
continue;
}
}
return null;
}
return new UnionTypeAnalysis($isNullableType, $hasIterable);
}
}

View File

@ -4,19 +4,15 @@ declare(strict_types=1);
namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
use PHP_CodeSniffer\Reports\Full;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\ClassExistenceStaticHelper;
use Rector\PHPStan\Type\AliasedObjectType;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
use Rector\PHPStan\Type\ShortenedObjectType;
@ -58,34 +54,9 @@ final class ObjectTypeMapper implements TypeMapperInterface
if ($type->getClassName() === 'object') {
return new Identifier('object');
}
return new FullyQualified($type->getClassName());
// if ($type->getClassName()) {
// dump($type);
// die;
// }
//
// if ($type->getClassName() === 'object') {
// dump($type);
// die;
// } else {
// die;
// }
//
// return new Identifier('object');
}
// ...
dump($type->getFullyQualifiedName());
die;
// fallback
return new FullyQualified($type->getFullyQualifiedName());
dump($type);
die;
return null;
return new FullyQualified($type->getClassName());
}
}

View File

@ -6,11 +6,12 @@ namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType as PhpParserUnionType;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ArrayType;
use PHPStan\Type\IterableType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
@ -20,8 +21,8 @@ use Rector\Php\PhpVersionProvider;
use Rector\PHPStanStaticTypeMapper\Contract\PHPStanStaticTypeMapperAwareInterface;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
use Rector\PHPStanStaticTypeMapper\TypeAnalyzer\UnionTypeAnalyzer;
use Rector\ValueObject\PhpVersionFeature;
use Traversable;
final class UnionTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMapperAwareInterface
{
@ -35,9 +36,15 @@ final class UnionTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMap
*/
private $phpVersionProvider;
public function __construct(PhpVersionProvider $phpVersionProvider)
/**
* @var UnionTypeAnalyzer
*/
private $unionTypeAnalyzer;
public function __construct(PhpVersionProvider $phpVersionProvider, UnionTypeAnalyzer $unionTypeAnalyzer)
{
$this->phpVersionProvider = $phpVersionProvider;
$this->unionTypeAnalyzer = $unionTypeAnalyzer;
}
public function getNodeClass(): string
@ -61,6 +68,9 @@ final class UnionTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMap
return new AttributeAwareUnionTypeNode($unionTypesNodes);
}
/**
* @required
*/
public function setPHPStanStaticTypeMapper(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void
{
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
@ -84,16 +94,16 @@ final class UnionTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMap
return $this->matchTypeForUnionedObjectTypes($type);
}
$nullabledTypeNode = $this->mapToPhpParserNode($nullabledType);
$nullabledTypeNode = $this->phpStanStaticTypeMapper->mapToPhpParserNode($nullabledType);
if ($nullabledTypeNode === null) {
return null;
}
if ($nullabledTypeNode instanceof Node\NullableType) {
if ($nullabledTypeNode instanceof NullableType) {
return $nullabledTypeNode;
}
if ($nullabledTypeNode instanceof Node\UnionType) {
if ($nullabledTypeNode instanceof PhpParserUnionType) {
throw new ShouldNotHappenException();
}
@ -102,36 +112,13 @@ final class UnionTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMap
private function matchArrayTypes(UnionType $unionType): ?Identifier
{
$isNullableType = false;
$hasIterable = false;
foreach ($unionType->getTypes() as $unionedType) {
if ($unionedType instanceof IterableType) {
$hasIterable = true;
continue;
}
if ($unionedType instanceof ArrayType) {
continue;
}
if ($unionedType instanceof NullType) {
$isNullableType = true;
continue;
}
if ($unionedType instanceof ObjectType) {
if ($unionedType->getClassName() === Traversable::class) {
$hasIterable = true;
continue;
}
}
$unionTypeAnalysis = $this->unionTypeAnalyzer->analyseForNullableAndIterable($unionType);
if ($unionTypeAnalysis === null) {
return null;
}
$type = $hasIterable ? 'iterable' : 'array';
if ($isNullableType) {
$type = $unionTypeAnalysis->hasIterable() ? 'iterable' : 'array';
if ($unionTypeAnalysis->isNullableType()) {
return new Identifier('?' . $type);
}
@ -158,9 +145,8 @@ final class UnionTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMap
return null;
}
/**
* @return Node\Name|Node\Name\FullyQualified|Node\UnionType|null
* @return Name|FullyQualified|PhpParserUnionType|null
*/
private function matchTypeForUnionedObjectTypes(UnionType $unionType): ?Node
{
@ -169,7 +155,47 @@ final class UnionTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMap
return $phpParserUnionType;
}
// do the type should be compatible with all other types, e.g. A extends B, B
// the type should be compatible with all other types, e.g. A extends B, B
$compatibleObjectCandidate = $this->resolveCompatibleObjectCandidate($unionType);
if ($compatibleObjectCandidate === null) {
return null;
}
return new FullyQualified($compatibleObjectCandidate);
}
private function matchPhpParserUnionType(UnionType $unionType): ?PhpParserUnionType
{
if (! $this->phpVersionProvider->isAtLeast(PhpVersionFeature::UNION_TYPES)) {
return null;
}
$phpParserUnionedTypes = [];
foreach ($unionType->getTypes() as $unionedType) {
/** @var Identifier|Name|null $phpParserNode */
$phpParserNode = $this->phpStanStaticTypeMapper->mapToPhpParserNode($unionedType);
if ($phpParserNode === null) {
return null;
}
$phpParserUnionedTypes[] = $phpParserNode;
}
return new PhpParserUnionType($phpParserUnionedTypes);
}
private function areTypeWithClassNamesRelated(TypeWithClassName $firstType, TypeWithClassName $secondType): bool
{
if (is_a($firstType->getClassName(), $secondType->getClassName(), true)) {
return true;
}
return is_a($secondType->getClassName(), $firstType->getClassName(), true);
}
private function resolveCompatibleObjectCandidate(UnionType $unionType): ?string
{
foreach ($unionType->getTypes() as $unionedType) {
if (! $unionedType instanceof TypeWithClassName) {
return null;
@ -185,38 +211,9 @@ final class UnionTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMap
}
}
return new Node\Name\FullyQualified($unionedType->getClassName());
return $unionedType->getClassName();
}
return null;
}
private function matchPhpParserUnionType(UnionType $unionType): ?Node\UnionType
{
if (! $this->phpVersionProvider->isAtLeast(PhpVersionFeature::UNION_TYPES)) {
return null;
}
$phpParserUnionedTypes = [];
foreach ($unionType->getTypes() as $unionedType) {
/** @var Identifier|Node\Name|null $phpParserNode */
$phpParserNode = $this->mapToPhpParserNode($unionedType);
if ($phpParserNode === null) {
return null;
}
$phpParserUnionedTypes[] = $phpParserNode;
}
return new Node\UnionType($phpParserUnionedTypes);
}
private function areTypeWithClassNamesRelated(TypeWithClassName $firstType, TypeWithClassName $secondType): bool
{
if (is_a($firstType->getClassName(), $secondType->getClassName(), true)) {
return true;
}
return is_a($secondType->getClassName(), $firstType->getClassName(), true);
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Rector\PHPStanStaticTypeMapper\ValueObject;
final class UnionTypeAnalysis
{
/**
* @var bool
*/
private $isNullableType = false;
/**
* @var bool
*/
private $hasIterable = false;
public function __construct(bool $isNullableType, bool $hasIterable)
{
$this->isNullableType = $isNullableType;
$this->hasIterable = $hasIterable;
}
public function isNullableType(): bool
{
return $this->isNullableType;
}
public function hasIterable(): bool
{
return $this->hasIterable;
}
}

View File

@ -270,6 +270,7 @@ final class DumpNodesCommand extends AbstractCommand
$node = new Function_('some_function');
} elseif ($nodeClass === ClassMethod::class) {
$node = new ClassMethod('someMethod');
$node->flags |= Class_::MODIFIER_PUBLIC;
} elseif ($nodeClass === Case_::class) {
$node = new Case_(new ConstFetch(new Name('true')));
} elseif ($nodeClass === Use_::class) {