diff --git a/packages/NodeTypeResolver/config/config.yaml b/packages/NodeTypeResolver/config/config.yaml index bb2046e4757d..faf069d85fac 100644 --- a/packages/NodeTypeResolver/config/config.yaml +++ b/packages/NodeTypeResolver/config/config.yaml @@ -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 diff --git a/packages/NodeTypeResolver/config/phpstan/type-extensions.neon b/packages/NodeTypeResolver/config/phpstan/type-extensions.neon index 292ee8925ee3..a1dddb6e8431 100644 --- a/packages/NodeTypeResolver/config/phpstan/type-extensions.neon +++ b/packages/NodeTypeResolver/config/phpstan/type-extensions.neon @@ -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] diff --git a/packages/NodeTypeResolver/src/Contract/PhpDocParser/PhpDocTypeMapperInterface.php b/packages/NodeTypeResolver/src/Contract/PhpDocParser/PhpDocTypeMapperInterface.php new file mode 100644 index 000000000000..44bbe76cd555 --- /dev/null +++ b/packages/NodeTypeResolver/src/Contract/PhpDocParser/PhpDocTypeMapperInterface.php @@ -0,0 +1,17 @@ +isSuperTypeOf(new NullType())->yes(); } + /** + * @deprecated + * Use @see NodeTypeResolver::resolve() instead + */ public function getStaticType(Node $node): Type { if ($this->isArrayExpr($node)) { diff --git a/packages/NodeTypeResolver/src/Type/TypeExtension/KernelGetContainerAfterBootReturnTypeExtension.php b/packages/NodeTypeResolver/src/PHPStan/TypeExtension/KernelGetContainerAfterBootReturnTypeExtension.php similarity index 96% rename from packages/NodeTypeResolver/src/Type/TypeExtension/KernelGetContainerAfterBootReturnTypeExtension.php rename to packages/NodeTypeResolver/src/PHPStan/TypeExtension/KernelGetContainerAfterBootReturnTypeExtension.php index e08ea040d7b0..a3c78163ff78 100644 --- a/packages/NodeTypeResolver/src/Type/TypeExtension/KernelGetContainerAfterBootReturnTypeExtension.php +++ b/packages/NodeTypeResolver/src/PHPStan/TypeExtension/KernelGetContainerAfterBootReturnTypeExtension.php @@ -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; diff --git a/packages/NodeTypeResolver/src/Type/TypeExtension/StaticContainerGetDynamicMethodReturnTypeExtension.php b/packages/NodeTypeResolver/src/PHPStan/TypeExtension/StaticContainerGetDynamicMethodReturnTypeExtension.php similarity index 95% rename from packages/NodeTypeResolver/src/Type/TypeExtension/StaticContainerGetDynamicMethodReturnTypeExtension.php rename to packages/NodeTypeResolver/src/PHPStan/TypeExtension/StaticContainerGetDynamicMethodReturnTypeExtension.php index 8814c1ab337f..bf6079537b24 100644 --- a/packages/NodeTypeResolver/src/Type/TypeExtension/StaticContainerGetDynamicMethodReturnTypeExtension.php +++ b/packages/NodeTypeResolver/src/PHPStan/TypeExtension/StaticContainerGetDynamicMethodReturnTypeExtension.php @@ -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; diff --git a/packages/NodeTypeResolver/src/PHPStan/TypeHasher.php b/packages/NodeTypeResolver/src/PHPStan/TypeHasher.php new file mode 100644 index 000000000000..bf6091102cbd --- /dev/null +++ b/packages/NodeTypeResolver/src/PHPStan/TypeHasher.php @@ -0,0 +1,64 @@ +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); + } +} diff --git a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php index fdc574b0eee4..77fb1e0f601c 100644 --- a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php +++ b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php @@ -45,6 +45,7 @@ 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 @@ public function __construct( PhpDocNodeTraverser $phpDocNodeTraverser, StaticTypeMapper $staticTypeMapper, DocBlockClassRenamer $docBlockClassRenamer, - DocBlockNameImporter $docBlockNameImporter + DocBlockNameImporter $docBlockNameImporter, + TypeHasher $typeHasher ) { $this->phpDocInfoFactory = $phpDocInfoFactory; $this->phpDocInfoPrinter = $phpDocInfoPrinter; @@ -110,6 +117,7 @@ public function __construct( $this->staticTypeMapper = $staticTypeMapper; $this->docBlockClassRenamer = $docBlockClassRenamer; $this->docBlockNameImporter = $docBlockNameImporter; + $this->typeHasher = $typeHasher; } public function hasTag(Node $node, string $name): bool @@ -566,8 +574,8 @@ private function areTypesEquals(Type $firstType, Type $secondType): bool 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; diff --git a/packages/NodeTypeResolver/src/PhpDoc/PhpDocTypeMapper.php b/packages/NodeTypeResolver/src/PhpDoc/PhpDocTypeMapper.php new file mode 100644 index 000000000000..2c06e5d26671 --- /dev/null +++ b/packages/NodeTypeResolver/src/PhpDoc/PhpDocTypeMapper.php @@ -0,0 +1,41 @@ +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)); + } +} diff --git a/packages/NodeTypeResolver/src/PhpDocParser/ArrayTypeMapper.php b/packages/NodeTypeResolver/src/PhpDocParser/ArrayTypeMapper.php new file mode 100644 index 000000000000..6f51f954a72a --- /dev/null +++ b/packages/NodeTypeResolver/src/PhpDocParser/ArrayTypeMapper.php @@ -0,0 +1,47 @@ +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); + } +} diff --git a/packages/NodeTypeResolver/src/PhpDocParser/GenericTypeMapper.php b/packages/NodeTypeResolver/src/PhpDocParser/GenericTypeMapper.php new file mode 100644 index 000000000000..e2d0b5e4a58c --- /dev/null +++ b/packages/NodeTypeResolver/src/PhpDocParser/GenericTypeMapper.php @@ -0,0 +1,36 @@ +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); + } +} diff --git a/packages/NodeTypeResolver/src/PhpDocParser/IdentifierTypeMapper.php b/packages/NodeTypeResolver/src/PhpDocParser/IdentifierTypeMapper.php new file mode 100644 index 000000000000..5f1763a28c1e --- /dev/null +++ b/packages/NodeTypeResolver/src/PhpDocParser/IdentifierTypeMapper.php @@ -0,0 +1,127 @@ +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); + } +} diff --git a/packages/NodeTypeResolver/src/PhpDocParser/IntersectionTypeMapper.php b/packages/NodeTypeResolver/src/PhpDocParser/IntersectionTypeMapper.php new file mode 100644 index 000000000000..b7cc5f791b36 --- /dev/null +++ b/packages/NodeTypeResolver/src/PhpDocParser/IntersectionTypeMapper.php @@ -0,0 +1,59 @@ +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); + } +} diff --git a/packages/NodeTypeResolver/src/PhpDocParser/ThisTypeMapper.php b/packages/NodeTypeResolver/src/PhpDocParser/ThisTypeMapper.php new file mode 100644 index 000000000000..56b0ceabed16 --- /dev/null +++ b/packages/NodeTypeResolver/src/PhpDocParser/ThisTypeMapper.php @@ -0,0 +1,34 @@ +getAttribute(AttributeKey::CLASS_NAME); + + return new ThisType($className); + } +} diff --git a/packages/NodeTypeResolver/src/PhpDocParser/UnionTypeMapper.php b/packages/NodeTypeResolver/src/PhpDocParser/UnionTypeMapper.php new file mode 100644 index 000000000000..8b8e44ec6f72 --- /dev/null +++ b/packages/NodeTypeResolver/src/PhpDocParser/UnionTypeMapper.php @@ -0,0 +1,59 @@ +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); + } +} diff --git a/packages/NodeTypeResolver/src/PhpParser/ExprNodeMapper.php b/packages/NodeTypeResolver/src/PhpParser/ExprNodeMapper.php new file mode 100644 index 000000000000..023c4a1d70f7 --- /dev/null +++ b/packages/NodeTypeResolver/src/PhpParser/ExprNodeMapper.php @@ -0,0 +1,35 @@ +getAttribute(AttributeKey::SCOPE); + if ($scope === null) { + return new MixedType(); + } + + return $scope->getType($node); + } +} diff --git a/packages/NodeTypeResolver/src/PhpParser/FullyQualifiedNodeMapper.php b/packages/NodeTypeResolver/src/PhpParser/FullyQualifiedNodeMapper.php new file mode 100644 index 000000000000..2b3068ab0727 --- /dev/null +++ b/packages/NodeTypeResolver/src/PhpParser/FullyQualifiedNodeMapper.php @@ -0,0 +1,27 @@ +toString()); + } +} diff --git a/packages/NodeTypeResolver/src/PhpParser/IdentifierNodeMapper.php b/packages/NodeTypeResolver/src/PhpParser/IdentifierNodeMapper.php new file mode 100644 index 000000000000..9e97f77287a1 --- /dev/null +++ b/packages/NodeTypeResolver/src/PhpParser/IdentifierNodeMapper.php @@ -0,0 +1,48 @@ +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(); + } +} diff --git a/packages/NodeTypeResolver/src/PhpParser/NameNodeMapper.php b/packages/NodeTypeResolver/src/PhpParser/NameNodeMapper.php new file mode 100644 index 000000000000..1d52d64fe6d5 --- /dev/null +++ b/packages/NodeTypeResolver/src/PhpParser/NameNodeMapper.php @@ -0,0 +1,35 @@ +toString(); + + if (ClassExistenceStaticHelper::doesClassLikeExist($name)) { + return new FullyQualifiedObjectType($node->toString()); + } + + return new MixedType(); + } +} diff --git a/packages/NodeTypeResolver/src/PhpParser/NullableTypeNodeMapper.php b/packages/NodeTypeResolver/src/PhpParser/NullableTypeNodeMapper.php new file mode 100644 index 000000000000..4394b277cb0a --- /dev/null +++ b/packages/NodeTypeResolver/src/PhpParser/NullableTypeNodeMapper.php @@ -0,0 +1,56 @@ +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); + } +} diff --git a/packages/NodeTypeResolver/src/StaticTypeMapper.php b/packages/NodeTypeResolver/src/StaticTypeMapper.php index e30900f7197a..85a432935053 100644 --- a/packages/NodeTypeResolver/src/StaticTypeMapper.php +++ b/packages/NodeTypeResolver/src/StaticTypeMapper.php @@ -6,7 +6,6 @@ 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,50 +13,18 @@ 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 @@ -65,42 +32,35 @@ final class StaticTypeMapper { /** - * @var TypeFactory - */ - private $typeFactory; - - /** - * @var ObjectTypeSpecifier + * @var PHPStanStaticTypeMapper */ - private $objectTypeSpecifier; + private $phpStanStaticTypeMapper; /** - * @var PHPStanStaticTypeMapper + * @var NameResolver */ - private $phpStanStaticTypeMapper; + private $nameResolver; /** - * @var TypeNodeResolver + * @var PhpParserNodeMapper */ - private $typeNodeResolver; + private $phpParserNodeMapper; /** - * @var NameResolver + * @var PhpDocTypeMapper */ - private $nameResolver; + 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 @@ public function mapPHPStanTypeToDocString(Type $phpStanType, ?Type $parentType = 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 @@ public function mapPHPStanPhpDocTypeToPHPStanType(PhpDocTagValueNode $phpDocTagV 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 @@ public function mapPHPStanPhpDocTypeNodeToPhpDocString(TypeNode $typeNode, Node 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); } /** diff --git a/packages/NodeTypeResolver/src/TypeMapper/PhpParserNodeMapper.php b/packages/NodeTypeResolver/src/TypeMapper/PhpParserNodeMapper.php new file mode 100644 index 000000000000..7a0777421b45 --- /dev/null +++ b/packages/NodeTypeResolver/src/TypeMapper/PhpParserNodeMapper.php @@ -0,0 +1,39 @@ +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(); + } +} diff --git a/packages/NodeTypeResolver/src/TypeMapper/ScalarStringToTypeMapper.php b/packages/NodeTypeResolver/src/TypeMapper/ScalarStringToTypeMapper.php new file mode 100644 index 000000000000..d4ecdaffcf00 --- /dev/null +++ b/packages/NodeTypeResolver/src/TypeMapper/ScalarStringToTypeMapper.php @@ -0,0 +1,57 @@ + ['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(); + } +} diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/ArrayTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/ArrayTypeMapper.php index 9ef44f736146..b0f23b3b9663 100644 --- a/packages/PHPStanStaticTypeMapper/src/TypeMapper/ArrayTypeMapper.php +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/ArrayTypeMapper.php @@ -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 @@ public function mapToDocString(Type $type, ?Type $parentType = null): string return implode('|', $docStringTypes); } - /** - * @required - */ - public function autowireArrayTypeMapper(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void - { - $this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper; - } - private function mapArrayUnionTypeToDocString(ArrayType $arrayType, UnionType $unionType): string { $unionedTypesAsString = []; diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/TypeWithClassNameTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/TypeWithClassNameTypeMapper.php index aa8fd75e83e4..a0dcfa4808fa 100644 --- a/packages/PHPStanStaticTypeMapper/src/TypeMapper/TypeWithClassNameTypeMapper.php +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/TypeWithClassNameTypeMapper.php @@ -8,8 +8,8 @@ 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 @@ public function __construct(PhpVersionProvider $phpVersionProvider) 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 @@ public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node return new Identifier('string'); } + /** + * @param TypeWithClassName $type + */ public function mapToDocString(Type $type, ?Type $parentType = null): string { return $type->describe(VerbosityLevel::typeOnly()); diff --git a/packages/TypeDeclaration/src/TypeNormalizer.php b/packages/TypeDeclaration/src/TypeNormalizer.php index bdf6b3a1f9e7..176e88cfcb60 100644 --- a/packages/TypeDeclaration/src/TypeNormalizer.php +++ b/packages/TypeDeclaration/src/TypeNormalizer.php @@ -11,17 +11,12 @@ 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 @@ public function uniqueateConstantArrayType(Type $type): Type $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); diff --git a/src/Rector/Property/InjectAnnotationClassRector.php b/src/Rector/Property/InjectAnnotationClassRector.php index 7e3b846d4395..015bfc264aaa 100644 --- a/src/Rector/Property/InjectAnnotationClassRector.php +++ b/src/Rector/Property/InjectAnnotationClassRector.php @@ -140,7 +140,6 @@ public function refactor(Node $node): ?Node } $type = $this->resolveType($node, $injectTagValueNode); - return $this->refactorPropertyWithAnnotation($node, $type, $tagClass); }