mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Fix #687 - interpret unpacked arguments in array_push and array_unshift
This commit is contained in:
parent
131cab2528
commit
0882b9c0f9
@ -285,21 +285,49 @@ class CallChecker
|
||||
return false;
|
||||
}
|
||||
|
||||
$by_ref_type = Type::combineUnionTypes(
|
||||
$by_ref_type,
|
||||
new Type\Union(
|
||||
[
|
||||
new TArray(
|
||||
if (!isset($arg->value->inferredType) || $arg->value->inferredType->isMixed()) {
|
||||
$by_ref_type = Type::combineUnionTypes(
|
||||
$by_ref_type,
|
||||
new Type\Union([new TArray([Type::getInt(), Type::getMixed()])])
|
||||
);
|
||||
} elseif ($arg->unpack) {
|
||||
if ($arg->value->inferredType->hasArray()) {
|
||||
/** @var Type\Atomic\TArray|Type\Atomic\ObjectLike */
|
||||
$array_atomic_type = $arg->value->inferredType->getTypes()['array'];
|
||||
|
||||
if ($array_atomic_type instanceof Type\Atomic\ObjectLike) {
|
||||
$array_atomic_type = $array_atomic_type->getGenericArrayType();
|
||||
}
|
||||
|
||||
$by_ref_type = Type::combineUnionTypes(
|
||||
$by_ref_type,
|
||||
new Type\Union(
|
||||
[
|
||||
Type::getInt(),
|
||||
isset($arg->value->inferredType)
|
||||
? clone $arg->value->inferredType
|
||||
: Type::getMixed(),
|
||||
new TArray(
|
||||
[
|
||||
Type::getInt(),
|
||||
clone $array_atomic_type->type_params[1]
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$by_ref_type = Type::combineUnionTypes(
|
||||
$by_ref_type,
|
||||
new Type\Union(
|
||||
[
|
||||
new TArray(
|
||||
[
|
||||
Type::getInt(),
|
||||
clone $arg->value->inferredType
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ExpressionChecker::assignByRefParam(
|
||||
@ -750,8 +778,7 @@ class CallChecker
|
||||
}
|
||||
}
|
||||
|
||||
// for now stop when we encounter a packed argument
|
||||
if ($arg->unpack) {
|
||||
if (!$context->check_variables) {
|
||||
break;
|
||||
}
|
||||
|
||||
@ -762,20 +789,69 @@ class CallChecker
|
||||
$fq_class_name
|
||||
);
|
||||
|
||||
if ($context->check_variables) {
|
||||
if (self::checkFunctionArgumentType(
|
||||
$statements_checker,
|
||||
$arg->value->inferredType,
|
||||
$fleshed_out_type,
|
||||
$cased_method_id,
|
||||
$argument_offset,
|
||||
new CodeLocation($statements_checker->getSource(), $arg->value),
|
||||
$arg->value,
|
||||
$context,
|
||||
$function_param->by_ref
|
||||
) === false) {
|
||||
return false;
|
||||
if ($arg->unpack) {
|
||||
if ($arg->value->inferredType->hasArray()) {
|
||||
/** @var Type\Atomic\TArray|Type\Atomic\ObjectLike */
|
||||
$array_atomic_type = $arg->value->inferredType->getTypes()['array'];
|
||||
|
||||
if ($array_atomic_type instanceof Type\Atomic\ObjectLike) {
|
||||
$array_atomic_type = $array_atomic_type->getGenericArrayType();
|
||||
}
|
||||
|
||||
if (self::checkFunctionArgumentType(
|
||||
$statements_checker,
|
||||
$array_atomic_type->type_params[1],
|
||||
$fleshed_out_type,
|
||||
$cased_method_id,
|
||||
$argument_offset,
|
||||
new CodeLocation($statements_checker->getSource(), $arg->value),
|
||||
$arg->value,
|
||||
$context,
|
||||
$function_param->by_ref
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
} elseif ($arg->value->inferredType->isMixed()) {
|
||||
$codebase->analyzer->incrementMixedCount($statements_checker->getCheckedFilePath());
|
||||
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedArgument(
|
||||
'Argument ' . ($argument_offset + 1) . $cased_method_id
|
||||
. ' cannot be mixed, expecting array',
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidArgument(
|
||||
'Argument ' . ($argument_offset + 1) . $cased_method_id
|
||||
. ' expects array, ' . $arg->value->inferredType . ' provided',
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (self::checkFunctionArgumentType(
|
||||
$statements_checker,
|
||||
$arg->value->inferredType,
|
||||
$fleshed_out_type,
|
||||
$cased_method_id,
|
||||
$argument_offset,
|
||||
new CodeLocation($statements_checker->getSource(), $arg->value),
|
||||
$arg->value,
|
||||
$context,
|
||||
$function_param->by_ref
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} elseif ($function_param) {
|
||||
|
@ -4,6 +4,7 @@ namespace Psalm\Tests;
|
||||
class Php56Test extends TestCase
|
||||
{
|
||||
use Traits\FileCheckerValidCodeParseTestTrait;
|
||||
use Traits\FileCheckerInvalidCodeParseTestTrait;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
@ -84,6 +85,22 @@ class Php56Test extends TestCase
|
||||
$operators = [2, 3];
|
||||
echo add(1, ...$operators);',
|
||||
],
|
||||
'arrayPushArgumentUnpacking' => [
|
||||
'<?php
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
function a(): array {
|
||||
$a = [];
|
||||
$b = ["foo", "bar"];
|
||||
|
||||
$a[] = "foo";
|
||||
|
||||
array_push($a, ...$b);
|
||||
|
||||
return $a;
|
||||
}',
|
||||
],
|
||||
'arrayMergeArgumentUnpacking' => [
|
||||
'<?php
|
||||
$a = [[1, 2]];
|
||||
@ -207,4 +224,23 @@ class Php56Test extends TestCase
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function providerFileCheckerInvalidCodeParse()
|
||||
{
|
||||
return [
|
||||
'arrayPushArgumentUnpackingWithBadArg' => [
|
||||
'<?php
|
||||
$a = [];
|
||||
$b = "hello";
|
||||
|
||||
$a[] = "foo";
|
||||
|
||||
array_push($a, ...$b);',
|
||||
'error_message' => 'InvalidArgument',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user