Skip to content

Commit

Permalink
[PHPStanStaticTypeMapper] Handle Nullable Type on UnionType on UnionT…
Browse files Browse the repository at this point in the history
…ypeMapper when possible (#3173)
  • Loading branch information
samsonasik committed Dec 9, 2022
1 parent bbd9d4f commit 863fee3
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 50 deletions.
61 changes: 49 additions & 12 deletions packages/PHPStanStaticTypeMapper/TypeMapper/UnionTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public function mapToPhpParserNode(Type $type, string $typeKind): ?Node
if ($this->boolUnionTypeAnalyzer->isNullableBoolUnionType(
$type
) && ! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::UNION_TYPES)) {
return new NullableType(new Name('bool'));
return $this->resolveNullableType(new NullableType(new Name('bool')));
}

if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::UNION_TYPES) && $this->isFalseBoolUnion(
Expand All @@ -126,6 +126,15 @@ public function mapToPhpParserNode(Type $type, string $typeKind): ?Node
return $this->mapNullabledType($nullabledType, $typeKind);
}

private function resolveNullableType(NullableType $nullableType): ?NullableType
{
if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::NULLABLE_TYPE)) {
return null;
}

return $nullableType;
}

/**
* @param TypeKind::* $typeKind
*/
Expand All @@ -147,7 +156,7 @@ private function mapNullabledType(Type $nullabledType, string $typeKind): ?Node

/** @var Name $nullabledTypeNode */
if (! $this->nodeNameResolver->isNames($nullabledTypeNode, ['false', 'mixed'])) {
return new NullableType($nullabledTypeNode);
return $this->resolveNullableType(new NullableType($nullabledTypeNode));
}

return null;
Expand Down Expand Up @@ -176,12 +185,36 @@ private function matchArrayTypes(UnionType $unionType): Name | NullableType | nu

$type = $unionTypeAnalysis->hasIterable() ? 'iterable' : 'array';
if ($unionTypeAnalysis->isNullableType()) {
return new NullableType($type);
return $this->resolveNullableType(new NullableType($type));
}

return new Name($type);
}

private function resolveTypeWithNullablePHPParserUnionType(
PhpParserUnionType $phpParserUnionType
): PhpParserUnionType|NullableType|null {
if (count($phpParserUnionType->types) === 2) {
$phpParserUnionType->types = array_values($phpParserUnionType->types);
$firstType = $phpParserUnionType->types[0];
$secondType = $phpParserUnionType->types[1];

if ($firstType instanceof Name && $firstType->toString() === 'null' && ! $secondType instanceof PHPParserNodeIntersectionType) {
return $this->resolveNullableType(new NullableType($secondType));
}

if ($secondType instanceof Name && $secondType->toString() === 'null' && ! $firstType instanceof PHPParserNodeIntersectionType) {
return $this->resolveNullableType(new NullableType($firstType));
}
}

if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::UNION_TYPES)) {
return null;
}

return $phpParserUnionType;
}

private function matchTypeForNullableUnionType(UnionType $unionType): ?Type
{
if (count($unionType->getTypes()) !== 2) {
Expand Down Expand Up @@ -217,6 +250,11 @@ private function hasObjectAndStaticType(PhpParserUnionType $phpParserUnionType):
private function matchTypeForUnionedObjectTypes(UnionType $unionType, string $typeKind): ?Node
{
$phpParserUnionType = $this->matchPhpParserUnionType($unionType, $typeKind);

if ($phpParserUnionType instanceof NullableType) {
return $phpParserUnionType;
}

if ($phpParserUnionType !== null) {
if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::UNION_TYPES)) {
// maybe all one type?
Expand Down Expand Up @@ -264,7 +302,7 @@ private function processResolveCompatibleObjectCandidates(UnionType $unionType):
if ($compatibleObjectType instanceof UnionType) {
$type = $this->matchTypeForNullableUnionType($compatibleObjectType);
if ($type instanceof ObjectType) {
return new NullableType(new FullyQualified($type->getClassName()));
return $this->resolveNullableType(new NullableType(new FullyQualified($type->getClassName())));
}
}

Expand All @@ -278,12 +316,10 @@ private function processResolveCompatibleObjectCandidates(UnionType $unionType):
/**
* @param TypeKind::* $typeKind
*/
private function matchPhpParserUnionType(UnionType $unionType, string $typeKind): ?PhpParserUnionType
{
if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::UNION_TYPES)) {
return null;
}

private function matchPhpParserUnionType(
UnionType $unionType,
string $typeKind
): PhpParserUnionType|NullableType|null {
$phpParserUnionedTypes = [];

foreach ($unionType->getTypes() as $unionedType) {
Expand Down Expand Up @@ -316,11 +352,12 @@ private function matchPhpParserUnionType(UnionType $unionType, string $typeKind)
/** @var Identifier[]|Name[] $phpParserUnionedTypes */
$phpParserUnionedTypes = array_unique($phpParserUnionedTypes);

if (count($phpParserUnionedTypes) < 2) {
$countPhpParserUnionedTypes = count($phpParserUnionedTypes);
if ($countPhpParserUnionedTypes < 2) {
return null;
}

return new PhpParserUnionType($phpParserUnionedTypes);
return $this->resolveTypeWithNullablePHPParserUnionType(new PhpParserUnionType($phpParserUnionedTypes));
}

private function resolveCompatibleObjectCandidate(UnionType $unionType): UnionType|TypeWithClassName|null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Rector\Tests\CodingStyle\Rector\Property\AddFalseDefaultToBoolPropertyRector\Fixture;

// __construct can be overridden by class that consume it, @see https://3v4l.org/MZFmT#v7.0.28
trait AssignInConstructTrait
{
/**
* @var bool
*/
private $property;

public function __construct(bool $property)
{
$this->property = $property;
}
}

?>
-----
<?php

namespace Rector\Tests\CodingStyle\Rector\Property\AddFalseDefaultToBoolPropertyRector\Fixture;

// __construct can be overridden by class that consume it, @see https://3v4l.org/MZFmT#v7.0.28
trait AssignInConstructTrait
{
/**
* @var bool
*/
private $property = false;

public function __construct(bool $property)
{
$this->property = $property;
}
}

?>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector\Fixture;

final class AnonymousExtendsExistingClassInUnion
{
private $x;

public function __construct()
{
if (rand(0,1)) {
$this->x = new \DateTime('now');
} else {
$this->x = new class extends \DateTime {};
}
}
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector\Fixture;

final class AnonymousExtendsExistingClassInUnion
{
private ?\DateTime $x = null;

public function __construct()
{
if (rand(0,1)) {
$this->x = new \DateTime('now');
} else {
$this->x = new class extends \DateTime {};
}
}
}

?>

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsR

final class AnonymousExtendsExistingClassInUnion
{
private \DateTime|null $x = null;
private ?\DateTime $x = null;

public function __construct()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsR

final class AnonymousExtendsExistingClassDocblockExists
{
/**
* @var \DateTime|null
*/
private \DateTime|null $x = null;
private ?\DateTime $x = null;

public function __construct()
{
Expand Down

0 comments on commit 863fee3

Please sign in to comment.