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:
parent
5e0a8c4339
commit
1320b6dd54
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -135,6 +135,11 @@ class ClassLikeStorage
|
||||
*/
|
||||
public $overridden_method_ids = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string>>
|
||||
*/
|
||||
public $interface_method_ids = [];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
|
@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user