1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Fix #81 - interface inheritance checks were broken

This commit is contained in:
Matt Brown 2017-01-30 18:38:23 -05:00
parent 4114456bf4
commit 8c68861cc3
4 changed files with 85 additions and 8 deletions

View File

@ -944,7 +944,14 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
$return_types_different = false;
if (!TypeChecker::isContainedBy($inferred_return_type, $declared_return_type, $this->getFileChecker())) {
if (!TypeChecker::isContainedBy(
$inferred_return_type,
$declared_return_type,
$this->getFileChecker(),
false,
$has_scalar_match,
$type_coerced
)) {
if ($update_docblock) {
if (!in_array('InvalidReturnType', $this->suppressed_issues)) {
FileChecker::addDocblockReturnType(
@ -968,9 +975,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
}
// is the declared return type more specific than the inferred one?
if ($declared_return_type->isNullable() === $inferred_return_type->isNullable() &&
TypeChecker::isContainedBy($declared_return_type, $inferred_return_type, $this->getFileChecker())
) {
if ($declared_return_type->isNullable() === $inferred_return_type->isNullable() && $type_coerced) {
if (IssueBuffer::accepts(
new MoreSpecificReturnType(
'The given return type \'' . $declared_return_type . '\' for ' . $cased_method_id .

View File

@ -790,6 +790,12 @@ class TypeChecker
$container_type_part->value
)
) ||
(InterfaceChecker::interfaceExists($input_type_part->value, $file_checker) &&
InterfaceChecker::interfaceExtends(
$input_type_part->value,
$container_type_part->value
)
) ||
ExpressionChecker::isMock($input_type_part->value)
)
)
@ -914,11 +920,19 @@ class TypeChecker
return true;
} elseif ($container_type_part instanceof TNamedObject &&
$input_type_part instanceof TNamedObject &&
ClassChecker::classExists($container_type_part->value, $file_checker) &&
ClassChecker::classOrInterfaceExists($input_type_part->value, $file_checker) &&
ClassChecker::classExtendsOrImplements(
$container_type_part->value,
$input_type_part->value
((
ClassChecker::classExists($container_type_part->value, $file_checker) &&
ClassChecker::classExtendsOrImplements(
$container_type_part->value,
$input_type_part->value
)) ||
(InterfaceChecker::interfaceExists($container_type_part->value, $file_checker) &&
InterfaceChecker::interfaceExtends(
$container_type_part->value,
$input_type_part->value
)
)
)
) {
$type_coerced = true;

View File

@ -566,4 +566,24 @@ class ClassTest extends PHPUnit_Framework_TestCase
$context = new Context();
$file_checker->visitAndAnalyzeMethods($context);
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage MoreSpecificReturnType
* @return void
*/
public function testMoreSpecificReturnType()
{
$stmts = self::$parser->parse('<?php
class A {}
class B extends A {}
function foo(A $a) : B {
return $a;
}
');
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
$context = new Context();
$file_checker->visitAndAnalyzeMethods($context);
}
}

View File

@ -544,4 +544,42 @@ class InterfaceTest extends PHPUnit_Framework_TestCase
$context = new Context();
$file_checker->visitAndAnalyzeMethods($context);
}
/**
* @return void
*/
public function testInterfaceExtendsReturnType()
{
$stmts = self::$parser->parse('<?php
interface A {}
interface B extends A {}
function foo(B $a) : A {
return $a;
}
');
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
$context = new Context();
$file_checker->visitAndAnalyzeMethods($context);
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage MoreSpecificReturnType
* @return void
*/
public function testMoreSpecificReturnType()
{
$stmts = self::$parser->parse('<?php
interface A {}
interface B extends A {}
function foo(A $a) : B {
return $a;
}
');
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
$context = new Context();
$file_checker->visitAndAnalyzeMethods($context);
}
}