Skip to content
Permalink
Browse files

Add ImplementedParamTypeMismatch issue

Fixes #1633
  • Loading branch information...
muglug committed May 14, 2019
1 parent 2c36a10 commit 30cbcb6c362e36d510b9950262422b310dcacc33
@@ -102,5 +102,6 @@
<TypeDoesNotContainNull errorLevel="info" />
<MissingDocblockType errorLevel="info" />
<ImplementedReturnTypeMismatch errorLevel="info" />
<ImplementedParamTypeMismatch errorLevel="info" />
</issueHandlers>
</psalm>
@@ -102,6 +102,7 @@
<TypeDoesNotContainNull errorLevel="info" />
<MissingDocblockType errorLevel="info" />
<ImplementedReturnTypeMismatch errorLevel="info" />
<ImplementedParamTypeMismatch errorLevel="info" />

<!-- level 6 issues - really bad things -->

@@ -102,6 +102,7 @@
<TypeDoesNotContainNull errorLevel="info" />
<MissingDocblockType errorLevel="info" />
<ImplementedReturnTypeMismatch errorLevel="info" />
<ImplementedParamTypeMismatch errorLevel="info" />

<!-- level 6 issues - really bad things -->

@@ -102,6 +102,7 @@
<TypeDoesNotContainNull errorLevel="info" />
<MissingDocblockType errorLevel="info" />
<ImplementedReturnTypeMismatch errorLevel="info" />
<ImplementedParamTypeMismatch errorLevel="info" />

<!-- level 6 issues - really bad things -->

