Skip to content

Commit

Permalink
[PHP 8.1] Add IntersectionTypesRector (#1227)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Nov 14, 2021
1 parent 62f2c30 commit 24b8ea9
Show file tree
Hide file tree
Showing 37 changed files with 455 additions and 48 deletions.
3 changes: 3 additions & 0 deletions config/set/php81.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
declare(strict_types=1);

use Rector\Php81\Rector\Class_\MyCLabsClassToEnumRector;

use Rector\Php81\Rector\Class_\SpatieEnumClassToEnumRector;
use Rector\Php81\Rector\ClassConst\FinalizePublicClassConstantRector;
use Rector\Php81\Rector\ClassMethod\NewInInitializerRector;
use Rector\Php81\Rector\FuncCall\Php81ResourceReturnToObjectRector;
use Rector\Php81\Rector\FunctionLike\IntersectionTypesRector;
use Rector\Php81\Rector\MethodCall\MyCLabsMethodCallToEnumConstRector;
use Rector\Php81\Rector\Property\ReadOnlyPropertyRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector;
Expand All @@ -22,4 +24,5 @@
$services->set(SpatieEnumClassToEnumRector::class);
$services->set(Php81ResourceReturnToObjectRector::class);
$services->set(NewInInitializerRector::class);
$services->set(IntersectionTypesRector::class);
};
5 changes: 2 additions & 3 deletions packages/FamilyTree/Reflection/FamilyRelationsAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@
namespace Rector\FamilyTree\Reflection;

