Skip to content

Commit

Permalink
[PHP 8.1] Add IntersectionTypesRector
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Nov 13, 2021
1 parent 82de366 commit 730498c
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 8 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);
};
3 changes: 2 additions & 1 deletion packages/FamilyTree/Reflection/FamilyRelationsAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\IntersectionType;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt;
Expand Down Expand Up @@ -68,7 +69,7 @@ public function getPossibleUnionPropertyType(
Property $property,
Type $varType,
?Scope $scope,
Name | NullableType | PhpParserUnionType | null $propertyTypeNode
Name | NullableType | PhpParserUnionType | IntersectionType| null $propertyTypeNode
): PropertyType {
if ($varType instanceof UnionType) {
return new PropertyType($varType, $propertyTypeNode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function mapToPHPStanPhpDocTypeNode(Type $type, TypeKind $typeKind): Type

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

namespace Rector\PHPStanStaticTypeMapper;

use PhpParser\Node\IntersectionType;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
Expand Down Expand Up @@ -47,8 +48,10 @@ 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 | NullableType | UnionType | IntersectionType | null {
foreach ($this->typeMappers as $typeMapper) {
if (! is_a($type, $typeMapper->getNodeClass(), true)) {
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use PHPStan\Type\IntersectionType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\ValueObject\Type\BracketsAwareIntersectionTypeNode;
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 +23,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 +58,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 +70,20 @@ 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) {
$intersectionedTypeNodes[] = $this->phpStanStaticTypeMapper->mapToPhpParserNode(
$intersectionedType,
$typeKind
);
}

dump($intersectionedTypeNodes);

return new Node\IntersectionType($intersectionedTypeNodes);
}
}
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);
}
}
2 changes: 1 addition & 1 deletion packages/StaticTypeMapper/StaticTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function mapPHPStanTypeToPHPStanPhpDocTypeNode(Type $phpStanType, TypeKin
}

/**
* @return Name|NullableType|PhpParserUnionType|null
* @return Name|NullableType|PhpParserUnionType|Node\IntersectionType|null
*/
public function mapPHPStanTypeToPhpParserNode(Type $phpStanType, TypeKind $typeKind): ?Node
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
{
}
}

?>
Original file line number Diff line number Diff line change
@@ -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';
}
}
Original file line number Diff line number Diff line change
@@ -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);
};
2 changes: 2 additions & 0 deletions rules/DowngradePhp56/NodeManipulator/ArgManipulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ public function canBeInlined(array $args): bool
if (! $arg->unpack) {
continue;
}

if ($arg->value instanceof Array_) {
continue;
}

return false;
}

Expand Down
118 changes: 118 additions & 0 deletions rules/Php81/Rector/FunctionLike/IntersectionTypesRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?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\FunctionLike;
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 [FunctionLike::class];
}

/**
* @param FunctionLike $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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public function provideMinPhpVersion(): int
}

/**
* @return Node\Name|UnionType|NullableType|null
* @return Node\Name|UnionType|NullableType|Node\IntersectionType|null
*/
private function matchPropertySingleTypeNode(PropertyFetch $propertyFetch): ?Node
{
Expand Down

0 comments on commit 730498c

Please sign in to comment.