1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

array_column return type inference (#787)

* Exception message typo fixed

* array_column return type inference

It takes a safe approach and only tries to be smart when array row type
is definite single shape

* cs fix
This commit is contained in:
Bruce Weirdan 2018-06-01 04:14:06 +03:00 committed by Matthew Brown
parent c31d963918
commit 6ecea35837
3 changed files with 98 additions and 1 deletions

View File

@ -195,6 +195,74 @@ class FunctionChecker extends FunctionLikeChecker
break;
case 'array_column':
$row_shape = null;
// calculate row shape
if (isset($call_args[0]->value->inferredType)
&& $call_args[0]->value->inferredType->isSingle()
&& $call_args[0]->value->inferredType->hasArray()
) {
$input_array = $call_args[0]->value->inferredType->getTypes()['array'];
if ($input_array instanceof Type\Atomic\ObjectLike) {
$row_type = $input_array->getGenericArrayType()->type_params[1];
if ($row_type->isSingle() && $row_type->hasArray()) {
$row_shape = $row_type->getTypes()['array'];
}
} elseif ($input_array instanceof Type\Atomic\TArray) {
$row_type = $input_array->type_params[1];
if ($row_type->isSingle() && $row_type->hasArray()) {
$row_shape = $row_type->getTypes()['array'];
}
}
}
$value_column_name = null;
// calculate value column name
if (isset($call_args[1]->value->inferredType)) {
$value_column_name_arg= $call_args[1]->value->inferredType;
if ($value_column_name_arg->isSingleIntLiteral()) {
$value_column_name = $value_column_name_arg->getSingleIntLiteral();
} elseif ($value_column_name_arg->isSingleStringLiteral()) {
$value_column_name = $value_column_name_arg->getSingleStringLiteral();
}
}
$key_column_name = null;
// calculate key column name
if (isset($call_args[2]->value->inferredType)) {
$key_column_name_arg = $call_args[2]->value->inferredType;
if ($key_column_name_arg->isSingleIntLiteral()) {
$key_column_name = $key_column_name_arg->getSingleIntLiteral();
} elseif ($key_column_name_arg->isSingleStringLiteral()) {
$key_column_name = $key_column_name_arg->getSingleStringLiteral();
}
}
$result_key_type = Type::getMixed();
$result_element_type = null;
// calculate results
if ($row_shape instanceof Type\Atomic\ObjectLike) {
if ((null !== $value_column_name) && isset($row_shape->properties[$value_column_name])) {
$result_element_type = $row_shape->properties[$value_column_name];
} else {
$result_element_type = Type::getMixed();
}
if ((null !== $key_column_name) && isset($row_shape->properties[$key_column_name])) {
$result_key_type = $row_shape->properties[$key_column_name];
}
}
if ($result_element_type) {
return new Type\Union([
new Type\Atomic\TArray([
$result_key_type,
$result_element_type
])
]);
}
break;
case 'abs':
if (isset($call_args[0]->value)) {
$first_arg = $call_args[0]->value;

View File

@ -963,7 +963,7 @@ class Union
public function getSingleIntLiteral()
{
if (count($this->types) !== 1 || count($this->literal_int_types) !== 1) {
throw new \InvalidArgumentException("Not a string literal");
throw new \InvalidArgumentException("Not an int literal");
}
return reset($this->literal_int_types)->value;

View File

@ -643,6 +643,35 @@ class FunctionCallTest extends TestCase
return array_filter(iterator_to_array(generator()), $filter);
}'
],
'arrayColumnInference' => [
'<?php
function makeMixedArray(): array { return []; }
/** @return array<array<int,bool>> */
function makeGenericArray(): array { return []; }
/** @return array<array{0:string}> */
function makeShapeArray(): array { return []; }
/** @return array<array{0:string}|int> */
function makeUnionArray(): array { return []; }
$a = array_column([[1], [2], [3]], 0);
$b = array_column([["a" => 1], ["a" => 2], ["a" => 3]], "a");
$c = array_column([["k" => "a", "v" => 1], ["k" => "b", "v" => 2]], "v", "k");
$d = array_column([], 0);
$e = array_column(makeMixedArray(), 0);
$f = array_column(makeGenericArray(), 0);
$g = array_column(makeShapeArray(), 0);
$h = array_column(makeUnionArray(), 0);
',
'assertions' => [
'$a' => 'array<mixed, int>',
'$b' => 'array<mixed, int>',
'$c' => 'array<string, int>',
'$d' => 'array<mixed, mixed>',
'$e' => 'array<mixed, mixed>',
'$f' => 'array<mixed, mixed>',
'$g' => 'array<mixed, string>',
'$h' => 'array<mixed, mixed>',
],
],
];
}