[PHP 8.1] Add IntersectionTypesRector (#1227)

This commit is contained in:
Tomas Votruba 2021-11-14 14:50:57 +03:00 committed by GitHub
parent 62f2c30358
commit 24b8ea974a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 455 additions and 48 deletions

View File

@ -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;
@ -22,4 +24,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(SpatieEnumClassToEnumRector::class);
$services->set(Php81ResourceReturnToObjectRector::class);
$services->set(NewInInitializerRector::class);
$services->set(IntersectionTypesRector::class);
};

View File

@ -5,17 +5,16 @@ declare(strict_types=1);
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;
@ -68,7 +67,7 @@ final class FamilyRelationsAnalyzer
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);

View File

@ -4,20 +4,15 @@ declare(strict_types=1);
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
) {
}
@ -26,10 +21,7 @@ final class PropertyType
return $this->varType;
}
/**
* @return Name|NullableType|PhpParserUnionType|null
*/
public function getPropertyTypeNode(): ?Node
public function getPropertyTypeNode(): Name|ComplexType|null
{
return $this->propertyTypeNode;
}

View File

@ -5,15 +5,14 @@ declare(strict_types=1);
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
{
@ -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;
}

View File

@ -4,9 +4,8 @@ declare(strict_types=1);
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;
@ -47,7 +46,7 @@ final class PHPStanStaticTypeMapper
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)) {

View File

@ -5,10 +5,14 @@ declare(strict_types=1);
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;
@ -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
{
@ -51,6 +60,10 @@ final class IntersectionTypeMapper implements TypeMapperInterface
$intersectionTypesNodes = array_unique($intersectionTypesNodes);
if (count($intersectionTypesNodes) === 1) {
return $intersectionTypesNodes[0];
}
return new BracketsAwareIntersectionTypeNode($intersectionTypesNodes);
}
@ -59,7 +72,21 @@ final class IntersectionTypeMapper implements TypeMapperInterface
*/
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);
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
use PhpParser\Node;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
@ -132,7 +133,7 @@ final class UnionTypeMapper implements TypeMapperInterface
return $nullabledTypeNode;
}
if ($nullabledTypeNode instanceof PhpParserUnionType) {
if ($nullabledTypeNode instanceof ComplexType) {
throw new ShouldNotHappenException();
}

View File

@ -7,12 +7,18 @@ namespace Rector\StaticTypeMapper\Contract\PhpParser;
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;
}

View File

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

View File

