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

Improve handling of variadic parameters

This commit is contained in:
Brown 2019-04-09 13:58:49 -04:00
parent 54c76be63b
commit 1348d634ed
9 changed files with 142 additions and 142 deletions

View File

@ -435,7 +435,18 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
}
}
$context->vars_in_scope['$' . $function_param->name] = $param_type;
$var_type = $param_type;
if ($function_param->is_variadic) {
$var_type = new Type\Union([
new Type\Atomic\TArray([
Type::getInt(),
$param_type,
]),
]);
}
$context->vars_in_scope['$' . $function_param->name] = $var_type;
$context->vars_possibly_in_scope['$' . $function_param->name] = true;
if ($context->collect_references && $function_param->location) {
@ -1019,7 +1030,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
$storage = $this->getFunctionLikeStorage($statements_analyzer);
foreach ($storage->params as $i => $param) {
if ($param->by_ref && isset($context->vars_in_scope['$' . $param->name])) {
if ($param->by_ref && isset($context->vars_in_scope['$' . $param->name]) && !$param->is_variadic) {
$actual_type = $context->vars_in_scope['$' . $param->name];
$param_out_type = $param->type;

View File

@ -665,28 +665,10 @@ class CallAnalyzer
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(
[
new TArray(
[
Type::getInt(),
clone $array_atomic_type->type_params[1]
]
),
]
)
);
}
$by_ref_type = Type::combineUnionTypes(
$by_ref_type,
clone $arg->value->inferredType
);
} else {
$by_ref_type = Type::combineUnionTypes(
$by_ref_type,
@ -1325,17 +1307,19 @@ class CallAnalyzer
if ($last_param) {
if ($argument_offset < count($function_params)) {
$by_ref_type = $function_params[$argument_offset]->type;
if ($by_ref_type && $by_ref_type->isNullable()) {
$check_null_ref = false;
}
if (isset($function_storage->param_out_types[$argument_offset])) {
$by_ref_out_type = $function_storage->param_out_types[$argument_offset];
}
$function_param = $function_params[$argument_offset];
} else {
$by_ref_type = $last_param->type;
$function_param = $last_param;
}
$by_ref_type = $function_param->type;
if (isset($function_storage->param_out_types[$argument_offset])) {
$by_ref_out_type = $function_storage->param_out_types[$argument_offset];
}
if ($by_ref_type && $by_ref_type->isNullable()) {
$check_null_ref = false;
}
if ($template_types && $by_ref_type) {
@ -1364,6 +1348,15 @@ class CallAnalyzer
$by_ref_type = $original_by_ref_type;
}
}
if ($by_ref_type && $function_param->is_variadic && $arg->unpack) {
$by_ref_type = new Type\Union([
new Type\Atomic\TArray([
Type::getInt(),
$by_ref_type,
]),
]);
}
}
$by_ref_type = $by_ref_type ?: Type::getMixed();
@ -1406,20 +1399,6 @@ class CallAnalyzer
if ($function_param && $function_param->type) {
$param_type = clone $function_param->type;
if ($function_param->is_variadic) {
if (!$param_type->hasArray()) {
return;
}
$array_atomic_type = $param_type->getTypes()['array'];
if (!$array_atomic_type instanceof TArray) {
return;
}
$param_type = clone $array_atomic_type->type_params[1];
}
if ($existing_generic_params) {
$empty_generic_params = [];
@ -1449,11 +1428,9 @@ class CallAnalyzer
if ($arg_type->hasArray()) {
/** @var Type\Atomic\TArray|Type\Atomic\ObjectLike */
$array_atomic_type = $arg_type->getTypes()['array'];
if ($array_atomic_type instanceof Type\Atomic\ObjectLike) {
$array_atomic_type = $array_atomic_type->getGenericArrayType();
}
$arg_type_param = $array_atomic_type->type_params[1];
} else {
$arg_type_param = Type::getMixed();
@ -1480,32 +1457,6 @@ class CallAnalyzer
);
if ($arg->unpack) {
if ($arg_type->hasArray()) {
/** @var Type\Atomic\TArray|Type\Atomic\ObjectLike */
$array_atomic_type = $arg_type->getTypes()['array'];
if ($array_atomic_type instanceof Type\Atomic\ObjectLike) {
$array_atomic_type = $array_atomic_type->getGenericArrayType();
}
if (self::checkFunctionArgumentType(
$statements_analyzer,
$array_atomic_type->type_params[1],
$fleshed_out_type,
$cased_method_id,
$argument_offset,
new CodeLocation($statements_analyzer->getSource(), $arg->value),
$arg->value,
$context,
$function_param->by_ref,
true
) === false) {
return false;
}
return;
}
if ($arg_type->hasMixed()) {
if (!$context->collect_initializations
&& !$context->collect_mutations
@ -1531,22 +1482,33 @@ class CallAnalyzer
return;
}
foreach ($arg_type->getTypes() as $atomic_type) {
if (!$atomic_type->isIterable($codebase)) {
if (IssueBuffer::accepts(
new InvalidArgument(
'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id
. ' expects array, ' . $atomic_type->getId() . ' provided',
new CodeLocation($statements_analyzer->getSource(), $arg->value)
),
$statements_analyzer->getSuppressedIssues()
)) {
return false;
if ($arg_type->hasArray()) {
/** @var Type\Atomic\TArray|Type\Atomic\ObjectLike */
$array_atomic_type = $arg_type->getTypes()['array'];
if ($array_atomic_type instanceof Type\Atomic\ObjectLike) {
$array_atomic_type = $array_atomic_type->getGenericArrayType();
}
$arg_type = $array_atomic_type->type_params[1];
} else {
foreach ($arg_type->getTypes() as $atomic_type) {
if (!$atomic_type->isIterable($codebase)) {
if (IssueBuffer::accepts(
new InvalidArgument(
'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id
. ' expects array, ' . $atomic_type->getId() . ' provided',
new CodeLocation($statements_analyzer->getSource(), $arg->value)
),
$statements_analyzer->getSuppressedIssues()
)) {
return false;
}
}
}
}
return;
return;
}
}
if (self::checkFunctionArgumentType(
@ -1559,7 +1521,8 @@ class CallAnalyzer
$arg->value,
$context,
$function_param->by_ref,
$function_param->is_variadic
$function_param->is_variadic,
$arg->unpack
) === false) {
return false;
}
@ -2078,8 +2041,9 @@ class CallAnalyzer
CodeLocation $code_location,
PhpParser\Node\Expr $input_expr,
Context $context,
$by_ref = false,
$variadic = false
bool $by_ref = false,
bool $variadic = false,
bool $unpack = false
) {
if ($param_type->hasMixed()) {
return null;
@ -2446,7 +2410,7 @@ class CallAnalyzer
if ($type_match_found
&& !$param_type->hasMixed()
&& !$param_type->from_docblock
&& !$variadic
&& !($variadic xor $unpack)
&& !$by_ref
&& $cased_method_id !== 'echo'
) {
@ -2471,6 +2435,15 @@ class CallAnalyzer
$context->removeVarFromConflictingClauses($var_id, null, $statements_analyzer);
if ($unpack) {
$input_type = new Type\Union([
new TArray([
Type::getInt(),
$input_type
]),
]);
}
$context->vars_in_scope[$var_id] = $input_type;
}
}

View File

@ -2163,23 +2163,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
if ($is_nullable) {
$param_type->addType(new Type\Atomic\TNull);
}
if ($param->variadic) {
$param_type = new Type\Union([
new Type\Atomic\TArray([
Type::getInt(),
$param_type,
]),
]);
}
}
} elseif ($param->variadic) {
$param_type = new Type\Union([
new Type\Atomic\TArray([
Type::getInt(),
Type::getMixed(),
]),
]);
}
$is_optional = $param->default !== null;
@ -2338,13 +2322,15 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
$storage->template_types ?: []
);
if ($docblock_param_variadic) {
$new_param_type = new Type\Union([
new Type\Atomic\TArray([
Type::getInt(),
$new_param_type,
]),
]);
if (!$docblock_param_variadic && $storage_param->is_variadic && $new_param_type->hasArray()) {
/** @var Type\Atomic\TArray|Type\Atomic\ObjectLike */
$array_type = $new_param_type->getTypes()['array'];
if ($array_type instanceof Type\Atomic\ObjectLike) {
$new_param_type = $array_type->getGenericValueType();
} else {
$new_param_type = $array_type->type_params[1];
}
}
$existing_param_type_nullable = $storage_param->is_nullable;

View File

@ -585,10 +585,16 @@ class AnnotationTest extends TestCase
$f = foo();
if ($f) {}',
],
'spreadOperatorArrayAnnotation' => [
'spreadOperatorAnnotation' => [
'<?php
/** @param string[] $s */
function foo(string ...$s) : void {}',
function foo(string ...$s) : void {}
/** @param string ...$s */
function bar(string ...$s) : void {}
foo("hello", "goodbye");
bar("hello", "goodbye");
foo(...["hello", "goodbye"]);
bar(...["hello", "goodbye"]);',
],
'valueReturnType' => [
'<?php
@ -1341,6 +1347,20 @@ class AnnotationTest extends TestCase
(new X())->boo([1, 2]);',
'error_message' => 'InvalidScalarArgument',
],
'spreadOperatorArrayAnnotationBadArg' => [
'<?php
/** @param string[] $s */
function foo(string ...$s) : void {}
foo(5);',
'error_message' => 'InvalidScalarArgument',
],
'spreadOperatorArrayAnnotationBadSpreadArg' => [
'<?php
/** @param string[] $s */
function foo(string ...$s) : void {}
foo(...[5]);',
'error_message' => 'InvalidScalarArgument',
],
];
}
}

View File

@ -1072,7 +1072,7 @@ class TemporaryUpdateTest extends \Psalm\Tests\TestCase
],
[
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
function variadic_arguments(string $_foo, ...$bars ) : void {}
function variadic_arguments(string $_foo, string ...$bars ) : void {}
function foo(string $baz, string $qux) : void {
variadic_arguments(
@ -1082,7 +1082,7 @@ class TemporaryUpdateTest extends \Psalm\Tests\TestCase
}',
],
],
'error_positions' => [[238, 238], []],
'error_positions' => [[79, 238, 238], []],
],
'fixClassRef' => [
[

View File

@ -848,6 +848,7 @@ class MethodSignatureTest extends TestCase
public function f($f): void {}
}',
'error_message' => 'MethodSignatureMismatch',
['MoreSpecificImplementedParamType']
],
];
}