use PhpParser\Node;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\UnionType as PhpParserUnionType;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
Expand Down Expand Up @@ -68,7 +67,7 @@ public function getPossibleUnionPropertyType(
Property $property,
Type $varType,
?Scope $scope,
Name | NullableType | PhpParserUnionType | null $propertyTypeNode
Name | ComplexType | null $propertyTypeNode
): PropertyType {
if ($varType instanceof UnionType) {
return new PropertyType($varType, $propertyTypeNode);
Expand Down
14 changes: 3 additions & 11 deletions packages/FamilyTree/ValueObject/PropertyType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,15 @@

namespace Rector\FamilyTree\ValueObject;

use PhpParser\Node;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType as PhpParserUnionType;
use PHPStan\Type\Type;

final class PropertyType
{
/**
* @param Name|NullableType|PhpParserUnionType|null $propertyTypeNode
*/
public function __construct(
private Type $varType,
private ?Node $propertyTypeNode
private Name|ComplexType|null $propertyTypeNode
) {
}

Expand All @@ -26,10 +21,7 @@ public function getVarType(): Type
return $this->varType;
}

/**
* @return Name|NullableType|PhpParserUnionType|null
*/
public function getPropertyTypeNode(): ?Node
public function getPropertyTypeNode(): Name|ComplexType|null
{
return $this->propertyTypeNode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@
namespace Rector\PHPStanStaticTypeMapper\Contract;

use PhpParser\Node;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Type;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;

/**
* @template T of Type
* @template TType of Type
*/
interface TypeMapperInterface
{
Expand All @@ -23,13 +22,13 @@ interface TypeMapperInterface
public function getNodeClass(): string;

/**
* @param T $type
* @param TType $type
*/
public function mapToPHPStanPhpDocTypeNode(Type $type, TypeKind $typeKind): TypeNode;

/**
* @param T $type
* @return Name|NullableType|UnionType|null
* @param TType $type
* @return Name|ComplexType|null
*/
public function mapToPhpParserNode(Type $type, TypeKind $typeKind): ?Node;
}
5 changes: 2 additions & 3 deletions packages/PHPStanStaticTypeMapper/PHPStanStaticTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

namespace Rector\PHPStanStaticTypeMapper;

use PhpParser\Node\ComplexType;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\Accessory\AccessoryNumericStringType;
Expand Down Expand Up @@ -47,7 +46,7 @@ public function mapToPHPStanPhpDocTypeNode(Type $type, TypeKind $typeKind): Type
throw new NotImplementedYetException(__METHOD__ . ' for ' . $type::class);
}

public function mapToPhpParserNode(Type $type, TypeKind $typeKind): Name | NullableType | UnionType | null
public function mapToPhpParserNode(Type $type, TypeKind $typeKind): Name | ComplexType | null
{
foreach ($this->typeMappers as $typeMapper) {
if (! is_a($type, $typeMapper->getNodeClass(), true)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
namespace Rector\PHPStanStaticTypeMapper\TypeMapper;

use PhpParser\Node;
use PhpParser\Node\Name;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\ValueObject\Type\BracketsAwareIntersectionTypeNode;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Php\PhpVersionProvider;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
Expand All @@ -21,6 +25,11 @@ final class IntersectionTypeMapper implements TypeMapperInterface
{
private PHPStanStaticTypeMapper $phpStanStaticTypeMapper;

public function __construct(
private PhpVersionProvider $phpVersionProvider
) {
}

#[Required]
public function autowireIntersectionTypeMapper(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void
{
Expand Down Expand Up @@ -51,6 +60,10 @@ public function mapToPHPStanPhpDocTypeNode(Type $type, TypeKind $typeKind): Type

$intersectionTypesNodes = array_unique($intersectionTypesNodes);

if (count($intersectionTypesNodes) === 1) {
return $intersectionTypesNodes[0];
}

return new BracketsAwareIntersectionTypeNode($intersectionTypesNodes);
}

Expand All @@ -59,7 +72,21 @@ public function mapToPHPStanPhpDocTypeNode(Type $type, TypeKind $typeKind): Type
*/
public function mapToPhpParserNode(Type $type, TypeKind $typeKind): ?Node
{
// intersection types in PHP are not yet supported
return null;
if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::INTERSECTION_TYPES)) {
return null;
}

$intersectionedTypeNodes = [];
foreach ($type->getTypes() as $intersectionedType) {
$resolvedType = $this->phpStanStaticTypeMapper->mapToPhpParserNode($intersectionedType, $typeKind);

if (! $resolvedType instanceof Name) {
throw new ShouldNotHappenException();
}

$intersectionedTypeNodes[] = $resolvedType;
}

return new Node\IntersectionType($intersectionedTypeNodes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Rector\PHPStanStaticTypeMapper\TypeMapper;

use PhpParser\Node;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
Expand Down Expand Up @@ -132,7 +133,7 @@ public function mapToPhpParserNode(Type $type, TypeKind $typeKind): ?Node
return $nullabledTypeNode;
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@
use PhpParser\Node;
use PHPStan\Type\Type;

/**
* @template TNode as \PhpParser\Node
*/
interface PhpParserNodeMapperInterface
{
/**
* @return class-string<Node>
* @return class-string<TNode>
*/
public function getNodeType(): string;

/**
* @param TNode $node
*/
public function mapToPHPStan(Node $node): Type;
}
51 changes: 51 additions & 0 deletions packages/StaticTypeMapper/PhpDocParser/IntersectionTypeMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Rector\StaticTypeMapper\PhpDocParser;

use PhpParser\Node;
use PHPStan\Analyser\NameScope;
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\Type;
use Rector\StaticTypeMapper\Contract\PhpDocParser\PhpDocTypeMapperInterface;
use Rector\StaticTypeMapper\PhpDoc\PhpDocTypeMapper;
use Symfony\Contracts\Service\Attribute\Required;

final class IntersectionTypeMapper implements PhpDocTypeMapperInterface
{
private PhpDocTypeMapper $phpDocTypeMapper;

/**
* @return class-string<TypeNode>
*/
public function getNodeType(): string
{
return IntersectionTypeNode::class;
}

#[Required]
public function autowireUnionTypeMapper(PhpDocTypeMapper $phpDocTypeMapper): void
{
$this->phpDocTypeMapper = $phpDocTypeMapper;
}

/**
* @param IntersectionTypeNode $typeNode
*/
public function mapToPHPStanType(TypeNode $typeNode, Node $node, NameScope $nameScope): Type
{
$intersectionedTypes = [];
foreach ($typeNode->types as $intersectionedTypeNode) {
$intersectionedTypes[] = $this->phpDocTypeMapper->mapToPHPStanType(
$intersectionedTypeNode,
$node,
$nameScope
);
}

return new IntersectionType($intersectionedTypes);
}
}
3 changes: 3 additions & 0 deletions packages/StaticTypeMapper/PhpParser/ExprNodeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;

/**
* @implements PhpParserNodeMapperInterface<Expr>
*/
final class ExprNodeMapper implements PhpParserNodeMapperInterface
{
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;

/**
* @implements PhpParserNodeMapperInterface<FullyQualified>
*/
final class FullyQualifiedNodeMapper implements PhpParserNodeMapperInterface
{
public function __construct(
Expand Down
3 changes: 3 additions & 0 deletions packages/StaticTypeMapper/PhpParser/IdentifierNodeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
use Rector\StaticTypeMapper\Mapper\ScalarStringToTypeMapper;

/**
* @implements PhpParserNodeMapperInterface<Identifier>
*/
final class IdentifierNodeMapper implements PhpParserNodeMapperInterface
{
public function __construct(
Expand Down
47 changes: 47 additions & 0 deletions packages/StaticTypeMapper/PhpParser/IntersectionTypeNodeMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace Rector\StaticTypeMapper\PhpParser;

use PhpParser\Node;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\Type;
use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
use Rector\StaticTypeMapper\Mapper\PhpParserNodeMapper;
use Symfony\Contracts\Service\Attribute\Required;

/**
* @implements PhpParserNodeMapperInterface<Node\IntersectionType>
*/
final class IntersectionTypeNodeMapper implements PhpParserNodeMapperInterface
{
private PhpParserNodeMapper $phpParserNodeMapper;

#[Required]
public function autowireUnionTypeNodeMapper(PhpParserNodeMapper $phpParserNodeMapper): void
{
$this->phpParserNodeMapper = $phpParserNodeMapper;
}

/**
* @return class-string<Node>
*/
public function getNodeType(): string
{
return Node\IntersectionType::class;
}

/**
* @param Node\IntersectionType $node
*/
public function mapToPHPStan(Node $node): Type
{
$types = [];
foreach ($node->types as $intersectionedType) {
$types[] = $this->phpParserNodeMapper->mapToPHPStanType($intersectionedType);
}

return new IntersectionType($types);
}
}
3 changes: 3 additions & 0 deletions packages/StaticTypeMapper/PhpParser/NameNodeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
use Rector\StaticTypeMapper\ValueObject\Type\ParentObjectWithoutClassType;
use Rector\StaticTypeMapper\ValueObject\Type\ParentStaticType;

/**
* @implements PhpParserNodeMapperInterface<Name>
*/
final class NameNodeMapper implements PhpParserNodeMapperInterface
{
public function __construct(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
use Rector\StaticTypeMapper\Mapper\PhpParserNodeMapper;
use Symfony\Contracts\Service\Attribute\Required;

/**
* @implements PhpParserNodeMapperInterface<NullableType>
*/
final class NullableTypeNodeMapper implements PhpParserNodeMapperInterface
{
private PhpParserNodeMapper $phpParserNodeMapper;
Expand Down
3 changes: 3 additions & 0 deletions packages/StaticTypeMapper/PhpParser/StringNodeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
use PHPStan\Type\Type;
use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;

/**
* @implements PhpParserNodeMapperInterface<String_>
*/
final class StringNodeMapper implements PhpParserNodeMapperInterface
{
/**
Expand Down

0 comments on commit 24b8ea9

Please sign in to comment.