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
188 changes: 1 addition & 187 deletions packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
use PHPStan\Type\VoidType;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareUnionTypeNode;
use Rector\Exception\NotImplementedException;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\ClassExistenceStaticHelper;
use Rector\Php\PhpVersionProvider;
use Rector\PHPStan\Type\AliasedObjectType;
Expand All @@ -47,7 +46,6 @@
use Rector\PHPStanStaticTypeMapper\Contract\PHPStanStaticTypeMapperAwareInterface;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\ValueObject\PhpVersionFeature;
use Traversable;

final class PHPStanStaticTypeMapper
{
Expand Down Expand Up @@ -103,19 +101,6 @@ public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode
*/
public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?Node
{
if ($phpStanType instanceof VoidType) {
if ($this->phpVersionProvider->isAtLeast(PhpVersionFeature::VOID_TYPE)) {
if (in_array($kind, ['param', 'property'], true)) {
// param cannot be void
return null;
}

return new Identifier('void');
}

return null;
}

if ($phpStanType instanceof SelfObjectType) {
return new Identifier('self');
}
Expand All @@ -126,7 +111,7 @@ public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?No
continue;
}

return $typeMapper->mapToPhpParserNode($phpStanType);
return $typeMapper->mapToPhpParserNode($phpStanType, $kind);
}

if ($phpStanType instanceof ArrayType) {
Expand All @@ -137,14 +122,6 @@ public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?No
return null;
}

if ($phpStanType instanceof CallableType || $phpStanType instanceof ClosureType) {
if ($kind === 'property') {
return null;
}

return new Identifier('callable');
}

if ($phpStanType instanceof TypeWithClassName) {
$lowerCasedClassName = strtolower($phpStanType->getClassName());
if ($lowerCasedClassName === 'callable') {
Expand All @@ -166,50 +143,6 @@ public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?No
return new FullyQualified($phpStanType->getClassName());
}

if ($phpStanType instanceof UnionType) {
// match array types
$arrayNode = $this->matchArrayTypes($phpStanType);
if ($arrayNode !== null) {
return $arrayNode;
}

// special case for nullable
$nullabledType = $this->matchTypeForNullableUnionType($phpStanType);
if ($nullabledType === null) {
// use first unioned type in case of unioned object types
return $this->matchTypeForUnionedObjectTypes($phpStanType);
}

$nullabledTypeNode = $this->mapToPhpParserNode($nullabledType);
if ($nullabledTypeNode === null) {
return null;
}

if ($nullabledTypeNode instanceof NullableType) {
return $nullabledTypeNode;
}

if ($nullabledTypeNode instanceof PhpParserUnionType) {
throw new ShouldNotHappenException();
}

return new NullableType($nullabledTypeNode);
}

if ($phpStanType instanceof VoidType ||
$phpStanType instanceof ResourceType
) {
return null;
}

if ($phpStanType instanceof ObjectWithoutClassType) {
if ($this->phpVersionProvider->isAtLeast(PhpVersionFeature::OBJECT_TYPE)) {
return new Identifier('object');
}

return null;
}

throw new NotImplementedException(__METHOD__ . ' for ' . get_class($phpStanType));
}

Expand Down Expand Up @@ -340,123 +273,4 @@ private function convertUnionArrayTypeNodesToArrayTypeOfUnionTypeNodes(

return new AttributeAwareUnionTypeNode($unionedArrayType);
}

private function matchTypeForNullableUnionType(UnionType $unionType): ?Type
{
if (count($unionType->getTypes()) !== 2) {
return null;
}

$firstType = $unionType->getTypes()[0];
$secondType = $unionType->getTypes()[1];

if ($firstType instanceof NullType) {
return $secondType;
}

if ($secondType instanceof NullType) {
return $firstType;
}

return null;
}

/**
* @return Name|FullyQualified|PhpParserUnionType|null
*/
private function matchTypeForUnionedObjectTypes(UnionType $unionType): ?Node
{
$phpParserUnionType = $this->matchPhpParserUnionType($unionType);
if ($phpParserUnionType !== null) {
return $phpParserUnionType;
}

// do the type should be compatible with all other types, e.g. A extends B, B
foreach ($unionType->getTypes() as $unionedType) {
if (! $unionedType instanceof TypeWithClassName) {
return null;
}

foreach ($unionType->getTypes() as $nestedUnionedType) {
if (! $nestedUnionedType instanceof TypeWithClassName) {
return null;
}

if (! $this->areTypeWithClassNamesRelated($unionedType, $nestedUnionedType)) {
continue 2;
}
}

return new FullyQualified($unionedType->getClassName());
}

return null;
}

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 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;
}
}

return null;
}

$type = $hasIterable ? 'iterable' : 'array';
if ($isNullableType) {
return new Identifier('?' . $type);
}

return new Identifier($type);
}

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->mapToPhpParserNode($unionedType);
if ($phpParserNode === null) {
return null;
}

$phpParserUnionedTypes[] = $phpParserNode;
}

return new PhpParserUnionType($phpParserUnionedTypes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Rector\PHPStanStaticTypeMapper\TypeMapper;

use PhpParser\Node;
use PhpParser\Node\Identifier;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\CallableType;
use PHPStan\Type\Type;
use Rector\Exception\NotImplementedException;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;

final class CallableTypeMapper implements TypeMapperInterface
{
public function getNodeClass(): string
{
return CallableType::class;
}

/**
* @param CallableType $type
*/
public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode
{
throw new NotImplementedException();
}

/**
* @param CallableType $type
*/
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node
{
if ($kind === 'property') {
return null;
}

return new Identifier('callable');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Rector\PHPStanStaticTypeMapper\TypeMapper;

use PhpParser\Node;
use PhpParser\Node\Identifier;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ClosureType;
use PHPStan\Type\Type;
use Rector\Exception\NotImplementedException;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;

final class ClosureTypeMapper implements TypeMapperInterface
{
public function getNodeClass(): string
{
return ClosureType::class;
}

/**
* @param ClosureType $type
*/
public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode
{
throw new NotImplementedException();
}

/**
* @param ClosureType $type
*/
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node
{
if ($kind === 'property') {
return null;
}

return new Identifier('callable');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Rector\PHPStanStaticTypeMapper\TypeMapper;

use PhpParser\Node;
use PhpParser\Node\Identifier;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\Type;
use Rector\Php\PhpVersionProvider;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\ValueObject\PhpVersionFeature;

final class ObjectWithoutClassTypeMapper implements TypeMapperInterface
{
/**
* @var PhpVersionProvider
*/
private $phpVersionProvider;

public function __construct(PhpVersionProvider $phpVersionProvider)
{
$this->phpVersionProvider = $phpVersionProvider;
}

public function getNodeClass(): string
{
return ObjectWithoutClassType::class;
}

/**
* @param ObjectWithoutClassType $type
*/
public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode
{
return new IdentifierTypeNode('object');
}

/**
* @param ObjectWithoutClassType $type
*/
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node
{
if (! $this->phpVersionProvider->isAtLeast(PhpVersionFeature::OBJECT_TYPE)) {
return null;
}

return new Identifier('object');
}
}
Loading