View File

@ -85,21 +85,17 @@ class Php56Test extends TestCase
$operators = [2, 3];
echo add(1, ...$operators);',
],
'arrayPushArgumentUnpacking' => [
'arrayPushArgumentUnpackingWithGoodArg' => [
'<?php
/**
* @return string[]
*/
function a(): array {
$a = [];
$b = ["foo", "bar"];
$a = [];
$b = ["foo", "bar"];
$a[] = "foo";
$a[] = "foo";
array_push($a, ...$b);
return $a;
}',
array_push($a, ...$b);',
'assertions' => [
'$a' => 'non-empty-array<int, string>',
],
],
'arrayMergeArgumentUnpacking' => [
'<?php
@ -154,11 +150,23 @@ class Php56Test extends TestCase
function Foo(string $a, string ...$b) : void {}
/** @return array<int, string> */
function Baz(string ...$b) {
Foo(...$b);
return $b;
function Baz(string ...$c) {
Foo(...$c);
return $c;
}',
],
'unpackByRefArg' => [
'<?php
function example (int &...$x): void {}
$y = 0;
example($y);
$z = [0];
example(...$z);',
'assertions' => [
'$y' => 'int',
'$z' => 'array<int, int>',
],
],
'exponentiation' => [
'<?php
$a = 2;
@ -237,8 +245,10 @@ class Php56Test extends TestCase
function foo(int ...$is) : void {}
$arr = [1, 2, 3, 4];
foo(...$arr);
foo(...$arr);',
'assertions' => [
'$arr' => 'array<int, int>'
],
],
'iterableSplat' => [
'<?php

View File

@ -665,7 +665,7 @@ class TemplateTest extends TestCase
[1, 2],
];
$a = splat_proof(... $foo);',
$a = splat_proof(...$foo);',
'assertions' => [
'$a' => 'array<int, int>',
],

View File

@ -19,13 +19,12 @@ class VariadicTest extends TestCase
'somefile.php',
'<?php
/**
* @param array<int, int> $a_list
* @param int ...$a_list
* @return void
*/
function f(int ...$a_list) {
}
f(1, 2, "3");
'
f(1, 2, "3");'
);
$this->analyzeFile('somefile.php', new Context());
@ -42,7 +41,7 @@ class VariadicTest extends TestCase
/**
* @param mixed $req
* @param mixed $opt
* @param array<int, mixed> $params
* @param mixed ...$params
* @return array<mixed>
*/
function f($req, $opt = null, ...$params) {
@ -65,7 +64,7 @@ class VariadicTest extends TestCase
'variadicArray' => [
'<?php
/**
* @param array<int, int> $a_list
* @param int ...$a_list
* @return array<int, int>
*/
function f(int ...$a_list) {