@@ -163,6 +163,7 @@
<xs:element name="ForbiddenCode" type="IssueHandlerType" minOccurs="0" />
<xs:element name="ForbiddenEcho" type="IssueHandlerType" minOccurs="0" />
<xs:element name="NoValue" type="IssueHandlerType" minOccurs="0" />
<xs:element name="ImplementedParamTypeMismatch" type="IssueHandlerType" minOccurs="0" />
<xs:element name="ImplementedReturnTypeMismatch" type="IssueHandlerType" minOccurs="0" />
<xs:element name="ImplicitToStringCast" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InaccessibleClassConstant" type="IssueHandlerType" minOccurs="0" />
@@ -271,6 +271,22 @@ Emitted when Psalm encounters an echo statement and the `forbidEcho` flag in you
echo("bah");
```

### ImplementedParamTypeMismatch

Emitted when a class that inherits another, or implements an interface, has docblock param type that's entirely different to the parent. Subclasses of the parent return type are permitted, in docblocks.

```php
class D {
/** @param string $a */
public function foo($a): void {}
}
class E extends D {
/** @param int $a */
public function foo($a): void {}
}
```

### ImplementedReturnTypeMismatch

Emitted when a class that inherits another, or implements an interface, has docblock return type that's entirely different to the parent. Subclasses of the parent return type are permitted, in docblocks.
@@ -1248,6 +1248,10 @@ private static function getParentIssueType($issue_type)
return 'MethodSignatureMismatch';
}
if ($issue_type === 'ImplementedParamTypeMismatch') {
return 'MoreSpecificImplementedParamType';
}
if ($issue_type === 'MixedArgumentTypeCoercion'
|| $issue_type === 'MixedPropertyTypeCoercion'
|| $issue_type === 'MixedReturnTypeCoercion'
@@ -7,6 +7,7 @@
use Psalm\Context;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Issue\DeprecatedMethod;
use Psalm\Issue\ImplementedParamTypeMismatch;
use Psalm\Issue\ImplementedReturnTypeMismatch;
use Psalm\Issue\InaccessibleMethod;
use Psalm\Issue\InternalMethod;
@@ -733,6 +734,57 @@ public static function compareMethods(
$implementer_param = $implementer_method_storage->params[$i];
if ($prevent_method_signature_mismatch
&& !$guide_classlike_storage->user_defined
&& $guide_param->type
) {
$implementer_param_type = $implementer_method_storage->params[$i]->signature_type;
$guide_param_signature_type = $guide_param->type;
$or_null_guide_param_signature_type = $guide_param->signature_type
? clone $guide_param->signature_type
: null;
if ($or_null_guide_param_signature_type) {
$or_null_guide_param_signature_type->addType(new Type\Atomic\TNull);
}
if ($cased_guide_method_id === 'Serializable::unserialize') {
$guide_param_signature_type = null;
$or_null_guide_param_signature_type = null;
}
if (!$guide_param->type->hasMixed()
&& !$guide_param->type->from_docblock
&& ($implementer_param_type || $guide_param_signature_type)
) {
if ($implementer_param_type
&& (!$guide_param_signature_type
|| strtolower($implementer_param_type->getId())
!== strtolower($guide_param_signature_type->getId()))
&& (!$or_null_guide_param_signature_type
|| strtolower($implementer_param_type->getId())
!== strtolower($or_null_guide_param_signature_type->getId()))
) {
if (IssueBuffer::accepts(
new MethodSignatureMismatch(
'Argument ' . ($i + 1) . ' of ' . $cased_implementer_method_id . ' has wrong type \'' .
$implementer_param_type . '\', expecting \'' .
$guide_param_signature_type . '\' as defined by ' .
$cased_guide_method_id,
$implementer_method_storage->params[$i]->location
?: $code_location
)
)) {
return false;
}
return null;
}
}
}
if ($prevent_method_signature_mismatch
&& $guide_classlike_storage->user_defined
&& $implementer_param->signature_type
@@ -790,8 +842,7 @@ public static function compareMethods(
}
}
if ($guide_classlike_storage->user_defined
&& $implementer_param->type
if ($implementer_param->type
&& $guide_param->type
&& $implementer_param->type->getId() !== $guide_param->type->getId()
) {
@@ -832,21 +883,44 @@ public static function compareMethods(
$codebase,
$guide_method_storage_param_type,
$implementer_method_storage_param_type,
false,
false
!$guide_classlike_storage->user_defined,
!$guide_classlike_storage->user_defined,
$has_scalar_match,
$type_coerced,
$type_coerced_from_mixed
)) {
if (IssueBuffer::accepts(
new MoreSpecificImplementedParamType(
'Argument ' . ($i + 1) . ' of ' . $cased_implementer_method_id . ' has wrong type \'' .
$implementer_method_storage_param_type->getId() . '\', expecting \'' .
$guide_method_storage_param_type->getId() . '\' as defined by ' .
$cased_guide_method_id,
$implementer_method_storage->params[$i]->location
?: $code_location
),
$suppressed_issues
)) {
return false;
// is the declared return type more specific than the inferred one?
if ($type_coerced) {
if ($guide_classlike_storage->user_defined) {
if (IssueBuffer::accepts(
new MoreSpecificImplementedParamType(
'Argument ' . ($i + 1) . ' of ' . $cased_implementer_method_id
. ' has the more specific type \'' .
$implementer_method_storage_param_type->getId() . '\', expecting \'' .
$guide_method_storage_param_type->getId() . '\' as defined by ' .
$cased_guide_method_id,
$implementer_method_storage->params[$i]->location
?: $code_location
),
$suppressed_issues
)) {
return false;
}
}
} else {
if (IssueBuffer::accepts(
new ImplementedParamTypeMismatch(
'Argument ' . ($i + 1) . ' of ' . $cased_implementer_method_id . ' has wrong type \'' .
$implementer_method_storage_param_type->getId() . '\', expecting \'' .
$guide_method_storage_param_type->getId() . '\' as defined by ' .
$cased_guide_method_id,
$implementer_method_storage->params[$i]->location
?: $code_location
),
$suppressed_issues
)) {
return false;
}
}
}
}
@@ -866,49 +940,6 @@ public static function compareMethods(
return null;
}
$implemeneter_param_type = $implementer_method_storage->params[$i]->type;
$or_null_guide_type = $guide_param->signature_type
? clone $guide_param->signature_type
: null;
if ($or_null_guide_type) {
$or_null_guide_type->addType(new Type\Atomic\TNull);
}
if (!$guide_classlike_storage->user_defined
&& $guide_param->type
&& !$guide_param->type->hasMixed()
&& !$guide_param->type->from_docblock
&& ($cased_guide_method_id !== 'SoapClient::__soapCall' || $implemeneter_param_type)
&& (
!$implemeneter_param_type
|| (
strtolower($implemeneter_param_type->getId()) !== strtolower($guide_param->type->getId())
&& (
!$or_null_guide_type
|| strtolower($implemeneter_param_type->getId())
!== strtolower($or_null_guide_type->getId())
)
)
)
) {
if (IssueBuffer::accepts(
new MethodSignatureMismatch(
'Argument ' . ($i + 1) . ' of ' . $cased_implementer_method_id . ' has wrong type \'' .
$implemeneter_param_type . '\', expecting \'' .
$guide_param->type . '\' as defined by ' .
$cased_guide_method_id,
$implementer_method_storage->params[$i]->location
?: $code_location
)
)) {
return false;
}
return null;
}
}
if ($guide_classlike_storage->user_defined
'SoapClient::__setCookie' => ['', 'name'=>'string', 'value='=>'string'],
'SoapClient::__setLocation' => ['string', 'new_location='=>'string'],
'SoapClient::__setSoapHeaders' => ['bool', 'soapheaders='=>''],
'SoapClient::__soapCall' => ['', 'function_name'=>'string', 'arguments'=>'array', 'options='=>'array', 'input_headers='=>'', '&w_output_headers='=>'array'],
'SoapClient::__soapCall' => ['', 'function_name'=>'string', 'arguments'=>'array', 'options='=>'array', 'input_headers='=>'SoapHeader|array', '&w_output_headers='=>'array'],
'SoapClient::SoapClient' => ['object', 'wsdl'=>'mixed', 'options='=>'array'],
'SoapFault::__clone' => ['void'],
'SoapFault::__construct' => ['void', 'faultcode'=>'string', 'faultstring'=>'string', 'faultactor='=>'string', 'detail='=>'string', 'faultname='=>'string', 'headerfault='=>'string'],
@@ -306,7 +306,7 @@ private function getReflectionParamData(\ReflectionParameter $param)
$is_optional = (bool)$param->isOptional();
return new FunctionLikeParameter(
$parameter = new FunctionLikeParameter(
$param_name,
(bool)$param->isPassedByReference(),
$param_type,
@@ -316,6 +316,10 @@ private function getReflectionParamData(\ReflectionParameter $param)
$param_type->isNullable(),
$param->isVariadic()
);
$parameter->signature_type = Type::getMixed();
return $parameter;
}
/**
@@ -0,0 +1,6 @@
<?php
namespace Psalm\Issue;
class ImplementedParamTypeMismatch extends CodeIssue
{
}
@@ -522,7 +522,7 @@ public function foo(string $s) : C {
/** @method D foo(int $s) */
class B extends A {}',
'error_message' => 'MoreSpecificImplementedParamType - src/somefile.php:11:21',
'error_message' => 'ImplementedParamTypeMismatch - src/somefile.php:11:21',
],
];
}

0 comments on commit 30cbcb6

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