Skip to content

Commit

Permalink
[TypeDeclaration] Handle nullable intersection on TypedPropertyFromAs…
Browse files Browse the repository at this point in the history
…signsRector (#3372)

Co-authored-by: Adam Dmitruczuk <adam.dmitruczuk@getresponse.com>
Co-authored-by: GitHub Action <action@github.com>
  • Loading branch information
3 people authored Feb 12, 2023
1 parent 67fd165 commit db4ec02
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\IntersectionType;
Expand Down Expand Up @@ -76,7 +75,7 @@ public function mapToPHPStanPhpDocTypeNode(Type $type, string $typeKind): TypeNo
public function mapToPhpParserNode(Type $type, string $typeKind): ?Node
{
if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::INTERSECTION_TYPES)) {
return $this->matchMockObjectType($type);
return null;
}

$intersectionedTypeNodes = [];
Expand Down Expand Up @@ -118,20 +117,4 @@ public function mapToPhpParserNode(Type $type, string $typeKind): ?Node

return new Node\IntersectionType($intersectionedTypeNodes);
}

private function matchMockObjectType(IntersectionType $intersectionType): ?FullyQualified
{
// return mock object as the strict one
foreach ($intersectionType->getTypes() as $intersectionedType) {
if (! $intersectionedType instanceof ObjectType) {
continue;
}

if ($intersectionedType->getClassName() === 'PHPUnit\Framework\MockObject\MockObject') {
return new FullyQualified($intersectionedType->getClassName());
}
}

return null;
}
}
19 changes: 16 additions & 3 deletions packages/PHPStanStaticTypeMapper/TypeMapper/UnionTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,26 @@ public function resolveTypeWithNullablePHPParserUnionType(
return $this->resolveUnionTypes($phpParserUnionType);
}

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

return $nullableType;
/** @var PHPParserNodeIntersectionType|Identifier|Name $type */
$type = $nullableType->type;
if (! $type instanceof PHPParserNodeIntersectionType) {
return $nullableType;
}

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

$types = [$type];
$types[] = new Identifier('null');

return new PhpParserUnionType($types);
}

/**
Expand Down Expand Up @@ -211,7 +224,7 @@ private function shouldSkipIterable(UnionType $unionType): bool
return $unionTypeAnalysis->hasArray();
}

private function matchArrayTypes(UnionType $unionType): Name | NullableType | null
private function matchArrayTypes(UnionType $unionType): Name | NullableType | PhpParserUnionType | null
{
$unionTypeAnalysis = $this->unionTypeAnalyzer->analyseForNullableAndIterable($unionType);
if (! $unionTypeAnalysis instanceof UnionTypeAnalysis) {
Expand Down

This file was deleted.

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

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

use PHPUnit\Framework\TestCase;

final class SkipNonNullableMockObject extends TestCase
{
private $someValue;

protected function setUp(): void
{
$this->someValue = $this->createMock('SomeClass');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

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

use DateTime;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class SkipNullableMockObject extends TestCase
{
/**
* @var DateTime&MockObject
*/
private $property;

public function testExample(): void
{
$this->property = $this->createMock(DateTime::class);
$this->property->expects(self::once())->method('format');
$this->property->format('Y');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

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

use DateTime;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class IntersectionTypeWithSetup extends TestCase
{
/**
* @var DateTime&MockObject
*/
private $property;

public function setUp(): void
{
$this->property = $this->createMock(DateTime::class);
}

public function testExample(): void
{
$this->property->expects(self::once())->method('format');
$this->property->format('Y');
}
}

?>
-----
<?php

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

use DateTime;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class IntersectionTypeWithSetup extends TestCase
{
private \DateTime&\PHPUnit\Framework\MockObject\MockObject $property;

public function setUp(): void
{
$this->property = $this->createMock(DateTime::class);
}

public function testExample(): void
{
$this->property->expects(self::once())->method('format');
$this->property->format('Y');
}
}

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

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

use DateTime;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class NullableIntersectionType extends TestCase
{
/**
* @var DateTime&MockObject
*/
private $property;

public function testExample(): void
{
$this->property = $this->createMock(DateTime::class);
$this->property->expects(self::once())->method('format');
$this->property->format('Y');
}
}

?>
-----
<?php

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

use DateTime;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class NullableIntersectionType extends TestCase
{
/**
* @var DateTime&MockObject
*/
private (\DateTime&\PHPUnit\Framework\MockObject\MockObject)|null $property = null;

public function testExample(): void
{
$this->property = $this->createMock(DateTime::class);
$this->property->expects(self::once())->method('format');
$this->property->format('Y');
}
}

?>
3 changes: 2 additions & 1 deletion rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\UnionType;
use Rector\NodeTypeResolver\TypeComparator\TypeComparator;
use Rector\StaticTypeMapper\StaticTypeMapper;
Expand All @@ -28,7 +29,7 @@ public function isDead(VarTagValueNode $varTagValueNode, Property $property): bo
$propertyType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($property->type);
$docType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($varTagValueNode->type, $property);

if ($propertyType instanceof UnionType && ! $docType instanceof UnionType) {
if ($propertyType instanceof UnionType && ! $docType instanceof UnionType && ! $docType instanceof IntersectionType) {
return true;
}

Expand Down

0 comments on commit db4ec02

Please sign in to comment.