Skip to content

Commit

Permalink
[TypeDeclaration] Skip anonymous class and other object on ReturnUnio…
Browse files Browse the repository at this point in the history
…nTypeRector (#4670)

Co-authored-by: GitHub Action <actions@github.com>
  • Loading branch information
samsonasik and actions-user committed Aug 6, 2023
1 parent f45662a commit 6110a50
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 9 deletions.
2 changes: 2 additions & 0 deletions packages/NodeNameResolver/NodeNameResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ public function areNamesEqual(Node $firstNode, Node $secondNode): bool
}

/**
* @api
*
* @param Name[]|Node[] $nodes
* @return string[]
*/
Expand Down
18 changes: 13 additions & 5 deletions packages/NodeTypeResolver/NodeTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,6 @@ public function getType(Node $node): Type
return new MixedType();
}

// skip anonymous classes, ref https://github.com/rectorphp/rector/issues/1574
if ($node instanceof New_ && $this->classAnalyzer->isAnonymousClass($node->class)) {
return new ObjectWithoutClassType();
}

$type = $scope->getType($node);
$type = $this->accessoryNonEmptyStringTypeCorrector->correct($type);
$type = $this->genericClassStringTypeCorrector->correct($type);
Expand Down Expand Up @@ -221,7 +216,20 @@ public function getNativeType(Expr $expr): Type
return new MixedType();
}

// cover anonymous class
if ($expr instanceof New_) {
$type = $this->resolveByNodeTypeResolvers($expr);
if ($type instanceof ObjectWithoutClassType) {
return $type;
}
}

$type = $scope->getNativeType($expr);
// ObjectType anonymous may be assigned first, fallback to ObjectWithoutClassType
if ($type instanceof ObjectType && $this->classAnalyzer->isAnonymousClassName($type->getClassName())) {
return new ObjectWithoutClassType();
}

return $this->accessoryNonEmptyStringTypeCorrector->correct($type);
}

Expand Down
18 changes: 14 additions & 4 deletions packages/PHPStanStaticTypeMapper/TypeMapper/UnionTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
use PHPStan\Type\UnionType;
use PHPStan\Type\VoidType;
use Rector\BetterPhpDocParser\ValueObject\Type\BracketsAwareUnionTypeNode;
use Rector\Core\Enum\ObjectReference;
use Rector\Core\Php\PhpVersionProvider;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
Expand Down Expand Up @@ -285,10 +284,21 @@ private function matchTypeForNullableUnionType(UnionType $unionType): ?Type

private function hasObjectAndStaticType(PhpParserUnionType $phpParserUnionType): bool
{
$typeNames = $this->nodeNameResolver->getNames($phpParserUnionType->types);
$diff = array_diff(['object', ObjectReference::STATIC], $typeNames);
$hasAnonymousObjectType = false;
$hasObjectType = false;
foreach ($phpParserUnionType->types as $type) {
if ($type instanceof Identifier && $type->toString() === 'object') {
$hasAnonymousObjectType = true;
continue;
}

if ($type instanceof FullyQualified || ($type instanceof Name && $type->isSpecialClassName())) {
$hasObjectType = true;
continue;
}
}

return $diff === [];
return $hasObjectType && $hasAnonymousObjectType;
}

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

namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\Source\SomeAnonymousInterface;

final class AnonymousClassImplementsInterface
{
public function run()
{
if (rand(0,1)) {
return new AnonymousClassImplementsInterface();
}

return new class implements SomeAnonymousInterface{};
}
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\Source\SomeAnonymousInterface;

final class AnonymousClassImplementsInterface
{
public function run(): \Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\Fixture\AnonymousClassImplementsInterface|\Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\Source\SomeAnonymousInterface
{
if (rand(0,1)) {
return new AnonymousClassImplementsInterface();
}

return new class implements SomeAnonymousInterface{};
}
}

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

namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\Fixture;

final class SkipAnonymousClassAndOtherObject
{
public function run()
{
if (rand(0,1)) {
return new SkipAnonymousClassAndOtherObject();
}

return new class {};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\Fixture;

final class SkipAnonymousClassAndOtherObject2
{
public function run()
{
if (rand(0,1)) {
return new SkipAnonymousClassAndOtherObject();
}

$obj = new class {};
return $obj;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\Fixture;

use stdClass;

final class SkipDocblockCall
{
public function run()
{
return $this->execute();
}

/**
* @return null|stdClass
*/
private function execute()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\Fixture;

use stdClass;

final class SkipFromDocblockParam
{
/**
* @param null|stdClass
*/
public function run($param)
{
return $param;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\Source;

interface SomeAnonymousInterface
{
}
12 changes: 12 additions & 0 deletions src/NodeAnalyzer/ClassAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,22 @@
use PhpParser\Node\Stmt\Class_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ClassReflection;
use Rector\Core\Util\StringUtils;
use Rector\NodeTypeResolver\Node\AttributeKey;

final class ClassAnalyzer
{
/**
* @var string
* @see https://regex101.com/r/FQH6RT/2
*/
private const ANONYMOUS_CLASS_REGEX = '#^AnonymousClass\w+$#';

public function isAnonymousClassName(string $className): bool
{
return StringUtils::isMatch($className, self::ANONYMOUS_CLASS_REGEX);
}

public function isAnonymousClass(Node $node): bool
{
if (! $node instanceof Class_) {
Expand Down

0 comments on commit 6110a50

Please sign in to comment.