1
0
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:
orklah 2022-10-18 00:07:13 +02:00 committed by GitHub
commit 48b8efde99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 137 additions and 5 deletions

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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> */

View File

@ -259,7 +259,6 @@ class EmptyTest extends TestCase
while (!empty($needle)) {
$key = key($needle);
if ($key === null) continue;
$val = $needle[$key];
unset($needle[$key]);