diff --git a/src/Psalm/Checker/FunctionLikeChecker.php b/src/Psalm/Checker/FunctionLikeChecker.php index 518c5cef6..3b3bfa801 100644 --- a/src/Psalm/Checker/FunctionLikeChecker.php +++ b/src/Psalm/Checker/FunctionLikeChecker.php @@ -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 . diff --git a/src/Psalm/Checker/TypeChecker.php b/src/Psalm/Checker/TypeChecker.php index 139655099..cfc36dd97 100644 --- a/src/Psalm/Checker/TypeChecker.php +++ b/src/Psalm/Checker/TypeChecker.php @@ -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; diff --git a/tests/ClassTest.php b/tests/ClassTest.php index a284c3520..9b60a3690 100644 --- a/tests/ClassTest.php +++ b/tests/ClassTest.php @@ -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('project_checker, $stmts); + $context = new Context(); + $file_checker->visitAndAnalyzeMethods($context); + } } diff --git a/tests/InterfaceTest.php b/tests/InterfaceTest.php index f25d724ef..393c3f660 100644 --- a/tests/InterfaceTest.php +++ b/tests/InterfaceTest.php @@ -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('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('project_checker, $stmts); + $context = new Context(); + $file_checker->visitAndAnalyzeMethods($context); + } }