@ -12,6 +12,9 @@ use PHPStan\Type\Type;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
/**
* @implements PhpParserNodeMapperInterface<Expr>
*/
final class ExprNodeMapper implements PhpParserNodeMapperInterface
{
/**

View File

@ -17,6 +17,9 @@ use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
/**
* @implements PhpParserNodeMapperInterface<FullyQualified>
*/
final class FullyQualifiedNodeMapper implements PhpParserNodeMapperInterface
{
public function __construct(

View File

@ -10,6 +10,9 @@ use PHPStan\Type\Type;
use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
use Rector\StaticTypeMapper\Mapper\ScalarStringToTypeMapper;
/**
* @implements PhpParserNodeMapperInterface<Identifier>
*/
final class IdentifierNodeMapper implements PhpParserNodeMapperInterface
{
public function __construct(

View File

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

View File

@ -29,6 +29,9 @@ use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use Rector\StaticTypeMapper\ValueObject\Type\ParentObjectWithoutClassType;
use Rector\StaticTypeMapper\ValueObject\Type\ParentStaticType;
/**
* @implements PhpParserNodeMapperInterface<Name>
*/
final class NameNodeMapper implements PhpParserNodeMapperInterface
{
public function __construct(

View File

@ -13,6 +13,9 @@ use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
use Rector\StaticTypeMapper\Mapper\PhpParserNodeMapper;
use Symfony\Contracts\Service\Attribute\Required;
/**
* @implements PhpParserNodeMapperInterface<NullableType>
*/
final class NullableTypeNodeMapper implements PhpParserNodeMapperInterface
{
private PhpParserNodeMapper $phpParserNodeMapper;

View File

@ -10,6 +10,9 @@ use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
/**
* @implements PhpParserNodeMapperInterface<String_>
*/
final class StringNodeMapper implements PhpParserNodeMapperInterface
{
/**

View File

@ -12,6 +12,9 @@ use Rector\StaticTypeMapper\Contract\PhpParser\PhpParserNodeMapperInterface;
use Rector\StaticTypeMapper\Mapper\PhpParserNodeMapper;
use Symfony\Contracts\Service\Attribute\Required;
/**
* @implements PhpParserNodeMapperInterface<UnionType>
*/
final class UnionTypeNodeMapper implements PhpParserNodeMapperInterface
{
private PhpParserNodeMapper $phpParserNodeMapper;

View File

@ -5,11 +5,10 @@ declare(strict_types=1);
namespace Rector\StaticTypeMapper;
use PhpParser\Node;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\UnionType as PhpParserUnionType;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
@ -47,7 +46,7 @@ final class StaticTypeMapper
}
/**
* @return Name|NullableType|PhpParserUnionType|null
* @return Name|ComplexType|null
*/
public function mapPHPStanTypeToPhpParserNode(Type $phpStanType, TypeKind $typeKind): ?Node
{

View File

@ -511,3 +511,9 @@ parameters:
-
path: rules/Php74/Rector/Property/TypedPropertyRector.php
message: '#Class cognitive complexity is 36, keep it under 30#'
-
message: '#Autowired/inject method name must respect "autowire/inject" \+ class name#'
paths:
- packages/StaticTypeMapper/PhpDocParser/IntersectionTypeMapper.php
- packages/StaticTypeMapper/PhpParser/IntersectionTypeNodeMapper.php

View File

@ -0,0 +1,37 @@
<?php
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
use Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection\FirstTypeInterface;
use Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection\SecondTypeInterface;
final class IncludeIntersectionType
{
private $firstAndSecond;
public function __construct(FirstTypeInterface&SecondTypeInterface $firstAndSecond)
{
$this->firstAndSecond = $firstAndSecond;
}
}
?>
-----
<?php
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
use Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection\FirstTypeInterface;
use Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection\SecondTypeInterface;
final class IncludeIntersectionType
{
private \Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection\FirstTypeInterface&\Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection\SecondTypeInterface $firstAndSecond;
public function __construct(FirstTypeInterface&SecondTypeInterface $firstAndSecond)
{
$this->firstAndSecond = $firstAndSecond;
}
}
?>

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionTypes;
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
class IncludeUnionedType
{
@ -27,7 +27,7 @@ class IncludeUnionedType
-----
<?php
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionTypes;
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
class IncludeUnionedType
{

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionTypes;
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
class SetIfElse
{
@ -20,7 +20,7 @@ class SetIfElse
-----
<?php
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionTypes;
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
class SetIfElse
{

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionTypes;
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
final class TwoTypes
{
@ -14,7 +14,7 @@ final class TwoTypes
-----
<?php
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionTypes;
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\FixtureUnionIntersectionTypes;
final class TwoTypes
{

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection;
interface FirstTypeInterface
{
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Php74\Rector\Property\TypedPropertyRector\Source\Intersection;
interface SecondTypeInterface
{
}

View File

@ -8,7 +8,7 @@ use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class UnionTypedPropertyRectorTest extends AbstractRectorTestCase
final class UnionIntersectionTypedPropertyRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
@ -23,11 +23,11 @@ final class UnionTypedPropertyRectorTest extends AbstractRectorTestCase
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureUnionTypes');
return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureUnionIntersectionTypes');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/union_type_rule.php';
return __DIR__ . '/config/union_and_intersection_type_rule.php';
}
}

View File

@ -12,5 +12,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(TypedPropertyRector::class);
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::UNION_TYPES);
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::INTERSECTION_TYPES);
};

View File

@ -0,0 +1,31 @@
<?php
namespace Rector\Tests\Php81\Rector\FunctionLike\IntersectionTypesRector\Fixture;
final class SomeClass
{
/**
* @param string&int $types
*/
public function process($types)
{
}
}
?>
-----
<?php
namespace Rector\Tests\Php81\Rector\FunctionLike\IntersectionTypesRector\Fixture;
final class SomeClass
{
/**
* @param string&int $types
*/
public function process(int&string $types)
{
}
}
?>

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Php81\Rector\FunctionLike\IntersectionTypesRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class IntersectionTypesRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
use Rector\Php81\Rector\FunctionLike\IntersectionTypesRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(IntersectionTypesRector::class);
};

View File

@ -134,7 +134,7 @@ CODE_SAMPLE
}
// Make it nullable?
if ($node->returnType instanceof NullableType && ! $parentReturnTypeNode instanceof NullableType && ! $parentReturnTypeNode instanceof UnionType) {
if ($node->returnType instanceof NullableType && ! $parentReturnTypeNode instanceof ComplexType) {
$parentReturnTypeNode = new NullableType($parentReturnTypeNode);
}

View File

@ -5,13 +5,13 @@ declare(strict_types=1);
namespace Rector\Php74\Rector\Property;
use PhpParser\Node;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\Node\UnionType as PhpParserUnionType;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\MixedType;
@ -165,6 +165,7 @@ CODE_SAMPLE
}
$scope = $node->getAttribute(AttributeKey::SCOPE);
$propertyType = $this->familyRelationsAnalyzer->getPossibleUnionPropertyType(
$node,
$varType,
@ -199,7 +200,7 @@ CODE_SAMPLE
}
private function isNullOrNonClassLikeTypeOrMixedOrVendorLockedIn(
Name | NullableType | PhpParserUnionType | null $node,
Name | ComplexType | null $node,
Property $property,
Type $type
): bool {
@ -226,7 +227,7 @@ CODE_SAMPLE
return true;
}
private function shouldSkipNonClassLikeType(Name|NullableType|PhpParserUnionType $node, Type $type): bool
private function shouldSkipNonClassLikeType(Name|ComplexType $node, Type $type): bool
{
// unwrap nullable type
if ($node instanceof NullableType) {

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Rector\Php74\TypeAnalyzer;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PHPStan\Type\NullType;
@ -12,7 +13,7 @@ use PHPStan\Type\UnionType;
final class PropertyUnionTypeResolver
{
public function resolve(Name|NullableType|\PhpParser\Node\UnionType $phpUnionType, Type $possibleUnionType): Type
public function resolve(Name|ComplexType $phpUnionType, Type $possibleUnionType): Type
{
if (! $phpUnionType instanceof NullableType) {
return $possibleUnionType;

View File

@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
namespace Rector\Php81\Rector\FunctionLike;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PHPStan\Type\IntersectionType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\Php81\Rector\FunctionLike\IntersectionTypesRector\IntersectionTypesRectorTest
*/
final class IntersectionTypesRector extends AbstractRector implements MinPhpVersionInterface
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Change docs to intersection types, where possible (properties are covered by TypedPropertyRector (@todo))',
[
new CodeSample(
<<<'CODE_SAMPLE'
final class SomeClass
{
/**
* @param string&int $types
*/
public function process($types)
{
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class SomeClass
{
public function process(string&int $types)
{
}
}
CODE_SAMPLE
),
]
);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [ArrowFunction::class, Closure::class, ClassMethod::class, Function_::class];
}
/**
* @param ArrowFunction|Closure|ClassMethod|Function_ $node
*/
public function refactor(Node $node): ?Node
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
if (! $phpDocInfo instanceof PhpDocInfo) {
return null;
}
$this->refactorParamTypes($node, $phpDocInfo);
// $this->refactorReturnType($node, $phpDocInfo);
return $node;
}
public function provideMinPhpVersion(): int
{
return PhpVersionFeature::INTERSECTION_TYPES;
}
private function refactorParamTypes(
ArrowFunction|Closure|ClassMethod|Function_ $functionLike,
PhpDocInfo $phpDocInfo
): void {
foreach ($functionLike->params as $param) {
if ($param->type !== null) {
continue;
}
/** @var string $paramName */
$paramName = $this->getName($param->var);
$paramType = $phpDocInfo->getParamType($paramName);
if (! $paramType instanceof IntersectionType) {
continue;
}
$phpParserIntersectionType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode(
$paramType,
TypeKind::PARAM()
);
if (! $phpParserIntersectionType instanceof Node\IntersectionType) {
continue;
}
$param->type = $phpParserIntersectionType;
}
}
}

View File

@ -10,11 +10,11 @@ use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\NullableType;
use PhpParser\Node\Param;
use PhpParser\Node\UnionType;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\Php\PhpPropertyReflection;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\Reflection\ReflectionResolver;
use Rector\Core\ValueObject\PhpVersionFeature;
@ -130,7 +130,7 @@ CODE_SAMPLE
}
/**
* @return Node\Name|UnionType|NullableType|null
* @return Node\Name|Node\ComplexType|null
*/
private function matchPropertySingleTypeNode(PropertyFetch $propertyFetch): ?Node
{
@ -145,7 +145,7 @@ CODE_SAMPLE
return null;
}
if ($propertyType instanceof \PHPStan\Type\UnionType) {
if ($propertyType instanceof UnionType) {
return null;
}

View File

@ -536,4 +536,10 @@ final class PhpVersionFeature
* @var int
*/
public const NEW_INITIALIZERS = PhpVersion::PHP_81;
/**
* @see https://wiki.php.net/rfc/pure-intersection-types
* @var int
*/
public const INTERSECTION_TYPES = PhpVersion::PHP_81;
}