Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NodeTypeResolver] Handle isObjectType() on new $class dynamic variable should return false compare to Object FQCN #4936

Merged
merged 10 commits into from
Sep 7, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\NodeTypeResolver\PerNodeTypeResolver\NewTypeResolver;

use Iterator;
use PhpParser\Node\Expr\New_;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\Type;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\NodeTypeResolver\PHPStan\ObjectWithoutClassTypeWithParentTypes;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use Rector\Tests\NodeTypeResolver\PerNodeTypeResolver\AbstractNodeTypeResolverTestCase;

/**
* @see \Rector\NodeTypeResolver\NodeTypeResolver\NewTypeResolver
*/
final class NewTypeResolverTest extends AbstractNodeTypeResolverTestCase
{
#[DataProvider('provideData')]
public function test(string $file, int $nodePosition, Type $expectedType, bool $isObjectType): void
{
$newNodes = $this->getNodesForFileOfType($file, New_::class);

$resolvedType = $this->nodeTypeResolver->getType($newNodes[$nodePosition]);
$this->assertEquals($expectedType, $resolvedType);

$this->assertEquals(
$isObjectType,
$this->nodeTypeResolver->isObjectType(
$newNodes[$nodePosition],
new ObjectType('Symfony\Bundle\TwigBundle\Loader\FilesystemLoader')
)
);
}

/**
* @return Iterator<int[]|string[]|ObjectWithoutClassType[]|ObjectWithoutClassTypeWithParentTypes[]|bool[]>
*/
public static function provideData(): Iterator
{
$objectWithoutClassType = new ObjectWithoutClassType();

# test new
yield [__DIR__ . '/Source/NewDynamicNew.php', 0, $objectWithoutClassType, false];

$objectWithoutClassTypeWithParentTypes = new ObjectWithoutClassTypeWithParentTypes(
[
new FullyQualifiedObjectType('Symfony\Bundle\TwigBundle\Loader\FilesystemLoader')
]
);
yield [__DIR__ . '/Source/NewDynamicNewExtends.php', 0, $objectWithoutClassTypeWithParentTypes, true];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Rector\Tests\NodeTypeResolver\PerNodeTypeResolver\NewTypeResolver\Source;

class NewDynamicNew
{
public function run($class)
{
new $class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Rector\Tests\NodeTypeResolver\PerNodeTypeResolver\NewTypeResolver\Source;

class NewDynamicNewExtends
{
public function run()
{
new class extends \Symfony\Bundle\TwigBundle\Loader\FilesystemLoader {};
}
}
20 changes: 20 additions & 0 deletions packages/NodeTypeResolver/NodeTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeCorrector\AccessoryNonEmptyStringTypeCorrector;
use Rector\NodeTypeResolver\NodeTypeCorrector\GenericClassStringTypeCorrector;
use Rector\NodeTypeResolver\PHPStan\ObjectWithoutClassTypeWithParentTypes;
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
use Rector\StaticTypeMapper\ValueObject\Type\ShortenedObjectType;
use Rector\TypeDeclaration\PHPStan\ObjectTypeSpecifier;
Expand Down Expand Up @@ -112,6 +113,10 @@ public function isObjectType(Node $node, ObjectType $requiredObjectType): bool
}
}

if ($resolvedType instanceof ObjectWithoutClassType) {
return $this->isMatchObjectWithoutClassType($resolvedType, $requiredObjectType);
}

return $this->isMatchingUnionType($resolvedType, $requiredObjectType);
}

Expand Down Expand Up @@ -326,6 +331,21 @@ public function isMethodStaticCallOrClassMethodObjectType(Node $node, ObjectType
return $classReflection->isSubclassOf($objectType->getClassName());
}

private function isMatchObjectWithoutClassType(
ObjectWithoutClassType $objectWithoutClassType,
ObjectType $requiredObjectType
): bool {
if ($objectWithoutClassType instanceof ObjectWithoutClassTypeWithParentTypes) {
foreach ($objectWithoutClassType->getParentTypes() as $typeWithClassName) {
if ($requiredObjectType->isSuperTypeOf($typeWithClassName)->yes()) {
return true;
}
}
}

return false;
}

private function isAnonymousObjectType(Type $type): bool
{
return $type instanceof ObjectType && $this->classAnalyzer->isAnonymousClassName($type->getClassName());
Expand Down