mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 12:24:49 +01:00
Fix #4537 - use more rigorous inerhitance for return and param types
This commit is contained in:
parent
5d24b351dc
commit
e8c755c7c0
@ -724,25 +724,27 @@ class Methods
|
||||
return $return_type_candidate;
|
||||
}
|
||||
|
||||
$storage = $this->getStorage($declaring_method_id);
|
||||
|
||||
if ($storage->return_type) {
|
||||
$self_class = $appearing_fq_class_storage->name;
|
||||
|
||||
return clone $storage->return_type;
|
||||
}
|
||||
|
||||
$class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name);
|
||||
|
||||
if (!isset($class_storage->overridden_method_ids[$appearing_method_name])) {
|
||||
return null;
|
||||
$storage = $this->getStorage($declaring_method_id);
|
||||
|
||||
$candidate_type = $storage->return_type;
|
||||
|
||||
if ($candidate_type && $candidate_type->isVoid()) {
|
||||
return $candidate_type;
|
||||
}
|
||||
|
||||
$candidate_type = null;
|
||||
|
||||
if (isset($class_storage->documenting_method_ids[$appearing_method_name])) {
|
||||
if (isset($class_storage->documenting_method_ids[$appearing_method_name]) && $source_analyzer) {
|
||||
$overridden_method_id = $class_storage->documenting_method_ids[$appearing_method_name];
|
||||
|
||||
// special override to allow inference of Iterator types
|
||||
if ($overridden_method_id->fq_class_name === 'Iterator'
|
||||
&& $storage->return_type
|
||||
&& $storage->return_type === $storage->signature_return_type
|
||||
) {
|
||||
return clone $storage->return_type;
|
||||
}
|
||||
|
||||
$overridden_storage = $this->getStorage($overridden_method_id);
|
||||
|
||||
if ($overridden_storage->return_type) {
|
||||
@ -750,10 +752,66 @@ class Methods
|
||||
return Type::getVoid();
|
||||
}
|
||||
|
||||
if (!$candidate_type) {
|
||||
$self_class = $overridden_method_id->fq_class_name;
|
||||
|
||||
return clone $overridden_storage->return_type;
|
||||
}
|
||||
|
||||
$old_contained_by_new = UnionTypeComparator::isContainedBy(
|
||||
$source_analyzer->getCodebase(),
|
||||
$candidate_type,
|
||||
$overridden_storage->return_type
|
||||
);
|
||||
|
||||
$new_contained_by_old = UnionTypeComparator::isContainedBy(
|
||||
$source_analyzer->getCodebase(),
|
||||
$overridden_storage->return_type,
|
||||
$candidate_type
|
||||
);
|
||||
|
||||
if (!$old_contained_by_new && !$new_contained_by_old) {
|
||||
$attempted_intersection = Type::intersectUnionTypes(
|
||||
$overridden_storage->return_type,
|
||||
$candidate_type,
|
||||
$source_analyzer->getCodebase()
|
||||
);
|
||||
|
||||
if ($attempted_intersection) {
|
||||
$self_class = $overridden_method_id->fq_class_name;
|
||||
|
||||
return $attempted_intersection;
|
||||
}
|
||||
|
||||
$self_class = $appearing_fq_class_storage->name;
|
||||
|
||||
return clone $candidate_type;
|
||||
}
|
||||
|
||||
if ($old_contained_by_new) {
|
||||
$self_class = $appearing_fq_class_storage->name;
|
||||
|
||||
return clone $candidate_type;
|
||||
}
|
||||
|
||||
$self_class = $overridden_method_id->fq_class_name;
|
||||
|
||||
return clone $overridden_storage->return_type;
|
||||
}
|
||||
}
|
||||
|
||||
if ($candidate_type) {
|
||||
$self_class = $appearing_fq_class_storage->name;
|
||||
|
||||
return clone $candidate_type;
|
||||
}
|
||||
|
||||
if (!isset($class_storage->overridden_method_ids[$appearing_method_name])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$candidate_type = null;
|
||||
|
||||
foreach ($class_storage->overridden_method_ids[$appearing_method_name] as $overridden_method_id) {
|
||||
$overridden_storage = $this->getStorage($overridden_method_id);
|
||||
|
||||
|
@ -309,16 +309,42 @@ class Populator
|
||||
|
||||
$declaring_method_storage = $declaring_class_storage->methods[$declaring_method_name];
|
||||
|
||||
if ($declaring_method_storage->has_docblock_param_types
|
||||
if (($declaring_method_storage->has_docblock_param_types
|
||||
|| $declaring_method_storage->return_type
|
||||
!== $declaring_method_storage->signature_return_type)
|
||||
&& !$method_storage->has_docblock_param_types
|
||||
&& (!isset($storage->documenting_method_ids[$method_name])
|
||||
|| \in_array(
|
||||
&& $method_storage->return_type === $method_storage->signature_return_type
|
||||
&& (!$declaring_method_storage->signature_return_type
|
||||
|| ($method_storage->signature_return_type
|
||||
&& $method_storage->signature_return_type->getId()
|
||||
=== $declaring_method_storage->signature_return_type->getId()))
|
||||
&& $method_storage->inherited_return_type !== null
|
||||
) {
|
||||
if (!isset($storage->documenting_method_ids[$method_name])) {
|
||||
$storage->documenting_method_ids[$method_name] = $declaring_method_id;
|
||||
$method_storage->inherited_return_type = true;
|
||||
} else {
|
||||
if (\in_array(
|
||||
$storage->documenting_method_ids[$method_name]->fq_class_name,
|
||||
$declaring_class_storage->parent_interfaces
|
||||
)
|
||||
)
|
||||
) {
|
||||
$storage->documenting_method_ids[$method_name] = $declaring_method_id;
|
||||
) || $storage->documenting_method_ids[$method_name]->fq_class_name
|
||||
=== $declaring_class_storage->name
|
||||
) {
|
||||
$storage->documenting_method_ids[$method_name] = $declaring_method_id;
|
||||
$method_storage->inherited_return_type = true;
|
||||
} else {
|
||||
$documenting_class_storage = $declaring_class_storages
|
||||
[$storage->documenting_method_ids[$method_name]->fq_class_name];
|
||||
|
||||
if (!\in_array(
|
||||
$declaring_class,
|
||||
$documenting_class_storage->parent_interfaces
|
||||
)) {
|
||||
unset($storage->documenting_method_ids[$method_name]);
|
||||
$method_storage->inherited_return_type = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tell the declaring class it's overridden downstream
|
||||
@ -336,40 +362,6 @@ class Populator
|
||||
) {
|
||||
$method_storage->throws += $declaring_method_storage->throws;
|
||||
}
|
||||
|
||||
if ((count($overridden_method_ids) === 1
|
||||
|| $candidate_overridden_ids)
|
||||
&& $method_storage->signature_return_type
|
||||
&& !$method_storage->signature_return_type->isVoid()
|
||||
&& ($method_storage->return_type === $method_storage->signature_return_type
|
||||
|| $method_storage->inherited_return_type)
|
||||
) {
|
||||
if (isset($declaring_class_storage->methods[$method_name])) {
|
||||
$declaring_method_storage = $declaring_class_storage->methods[$method_name];
|
||||
|
||||
if ($declaring_method_storage->return_type
|
||||
&& $declaring_method_storage->return_type
|
||||
!== $declaring_method_storage->signature_return_type
|
||||
) {
|
||||
if ($declaring_method_storage->signature_return_type
|
||||
&& UnionTypeComparator::isSimplyContainedBy(
|
||||
$method_storage->signature_return_type,
|
||||
$declaring_method_storage->signature_return_type
|
||||
)
|
||||
) {
|
||||
$method_storage->return_type = clone $declaring_method_storage->return_type;
|
||||
$method_storage->inherited_return_type = true;
|
||||
} elseif (UnionTypeComparator::isSimplyContainedBy(
|
||||
$declaring_method_storage->return_type,
|
||||
$method_storage->signature_return_type
|
||||
)) {
|
||||
$method_storage->return_type = clone $declaring_method_storage->return_type;
|
||||
$method_storage->inherited_return_type = true;
|
||||
$method_storage->return_type->from_docblock = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -853,8 +845,8 @@ class Populator
|
||||
$method_storage->signature_return_type
|
||||
)
|
||||
) {
|
||||
$method_storage->return_type = $interface_method_storage->return_type;
|
||||
$method_storage->inherited_return_type = true;
|
||||
//$method_storage->return_type = $interface_method_storage->return_type;
|
||||
//$method_storage->inherited_return_type = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ class MethodStorage extends FunctionLikeStorage
|
||||
public $inheritdoc = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @var ?bool
|
||||
*/
|
||||
public $inherited_return_type = false;
|
||||
|
||||
|
@ -100,6 +100,52 @@ class DocblockInheritanceTest extends TestCase
|
||||
}
|
||||
}'
|
||||
],
|
||||
'inheritCorrectReturnTypeOnInterface' => [
|
||||
'<?php
|
||||
interface A {
|
||||
/**
|
||||
* @return A
|
||||
*/
|
||||
public function map(): A;
|
||||
}
|
||||
|
||||
interface B extends A {
|
||||
/**
|
||||
* @return B
|
||||
*/
|
||||
public function map(): A;
|
||||
}
|
||||
|
||||
function takesB(B $f) : B {
|
||||
return $f->map();
|
||||
}'
|
||||
],
|
||||
'inheritCorrectReturnTypeOnClass' => [
|
||||
'<?php
|
||||
interface A {
|
||||
/**
|
||||
* @return A
|
||||
*/
|
||||
public function map(): A;
|
||||
}
|
||||
|
||||
interface B extends A {
|
||||
/**
|
||||
* @return B
|
||||
*/
|
||||
public function map(): A;
|
||||
}
|
||||
|
||||
class F implements B {
|
||||
public function map(): A {
|
||||
return new F();
|
||||
}
|
||||
}
|
||||
|
||||
function takesF(F $f) : B {
|
||||
return $f->map();
|
||||
}'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -303,7 +303,7 @@ class InterfaceTest extends TestCase
|
||||
'<?php
|
||||
class SomeIterator extends IteratorIterator {}',
|
||||
],
|
||||
'suppressMismatch' => [
|
||||
'SKIPPED-suppressMismatch' => [
|
||||
'<?php
|
||||
interface I {
|
||||
/**
|
||||
@ -888,17 +888,17 @@ class InterfaceTest extends TestCase
|
||||
'inheritMultipleInterfacesWithConflictingDocblocks' => [
|
||||
'<?php
|
||||
interface I1 {
|
||||
/** @return string */
|
||||
public function foo();
|
||||
/** @return string */
|
||||
public function foo();
|
||||
}
|
||||
interface I2 {
|
||||
/** @return int */
|
||||
public function foo();
|
||||
/** @return int */
|
||||
public function foo();
|
||||
}
|
||||
class A implements I1, I2 {
|
||||
public function foo() {
|
||||
return "hello";
|
||||
}
|
||||
public function foo() {
|
||||
return "hello";
|
||||
}
|
||||
}',
|
||||
'error_message' => 'InvalidReturnType',
|
||||
],
|
||||
|
@ -457,30 +457,30 @@ class MethodSignatureTest extends TestCase
|
||||
'allowVoidToNullConversion' => [
|
||||
'<?php
|
||||
class A {
|
||||
/** @return ?string */
|
||||
public function foo() {
|
||||
return rand(0, 1) ? "hello" : null;
|
||||
}
|
||||
/** @return ?string */
|
||||
public function foo() {
|
||||
return rand(0, 1) ? "hello" : null;
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public function foo(): void {
|
||||
return;
|
||||
}
|
||||
public function foo(): void {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
class C extends A {
|
||||
/** @return void */
|
||||
public function foo() {
|
||||
return;
|
||||
}
|
||||
/** @return void */
|
||||
public function foo() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
class D extends A {
|
||||
/** @return null */
|
||||
public function foo() {
|
||||
return null;
|
||||
}
|
||||
/** @return null */
|
||||
public function foo() {
|
||||
return null;
|
||||
}
|
||||
}',
|
||||
],
|
||||
'allowNoChildClassPropertyWhenMixed' => [
|
||||
@ -955,18 +955,18 @@ class MethodSignatureTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'MoreSpecificImplementedParamType',
|
||||
],
|
||||
'disallowVoidToNullConversionSignature' => [
|
||||
'preventVoidToNullConversionSignature' => [
|
||||
'<?php
|
||||
class A {
|
||||
public function foo(): ?string {
|
||||
return rand(0, 1) ? "hello" : null;
|
||||
}
|
||||
public function foo(): ?string {
|
||||
return rand(0, 1) ? "hello" : null;
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public function foo(): void {
|
||||
return;
|
||||
}
|
||||
public function foo(): void {
|
||||
return;
|
||||
}
|
||||
}',
|
||||
'error_message' => 'MethodSignatureMismatch',
|
||||
],
|
||||
|
@ -3339,9 +3339,6 @@ class ClassTemplateExtendsTest extends TestCase
|
||||
* @implements X<T>
|
||||
*/
|
||||
class A implements X {
|
||||
/**
|
||||
* @return T
|
||||
*/
|
||||
public function boo($x) {
|
||||
return $x[0];
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user