mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
Merge pull request #8584 from boesing/feature/8521
Enhance type detection for internal php functions `key`, `current`, `end` and `reset`
This commit is contained in:
commit
48b8efde99
@ -19,9 +19,19 @@ use UnexpectedValueException;
|
||||
|
||||
use function array_merge;
|
||||
use function array_shift;
|
||||
use function in_array;
|
||||
|
||||
class ArrayPointerAdjustmentReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
{
|
||||
/**
|
||||
* These functions are already handled by the CoreGenericFunctions stub
|
||||
*/
|
||||
const IGNORE_FUNCTION_IDS_FOR_FALSE_RETURN_TYPE = [
|
||||
'reset',
|
||||
'end',
|
||||
'current',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array<lowercase-string>
|
||||
*/
|
||||
@ -82,7 +92,7 @@ class ArrayPointerAdjustmentReturnTypeProvider implements FunctionReturnTypeProv
|
||||
|
||||
if ($value_type->isEmpty()) {
|
||||
$value_type = Type::getFalse();
|
||||
} elseif (($function_id !== 'reset' && $function_id !== 'end') || !$definitely_has_items) {
|
||||
} elseif (!$definitely_has_items || self::isFunctionAlreadyHandledByStub($function_id)) {
|
||||
$value_type->addType(new TFalse);
|
||||
|
||||
$codebase = $statements_source->getCodebase();
|
||||
@ -102,4 +112,9 @@ class ArrayPointerAdjustmentReturnTypeProvider implements FunctionReturnTypeProv
|
||||
|
||||
return $value_type;
|
||||
}
|
||||
|
||||
private static function isFunctionAlreadyHandledByStub(string $function_id): bool
|
||||
{
|
||||
return !in_array($function_id, self::IGNORE_FUNCTION_IDS_FOR_FALSE_RETURN_TYPE, true);
|
||||
}
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ function array_flip(array $array)
|
||||
*
|
||||
* @param TArray $array
|
||||
*
|
||||
* @return (TArray is array<empty, empty> ? null : TKey|null)
|
||||
* @return (TArray is array<empty,empty> ? null : (TArray is non-empty-list ? int<0,max> : (TArray is non-empty-array ? TKey : TKey|null)))
|
||||
* @psalm-pure
|
||||
* @psalm-ignore-nullable-return
|
||||
*/
|
||||
@ -144,6 +144,46 @@ function key($array)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-template TKey as array-key
|
||||
* @psalm-template TValue
|
||||
* @psalm-template TArray as array<TKey, TValue>
|
||||
*
|
||||
* @param TArray $array
|
||||
*
|
||||
* @return (TArray is array<empty,empty> ? false : (TArray is non-empty-array ? TValue : TValue|false))
|
||||
* @psalm-pure
|
||||
*/
|
||||
function current($array)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-template TKey as array-key
|
||||
* @psalm-template TValue
|
||||
* @psalm-template TArray as array<TKey, TValue>
|
||||
*
|
||||
* @param TArray $array
|
||||
*
|
||||
* @return (TArray is array<empty,empty> ? false : (TArray is non-empty-array ? TValue : TValue|false))
|
||||
*/
|
||||
function reset(&$array)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-template TKey as array-key
|
||||
* @psalm-template TValue
|
||||
* @psalm-template TArray as array<TKey, TValue>
|
||||
*
|
||||
* @param TArray $array
|
||||
*
|
||||
* @return (TArray is array<empty,empty> ? false : (TArray is non-empty-array ? TValue : TValue|false))
|
||||
*/
|
||||
function end(&$array)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-template TKey as array-key
|
||||
* @psalm-template TArray as array<TKey, mixed>
|
||||
|
@ -1085,7 +1085,7 @@ class ArrayFunctionCallTest extends TestCase
|
||||
$a = ["one" => 1, "two" => 3];
|
||||
$b = key($a);',
|
||||
'assertions' => [
|
||||
'$b' => 'null|string',
|
||||
'$b' => 'string',
|
||||
],
|
||||
],
|
||||
'keyEmptyArray' => [
|
||||
@ -1100,12 +1100,90 @@ class ArrayFunctionCallTest extends TestCase
|
||||
'<?php
|
||||
/**
|
||||
* @param non-empty-array $arr
|
||||
* @return null|array-key
|
||||
* @return array-key
|
||||
*/
|
||||
function foo(array $arr) {
|
||||
return key($arr);
|
||||
}',
|
||||
],
|
||||
'current' => [
|
||||
'<?php
|
||||
$a = ["one" => 1, "two" => 3];
|
||||
$b = current($a);',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'currentEmptyArray' => [
|
||||
'<?php
|
||||
$a = [];
|
||||
$b = current($a);',
|
||||
'assertions' => [
|
||||
'$b' => 'false',
|
||||
],
|
||||
],
|
||||
'currentNonEmptyArray' => [
|
||||
'<?php
|
||||
/**
|
||||
* @param non-empty-array<int> $arr
|
||||
* @return int
|
||||
*/
|
||||
function foo(array $arr) {
|
||||
return current($arr);
|
||||
}',
|
||||
],
|
||||
'reset' => [
|
||||
'<?php
|
||||
$a = ["one" => 1, "two" => 3];
|
||||
$b = reset($a);',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'resetEmptyArray' => [
|
||||
'<?php
|
||||
$a = [];
|
||||
$b = reset($a);',
|
||||
'assertions' => [
|
||||
'$b' => 'false',
|
||||
],
|
||||
],
|
||||
'resetNonEmptyArray' => [
|
||||
'<?php
|
||||
/**
|
||||
* @param non-empty-array<int> $arr
|
||||
* @return int
|
||||
*/
|
||||
function foo(array $arr) {
|
||||
return reset($arr);
|
||||
}',
|
||||
],
|
||||
'end' => [
|
||||
'<?php
|
||||
$a = ["one" => 1, "two" => 3];
|
||||
$b = end($a);',
|
||||
'assertions' => [
|
||||
'$b' => 'int',
|
||||
],
|
||||
],
|
||||
'endEmptyArray' => [
|
||||
'<?php
|
||||
$a = [];
|
||||
$b = end($a);',
|
||||
'assertions' => [
|
||||
'$b' => 'false',
|
||||
],
|
||||
],
|
||||
'endNonEmptyArray' => [
|
||||
'<?php
|
||||
/**
|
||||
* @param non-empty-array<int> $arr
|
||||
* @return int
|
||||
*/
|
||||
function foo(array $arr) {
|
||||
return end($arr);
|
||||
}',
|
||||
],
|
||||
'arrayKeyFirst' => [
|
||||
'<?php
|
||||
/** @return array<string, int> */
|
||||
|
@ -259,7 +259,6 @@ class EmptyTest extends TestCase
|
||||
|
||||
while (!empty($needle)) {
|
||||
$key = key($needle);
|
||||
if ($key === null) continue;
|
||||
$val = $needle[$key];
|
||||
unset($needle[$key]);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user