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
|
||||
) : 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(
|
||||
|
@ -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 {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user