mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 05:41:20 +01:00
Support covariant return types & contravariant param types
Fixes #2102 and #2264
This commit is contained in:
parent
2c08321603
commit
d1c4c85f97
@ -580,7 +580,17 @@ class MethodAnalyzer extends FunctionLikeAnalyzer
|
|||||||
$implementer_classlike_storage->parent_class
|
$implementer_classlike_storage->parent_class
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
if (!TypeAnalyzer::isContainedByInPhp($implementer_signature_return_type, $guide_signature_return_type)) {
|
$is_contained_by = $codebase->php_major_version >= 7
|
||||||
|
&& $codebase->php_minor_version >= 4
|
||||||
|
&& $implementer_signature_return_type
|
||||||
|
? TypeAnalyzer::isContainedBy(
|
||||||
|
$codebase,
|
||||||
|
$implementer_signature_return_type,
|
||||||
|
$guide_signature_return_type
|
||||||
|
)
|
||||||
|
: TypeAnalyzer::isContainedByInPhp($implementer_signature_return_type, $guide_signature_return_type);
|
||||||
|
|
||||||
|
if (!$is_contained_by) {
|
||||||
if ($guide_classlike_storage->is_trait === $implementer_classlike_storage->is_trait) {
|
if ($guide_classlike_storage->is_trait === $implementer_classlike_storage->is_trait) {
|
||||||
if (IssueBuffer::accepts(
|
if (IssueBuffer::accepts(
|
||||||
new MethodSignatureMismatch(
|
new MethodSignatureMismatch(
|
||||||
@ -860,10 +870,19 @@ class MethodAnalyzer extends FunctionLikeAnalyzer
|
|||||||
$implementer_classlike_storage->parent_class
|
$implementer_classlike_storage->parent_class
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!TypeAnalyzer::isContainedByInPhp(
|
$is_contained_by = $codebase->php_major_version >= 7
|
||||||
$guide_param_signature_type,
|
&& $codebase->php_minor_version >= 4
|
||||||
$implementer_param_signature_type
|
&& $guide_param_signature_type
|
||||||
)) {
|
? TypeAnalyzer::isContainedBy(
|
||||||
|
$codebase,
|
||||||
|
$guide_param_signature_type,
|
||||||
|
$implementer_param_signature_type
|
||||||
|
)
|
||||||
|
: TypeAnalyzer::isContainedByInPhp(
|
||||||
|
$guide_param_signature_type,
|
||||||
|
$implementer_param_signature_type
|
||||||
|
);
|
||||||
|
if (!$is_contained_by) {
|
||||||
if ($guide_classlike_storage->is_trait === $implementer_classlike_storage->is_trait) {
|
if ($guide_classlike_storage->is_trait === $implementer_classlike_storage->is_trait) {
|
||||||
if (IssueBuffer::accepts(
|
if (IssueBuffer::accepts(
|
||||||
new MethodSignatureMismatch(
|
new MethodSignatureMismatch(
|
||||||
|
@ -111,6 +111,167 @@ class MethodSignatureTest extends TestCase
|
|||||||
$this->analyzeFile('somefile.php', new Context());
|
$this->analyzeFile('somefile.php', new Context());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testMismatchingCovariantReturnIn73()
|
||||||
|
{
|
||||||
|
$this->expectExceptionMessage('MethodSignatureMismatch');
|
||||||
|
$this->expectException(\Psalm\Exception\CodeException::class);
|
||||||
|
|
||||||
|
$this->project_analyzer->setPhpVersion('7.3');
|
||||||
|
|
||||||
|
$this->addFile(
|
||||||
|
'somefile.php',
|
||||||
|
'<?php
|
||||||
|
class A {
|
||||||
|
function foo(): C {
|
||||||
|
return new C();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class B extends A {
|
||||||
|
function foo(): D {
|
||||||
|
return new D();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class C {}
|
||||||
|
class D extends C {}'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->analyzeFile('somefile.php', new Context());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testMismatchingCovariantReturnIn74()
|
||||||
|
{
|
||||||
|
$this->project_analyzer->setPhpVersion('7.4');
|
||||||
|
|
||||||
|
$this->addFile(
|
||||||
|
'somefile.php',
|
||||||
|
'<?php
|
||||||
|
class A {
|
||||||
|
function foo(): C {
|
||||||
|
return new C();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class B extends A {
|
||||||
|
function foo(): D {
|
||||||
|
return new D();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class C {}
|
||||||
|
class D extends C {}'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->analyzeFile('somefile.php', new Context());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testMismatchingCovariantReturnIn73WithSelf()
|
||||||
|
{
|
||||||
|
$this->expectExceptionMessage('MethodSignatureMismatch');
|
||||||
|
$this->expectException(\Psalm\Exception\CodeException::class);
|
||||||
|
|
||||||
|
$this->project_analyzer->setPhpVersion('7.3');
|
||||||
|
|
||||||
|
$this->addFile(
|
||||||
|
'somefile.php',
|
||||||
|
'<?php
|
||||||
|
class A {
|
||||||
|
function foo(): self {
|
||||||
|
return new A();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class B extends A {
|
||||||
|
function foo(): self {
|
||||||
|
return new B();
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->analyzeFile('somefile.php', new Context());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testMismatchingCovariantReturnIn74WithSelf()
|
||||||
|
{
|
||||||
|
$this->project_analyzer->setPhpVersion('7.4');
|
||||||
|
|
||||||
|
$this->addFile(
|
||||||
|
'somefile.php',
|
||||||
|
'<?php
|
||||||
|
class A {
|
||||||
|
function foo(): self {
|
||||||
|
return new A();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class B extends A {
|
||||||
|
function foo(): self {
|
||||||
|
return new B();
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->analyzeFile('somefile.php', new Context());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testMismatchingCovariantParamIn73()
|
||||||
|
{
|
||||||
|
$this->expectExceptionMessage('MethodSignatureMismatch');
|
||||||
|
$this->expectException(\Psalm\Exception\CodeException::class);
|
||||||
|
|
||||||
|
$this->project_analyzer->setPhpVersion('7.3');
|
||||||
|
|
||||||
|
$this->addFile(
|
||||||
|
'somefile.php',
|
||||||
|
'<?php
|
||||||
|
class A {
|
||||||
|
public function foo(D $d) : void {}
|
||||||
|
}
|
||||||
|
class B extends A {
|
||||||
|
public function foo(C $c): void {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class C {}
|
||||||
|
class D extends C {}'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->analyzeFile('somefile.php', new Context());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testMismatchingCovariantParamIn74()
|
||||||
|
{
|
||||||
|
$this->project_analyzer->setPhpVersion('7.4');
|
||||||
|
|
||||||
|
$this->addFile(
|
||||||
|
'somefile.php',
|
||||||
|
'<?php
|
||||||
|
class A {
|
||||||
|
public function foo(D $d) : void {}
|
||||||
|
}
|
||||||
|
class B extends A {
|
||||||
|
public function foo(C $c): void {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class C {}
|
||||||
|
class D extends C {}'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->analyzeFile('somefile.php', new Context());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@ -643,36 +804,6 @@ class MethodSignatureTest extends TestCase
|
|||||||
}',
|
}',
|
||||||
'error_message' => 'Argument 1 of B::foo has wrong type \'string\', expecting \'null|string\' as',
|
'error_message' => 'Argument 1 of B::foo has wrong type \'string\', expecting \'null|string\' as',
|
||||||
],
|
],
|
||||||
'mismatchingCovariantReturn' => [
|
|
||||||
'<?php
|
|
||||||
class A {
|
|
||||||
function foo(): C {
|
|
||||||
return new C();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class B extends A {
|
|
||||||
function foo(): D {
|
|
||||||
return new D();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class C {}
|
|
||||||
class D extends C {}',
|
|
||||||
'error_message' => 'MethodSignatureMismatch',
|
|
||||||
],
|
|
||||||
'mismatchingCovariantReturnWithSelf' => [
|
|
||||||
'<?php
|
|
||||||
class A {
|
|
||||||
function foo(): self {
|
|
||||||
return new A();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class B extends A {
|
|
||||||
function foo(): self {
|
|
||||||
return new B();
|
|
||||||
}
|
|
||||||
}',
|
|
||||||
'error_message' => 'MethodSignatureMismatch',
|
|
||||||
],
|
|
||||||
'misplacedRequiredParam' => [
|
'misplacedRequiredParam' => [
|
||||||
'<?php
|
'<?php
|
||||||
function foo(string $bar = null, int $bat): void {}
|
function foo(string $bar = null, int $bat): void {}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user