Skip to content
Permalink
Browse files

Support covariant return types & contravariant param types

Fixes #2102 and #2264
  • Loading branch information
muglug committed Nov 27, 2019
1 parent 2c08321 commit d1c4c85f97764f159e6fb39431c13942d93274d6
Showing with 185 additions and 35 deletions.
  1. +24 −5 src/Psalm/Internal/Analyzer/MethodAnalyzer.php
  2. +161 −30 tests/MethodSignatureTest.php
@@ -580,7 +580,17 @@ public static function compareMethods(
$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 @@ public static function compareMethods(
$implementer_classlike_storage->parent_class
);
if (!TypeAnalyzer::isContainedByInPhp(
$guide_param_signature_type,
$implementer_param_signature_type
)) {
$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 @@ public function __soapCall(
$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 @@ public function foo(string $s): string {
}',
'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 {}

0 comments on commit d1c4c85

Please sign in to comment.
You can’t perform that action at this time.