1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Support array_combine types and introduce a MoreSpecificReturnType issue

This commit is contained in:
Matt Brown 2017-01-17 11:17:49 -05:00
parent e4cceee140
commit dae7718ae8
5 changed files with 81 additions and 11 deletions

View File

@ -117,6 +117,7 @@
<xs:element name="MixedPropertyAssignment" type="IssueHandlerType" minOccurs="0" />
<xs:element name="MixedPropertyFetch" type="IssueHandlerType" minOccurs="0" />
<xs:element name="MixedStringOffsetAssignment" type="IssueHandlerType" minOccurs="0" />
<xs:element name="MoreSpecificReturnType" type="IssueHandlerType" minOccurs="0" />
<xs:element name="NoInterfaceProperties" type="IssueHandlerType" minOccurs="0" />
<xs:element name="NonStaticSelfCall" type="IssueHandlerType" minOccurs="0" />
<xs:element name="NullArgument" type="IssueHandlerType" minOccurs="0" />

View File

@ -405,6 +405,7 @@ class FunctionChecker extends FunctionLikeChecker
}
$first_arg = isset($call_args[0]->value) ? $call_args[0]->value : null;
$second_arg = isset($call_args[1]->value) ? $call_args[1]->value : null;
$first_arg_array_generic = $first_arg
&& isset($first_arg->inferredType)
@ -493,6 +494,49 @@ class FunctionChecker extends FunctionLikeChecker
return Type::getArray();
}
if ($call_map_key === 'array_combine') {
$second_arg_array_generic = $second_arg
&& isset($second_arg->inferredType)
&& isset($second_arg->inferredType->types['array'])
&& $second_arg->inferredType->types['array'] instanceof Type\Atomic\TArray
? $second_arg->inferredType->types['array']
: null;
$second_arg_array_objectlike = $second_arg
&& isset($second_arg->inferredType)
&& isset($second_arg->inferredType->types['array'])
&& $second_arg->inferredType->types['array'] instanceof Type\Atomic\ObjectLike
? $second_arg->inferredType->types['array']
: null;
if ($second_arg_array_generic || $second_arg_array_objectlike) {
if ($first_arg && isset($first_arg->inferredType) && $first_arg->inferredType->hasArray()) {
if ($first_arg_array_generic) {
$keys_inner_type = clone $first_arg_array_generic->type_params[1];
} else {
/** @var Type\Atomic\ObjectLike $first_arg_array_objectlike */
$keys_inner_type = $first_arg_array_objectlike->getGenericTypeParam();
}
} else {
$keys_inner_type = Type::getMixed();
}
if ($second_arg_array_generic) {
$values_inner_type = clone $second_arg_array_generic->type_params[1];
} else {
/** @var Type\Atomic\ObjectLike $second_arg_array_objectlike */
$values_inner_type = $second_arg_array_objectlike->getGenericTypeParam();
}
return new Type\Union([
new Type\Atomic\TArray([
$keys_inner_type,
$values_inner_type
])
]);
}
}
if ($call_map_key === 'array_diff') {
if (!$first_arg_array_generic) {
return Type::getArray();
@ -644,7 +688,7 @@ class FunctionChecker extends FunctionLikeChecker
* @return array<string, array<string, string>>
* @psalm-suppress MixedInferredReturnType as the use of require buggers things up
* @psalm-suppress MixedAssignment
* @psalm-suppress InvalidReturnType
* @psalm-suppress MoreSpecificReturnType
*/
protected static function getCallMap()
{

View File

@ -23,6 +23,7 @@ use Psalm\Issue\MisplacedRequiredParam;
use Psalm\Issue\MissingClosureReturnType;
use Psalm\Issue\MissingReturnType;
use Psalm\Issue\MixedInferredReturnType;
use Psalm\Issue\MoreSpecificReturnType;
use Psalm\Issue\OverriddenMethodAccess;
use Psalm\IssueBuffer;
use Psalm\StatementsSource;
@ -953,16 +954,34 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
return null;
}
if (IssueBuffer::accepts(
new InvalidReturnType(
'The given return type \'' . $declared_return_type . '\' for ' . $cased_method_id .
' is incorrect, got \'' . $inferred_return_type . '\'',
$secondary_return_type_location ?: $return_type_location
),
$this->suppressed_issues
)) {
return false;
// is the declared return type more specific than the inferred one?
if ($declared_return_type->isNullable() === $inferred_return_type->isNullable() &&
TypeChecker::isContainedBy($declared_return_type, $inferred_return_type, $this->getFileChecker())
) {
if (IssueBuffer::accepts(
new MoreSpecificReturnType(
'The given return type \'' . $declared_return_type . '\' for ' . $cased_method_id .
' is more specific than the inferred return type \'' . $inferred_return_type . '\'',
$secondary_return_type_location ?: $return_type_location
),
$this->suppressed_issues
)) {
return false;
}
} else {
if (IssueBuffer::accepts(
new InvalidReturnType(
'The given return type \'' . $declared_return_type . '\' for ' . $cased_method_id .
' is incorrect, got \'' . $inferred_return_type . '\'',
$secondary_return_type_location ?: $return_type_location
),
$this->suppressed_issues
)) {
return false;
}
}
}
if (!TypeChecker::hasIdenticalTypes(

View File

@ -25,7 +25,7 @@ class AssertionFinder
* @param string|null $this_class_name
* @param StatementsSource $source
* @return array<string, string>
* @psalm-suppress InvalidReturnType
* @psalm-suppress MoreSpecificReturnType
*/
public static function getAssertions(
PhpParser\Node\Expr $conditional,

View File

@ -0,0 +1,6 @@
<?php
namespace Psalm\Issue;
class MoreSpecificReturnType extends CodeError
{
}