decopule PhpDoTypeMapper, PhpParserNodeMapper

This commit is contained in:
TomasVotruba 2020-01-28 17:39:05 +01:00
parent 3f15e1b558
commit 94bf9d1522
28 changed files with 863 additions and 312 deletions

View File

@ -6,9 +6,9 @@ services:
Rector\NodeTypeResolver\:
resource: '../src'
exclude:
- '../src/Contract'
- '../src/Contract/*'
# "Type" is because the file is needed for PHPStan container only
- '../src/Type'
- '../src/PHPStan/TypeExtension/*'
Rector\Php\TypeAnalyzer: null
Rector\FileSystem\FilesFinder: null

View File

@ -1,8 +1,8 @@
services:
-
class: Rector\NodeTypeResolver\Type\TypeExtension\StaticContainerGetDynamicMethodReturnTypeExtension
class: Rector\NodeTypeResolver\PHPStan\TypeExtension\StaticContainerGetDynamicMethodReturnTypeExtension
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
-
class: Rector\NodeTypeResolver\Type\TypeExtension\KernelGetContainerAfterBootReturnTypeExtension
class: Rector\NodeTypeResolver\PHPStan\TypeExtension\KernelGetContainerAfterBootReturnTypeExtension
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\Contract\PhpDocParser;
use PhpParser\Node;
use PHPStan\Analyser\NameScope;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Type;
interface PhpDocTypeMapperInterface
{
public function getNodeType(): string;
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type;
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\Contract\PhpParser;
use PhpParser\Node;
use PHPStan\Type\Type;
interface PhpParserNodeMapperInterface
{
public function getNodeType(): string;
public function mapToPHPStan(Node $node): Type;
}

View File

@ -196,6 +196,10 @@ final class NodeTypeResolver
return $nodeType->isSuperTypeOf(new NullType())->yes();
}
/**
* @deprecated
* Use @see NodeTypeResolver::resolve() instead
*/
public function getStaticType(Node $node): Type
{
if ($this->isArrayExpr($node)) {

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Rector\NodeTypeResolver\Type\TypeExtension;
namespace Rector\NodeTypeResolver\PHPStan\TypeExtension;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Rector\NodeTypeResolver\Type\TypeExtension;
namespace Rector\NodeTypeResolver\PHPStan\TypeExtension;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;

View File

@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PHPStan;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\ArrayType;
use PHPStan\Type\ConstantType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\PHPStan\Type\ShortenedObjectType;
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
final class TypeHasher
{
/**
* @var PHPStanStaticTypeMapper
*/
private $phpStanStaticTypeMapper;
public function __construct(PHPStanStaticTypeMapper $phpStanStaticTypeMapper)
{
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
}
public function createTypeHash(Type $type): string
{
if ($type instanceof MixedType) {
return serialize($type);
}
if ($type instanceof ArrayType) {
// @todo sort to make different order identical
return $this->createTypeHash($type->getItemType()) . '[]';
}
if ($type instanceof ShortenedObjectType) {
return $type->getFullyQualifiedName();
}
if ($type instanceof TypeWithClassName) {
return $type->getClassName();
}
if ($type instanceof ConstantType) {
if (method_exists($type, 'getValue')) {
return get_class($type) . $type->getValue();
}
throw new ShouldNotHappenException();
}
if ($type instanceof UnionType) {
$types = $type->getTypes();
sort($types);
$type = new UnionType($types);
}
return $this->phpStanStaticTypeMapper->mapToDocString($type);
}
}

View File

@ -45,6 +45,7 @@ use Rector\BetterPhpDocParser\Printer\PhpDocInfoPrinter;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Exception\MissingTagException;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PHPStan\TypeHasher;
use Rector\NodeTypeResolver\StaticTypeMapper;
use Rector\PHPStan\Type\AliasedObjectType;
use Rector\PHPStan\Type\ShortenedObjectType;
@ -94,6 +95,11 @@ final class DocBlockManipulator
*/
private $docBlockNameImporter;
/**
* @var TypeHasher
*/
private $typeHasher;
public function __construct(
PhpDocInfoFactory $phpDocInfoFactory,
PhpDocInfoPrinter $phpDocInfoPrinter,
@ -101,7 +107,8 @@ final class DocBlockManipulator
PhpDocNodeTraverser $phpDocNodeTraverser,
StaticTypeMapper $staticTypeMapper,
DocBlockClassRenamer $docBlockClassRenamer,
DocBlockNameImporter $docBlockNameImporter
DocBlockNameImporter $docBlockNameImporter,
TypeHasher $typeHasher
) {
$this->phpDocInfoFactory = $phpDocInfoFactory;
$this->phpDocInfoPrinter = $phpDocInfoPrinter;
@ -110,6 +117,7 @@ final class DocBlockManipulator
$this->staticTypeMapper = $staticTypeMapper;
$this->docBlockClassRenamer = $docBlockClassRenamer;
$this->docBlockNameImporter = $docBlockNameImporter;
$this->typeHasher = $typeHasher;
}
public function hasTag(Node $node, string $name): bool
@ -566,8 +574,8 @@ final class DocBlockManipulator
return true;
}
$firstTypeHash = $this->staticTypeMapper->createTypeHash($firstType);
$secondTypeHash = $this->staticTypeMapper->createTypeHash($secondType);
$firstTypeHash = $this->typeHasher->createTypeHash($firstType);
$secondTypeHash = $this->typeHasher->createTypeHash($secondType);
if ($firstTypeHash === $secondTypeHash) {
return true;

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpDoc;
use PhpParser\Node;
use PHPStan\Analyser\NameScope;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Type;
use Rector\Exception\NotImplementedException;
use Rector\NodeTypeResolver\Contract\PhpDocParser\PhpDocTypeMapperInterface;
final class PhpDocTypeMapper
{
/**
* @var PhpDocTypeMapperInterface[]
*/
private $phpDocTypeMappers = [];
/**
* @param PhpDocTypeMapperInterface[] $phpDocTypeMappers
*/
public function __construct(array $phpDocTypeMappers)
{
$this->phpDocTypeMappers = $phpDocTypeMappers;
}
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
{
foreach ($this->phpDocTypeMappers as $phpDocTypeMapper) {
if (! is_a($typeNode, $phpDocTypeMapper->getNodeType())) {
continue;
}
return $phpDocTypeMapper->mapToPHPStanType($typeNode, $node, $nameScope);
}
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($typeNode));
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpDocParser;
use PhpParser\Node;
use PHPStan\Analyser\NameScope;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ArrayType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\Contract\PhpDocParser\PhpDocTypeMapperInterface;
use Rector\NodeTypeResolver\PhpDoc\PhpDocTypeMapper;
final class ArrayTypeMapper implements PhpDocTypeMapperInterface
{
/**
* @var PhpDocTypeMapper
*/
private $phpDocTypeMapper;
public function getNodeType(): string
{
return ArrayTypeNode::class;
}
/**
* @required
*/
public function autowireGenericPhpDocTypeMapper(PhpDocTypeMapper $phpDocTypeMapper): void
{
$this->phpDocTypeMapper = $phpDocTypeMapper;
}
/**
* @param ArrayTypeNode $typeNode
*/
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
{
$nestedType = $this->phpDocTypeMapper->mapToPHPStanType($typeNode->type, $node, $nameScope);
// @todo improve for key!
return new ArrayType(new MixedType(), $nestedType);
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpDocParser;
use PhpParser\Node;
use PHPStan\Analyser\NameScope;
use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\Contract\PhpDocParser\PhpDocTypeMapperInterface;
final class GenericTypeMapper implements PhpDocTypeMapperInterface
{
/**
* @var TypeNodeResolver
*/
private $typeNodeResolver;
public function __construct(TypeNodeResolver $typeNodeResolver)
{
$this->typeNodeResolver = $typeNodeResolver;
}
public function getNodeType(): string
{
return GenericTypeNode::class;
}
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
{
return $this->typeNodeResolver->resolve($typeNode, $nameScope);
}
}

View File

@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpDocParser;
use PhpParser\Node;
use PHPStan\Analyser\NameScope;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ClassStringType;
use PHPStan\Type\IterableType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StaticType;
use PHPStan\Type\Type;
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareIdentifierTypeNode;
use Rector\BetterPhpDocParser\Type\PreSlashStringType;
use Rector\NodeTypeResolver\Contract\PhpDocParser\PhpDocTypeMapperInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\TypeMapper\ScalarStringToTypeMapper;
use Rector\PHPStan\Type\ParentStaticType;
use Rector\PHPStan\Type\SelfObjectType;
use Rector\TypeDeclaration\PHPStan\Type\ObjectTypeSpecifier;
final class IdentifierTypeMapper implements PhpDocTypeMapperInterface
{
/**
* @var ScalarStringToTypeMapper
*/
private $scalarStringToTypeMapper;
/**
* @var ObjectTypeSpecifier
*/
private $objectTypeSpecifier;
public function __construct(
ScalarStringToTypeMapper $scalarStringToTypeMapper,
ObjectTypeSpecifier $objectTypeSpecifier
) {
$this->scalarStringToTypeMapper = $scalarStringToTypeMapper;
$this->objectTypeSpecifier = $objectTypeSpecifier;
}
public function getNodeType(): string
{
return IdentifierTypeNode::class;
}
/**
* @param AttributeAwareIdentifierTypeNode&IdentifierTypeNode $typeNode
*/
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
{
$type = $this->scalarStringToTypeMapper->mapScalarStringToType($typeNode->name);
if (! $type instanceof MixedType) {
return $type;
}
$loweredName = strtolower($typeNode->name);
// @todo for all scalars
if ($loweredName === '\string') {
return new PreSlashStringType();
}
if ($loweredName === 'class-string') {
return new ClassStringType();
}
if ($loweredName === 'self') {
return $this->mapSelf($node);
}
if ($loweredName === 'parent') {
return $this->mapParent($node);
}
if ($loweredName === 'static') {
return $this->mapStatic($node);
}
if ($loweredName === 'iterable') {
return new IterableType(new MixedType(), new MixedType());
}
// @todo improve - making many false positives now
$objectType = new ObjectType($typeNode->name);
return $this->objectTypeSpecifier->narrowToFullyQualifiedOrAlaisedObjectType($node, $objectType);
}
private function mapStatic(Node $node): Type
{
/** @var string|null $className */
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
if ($className === null) {
return new MixedType();
}
return new StaticType($className);
}
private function mapParent(Node $node): Type
{
/** @var string|null $parentClassName */
$parentClassName = $node->getAttribute(AttributeKey::PARENT_CLASS_NAME);
if ($parentClassName === null) {
return new MixedType();
}
return new ParentStaticType($parentClassName);
}
private function mapSelf(Node $node): Type
{
/** @var string|null $className */
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
if ($className === null) {
// self outside the class, e.g. in a function
return new MixedType();
}
return new SelfObjectType($className);
}
}

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpDocParser;
use PhpParser\Node;
use PHPStan\Analyser\NameScope;
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\Contract\PhpDocParser\PhpDocTypeMapperInterface;
use Rector\NodeTypeResolver\PhpDoc\PhpDocTypeMapper;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
final class IntersectionTypeMapper implements PhpDocTypeMapperInterface
{
/**
* @var PhpDocTypeMapper
*/
private $phpDocTypeMapper;
/**
* @var TypeFactory
*/
private $typeFactory;
public function __construct(TypeFactory $typeFactory)
{
$this->typeFactory = $typeFactory;
}
public function getNodeType(): string
{
return IntersectionTypeNode::class;
}
/**
* @required
*/
public function autowireIntersectionTypeMapper(PhpDocTypeMapper $phpDocTypeMapper): void
{
$this->phpDocTypeMapper = $phpDocTypeMapper;
}
/**
* @param IntersectionTypeNode $typeNode
*/
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
{
$unionedTypes = [];
foreach ($typeNode->types as $unionedTypeNode) {
$unionedTypes[] = $this->phpDocTypeMapper->mapToPHPStanType($unionedTypeNode, $node, $nameScope);
}
// to prevent missing class error, e.g. in tests
return $this->typeFactory->createMixedPassedOrUnionType($unionedTypes);
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpDocParser;
use PhpParser\Node;
use PHPStan\Analyser\NameScope;
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Contract\PhpDocParser\PhpDocTypeMapperInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class ThisTypeMapper implements PhpDocTypeMapperInterface
{
public function getNodeType(): string
{
return ThisTypeNode::class;
}
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
{
if ($node === null) {
throw new ShouldNotHappenException();
}
/** @var string $className */
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
return new ThisType($className);
}
}

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpDocParser;
use PhpParser\Node;
use PHPStan\Analyser\NameScope;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\Contract\PhpDocParser\PhpDocTypeMapperInterface;
use Rector\NodeTypeResolver\PhpDoc\PhpDocTypeMapper;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
final class UnionTypeMapper implements PhpDocTypeMapperInterface
{
/**
* @var PhpDocTypeMapper
*/
private $phpDocTypeMapper;
/**
* @var TypeFactory
*/
private $typeFactory;
public function __construct(TypeFactory $typeFactory)
{
$this->typeFactory = $typeFactory;
}
public function getNodeType(): string
{
return UnionTypeNode::class;
}
/**
* @required
*/
public function autowireUnionTypeMapper(PhpDocTypeMapper $phpDocTypeMapper): void
{
$this->phpDocTypeMapper = $phpDocTypeMapper;
}
/**
* @param UnionTypeNode $typeNode
*/
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
{
$unionedTypes = [];
foreach ($typeNode->types as $unionedTypeNode) {
$unionedTypes[] = $this->phpDocTypeMapper->mapToPHPStanType($unionedTypeNode, $node, $nameScope);
}
// to prevent missing class error, e.g. in tests
return $this->typeFactory->createMixedPassedOrUnionType($unionedTypes);
}
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpParser;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PHPStan\Analyser\Scope;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\Contract\PhpParser\PhpParserNodeMapperInterface;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class ExprNodeMapper implements PhpParserNodeMapperInterface
{
public function getNodeType(): string
{
return Expr::class;
}
/**
* @param Expr $node
*/
public function mapToPHPStan(Node $node): Type
{
/** @var Scope|null $scope */
$scope = $node->getAttribute(AttributeKey::SCOPE);
if ($scope === null) {
return new MixedType();
}
return $scope->getType($node);
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpParser;
use PhpParser\Node;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\Contract\PhpParser\PhpParserNodeMapperInterface;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
final class FullyQualifiedNodeMapper implements PhpParserNodeMapperInterface
{
public function getNodeType(): string
{
return FullyQualified::class;
}
/**
* @param FullyQualified $node
*/
public function mapToPHPStan(Node $node): Type
{
return new FullyQualifiedObjectType($node->toString());
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpParser;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PHPStan\Type\MixedType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\Contract\PhpParser\PhpParserNodeMapperInterface;
use Rector\NodeTypeResolver\TypeMapper\ScalarStringToTypeMapper;
final class IdentifierNodeMapper implements PhpParserNodeMapperInterface
{
/**
* @var ScalarStringToTypeMapper
*/
private $scalarStringToTypeMapper;
public function __construct(ScalarStringToTypeMapper $scalarStringToTypeMapper)
{
$this->scalarStringToTypeMapper = $scalarStringToTypeMapper;
}
public function getNodeType(): string
{
return Identifier::class;
}
/**
* @param Identifier $node
*/
public function mapToPHPStan(Node $node): Type
{
if ($node->name === 'string') {
return new StringType();
}
$type = $this->scalarStringToTypeMapper->mapScalarStringToType($node->name);
if ($type !== null) {
return $type;
}
return new MixedType();
}
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpParser;
use PhpParser\Node;
use PhpParser\Node\Name;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\ClassExistenceStaticHelper;
use Rector\NodeTypeResolver\Contract\PhpParser\PhpParserNodeMapperInterface;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
final class NameNodeMapper implements PhpParserNodeMapperInterface
{
public function getNodeType(): string
{
return Name::class;
}
/**
* @param Name $node
*/
public function mapToPHPStan(Node $node): Type
{
$name = $node->toString();
if (ClassExistenceStaticHelper::doesClassLikeExist($name)) {
return new FullyQualifiedObjectType($node->toString());
}
return new MixedType();
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\PhpParser;
use PhpParser\Node;
use PhpParser\Node\NullableType;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use Rector\NodeTypeResolver\Contract\PhpParser\PhpParserNodeMapperInterface;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\NodeTypeResolver\TypeMapper\PhpParserNodeMapper;
final class NullableTypeNodeMapper implements PhpParserNodeMapperInterface
{
/**
* @var TypeFactory
*/
private $typeFactory;
/**
* @var PhpParserNodeMapper
*/
private $phpParserNodeMapper;
public function __construct(TypeFactory $typeFactory)
{
$this->typeFactory = $typeFactory;
}
/**
* @required
*/
public function autowireNullableTypeNodeMapper(PhpParserNodeMapper $phpParserNodeMapper): void
{
$this->phpParserNodeMapper = $phpParserNodeMapper;
}
public function getNodeType(): string
{
return NullableType::class;
}
/**
* @param NullableType $node
*/
public function mapToPHPStan(Node $node): Type
{
$types = [];
$types[] = $this->phpParserNodeMapper->mapToPHPStanType($node->type);
$types[] = new NullType();
return $this->typeFactory->createMixedPassedOrUnionType($types);
}
}

View File

@ -6,7 +6,6 @@ namespace Rector\NodeTypeResolver;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
@ -14,93 +13,54 @@ use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\UseUse;
use PhpParser\Node\UnionType as PhpParserUnionType;
use PHPStan\Analyser\NameScope;
use PHPStan\Analyser\Scope;
use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\CallableType;
use PHPStan\Type\ClassStringType;
use PHPStan\Type\ConstantType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IterableType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\ResourceType;
use PHPStan\Type\StaticType;
use PHPStan\Type\StringType;
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use PHPStan\Type\VoidType;
use Rector\BetterPhpDocParser\Type\PreSlashStringType;
use Rector\Exception\NotImplementedException;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\NodeTypeResolver\PhpDoc\PhpDocTypeMapper;
use Rector\NodeTypeResolver\TypeMapper\PhpParserNodeMapper;
use Rector\PhpParser\Node\Resolver\NameResolver;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
use Rector\PHPStan\Type\ParentStaticType;
use Rector\PHPStan\Type\SelfObjectType;
use Rector\PHPStan\Type\ShortenedObjectType;
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
use Rector\TypeDeclaration\PHPStan\Type\ObjectTypeSpecifier;
/**
* Maps PhpParser <=> PHPStan <=> PHPStan doc <=> string type nodes between all possible formats
*/
final class StaticTypeMapper
{
/**
* @var TypeFactory
*/
private $typeFactory;
/**
* @var ObjectTypeSpecifier
*/
private $objectTypeSpecifier;
/**
* @var PHPStanStaticTypeMapper
*/
private $phpStanStaticTypeMapper;
/**
* @var TypeNodeResolver
*/
private $typeNodeResolver;
/**
* @var NameResolver
*/
private $nameResolver;
/**
* @var PhpParserNodeMapper
*/
private $phpParserNodeMapper;
/**
* @var PhpDocTypeMapper
*/
private $phpDocTypeMapper;
public function __construct(
TypeFactory $typeFactory,
ObjectTypeSpecifier $objectTypeSpecifier,
PHPStanStaticTypeMapper $phpStanStaticTypeMapper,
TypeNodeResolver $typeNodeResolver,
NameResolver $nameResolver
NameResolver $nameResolver,
PhpParserNodeMapper $phpParserNodeMapper,
PhpDocTypeMapper $phpDocTypeMapper
) {
$this->typeFactory = $typeFactory;
$this->objectTypeSpecifier = $objectTypeSpecifier;
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
$this->typeNodeResolver = $typeNodeResolver;
$this->nameResolver = $nameResolver;
$this->phpParserNodeMapper = $phpParserNodeMapper;
$this->phpDocTypeMapper = $phpDocTypeMapper;
}
public function mapPHPStanTypeToPHPStanPhpDocTypeNode(Type $phpStanType): TypeNode
@ -123,47 +83,7 @@ final class StaticTypeMapper
public function mapPhpParserNodePHPStanType(Node $node): Type
{
if ($node instanceof Expr) {
/** @var Scope $scope */
$scope = $node->getAttribute(AttributeKey::SCOPE);
return $scope->getType($node);
}
if ($node instanceof NullableType) {
$types = [];
$types[] = $this->mapPhpParserNodePHPStanType($node->type);
$types[] = new NullType();
return $this->typeFactory->createMixedPassedOrUnionType($types);
}
if ($node instanceof Identifier) {
if ($node->name === 'string') {
return new StringType();
}
$type = $this->mapScalarStringToType($node->name);
if ($type !== null) {
return $type;
}
}
if ($node instanceof FullyQualified) {
return new FullyQualifiedObjectType($node->toString());
}
if ($node instanceof Name) {
$name = $node->toString();
if (ClassExistenceStaticHelper::doesClassLikeExist($name)) {
return new FullyQualifiedObjectType($node->toString());
}
return new MixedType();
}
throw new NotImplementedException(__METHOD__ . 'for type ' . get_class($node));
return $this->phpParserNodeMapper->mapToPHPStanType($node);
}
public function mapPHPStanPhpDocTypeToPHPStanType(PhpDocTagValueNode $phpDocTagValueNode, Node $node): Type
@ -178,42 +98,6 @@ final class StaticTypeMapper
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($phpDocTagValueNode));
}
public function createTypeHash(Type $type): string
{
if ($type instanceof MixedType) {
return serialize($type);
}
if ($type instanceof ArrayType) {
// @todo sort to make different order identical
return $this->createTypeHash($type->getItemType()) . '[]';
}
if ($type instanceof ShortenedObjectType) {
return $type->getFullyQualifiedName();
}
if ($type instanceof FullyQualifiedObjectType || $type instanceof ObjectType) {
return $type->getClassName();
}
if ($type instanceof ConstantType) {
if (method_exists($type, 'getValue')) {
return get_class($type) . $type->getValue();
}
throw new ShouldNotHappenException();
}
if ($type instanceof UnionType) {
$types = $type->getTypes();
sort($types);
$type = new UnionType($types);
}
return $this->mapPHPStanTypeToDocString($type);
}
/**
* @return Identifier|Name|NullableType|null
*/
@ -265,154 +149,8 @@ final class StaticTypeMapper
public function mapPHPStanPhpDocTypeNodeToPHPStanType(TypeNode $typeNode, Node $node): Type
{
$nameScope = $this->createNameScopeFromNode($node);
// @todo use in the future
$type = $this->typeNodeResolver->resolve($typeNode, $nameScope);
if ($typeNode instanceof GenericTypeNode) {
return $type;
}
return $this->customMapPHPStanPhpDocTypeNodeToPHPStanType($typeNode, $node);
}
/**
* @deprecated Move gradualy to @see \PHPStan\PhpDoc\TypeNodeResolver from PHPStan
*/
private function customMapPHPStanPhpDocTypeNodeToPHPStanType(TypeNode $typeNode, Node $node): Type
{
if ($typeNode instanceof IdentifierTypeNode) {
$type = $this->mapScalarStringToType($typeNode->name);
if ($type !== null) {
return $type;
}
$loweredName = strtolower($typeNode->name);
if ($loweredName === '\string') {
return new PreSlashStringType();
}
if ($loweredName === 'class-string') {
return new ClassStringType();
}
if ($loweredName === 'self') {
/** @var string|null $className */
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
if ($className === null) {
// self outside the class, e.g. in a function
return new MixedType();
}
return new SelfObjectType($className);
}
if ($loweredName === 'parent') {
/** @var string|null $parentClassName */
$parentClassName = $node->getAttribute(AttributeKey::PARENT_CLASS_NAME);
if ($parentClassName === null) {
return new MixedType();
}
return new ParentStaticType($parentClassName);
}
if ($loweredName === 'static') {
/** @var string|null $className */
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
if ($className === null) {
return new MixedType();
}
return new StaticType($className);
}
if ($loweredName === 'iterable') {
return new IterableType(new MixedType(), new MixedType());
}
// @todo improve - making many false positives now
$objectType = new ObjectType($typeNode->name);
return $this->objectTypeSpecifier->narrowToFullyQualifiedOrAlaisedObjectType($node, $objectType);
}
if ($typeNode instanceof ArrayTypeNode) {
$nestedType = $this->mapPHPStanPhpDocTypeNodeToPHPStanType($typeNode->type, $node);
return new ArrayType(new MixedType(), $nestedType);
}
if ($typeNode instanceof UnionTypeNode || $typeNode instanceof IntersectionTypeNode) {
$unionedTypes = [];
foreach ($typeNode->types as $unionedTypeNode) {
$unionedTypes[] = $this->mapPHPStanPhpDocTypeNodeToPHPStanType($unionedTypeNode, $node);
}
// to prevent missing class error, e.g. in tests
return $this->typeFactory->createMixedPassedOrUnionType($unionedTypes);
}
if ($typeNode instanceof ThisTypeNode) {
if ($node === null) {
throw new ShouldNotHappenException();
}
/** @var string $className */
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
return new ThisType($className);
}
throw new NotImplementedException(__METHOD__ . ' for ' . get_class($typeNode));
}
private function mapScalarStringToType(string $scalarName): ?Type
{
$loweredScalarName = Strings::lower($scalarName);
if ($loweredScalarName === 'string') {
return new StringType();
}
if (in_array($loweredScalarName, ['float', 'real', 'double'], true)) {
return new FloatType();
}
if (in_array($loweredScalarName, ['int', 'integer'], true)) {
return new IntegerType();
}
if (in_array($loweredScalarName, ['false', 'true', 'bool', 'boolean'], true)) {
return new BooleanType();
}
if ($loweredScalarName === 'array') {
return new ArrayType(new MixedType(), new MixedType());
}
if ($loweredScalarName === 'null') {
return new NullType();
}
if ($loweredScalarName === 'void') {
return new VoidType();
}
if ($loweredScalarName === 'object') {
return new ObjectWithoutClassType();
}
if ($loweredScalarName === 'resource') {
return new ResourceType();
}
if (in_array($loweredScalarName, ['callback', 'callable'], true)) {
return new CallableType();
}
if ($loweredScalarName === 'mixed') {
return new MixedType(true);
}
return null;
return $this->phpDocTypeMapper->mapToPHPStanType($typeNode, $node, $nameScope);
}
/**

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\TypeMapper;
use PhpParser\Node;
use PHPStan\Type\Type;
use Rector\Exception\NotImplementedException;
use Rector\NodeTypeResolver\Contract\PhpParser\PhpParserNodeMapperInterface;
final class PhpParserNodeMapper
{
/**
* @var PhpParserNodeMapperInterface[]
*/
private $phpParserNodeMappers = [];
/**
* @param PhpParserNodeMapperInterface[] $phpParserNodeMappers
*/
public function __construct(array $phpParserNodeMappers)
{
$this->phpParserNodeMappers = $phpParserNodeMappers;
}
public function mapToPHPStanType(Node $node): Type
{
foreach ($this->phpParserNodeMappers as $phpParserNodeMapper) {
if (! is_a($node, $phpParserNodeMapper->getNodeType())) {
continue;
}
return $phpParserNodeMapper->mapToPHPStan($node);
}
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\TypeMapper;
use Nette\Utils\Strings;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\CallableType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\ResourceType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\VoidType;
final class ScalarStringToTypeMapper
{
public function mapScalarStringToType(string $scalarName): Type
{
$loweredScalarName = Strings::lower($scalarName);
$scalarNameByType = [
StringType::class => ['string'],
FloatType::class => ['float', 'real', 'double'],
IntegerType::class => ['int', 'integer'],
BooleanType::class => ['false', 'true', 'bool', 'boolean'],
NullType::class => ['null'],
VoidType::class => ['void'],
ResourceType::class => ['resource'],
CallableType::class => ['callback', 'callable'],
ObjectWithoutClassType::class => ['object'],
];
foreach ($scalarNameByType as $objectType => $scalarNames) {
if (! in_array($loweredScalarName, $scalarNames, true)) {
continue;
}
return new $objectType();
}
if ($loweredScalarName === 'array') {
return new ArrayType(new MixedType(), new MixedType());
}
if ($loweredScalarName === 'mixed') {
return new MixedType(true);
}
return new MixedType();
}
}

View File

@ -23,6 +23,14 @@ final class ArrayTypeMapper implements TypeMapperInterface
*/
private $phpStanStaticTypeMapper;
/**
* @required
*/
public function autowireArrayTypeMapper(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void
{
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
}
public function getNodeClass(): string
{
return ArrayType::class;
@ -74,14 +82,6 @@ final class ArrayTypeMapper implements TypeMapperInterface
return implode('|', $docStringTypes);
}
/**
* @required
*/
public function autowireArrayTypeMapper(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void
{
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
}
private function mapArrayUnionTypeToDocString(ArrayType $arrayType, UnionType $unionType): string
{
$unionedTypesAsString = [];

View File

@ -8,8 +8,8 @@ use PhpParser\Node;
use PhpParser\Node\Identifier;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\VerbosityLevel;
use Rector\Php\PhpVersionProvider;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
@ -29,17 +29,20 @@ final class TypeWithClassNameTypeMapper implements TypeMapperInterface
public function getNodeClass(): string
{
return StringType::class;
return TypeWithClassName::class;
}
/**
* @param StringType $type
* @param TypeWithClassName $type
*/
public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode
{
return new IdentifierTypeNode('string');
return new IdentifierTypeNode('string-class');
}
/**
* @param TypeWithClassName $type
*/
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node
{
if (! $this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) {
@ -49,6 +52,9 @@ final class TypeWithClassNameTypeMapper implements TypeMapperInterface
return new Identifier('string');
}
/**
* @param TypeWithClassName $type
*/
public function mapToDocString(Type $type, ?Type $parentType = null): string
{
return $type->describe(VerbosityLevel::typeOnly());

View File

@ -11,17 +11,12 @@ use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\NodeTypeResolver\StaticTypeMapper;
use Rector\NodeTypeResolver\PHPStan\TypeHasher;
use Rector\PHPStan\TypeFactoryStaticHelper;
use Rector\TypeDeclaration\ValueObject\NestedArrayTypeValueObject;
final class TypeNormalizer
{
/**
* @var StaticTypeMapper
*/
private $staticTypeMapper;
/**
* @var TypeFactory
*/
@ -32,10 +27,15 @@ final class TypeNormalizer
*/
private $collectedNestedArrayTypes = [];
public function __construct(StaticTypeMapper $staticTypeMapper, TypeFactory $typeFactory)
/**
* @var TypeHasher
*/
private $typeHasher;
public function __construct(TypeFactory $typeFactory, TypeHasher $typeHasher)
{
$this->staticTypeMapper = $staticTypeMapper;
$this->typeFactory = $typeFactory;
$this->typeHasher = $typeHasher;
}
/**
@ -84,7 +84,7 @@ final class TypeNormalizer
$uniqueTypes = [];
$removedKeys = [];
foreach ($type->getValueTypes() as $key => $valueType) {
$typeHash = $this->staticTypeMapper->createTypeHash($valueType);
$typeHash = $this->typeHasher->createTypeHash($valueType);
$valueType = $this->uniqueateConstantArrayType($valueType);
$valueType = $this->normalizeArrayOfUnionToUnionArray($valueType);

View File

@ -140,7 +140,6 @@ PHP
}
$type = $this->resolveType($node, $injectTagValueNode);
return $this->refactorPropertyWithAnnotation($node, $type, $tagClass);
}