Skip to content

Commit

Permalink
Fix checking overriden method signature when method-level generics ar…
Browse files Browse the repository at this point in the history
…e involved
  • Loading branch information
ondrejmirtes committed Nov 1, 2020
1 parent 3a57521 commit 1c2b727
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 6 deletions.
19 changes: 13 additions & 6 deletions src/Rules/Methods/MethodSignatureRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Generic\TemplateTypeHelper;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\TypehintHelper;
Expand Down Expand Up @@ -154,11 +155,11 @@ private function checkReturnTypeCompatibility(
{
$returnType = TypehintHelper::decideType(
$currentVariant->getNativeReturnType(),
$currentVariant->getPhpDocReturnType()
TemplateTypeHelper::resolveToBounds($currentVariant->getPhpDocReturnType())
);
$parentReturnType = TypehintHelper::decideType(
$parentVariant->getNativeReturnType(),
$parentVariant->getPhpDocReturnType()
TemplateTypeHelper::resolveToBounds($parentVariant->getPhpDocReturnType())
);
// Allow adding `void` return type hints when the parent defines no return type
if ($returnType instanceof VoidType && $parentReturnType instanceof MixedType) {
Expand All @@ -170,7 +171,10 @@ private function checkReturnTypeCompatibility(
return [TrinaryLogic::createYes(), $returnType, $parentReturnType];
}

return [$parentReturnType->isSuperTypeOf($returnType), $returnType, $parentReturnType];
return [$parentReturnType->isSuperTypeOf($returnType), TypehintHelper::decideType(
$currentVariant->getNativeReturnType(),
$currentVariant->getPhpDocReturnType()
), $parentReturnType];
}

/**
Expand All @@ -192,14 +196,17 @@ private function checkParameterTypeCompatibility(

$parameterType = TypehintHelper::decideType(
$parameter->getNativeType(),
$parameter->getPhpDocType()
TemplateTypeHelper::resolveToBounds($parameter->getPhpDocType())
);
$parentParameterType = TypehintHelper::decideType(
$parentParameter->getNativeType(),
$parentParameter->getPhpDocType()
TemplateTypeHelper::resolveToBounds($parentParameter->getPhpDocType())
);

$parameterResults[] = [$parameterType->isSuperTypeOf($parentParameterType), $parameterType, $parentParameterType];
$parameterResults[] = [$parameterType->isSuperTypeOf($parentParameterType), TypehintHelper::decideType(
$parameter->getNativeType(),
$parameter->getPhpDocType()
), $parentParameterType];
}

return $parameterResults;
Expand Down
38 changes: 38 additions & 0 deletions tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,42 @@ public function testBug4003(): void
]);
}

public function testBug4017(): void
{
$this->reportMaybes = true;
$this->reportStatic = true;
$this->analyse([__DIR__ . '/data/bug-4017.php'], []);
}

public function testBug4017Two(): void
{
$this->reportMaybes = true;
$this->reportStatic = true;
$this->analyse([__DIR__ . '/data/bug-4017_2.php'], [
[
'Parameter #1 $a (Bug4017_2\Foo) of method Bug4017_2\Lorem::doFoo() should be compatible with parameter $a (stdClass) of method Bug4017_2\Bar<stdClass>::doFoo()',
51,
],
]);
}

public function testBug4017Three(): void
{
$this->reportMaybes = true;
$this->reportStatic = true;
$this->analyse([__DIR__ . '/data/bug-4017_3.php'], [
[
'Parameter #1 $a (T of stdClass) of method Bug4017_3\Lorem::doFoo() should be compatible with parameter $a (Bug4017_3\Foo) of method Bug4017_3\Bar::doFoo()',
45,
],
]);
}

public function testBug4023(): void
{
$this->reportMaybes = true;
$this->reportStatic = true;
$this->analyse([__DIR__ . '/data/bug-4023.php'], []);
}

}
40 changes: 40 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-4017.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace Bug4017;

/**
* @template T
*/
interface DoctrineEntityRepository
{

}

interface DoctrineEntityManagerInterface
{
/**
* @template T
* @param class-string<T> $className
* @return DoctrineEntityRepository<T>
*/
public function getRepository(string $className): DoctrineEntityRepository;
}


/**
* @phpstan-template TEntityClass
* @phpstan-extends DoctrineEntityRepository<TEntityClass>
*/
interface MyEntityRepositoryInterface extends DoctrineEntityRepository
{
}

interface MyEntityManagerInterface extends DoctrineEntityManagerInterface
{
/**
* @template T
* @param class-string<T> $className
* @return MyEntityRepositoryInterface<T>
*/
public function getRepository(string $className): MyEntityRepositoryInterface;
}
56 changes: 56 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-4017_2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace Bug4017_2;

class Foo
{

}

/**
* @template T
*/
class Bar
{

/**
* @param T $a
*/
public function doFoo($a)
{

}

}

/**
* @extends Bar<Foo>
*/
class Baz extends Bar
{

/**
* @param Foo $a
*/
public function doFoo($a)
{

}

}

/**
* @extends Bar<\stdClass>
*/
class Lorem extends Bar
{

/**
* @param Foo $a
*/
public function doFoo($a)
{

}

}
50 changes: 50 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-4017_3.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Bug4017_3;

class Foo
{

}

class Bar
{

/**
* @template T of Foo
* @param T $a
*/
public function doFoo($a)
{

}

}

class Baz extends Bar
{

/**
* @template T of Foo
* @param T $a
*/
public function doFoo($a)
{

}

}

class Lorem extends Bar
{

/**
* @template T of \stdClass
* @param T $a
*/
public function doFoo($a)
{

}

}
30 changes: 30 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-4023.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Bug4023;

interface A
{
/**
* @template T of object
*
* @param mixed[]|T $data
*
* @return T
*/
public function x($data): object;
}

final class B implements A
{
/**
* @template T of object
*
* @param mixed[]|T $data
*
* @return T
*/
public function x($data): object
{
throw new \Exception();
}
}

0 comments on commit 1c2b727

Please sign in to comment.