1
0
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:
Matthew Brown 2019-11-27 00:19:36 -05:00
parent 2c08321603
commit d1c4c85f97
2 changed files with 185 additions and 35 deletions

View File

@ -580,7 +580,17 @@ class MethodAnalyzer extends FunctionLikeAnalyzer
$implementer_classlike_storage->parent_class
) : 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 (IssueBuffer::accepts(
new MethodSignatureMismatch(
@ -860,10 +870,19 @@ class MethodAnalyzer extends FunctionLikeAnalyzer
$implementer_classlike_storage->parent_class
);
if (!TypeAnalyzer::isContainedByInPhp(
$is_contained_by = $codebase->php_major_version >= 7
&& $codebase->php_minor_version >= 4
&& $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 (IssueBuffer::accepts(
new MethodSignatureMismatch(

View File

@ -111,6 +111,167 @@ class MethodSignatureTest extends TestCase
$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
*/
@ -643,36 +804,6 @@ class MethodSignatureTest extends TestCase
}',
'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' => [
'<?php
function foo(string $bar = null, int $bat): void {}