1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Fix #472 - inherit interface method docblocks if defined just once

This commit is contained in:
Matt Brown 2018-01-26 11:50:29 -05:00
parent 5e0a8c4339
commit 1320b6dd54
4 changed files with 100 additions and 28 deletions

View File

@ -19,6 +19,7 @@ use Psalm\Issue\UnimplementedAbstractMethod;
use Psalm\Issue\UnimplementedInterfaceMethod;
use Psalm\IssueBuffer;
use Psalm\StatementsSource;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Type;
class ClassChecker extends ClassLikeChecker
@ -426,6 +427,7 @@ class ClassChecker extends ClassLikeChecker
if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod) {
$method_checker = $this->analyzeClassMethod(
$stmt,
$storage,
$this,
$class_context,
$global_context
@ -483,6 +485,7 @@ class ClassChecker extends ClassLikeChecker
if ($trait_stmt instanceof PhpParser\Node\Stmt\ClassMethod) {
$trait_method_checker = $this->analyzeClassMethod(
$trait_stmt,
$storage,
$trait_checker,
$class_context,
$global_context
@ -606,6 +609,7 @@ class ClassChecker extends ClassLikeChecker
$constructor_checker = $this->analyzeClassMethod(
$fake_stmt,
$storage,
$this,
$class_context,
$global_context
@ -764,6 +768,7 @@ class ClassChecker extends ClassLikeChecker
*/
private function analyzeClassMethod(
PhpParser\Node\Stmt\ClassMethod $stmt,
ClassLikeStorage $class_storage,
StatementsSource $source,
Context $class_context,
Context $global_context = null
@ -815,6 +820,30 @@ class ClassChecker extends ClassLikeChecker
$self_class
);
if (!$return_type && $class_storage->interface_method_ids) {
foreach ($class_storage->interface_method_ids[$stmt->name] as $interface_method_id) {
list($interface_class) = explode('::', $interface_method_id);
$interface_return_type = MethodChecker::getMethodReturnType(
$project_checker,
$interface_method_id,
$interface_class
);
$interface_return_type_location = MethodChecker::getMethodReturnTypeLocation(
$project_checker,
$interface_method_id
);
$method_checker->verifyReturnType(
$project_checker,
$interface_return_type,
$interface_class,
$interface_return_type_location
);
}
}
$method_checker->verifyReturnType(
$project_checker,
$return_type,

View File

@ -521,6 +521,8 @@ class Codebase
$storage->class_implements = array_merge($extra_interfaces, $storage->class_implements);
$interface_method_implementers = [];
foreach ($storage->class_implements as $implemented_interface) {
try {
$implemented_interface_storage = $storage_provider->get($implemented_interface);
@ -531,19 +533,23 @@ class Codebase
foreach ($implemented_interface_storage->methods as $method_name => $method) {
if ($method->visibility === ClassLikeChecker::VISIBILITY_PUBLIC) {
$mentioned_method_id = $implemented_interface . '::' . $method_name;
$implemented_method_id = $storage->name . '::' . $method_name;
if ($storage->abstract) {
MethodChecker::setOverriddenMethodId(
$this->classlike_storage_provider,
$implemented_method_id,
$mentioned_method_id
);
}
$interface_method_implementers[$method_name][] = $mentioned_method_id;
}
}
}
foreach ($interface_method_implementers as $method_name => $interface_method_ids) {
if (count($interface_method_ids) === 1) {
MethodChecker::setOverriddenMethodId(
$this->classlike_storage_provider,
$storage->name . '::' . $method_name,
$interface_method_ids[0]
);
} else {
$storage->interface_method_ids[$method_name] = $interface_method_ids;
}
}
if ($storage->location) {
$file_path = $storage->location->file_path;

View File

@ -135,6 +135,11 @@ class ClassLikeStorage
*/
public $overridden_method_ids = [];
/**
* @var array<string, array<string>>
*/
public $interface_method_ids = [];
/**
* @var array<string, string>
*/

View File

@ -338,6 +338,40 @@ class InterfaceTest extends TestCase
}
}',
],
'inheritMultipleInterfacesWithDocblocks' => [
'<?php
interface I1 {
/** @return string */
public function foo();
}
interface I2 {
/** @return string */
public function bar();
}
class A implements I1, I2 {
public function foo() {
return "hello";
}
public function bar() {
return "goodbye";
}
}',
],
'interfaceReturnType' => [
'<?php
interface A {
/** @return string|null */
public function blah();
}
class B implements A {
public function blah() {
return rand(0, 10) === 4 ? "blah" : null;
}
}
$blah = (new B())->blah();',
],
];
}
@ -487,25 +521,6 @@ class InterfaceTest extends TestCase
}',
'error_message' => 'LessSpecificReturnStatement',
],
'interfaceReturnType' => [
'<?php
interface A {
/** @return string|null */
public function blah();
}
class B implements A {
public function blah() {
return rand(0, 10) === 4 ? "blah" : null;
}
}
$blah = (new B())->blah();',
'error_message' => 'MixedAssignment',
'error_levels' => [
'MissingReturnType',
],
],
'interfaceInstanceofAndTwoReturns' => [
'<?php
interface A {}
@ -532,6 +547,23 @@ class InterfaceTest extends TestCase
class A implements Container {}',
'error_message' => 'DeprecatedInterface',
],
'inheritMultipleInterfacesWithConflictingDocblocks' => [
'<?php
interface I1 {
/** @return string */
public function foo();
}
interface I2 {
/** @return int */
public function foo();
}
class A implements I1, I2 {
public function foo() {
return "hello";
}
}',
'error_message' => 'InvalidReturnType',
],
];
}
}