2020-01-31 19:58:02 +01:00
< ? php
namespace Psalm\Tests ;
use const DIRECTORY_SEPARATOR ;
class ArrayFunctionCallTest extends TestCase
{
use Traits\InvalidCodeAnalysisTestTrait ;
use Traits\ValidCodeAnalysisTestTrait ;
/**
* @ return iterable < string , array { string , assertions ? : array < string , string > , error_levels ? : string []} >
*/
2020-09-12 17:24:05 +02:00
public function providerValidCodeParse () : iterable
2020-01-31 19:58:02 +01:00
{
return [
'arrayFilter' => [
' < ? php
2020-09-14 19:31:53 +02:00
$d = array_filter ([ " a " => rand ( 0 , 10 ), " b " => rand ( 0 , 10 ), " c " => null ]);
2020-01-31 19:58:02 +01:00
$e = array_filter (
2020-09-14 19:31:53 +02:00
[ " a " => rand ( 0 , 10 ), " b " => rand ( 0 , 10 ), " c " => null ],
2020-01-31 19:58:02 +01:00
function ( ? int $i ) : bool {
return true ;
}
); ' ,
'assertions' => [
2021-10-19 20:58:19 +02:00
'$d' => 'array{a?: int<1, 10>, b?: int<1, 10>}' ,
2021-08-21 00:52:16 +02:00
'$e' => 'array<string, int<0, 10>|null>' ,
2020-01-31 19:58:02 +01:00
],
],
2021-10-16 04:40:41 +02:00
'positiveIntArrayFilter' => [
' < ? php
/**
* @ param numeric $a
* @ param positive - int $positiveOne
* @ param int < 0 , 12 > $d
* @ param int < 1 , 12 > $f
* @ psalm - return array { a : numeric , b ? : int , c : positive - int , d ? : int < 0 , 12 > , f : int < 1 , 12 > }
*/
function makeAList ( $a , int $anyInt , int $positiveOne , int $d , int $f ) : array {
return array_filter ([ " a " => " 1 " , " b " => $anyInt , " c " => $positiveOne , " d " => $d , " f " => $f ]);
} '
],
2020-01-31 19:58:02 +01:00
'arrayFilterAdvanced' => [
' < ? php
$f = array_filter ([ " a " => 5 , " b " => 12 , " c " => null ], function ( ? int $val , string $key ) : bool {
return true ;
}, ARRAY_FILTER_USE_BOTH );
$g = array_filter ([ " a " => 5 , " b " => 12 , " c " => null ], function ( string $val ) : bool {
return true ;
}, ARRAY_FILTER_USE_KEY );
$bar = " bar " ;
$foo = [
$bar => function () : string {
return " baz " ;
},
];
$foo = array_filter (
$foo ,
function ( string $key ) : bool {
return $key === " bar " ;
},
ARRAY_FILTER_USE_KEY
); ' ,
'assertions' => [
'$f' => 'array<string, int|null>' ,
'$g' => 'array<string, int|null>' ,
],
],
'arrayFilterIgnoreNullable' => [
' < ? php
class A {
/**
* @ return array < int , self | null >
*/
public function getRows () : array {
return [ new self , null ];
}
public function filter () : void {
$arr = array_filter (
static :: getRows (),
function ( self $row ) : bool {
return is_a ( $row , static :: class );
}
);
}
} ' ,
'assertions' => [],
'error_levels' => [ 'PossiblyInvalidArgument' ],
],
'arrayFilterAllowTrim' => [
' < ? php
$foo = array_filter ([ " hello " , " " ], " trim " ); ' ,
],
'arrayFilterAllowNull' => [
' < ? php
function foo () : array {
return array_filter (
array_map (
/** @return null */
function ( int $arg ) {
return null ;
},
[ 1 , 2 , 3 ]
)
);
} ' ,
],
'arrayFilterNamedFunction' => [
' < ? php
/**
* @ param array < int , DateTimeImmutable | null > $a
* @ return array < int , DateTimeImmutable >
*/
function foo ( array $a ) : array {
return array_filter ( $a , " is_object " );
} ' ,
],
2020-07-31 20:56:29 +02:00
'arrayFilterFleshOutType' => [
' < ? php
class Baz {
public const STATUS_FOO = " foo " ;
public const STATUS_BAR = " bar " ;
public const STATUS_QUX = " qux " ;
/**
* @ psalm - param self :: STATUS_ * $role
*/
public static function isStatus ( string $role ) : bool
{
return ! \in_array ( $role , [ self :: STATUS_BAR , self :: STATUS_QUX ], true );
}
}
/** @psalm-var array<Baz::STATUS_*> $statusList */
$statusList = [ Baz :: STATUS_FOO , Baz :: STATUS_QUX ];
$statusList = array_filter ( $statusList , [ Baz :: class , " isStatus " ]); '
],
2020-04-18 19:02:55 +02:00
'arrayKeysNonEmpty' => [
2020-01-31 19:58:02 +01:00
' < ? php
$a = array_keys ([ " a " => 1 , " b " => 2 ]); ' ,
'assertions' => [
2020-04-18 19:02:55 +02:00
'$a' => 'non-empty-list<string>' ,
2020-01-31 19:58:02 +01:00
],
],
'arrayKeysMixed' => [
' < ? php
/** @var array */
$b = [ " a " => 5 ];
$a = array_keys ( $b ); ' ,
'assertions' => [
'$a' => 'list<array-key>' ,
],
'error_levels' => [ 'MixedArgument' ],
],
'arrayValues' => [
' < ? php
$b = array_values ([ " a " => 1 , " b " => 2 ]);
$c = array_values ([ " a " => " hello " , " b " => " jello " ]); ' ,
'assertions' => [
'$b' => 'non-empty-list<int>' ,
'$c' => 'non-empty-list<string>' ,
],
],
'arrayCombine' => [
' < ? php
$c = array_combine ([ " a " , " b " , " c " ], [ 1 , 2 , 3 ]); ' ,
'assertions' => [
2021-03-15 02:18:19 +01:00
'$c' => 'false|non-empty-array<string, int>' ,
2020-01-31 19:58:02 +01:00
],
2021-03-16 18:43:49 +01:00
'error_levels' => [],
'7.4' ,
2020-01-31 19:58:02 +01:00
],
2021-03-16 18:43:49 +01:00
'arrayCombinePHP8' => [
' < ? php
$c = array_combine ([ " a " , " b " ], [ 1 , 2 , 3 ]); ' ,
'assertions' => [
'$c' => 'non-empty-array<string, int>' ,
],
'error_levels' => [],
'8.0' ,
],
'arrayCombineNotMatching' => [
2020-01-31 19:58:02 +01:00
' < ? php
$c = array_combine ([ " a " , " b " ], [ 1 , 2 , 3 ]); ' ,
'assertions' => [
2021-03-15 02:18:19 +01:00
'$c' => 'false|non-empty-array<string, int>' ,
2020-01-31 19:58:02 +01:00
],
2021-03-16 18:43:49 +01:00
'error_levels' => [],
'7.4' ,
],
'arrayCombineDynamicParams' => [
' < ? php
/** @return array<string> */
function getStrings () : array { return []; }
/** @return array<int> */
function getInts () : array { return []; }
$c = array_combine ( getStrings (), getInts ()); ' ,
'assertions' => [
'$c' => 'array<string, int>|false' ,
],
2020-01-31 19:58:02 +01:00
],
2020-05-20 02:10:01 +02:00
'arrayMergeIntArrays' => [
2020-01-31 19:58:02 +01:00
' < ? php
$d = array_merge ([ " a " , " b " , " c " ], [ 1 , 2 , 3 ]); ' ,
'assertions' => [
'$d' => 'array{0: string, 1: string, 2: string, 3: int, 4: int, 5: int}' ,
],
],
2020-05-20 02:10:01 +02:00
'arrayMergePossiblyUndefined' => [
' < ? php
/**
* @ param array { host ? : string } $opts
* @ return array { host : string | int }
*/
function b ( array $opts ) : array {
return array_merge ([ " host " => 5 ], $opts );
} ' ,
],
2020-09-14 19:06:15 +02:00
'arrayMergeListResultWithArray' => [
2020-01-31 19:58:02 +01:00
' < ? php
/**
2020-09-14 19:06:15 +02:00
* @ param array < int , string > $list
2020-01-31 19:58:02 +01:00
* @ return list < string >
*/
2020-09-14 19:06:15 +02:00
function bar ( array $list ) : array {
2020-01-31 19:58:02 +01:00
return array_merge ( $list , [ " test " ]);
2020-09-14 19:06:15 +02:00
} ' ,
],
'arrayMergeListResultWithList' => [
' < ? php
2020-01-31 19:58:02 +01:00
/**
2020-09-14 19:06:15 +02:00
* @ param list < string > $list
2020-01-31 19:58:02 +01:00
* @ return list < string >
*/
2020-09-14 19:06:15 +02:00
function foo ( array $list ) : array {
2020-01-31 19:58:02 +01:00
return array_merge ( $list , [ " test " ]);
} ' ,
],
2021-10-15 12:06:19 +02:00
'arrayMergeTypes' => [
' < ? php
/**
* @ psalm - type A = array { name : string }
* @ psalm - type B = array { age : int }
*/
class Demo
{
/**
* @ param A $a
* @ param B $b
* @ return A & B
*/
public function merge ( $a , $b ) : array
{
return array_merge ( $a , $b );
}
} ' ,
],
2021-10-15 12:06:19 +02:00
'arrayReplaceIntArrays' => [
' < ? php
$d = array_replace ([ " a " , " b " , " c " ], [ 1 , 2 , 3 ]); ' ,
'assertions' => [
'$d' => 'array{0: string, 1: string, 2: string, 3: int, 4: int, 5: int}' ,
],
],
'arrayReplacePossiblyUndefined' => [
' < ? php
/**
* @ param array { host ? : string } $opts
* @ return array { host : string | int }
*/
function b ( array $opts ) : array {
return array_replace ([ " host " => 5 ], $opts );
} ' ,
],
'arrayReplaceListResultWithArray' => [
' < ? php
/**
* @ param array < int , string > $list
* @ return list < string >
*/
function bar ( array $list ) : array {
return array_replace ( $list , [ " test " ]);
} ' ,
],
'arrayReplaceListResultWithList' => [
' < ? php
/**
* @ param list < string > $list
* @ return list < string >
*/
function foo ( array $list ) : array {
return array_replace ( $list , [ " test " ]);
} ' ,
],
2021-10-15 12:06:19 +02:00
'arrayReplaceTypes' => [
' < ? php
/**
* @ psalm - type A = array { name : string }
* @ psalm - type B = array { age : int }
*/
class Demo
{
/**
* @ param A $a
* @ param B $b
* @ return A & B
*/
public function replace ( $a , $b ) : array
{
return array_replace ( $a , $b );
}
} ' ,
],
2020-01-31 19:58:02 +01:00
'arrayReverseDontPreserveKey' => [
' < ? php
$d = array_reverse ([ " a " , " b " , 1 , " d " => 4 ]); ' ,
'assertions' => [
'$d' => 'non-empty-array<int|string, int|string>' ,
],
],
'arrayReverseDontPreserveKeyExplicitArg' => [
' < ? php
$d = array_reverse ([ " a " , " b " , 1 , " d " => 4 ], false ); ' ,
'assertions' => [
'$d' => 'non-empty-array<int|string, int|string>' ,
],
],
'arrayReversePreserveKey' => [
' < ? php
$d = array_reverse ([ " a " , " b " , 1 ], true ); ' ,
'assertions' => [
'$d' => 'non-empty-array<int, int|string>' ,
],
],
'arrayDiff' => [
' < ? php
$d = array_diff ([ " a " => 5 , " b " => 12 ], [ 5 ]); ' ,
'assertions' => [
'$d' => 'array<string, int>' ,
],
],
'arrayDiffIsVariadic' => [
' < ? php
array_diff ([], [], [], [], []); ' ,
'assertions' => [],
],
'arrayDiffKeyIsVariadic' => [
' < ? php
array_diff_key ([], [], [], [], []); ' ,
'assertions' => [],
],
'arrayDiffAssoc' => [
' < ? php
/**
* @ var array < string , int > $a
* @ var array $b
* @ var array $c
*/
$r = array_diff_assoc ( $a , $b , $c ); ' ,
'assertions' => [
'$r' => 'array<string, int>' ,
],
],
'arrayPopMixed' => [
' < ? php
/** @var mixed */
$b = [ " a " => 5 , " c " => 6 ];
$a = array_pop ( $b ); ' ,
'assertions' => [
'$a' => 'mixed' ,
'$b' => 'mixed' ,
],
'error_levels' => [ 'MixedAssignment' , 'MixedArgument' ],
],
'arrayPopNonEmpty' => [
' < ? php
/** @var array<string, int> */
$a = [ " a " => 5 , " b " => 6 , " c " => 7 ];
$b = 5 ;
if ( $a ) {
$b = array_pop ( $a );
}
$c = array_pop ( $a ); ' ,
'assertions' => [
'$b' => 'int' ,
'$c' => 'int|null' ,
],
],
'arrayPopNonEmptyAfterIsset' => [
' < ? php
/** @var array<string, int> */
$a = [ " a " => 5 , " b " => 6 , " c " => 7 ];
$b = 5 ;
if ( isset ( $a [ " a " ])) {
$b = array_pop ( $a );
} ' ,
'assertions' => [
'$b' => 'int' ,
],
],
'arrayPopNonEmptyAfterCount' => [
' < ? php
/** @var array<string, int> */
$a = [ " a " => 5 , " b " => 6 , " c " => 7 ];
$b = 5 ;
if ( count ( $a )) {
$b = array_pop ( $a );
} ' ,
'assertions' => [
'$b' => 'int' ,
],
],
'arrayShiftNonEmptyList' => [
' < ? php
/** @param non-empty-list $arr */
function type_of_array_shift ( array $arr ) : int {
if ( \is_int ( $arr [ 0 ])) {
return \array_shift ( $arr );
}
return 0 ;
} ' ,
],
2020-08-30 17:44:14 +02:00
'arrayShiftFunkyTKeyedArrayList' => [
2020-07-22 05:59:11 +02:00
' < ? php
/**
* @ param non - empty - list < string >| array { null } $arr
* @ return array < int , string >
*/
function foo ( array $arr ) {
array_shift ( $arr );
return $arr ;
} '
],
2020-01-31 19:58:02 +01:00
'arrayPopNonEmptyAfterCountEqualsOne' => [
' < ? php
/** @var array<string, int> */
$a = [ " a " => 5 , " b " => 6 , " c " => 7 ];
$b = 5 ;
if ( count ( $a ) === 1 ) {
$b = array_pop ( $a );
} ' ,
'assertions' => [
'$b' => 'int' ,
],
],
'arrayPopNonEmptyAfterCountSoftEqualsOne' => [
' < ? php
/** @var array<string, int> */
$a = [ " a " => 5 , " b " => 6 , " c " => 7 ];
$b = 5 ;
if ( count ( $a ) == 1 ) {
$b = array_pop ( $a );
} ' ,
'assertions' => [
'$b' => 'int' ,
],
],
'arrayPopNonEmptyAfterCountGreaterThanOne' => [
' < ? php
/** @var array<string, int> */
$a = [ " a " => 5 , " b " => 6 , " c " => 7 ];
$b = 5 ;
if ( count ( $a ) > 0 ) {
$b = array_pop ( $a );
} ' ,
'assertions' => [
'$b' => 'int' ,
],
],
'arrayPopNonEmptyAfterCountGreaterOrEqualsOne' => [
' < ? php
/** @var array<string, int> */
$a = [ " a " => 5 , " b " => 6 , " c " => 7 ];
$b = 5 ;
if ( count ( $a ) >= 1 ) {
$b = array_pop ( $a );
} ' ,
'assertions' => [
'$b' => 'int' ,
],
],
'arrayPopNonEmptyAfterCountEqualsOneReversed' => [
' < ? php
/** @var array<string, int> */
$a = [ " a " => 5 , " b " => 6 , " c " => 7 ];
$b = 5 ;
if ( 1 === count ( $a )) {
$b = array_pop ( $a );
} ' ,
'assertions' => [
'$b' => 'int' ,
],
],
'arrayPopNonEmptyAfterCountSoftEqualsOneReversed' => [
' < ? php
/** @var array<string, int> */
$a = [ " a " => 5 , " b " => 6 , " c " => 7 ];
$b = 5 ;
if ( 1 == count ( $a )) {
$b = array_pop ( $a );
} ' ,
'assertions' => [
'$b' => 'int' ,
],
],
'arrayPopNonEmptyAfterCountGreaterThanOneReversed' => [
' < ? php
/** @var array<string, int> */
$a = [ " a " => 5 , " b " => 6 , " c " => 7 ];
$b = 5 ;
if ( 0 < count ( $a )) {
$b = array_pop ( $a );
} ' ,
'assertions' => [
'$b' => 'int' ,
],
],
'arrayPopNonEmptyAfterCountGreatorOrEqualToOneReversed' => [
' < ? php
/** @var array<string, int> */
$a = [ " a " => 5 , " b " => 6 , " c " => 7 ];
$b = 5 ;
if ( 1 <= count ( $a )) {
$b = array_pop ( $a );
} ' ,
'assertions' => [
'$b' => 'int' ,
],
],
2020-09-12 22:13:13 +02:00
'arrayNotEmptyArrayAfterCountLessThanEqualToOne' => [
2020-09-12 17:33:26 +02:00
' < ? php
/** @var list<int> */
2020-09-12 22:13:13 +02:00
$leftCount = [ 1 , 2 , 3 ];
if ( count ( $leftCount ) <= 1 ) {
echo $leftCount [ 0 ];
}
/** @var list<int> */
$rightCount = [ 1 , 2 , 3 ];
if ( 1 >= count ( $rightCount )) {
echo $rightCount [ 0 ];
} ' ,
],
'arrayNotEmptyArrayAfterCountLessThanTwo' => [
' < ? php
/** @var list<int> */
$leftCount = [ 1 , 2 , 3 ];
if ( count ( $leftCount ) < 2 ) {
echo $leftCount [ 0 ];
}
/** @var list<int> */
$rightCount = [ 1 , 2 , 3 ];
if ( 2 > count ( $rightCount )) {
echo $rightCount [ 0 ];
2020-09-12 17:33:26 +02:00
} ' ,
],
2020-09-12 22:13:13 +02:00
'arrayEmptyArrayAfterCountLessThanOne' => [
' < ? php
/** @var list<int> */
$leftCount = [ 1 , 2 , 3 ];
assert ( count ( $leftCount ) < 1 );
/** @var list<int> */
$rightCount = [ 1 , 2 , 3 ];
assert ( 1 > count ( $rightCount )); ' ,
'assertions' => [
'$leftCount' => 'array<empty, empty>' ,
'$rightCount' => 'array<empty, empty>' ,
],
],
'arrayEmptyArrayAfterCountLessThanEqualToZero' => [
' < ? php
/** @var list<int> */
$leftCount = [ 1 , 2 , 3 ];
assert ( count ( $leftCount ) <= 0 );
/** @var list<int> */
$rightCount = [ 1 , 2 , 3 ];
assert ( 0 >= count ( $rightCount )); ' ,
'assertions' => [
'$leftCount' => 'array<empty, empty>' ,
'$rightCount' => 'array<empty, empty>' ,
],
],
'arrayNotNonEmptyArrayAfterCountGreaterThanEqualToZero' => [
' < ? php
/** @var list<int> */
$leftCount = [ 1 , 2 , 3 ];
assert ( count ( $leftCount ) >= 0 );
/** @var list<int> */
$rightCount = [ 1 , 2 , 3 ];
assert ( 0 <= count ( $rightCount )); ' ,
'assertions' => [
'$leftCount' => 'list<int>' ,
'$rightCount' => 'list<int>' ,
],
],
'arrayNotNonEmptyArrayAfterCountGreaterThanMinusOne' => [
' < ? php
/** @var list<int> */
$leftCount = [ 1 , 2 , 3 ];
assert ( count ( $leftCount ) > - 1 );
/** @var list<int> */
$rightCount = [ 1 , 2 , 3 ];
assert ( - 1 < count ( $rightCount )); ' ,
'assertions' => [
'$leftCount' => 'list<int>' ,
'$rightCount' => 'list<int>' ,
],
],
'arrayNonEmptyArrayAfterCountGreaterThanEqualToOne' => [
' < ? php
/** @var list<int> */
$leftCount = [ 1 , 2 , 3 ];
assert ( count ( $leftCount ) >= 1 );
/** @var list<int> */
$rightCount = [ 1 , 2 , 3 ];
assert ( 1 <= count ( $rightCount )); ' ,
'assertions' => [
'$leftCount' => 'non-empty-list<int>' ,
'$rightCount' => 'non-empty-list<int>' ,
],
],
'arrayNonEmptyArrayAfterCountGreaterThanZero' => [
' < ? php
/** @var list<int> */
$leftCount = [ 1 , 2 , 3 ];
assert ( count ( $leftCount ) > 0 );
/** @var list<int> */
$rightCount = [ 1 , 2 , 3 ];
assert ( 0 < count ( $rightCount )); ' ,
'assertions' => [
'$leftCount' => 'non-empty-list<int>' ,
'$rightCount' => 'non-empty-list<int>' ,
],
],
2020-01-31 19:58:02 +01:00
'arrayPopNonEmptyAfterArrayAddition' => [
' < ? php
/** @var array<string, int> */
$a = [ " a " => 5 , " b " => 6 , " c " => 7 ];
$a [ " foo " ] = 10 ;
$b = array_pop ( $a ); ' ,
'assertions' => [
'$b' => 'int' ,
],
],
'arrayPopNonEmptyAfterMixedArrayAddition' => [
' < ? php
/** @var array */
$a = [ " a " => 5 , " b " => 6 , " c " => 7 ];
$a [] = " hello " ;
$b = array_pop ( $a ); ' ,
'assertions' => [
'$b' => 'mixed|string' ,
],
'error_levels' => [
'MixedAssignment' ,
],
],
'uasort' => [
' < ? php
$manifest = [ " a " => 1 , " b " => 2 ];
uasort (
$manifest ,
function ( int $a , int $b ) {
return $a > $b ? 1 : - 1 ;
}
); ' ,
'assertions' => [
'$manifest' => 'array<string, int>'
],
],
'uksort' => [
' < ? php
$array = [ " b " => 1 , " a " => 2 ];
uksort (
$array ,
function ( string $a , string $b ) {
return $a <=> $b ;
}
); ' ,
'assertions' => [
'$array' => 'array<string, int>' ,
],
],
2020-08-30 17:44:14 +02:00
'arrayMergeTKeyedArray' => [
2020-01-31 19:58:02 +01:00
' < ? php
/**
* @ param array < string , int > $a
* @ return array < string , int >
*/
function foo ( $a )
{
return $a ;
}
$a1 = [ " hi " => 3 ];
$a2 = [ " bye " => 5 ];
$a3 = array_merge ( $a1 , $a2 );
foo ( $a3 ); ' ,
'assertions' => [
2020-09-14 19:06:15 +02:00
'$a3' => 'array{hi: int, bye: int}' ,
2020-01-31 19:58:02 +01:00
],
],
2021-10-15 12:06:19 +02:00
'arrayReplaceTKeyedArray' => [
' < ? php
/**
* @ param array < string , int > $a
* @ return array < string , int >
*/
function foo ( $a )
{
return $a ;
}
$a1 = [ " hi " => 3 ];
$a2 = [ " bye " => 5 ];
$a3 = array_replace ( $a1 , $a2 );
foo ( $a3 ); ' ,
'assertions' => [
'$a3' => 'array{hi: int, bye: int}' ,
],
],
2020-01-31 19:58:02 +01:00
'arrayRand' => [
' < ? php
$vars = [ " x " => " a " , " y " => " b " ];
$c = array_rand ( $vars );
$d = $vars [ $c ];
$more_vars = [ " a " , " b " ];
$e = array_rand ( $more_vars ); ' ,
'assertions' => [
'$vars' => 'array{x: string, y: string}' ,
'$c' => 'string' ,
'$d' => 'string' ,
2020-05-11 15:08:53 +02:00
'$more_vars' => 'array{string, string}' ,
2020-01-31 19:58:02 +01:00
'$e' => 'int' ,
],
],
'arrayRandMultiple' => [
' < ? php
$vars = [ " x " => " a " , " y " => " b " ];
$b = 3 ;
$c = array_rand ( $vars , 1 );
$d = array_rand ( $vars , 2 );
$e = array_rand ( $vars , 3 );
$f = array_rand ( $vars , $b ); ' ,
'assertions' => [
'$vars' => 'array{x: string, y: string}' ,
'$c' => 'string' ,
'$e' => 'list<string>' ,
'$f' => 'list<string>|string' ,
],
],
'arrayKeysNoEmpty' => [
' < ? php
function expect_string ( string $x ) : void {
echo $x ;
}
function test () : void {
foreach ( array_keys ([]) as $key ) {
expect_string ( $key );
}
} ' ,
'assertions' => [],
'error_levels' => [ 'MixedAssignment' , 'MixedArgument' , 'MixedArgumentTypeCoercion' ],
],
'arrayPopNotNullable' => [
' < ? php
function expectsInt ( int $a ) : void {}
/**
* @ param array < array - key , array { item : int } > $list
*/
function test ( array $list ) : void
{
while ( ! empty ( $list )) {
$tmp = array_pop ( $list );
expectsInt ( $tmp [ " item " ]);
}
} ' ,
],
'arrayFilterWithAssert' => [
' < ? php
$a = array_filter (
[ 1 , " hello " , 6 , " goodbye " ],
function ( $s ) : bool {
return is_string ( $s );
}
); ' ,
'assertions' => [
'$a' => 'array<int, string>' ,
],
'error_levels' => [
'MissingClosureParamType' ,
],
],
'arrayFilterUseKey' => [
' < ? php
$bar = " bar " ;
$foo = [
$bar => function () : string {
return " baz " ;
},
];
$foo = array_filter (
$foo ,
function ( string $key ) : bool {
return $key === " bar " ;
},
ARRAY_FILTER_USE_KEY
); ' ,
'assertions' => [
2021-01-26 05:41:42 +01:00
'$foo' => 'array<string, pure-Closure():"baz">' ,
2020-01-31 19:58:02 +01:00
],
],
'ignoreFalsableCurrent' => [
' < ? php
/** @param string[] $arr */
function foo ( array $arr ) : string {
return current ( $arr );
}
/** @param string[] $arr */
function bar ( array $arr ) : string {
$a = current ( $arr );
if ( $a === false ) {
return " hello " ;
}
return $a ;
}
/**
* @ param string [] $arr
* @ return false | string
*/
function bat ( array $arr ) {
return current ( $arr );
} ' ,
],
'arraySumEmpty' => [
' < ? php
$foo = array_sum ([]) + 1 ; ' ,
2020-05-18 23:23:21 +02:00
'assertions' => [
'$foo' => 'int' ,
],
],
'arraySumOnlyInt' => [
' < ? php
$foo = array_sum ([ 5 , 18 ]); ' ,
'assertions' => [
'$foo' => 'int' ,
],
],
'arraySumOnlyFloat' => [
' < ? php
$foo = array_sum ([ 5.1 , 18.2 ]); ' ,
'assertions' => [
'$foo' => 'float' ,
],
],
'arraySumNumeric' => [
' < ? php
$foo = array_sum ([ " 5 " , " 18 " ]); ' ,
2020-01-31 19:58:02 +01:00
'assertions' => [
'$foo' => 'float|int' ,
],
],
2020-05-18 23:23:21 +02:00
'arraySumMix' => [
' < ? php
$foo = array_sum ([ 5 , 18.5 ]); ' ,
'assertions' => [
'$foo' => 'float' ,
],
],
2020-01-31 19:58:02 +01:00
'arrayMapWithArrayAndCallable' => [
' < ? php
/**
* @ psalm - return array < array - key , int >
*/
function foo ( array $v ) : array {
$r = array_map ( " intval " , $v );
return $r ;
} ' ,
],
2020-08-30 17:44:14 +02:00
'arrayMapTKeyedArrayAndCallable' => [
2020-01-31 19:58:02 +01:00
' < ? php
/**
* @ psalm - return array { key1 : int , key2 : int }
*/
function foo () : array {
$v = [ " key1 " => 1 , " key2 " => " 2 " ];
$r = array_map ( " intval " , $v );
return $r ;
} ' ,
],
2020-08-30 17:44:14 +02:00
'arrayMapTKeyedArrayListAndCallable' => [
2020-01-31 19:58:02 +01:00
' < ? php
/** @param list<int> $list */
function takesList ( array $list ) : void {}
takesList (
array_map (
" intval " ,
[ " 1 " , " 2 " , " 3 " ]
)
); ' ,
],
2020-08-30 17:44:14 +02:00
'arrayMapTKeyedArrayAndClosure' => [
2020-01-31 19:58:02 +01:00
' < ? php
/**
* @ psalm - return array { key1 : int , key2 : int }
*/
function foo () : array {
$v = [ " key1 " => 1 , " key2 " => " 2 " ];
$r = array_map ( function ( $i ) : int { return intval ( $i );}, $v );
return $r ;
} ' ,
'assertions' => [],
'error_levels' => [
2020-08-30 17:44:14 +02:00
'MissingClosureParamType'
2020-01-31 19:58:02 +01:00
],
],
2020-08-30 17:44:14 +02:00
'arrayMapTKeyedArrayListAndClosure' => [
2020-01-31 19:58:02 +01:00
' < ? php
/** @param list<string> $list */
function takesList ( array $list ) : void {}
takesList (
array_map (
function ( string $str ) : string { return $str . " x " ; },
[ " foo " , " bar " , " baz " ]
)
); ' ,
],
'arrayMapUntypedCallable' => [
' < ? php
/**
* @ var callable $callable
* @ var array < string , int > $array
*/
$a = array_map ( $callable , $array );
/**
* @ var callable $callable
* @ var array < string , int > $array
*/
$b = array_map ( $callable , $array , $array );
/**
* @ var callable $callable
* @ var list < string > $list
*/
$c = array_map ( $callable , $list );
/**
* @ var callable $callable
* @ var list < string > $list
*/
$d = array_map ( $callable , $list , $list ); ' ,
'assertions' => [
'$a' => 'array<string, mixed>' ,
'$b' => 'list<mixed>' ,
'$c' => 'list<mixed>' ,
'$d' => 'list<mixed>' ,
],
],
'arrayFilterGoodArgs' => [
' < ? php
function fooFoo ( int $i ) : bool {
return true ;
}
class A {
public static function barBar ( int $i ) : bool {
return true ;
}
}
array_filter ([ 1 , 2 , 3 ], " fooFoo " );
array_filter ([ 1 , 2 , 3 ], " foofoo " );
array_filter ([ 1 , 2 , 3 ], " FOOFOO " );
array_filter ([ 1 , 2 , 3 ], " A::barBar " );
array_filter ([ 1 , 2 , 3 ], " A::BARBAR " );
array_filter ([ 1 , 2 , 3 ], " A::barbar " ); ' ,
],
'arrayFilterIgnoreMissingClass' => [
' < ? php
array_filter ([ 1 , 2 , 3 ], " A::bar " ); ' ,
'assertions' => [],
'error_levels' => [ 'UndefinedClass' ],
],
'arrayFilterIgnoreMissingMethod' => [
' < ? php
class A {
public static function bar ( int $i ) : bool {
return true ;
}
}
array_filter ([ 1 , 2 , 3 ], " A::foo " ); ' ,
'assertions' => [],
'error_levels' => [ 'UndefinedMethod' ],
],
'arrayMapParamDefault' => [
' < ? php
$arr = [ " a " , " b " ];
array_map ( " mapdef " , $arr , array_fill ( 0 , count ( $arr ), 1 ));
function mapdef ( string $_a , int $_b = 0 ) : string {
return " a " ;
} ' ,
],
2021-05-15 02:12:39 +02:00
'arrayFillZeroLength' => [
' < ? php
count ( array_fill ( 0 , 0 , 0 )) === 0 ; ' ,
],
2020-01-31 19:58:02 +01:00
'implodeMultiDimensionalArray' => [
' < ? php
$urls = array_map ( " implode " , [[ " a " , " b " ]]); ' ,
],
2020-05-15 16:18:05 +02:00
'implodeNonEmptyArrayAndString' => [
' < ? php
$l = [ " a " , " b " ];
2021-06-15 05:24:09 +02:00
$k = [ 1 , 2 , 3 ];
$a = implode ( " : " , $l );
$b = implode ( " : " , $k ); ' ,
2020-05-15 16:18:05 +02:00
[
2021-06-14 22:03:37 +02:00
'$a===' => 'non-empty-literal-string' ,
2021-06-15 05:24:09 +02:00
'$b===' => 'non-empty-literal-string' ,
2020-05-15 16:18:05 +02:00
]
],
2020-01-31 19:58:02 +01:00
'key' => [
' < ? php
$a = [ " one " => 1 , " two " => 3 ];
2020-07-15 07:53:31 +02:00
$b = key ( $a ); ' ,
2020-01-31 19:58:02 +01:00
'assertions' => [
2020-07-20 10:49:36 +02:00
'$b' => 'null|string' ,
],
],
'keyEmptyArray' => [
' < ? php
$a = [];
$b = key ( $a ); ' ,
'assertions' => [
'$b' => 'null' ,
2020-01-31 19:58:02 +01:00
],
],
2020-07-15 15:49:30 +02:00
'keyNonEmptyArray' => [
' < ? php
/**
* @ param non - empty - array $arr
2020-07-20 10:49:36 +02:00
* @ return null | array - key
2020-07-15 15:49:30 +02:00
*/
function foo ( array $arr ) {
return key ( $arr );
} ' ,
],
2020-07-14 23:43:26 +02:00
'arrayKeyFirst' => [
' < ? php
/** @return array<string, int> */
function makeArray () : array { return [ " one " => 1 , " two " => 3 ]; }
$a = makeArray ();
$b = array_key_first ( $a );
$c = null ;
if ( $b !== null ) {
$c = $a [ $b ];
} ' ,
'assertions' => [
'$b' => 'null|string' ,
'$c' => 'int|null' ,
],
],
'arrayKeyFirstNonEmpty' => [
2020-01-31 19:58:02 +01:00
' < ? php
$a = [ " one " => 1 , " two " => 3 ];
$b = array_key_first ( $a );
$c = $a [ $b ]; ' ,
'assertions' => [
2020-07-14 23:43:26 +02:00
'$b' => 'string' ,
2020-01-31 19:58:02 +01:00
'$c' => 'int' ,
],
],
2020-07-14 23:43:26 +02:00
'arrayKeyFirstEmpty' => [
' < ? php
$a = [];
$b = array_key_first ( $a ); ' ,
'assertions' => [
'$b' => 'null'
],
],
'arrayKeyLast' => [
' < ? php
/** @return array<string, int> */
function makeArray () : array { return [ " one " => 1 , " two " => 3 ]; }
$a = makeArray ();
$b = array_key_last ( $a );
$c = null ;
if ( $b !== null ) {
$c = $a [ $b ];
} ' ,
'assertions' => [
'$b' => 'null|string' ,
'$c' => 'int|null' ,
],
],
'arrayKeyLastNonEmpty' => [
2020-01-31 19:58:02 +01:00
' < ? php
$a = [ " one " => 1 , " two " => 3 ];
$b = array_key_last ( $a );
$c = $a [ $b ]; ' ,
'assertions' => [
2020-07-14 23:43:26 +02:00
'$b' => 'string' ,
2020-01-31 19:58:02 +01:00
'$c' => 'int' ,
],
],
2020-07-14 23:43:26 +02:00
'arrayKeyLastEmpty' => [
' < ? php
$a = [];
$b = array_key_last ( $a ); ' ,
'assertions' => [
'$b' => 'null'
],
],
2020-08-07 18:23:20 +02:00
'arrayResetNonEmptyArray' => [
' < ? php
/** @return non-empty-array<string, int> */
function makeArray () : array { return [ " one " => 1 , " two " => 3 ]; }
$a = makeArray ();
$b = reset ( $a ); ' ,
'assertions' => [
'$b' => 'int'
],
],
'arrayResetNonEmptyList' => [
' < ? php
/** @return non-empty-list<int> */
function makeArray () : array { return [ 1 , 3 ]; }
$a = makeArray ();
$b = reset ( $a ); ' ,
'assertions' => [
'$b' => 'int'
],
],
2020-08-30 17:44:14 +02:00
'arrayResetNonEmptyTKeyedArray' => [
2020-08-07 18:23:20 +02:00
' < ? php
$a = [ " one " => 1 , " two " => 3 ];
$b = reset ( $a ); ' ,
'assertions' => [
'$b' => 'int'
],
],
'arrayResetEmptyArray' => [
' < ? php
$a = [];
$b = reset ( $a ); ' ,
'assertions' => [
'$b' => 'false'
],
],
'arrayResetEmptyList' => [
' < ? php
/** @return list<empty> */
function makeArray () : array { return []; }
$a = makeArray ();
$b = reset ( $a ); ' ,
'assertions' => [
'$b' => 'false'
],
],
'arrayResetMaybeEmptyArray' => [
' < ? php
/** @return array<string, int> */
function makeArray () : array { return [ " one " => 1 , " two " => 3 ]; }
$a = makeArray ();
$b = reset ( $a ); ' ,
'assertions' => [
'$b' => 'false|int'
],
],
'arrayResetMaybeEmptyList' => [
' < ? php
/** @return list<int> */
function makeArray () : array { return []; }
$a = makeArray ();
$b = reset ( $a ); ' ,
'assertions' => [
'$b' => 'false|int'
],
],
2020-08-30 17:44:14 +02:00
'arrayResetMaybeEmptyTKeyedArray' => [
2020-08-07 18:23:20 +02:00
' < ? php
/** @return array{foo?: int} */
function makeArray () : array { return []; }
$a = makeArray ();
$b = reset ( $a ); ' ,
'assertions' => [
'$b' => 'false|int'
],
],
'arrayEndNonEmptyArray' => [
' < ? php
/** @return non-empty-array<string, int> */
function makeArray () : array { return [ " one " => 1 , " two " => 3 ]; }
$a = makeArray ();
$b = end ( $a ); ' ,
'assertions' => [
'$b' => 'int'
],
],
'arrayEndNonEmptyList' => [
' < ? php
/** @return non-empty-list<int> */
function makeArray () : array { return [ 1 , 3 ]; }
$a = makeArray ();
$b = end ( $a ); ' ,
'assertions' => [
'$b' => 'int'
],
],
2020-08-30 17:44:14 +02:00
'arrayEndNonEmptyTKeyedArray' => [
2020-08-07 18:23:20 +02:00
' < ? php
$a = [ " one " => 1 , " two " => 3 ];
$b = end ( $a ); ' ,
'assertions' => [
'$b' => 'int'
],
],
'arrayEndEmptyArray' => [
' < ? php
$a = [];
$b = end ( $a ); ' ,
'assertions' => [
'$b' => 'false'
],
],
'arrayEndEmptyList' => [
' < ? php
/** @return list<empty> */
function makeArray () : array { return []; }
$a = makeArray ();
$b = end ( $a ); ' ,
'assertions' => [
'$b' => 'false'
],
],
'arrayEndMaybeEmptyArray' => [
' < ? php
/** @return array<string, int> */
function makeArray () : array { return [ " one " => 1 , " two " => 3 ]; }
$a = makeArray ();
$b = end ( $a ); ' ,
'assertions' => [
'$b' => 'false|int'
],
],
'arrayEndMaybeEmptyList' => [
' < ? php
/** @return list<int> */
function makeArray () : array { return []; }
$a = makeArray ();
$b = end ( $a ); ' ,
'assertions' => [
'$b' => 'false|int'
],
],
2020-08-30 17:44:14 +02:00
'arrayEndMaybeEmptyTKeyedArray' => [
2020-08-07 18:23:20 +02:00
' < ? php
/** @return array{foo?: int} */
function makeArray () : array { return []; }
$a = makeArray ();
$b = end ( $a ); ' ,
'assertions' => [
'$b' => 'false|int'
],
],
2020-01-31 19:58:02 +01:00
'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 []; }
2020-11-27 23:05:54 +01:00
/** @return array<string, array{x?:int, y?:int, width?:int, height?:int}> */
function makeKeyedArray () : array { return []; }
2020-01-31 19:58:02 +01:00
$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 ( makeMixedArray (), 0 , " k " );
$g = array_column ( makeMixedArray (), 0 , null );
$h = array_column ( makeGenericArray (), 0 );
$i = array_column ( makeShapeArray (), 0 );
$j = array_column ( makeUnionArray (), 0 );
2020-07-14 23:13:45 +02:00
$k = array_column ([[ 0 => " test " ]], 0 );
2020-11-27 23:05:54 +01:00
$l = array_column ( makeKeyedArray (), " y " );
$m_prepare = makeKeyedArray ();
assert ( $m_prepare !== []);
$m = array_column ( $m_prepare , " y " );
2020-01-31 19:58:02 +01:00
' ,
'assertions' => [
2020-07-14 23:13:45 +02:00
'$a' => 'non-empty-list<int>' ,
'$b' => 'non-empty-list<int>' ,
'$c' => 'non-empty-array<string, int>' ,
2020-01-31 19:58:02 +01:00
'$d' => 'list<mixed>' ,
'$e' => 'list<mixed>' ,
'$f' => 'array<array-key, mixed>' ,
'$g' => 'list<mixed>' ,
'$h' => 'list<mixed>' ,
'$i' => 'list<string>' ,
'$j' => 'list<mixed>' ,
2020-07-14 23:13:45 +02:00
'$k' => 'non-empty-list<string>' ,
2020-11-27 23:05:54 +01:00
'$l' => 'list<int>' ,
'$m' => 'list<int>' ,
2020-01-31 19:58:02 +01:00
],
],
'splatArrayIntersect' => [
' < ? php
$foo = [
[ 1 , 2 , 3 ],
[ 1 , 2 ],
];
$bar = array_intersect ( ... $foo ); ' ,
'assertions' => [
'$bar' => 'array<int, int>' ,
],
],
'arrayIntersectIsVariadic' => [
' < ? php
array_intersect ([], [], [], [], []); ' ,
'assertions' => [],
],
'arrayIntersectKeyIsVariadic' => [
' < ? php
array_intersect_key ([], [], [], [], []); ' ,
'assertions' => [],
],
'arrayIntersectKeyNoReturnType' => [
' < ? php
/**
* @ psalm - suppress MissingReturnType
*/
function unknown () {
return [ " x " => " hello " ];
}
class C {
/**
* @ psalm - suppress MissingReturnType
*/
public static function unknownStatic () {
return [ " x " => " hello " ];
}
/**
* @ psalm - suppress MissingReturnType
*/
public static function unknownInstance () {
return [ " x " => " hello " ];
}
}
/**
* @ psalm - suppress MixedArgument
*/
function sdn ( array $s ) : void {
$r = array_intersect_key ( unknown (), array_filter ( $s ));
if ( empty ( $r )) {}
$r = array_intersect_key ( C :: unknownStatic (), array_filter ( $s ));
if ( empty ( $r )) {}
$r = array_intersect_key (( new C ) -> unknownInstance (), array_filter ( $s ));
if ( empty ( $r )) {}
} ' ,
],
'arrayIntersectAssoc' => [
' < ? php
/**
* @ var array < string , int > $a
* @ var array $b
* @ var array $c
*/
$r = array_intersect_assoc ( $a , $b , $c ); ' ,
'assertions' => [
'$r' => 'array<string, int>' ,
],
],
'arrayReduce' => [
' < ? php
$arr = [ 2 , 3 , 4 , 5 ];
function multiply ( int $carry , int $item ) : int {
return $carry * $item ;
}
$f2 = function ( int $carry , int $item ) : int {
return $carry * $item ;
};
$direct_closure_result = array_reduce (
$arr ,
function ( int $carry , int $item ) : int {
return $carry * $item ;
},
1
);
$passed_closure_result = array_reduce (
$arr ,
$f2 ,
1
);
$function_call_result = array_reduce (
$arr ,
" multiply " ,
1
); ' ,
'assertions' => [
'$direct_closure_result' => 'int' ,
'$passed_closure_result' => 'int' ,
'$function_call_result' => 'int' ,
],
],
2021-06-29 19:08:57 +02:00
'arrayReduceStaticMethods' => [
' < ? php
$arr = [ 2 , 3 , 4 , 5 ];
class C {
public static function multiply ( int $carry , int $item ) : int {
return $carry * $item ;
}
public static function multiplySelf ( array $arr ) : int {
return array_reduce ( $arr , [ self :: class , " multiply " ], 1 );
}
public static function multiplyStatic ( array $arr ) : int {
return array_reduce ( $arr , [ static :: class , " multiply " ], 1 );
}
}
$self_call_result = C :: multiplySelf ( $arr );
$static_call_result = C :: multiplyStatic ( $arr ); ' ,
'assertions' => [],
],
2020-01-31 19:58:02 +01:00
'arrayReduceMixedReturn' => [
' < ? php
$arr = [ 2 , 3 , 4 , 5 ];
$direct_closure_result = array_reduce (
$arr ,
function ( int $carry , int $item ) {
return $_GET [ " boo " ];
},
1
); ' ,
'assertions' => [],
'error_levels' => [ 'MissingClosureReturnType' , 'MixedAssignment' ],
],
2020-07-22 05:59:11 +02:00
'arraySpliceArray' => [
2020-01-31 19:58:02 +01:00
' < ? php
$a = [ 1 , 2 , 3 ];
$c = $a ;
$b = [ " a " , " b " , " c " ];
2020-07-22 05:59:11 +02:00
array_splice ( $a , rand ( - 10 , 0 ), rand ( 0 , 10 ), $b ); ' ,
2020-01-31 19:58:02 +01:00
'assertions' => [
2020-03-09 06:15:52 +01:00
'$a' => 'non-empty-list<int|string>' ,
2020-05-11 15:08:53 +02:00
'$b' => 'array{string, string, string}' ,
'$c' => 'array{int, int, int}' ,
2020-07-22 05:59:11 +02:00
],
],
'arraySpliceReturn' => [
' < ? php
$d = [ 1 , 2 , 3 ];
$e = array_splice ( $d , - 1 , 1 ); ' ,
'assertions' => [
2021-05-10 17:45:52 +02:00
'$e' => 'list<int>'
2020-01-31 19:58:02 +01:00
],
],
'arraySpliceOtherType' => [
' < ? php
$d = [[ " red " ], [ " green " ], [ " blue " ]];
array_splice ( $d , - 1 , 1 , " foo " ); ' ,
'assertions' => [
2020-05-11 15:08:53 +02:00
'$d' => 'array<int, array{string}|string>' ,
2020-01-31 19:58:02 +01:00
],
],
'ksortPreserveShape' => [
' < ? php
$a = [ " a " => 3 , " b " => 4 ];
ksort ( $a );
acceptsAShape ( $a );
/**
* @ param array { a : int , b : int } $a
*/
function acceptsAShape ( array $a ) : void {} ' ,
],
'arraySlicePreserveKeys' => [
' < ? php
$a = [ " a " => 1 , " b " => 2 , " c " => 3 ];
$b = array_slice ( $a , 1 , 2 , true );
$c = array_slice ( $a , 1 , 2 , false );
$d = array_slice ( $a , 1 , 2 ); ' ,
'assertions' => [
'$b' => 'array<string, int>' ,
'$c' => 'array<string, int>' ,
'$d' => 'array<string, int>' ,
],
],
'arraySliceDontPreserveIntKeys' => [
' < ? php
$a = [ 1 => " a " , 4 => " b " , 3 => " c " ];
$b = array_slice ( $a , 1 , 2 , true );
$c = array_slice ( $a , 1 , 2 , false );
$d = array_slice ( $a , 1 , 2 ); ' ,
'assertions' => [
'$b' => 'array<int, string>' ,
'$c' => 'list<string>' ,
'$d' => 'list<string>' ,
],
],
'arrayReversePreserveNonEmptiness' => [
' < ? php
/** @param string[] $arr */
function getOrderings ( array $arr ) : int {
if ( $arr ) {
$next = null ;
foreach ( array_reverse ( $arr ) as $v ) {
$next = 1 ;
}
return $next ;
}
return 2 ;
} ' ,
],
'inferArrayMapReturnType' => [
' < ? php
/** @return array<string> */
function Foo ( DateTime ... $dateTimes ) : array {
return array_map (
function ( $dateTime ) {
2021-07-03 07:19:37 +02:00
return ( $dateTime -> format ( " c " ));
2020-01-31 19:58:02 +01:00
},
$dateTimes
);
} ' ,
],
'inferArrayMapArrowFunctionReturnType' => [
' < ? php
/** @return array<string> */
function Foo ( DateTime ... $dateTimes ) : array {
return array_map (
2021-07-03 07:19:37 +02:00
fn ( $dateTime ) => ( $dateTime -> format ( " c " )),
2020-01-31 19:58:02 +01:00
$dateTimes
);
} ' ,
2020-08-23 16:32:07 +02:00
'assertions' => [],
'error_levels' => [],
2021-07-03 07:19:37 +02:00
'php_version' => '7.4' ,
2020-01-31 19:58:02 +01:00
],
'arrayPad' => [
' < ? php
$a = array_pad ([ " foo " => 1 , " bar " => 2 ], 10 , 123 );
$b = array_pad ([ " a " , " b " , " c " ], 10 , " x " );
/** @var list<int> $list */
$c = array_pad ( $list , 10 , 0 );
/** @var array<string, string> $array */
$d = array_pad ( $array , 10 , " " ); ' ,
'assertions' => [
'$a' => 'non-empty-array<int|string, int>' ,
'$b' => 'non-empty-list<string>' ,
'$c' => 'non-empty-list<int>' ,
'$d' => 'non-empty-array<int|string, string>' ,
],
],
'arrayPadDynamicSize' => [
' < ? php
function getSize () : int { return random_int ( 1 , 10 ); }
$a = array_pad ([ " foo " => 1 , " bar " => 2 ], getSize (), 123 );
$b = array_pad ([ " a " , " b " , " c " ], getSize (), " x " );
/** @var list<int> $list */
$c = array_pad ( $list , getSize (), 0 );
/** @var array<string, string> $array */
$d = array_pad ( $array , getSize (), " " ); ' ,
'assertions' => [
'$a' => 'array<int|string, int>' ,
'$b' => 'list<string>' ,
'$c' => 'list<int>' ,
'$d' => 'array<int|string, string>' ,
],
],
'arrayPadZeroSize' => [
' < ? php
/** @var array $arr */
$result = array_pad ( $arr , 0 , null ); ' ,
'assertions' => [
'$result' => 'array<array-key, mixed|null>' ,
],
],
'arrayPadTypeCombination' => [
' < ? php
$a = array_pad ([ " foo " => 1 , " bar " => " two " ], 5 , false );
$b = array_pad ([ " a " , 2 , 3.14 ], 5 , null );
/** @var list<string|bool> $list */
$c = array_pad ( $list , 5 , 0 );
/** @var array<string, string> $array */
$d = array_pad ( $array , 5 , null ); ' ,
'assertions' => [
'$a' => 'non-empty-array<int|string, false|int|string>' ,
'$b' => 'non-empty-list<float|int|null|string>' ,
'$c' => 'non-empty-list<bool|int|string>' ,
'$d' => 'non-empty-array<int|string, null|string>' ,
],
],
'arrayPadMixed' => [
' < ? php
/** @var array{foo: mixed, bar: mixed} $arr */
$a = array_pad ( $arr , 5 , null );
/** @var mixed $mixed */
$b = array_pad ([ $mixed , $mixed ], 5 , null );
/** @var list $list */
$c = array_pad ( $list , 5 , null );
/** @var mixed[] $array */
$d = array_pad ( $array , 5 , null ); ' ,
'assertions' => [
'$a' => 'non-empty-array<int|string, mixed|null>' ,
'$b' => 'non-empty-list<mixed|null>' ,
'$c' => 'non-empty-list<mixed|null>' ,
'$d' => 'non-empty-array<array-key, mixed|null>' ,
],
],
'arrayPadFallback' => [
' < ? php
/**
* @ var mixed $mixed
* @ psalm - suppress MixedArgument
*/
$result = array_pad ( $mixed , $mixed , $mixed ); ' ,
'assertions' => [
'$result' => 'array<array-key, mixed>' ,
],
],
'arrayChunk' => [
' < ? php
/** @var array{a: int, b: int, c: int, d: int} $arr */
$a = array_chunk ( $arr , 2 );
/** @var list<string> $list */
$b = array_chunk ( $list , 2 );
/** @var array<string, float> $arr */
$c = array_chunk ( $arr , 2 );
' ,
'assertions' => [
'$a' => 'list<non-empty-list<int>>' ,
'$b' => 'list<non-empty-list<string>>' ,
'$c' => 'list<non-empty-list<float>>' ,
],
],
'arrayChunkPreservedKeys' => [
' < ? php
/** @var array{a: int, b: int, c: int, d: int} $arr */
$a = array_chunk ( $arr , 2 , true );
/** @var list<string> $list */
$b = array_chunk ( $list , 2 , true );
/** @var array<string, float> $arr */
$c = array_chunk ( $arr , 2 , true ); ' ,
'assertions' => [
'$a' => 'list<non-empty-array<string, int>>' ,
'$b' => 'list<non-empty-array<int, string>>' ,
'$c' => 'list<non-empty-array<string, float>>' ,
],
],
'arrayChunkPreservedKeysExplicitFalse' => [
' < ? php
/** @var array<string, string> $arr */
$result = array_chunk ( $arr , 2 , false ); ' ,
'assertions' => [
'$result' => 'list<non-empty-list<string>>' ,
],
],
'arrayChunkMixed' => [
' < ? php
/** @var array{a: mixed, b: mixed, c: mixed} $arr */
$a = array_chunk ( $arr , 2 );
/** @var list<mixed> $list */
$b = array_chunk ( $list , 2 );
/** @var mixed[] $arr */
$c = array_chunk ( $arr , 2 ); ' ,
'assertions' => [
'$a' => 'list<non-empty-list<mixed>>' ,
'$b' => 'list<non-empty-list<mixed>>' ,
'$c' => 'list<non-empty-list<mixed>>' ,
],
],
'arrayChunkFallback' => [
' < ? php
/**
* @ var mixed $mixed
* @ psalm - suppress MixedArgument
*/
$result = array_chunk ( $mixed , $mixed , $mixed ); ' ,
'assertions' => [
'$result' => 'list<array<array-key, mixed>>' ,
],
],
'arrayMapPreserveNonEmptiness' => [
' < ? php
/**
* @ psalm - param non - empty - list < string > $strings
* @ psalm - return non - empty - list < int >
*/
function foo ( array $strings ) : array {
return array_map ( " intval " , $strings );
} '
],
2020-10-28 18:32:55 +01:00
'SKIPPED-arrayMapZip' => [
2020-01-31 19:58:02 +01:00
' < ? php
/**
2020-10-28 18:32:55 +01:00
* @ return array < int , array { string , ? string } >
2020-01-31 19:58:02 +01:00
*/
function getCharPairs ( string $line ) : array {
$chars = str_split ( $line );
return array_map (
null ,
$chars ,
array_slice ( $chars , 1 )
);
} '
],
'arrayFillKeys' => [
' < ? php
$keys = [ 1 , 2 , 3 ];
$result = array_fill_keys ( $keys , true ); ' ,
'assertions' => [
'$result' => 'array<int, true>' ,
],
],
'shuffle' => [
' < ? php
$array = [ " foo " => 123 , " bar " => 456 ];
shuffle ( $array ); ' ,
'assertions' => [
'$array' => 'list<int>' ,
],
],
'sort' => [
' < ? php
$array = [ " foo " => 123 , " bar " => 456 ];
sort ( $array ); ' ,
'assertions' => [
'$array' => 'list<int>' ,
],
],
'rsort' => [
' < ? php
$array = [ " foo " => 123 , " bar " => 456 ];
sort ( $array ); ' ,
'assertions' => [
'$array' => 'list<int>' ,
],
],
'usort' => [
' < ? php
$array = [ " foo " => 123 , " bar " => 456 ];
usort ( $array , function ( int $a , int $b ) { return $a <=> $b ; }); ' ,
'assertions' => [
'$array' => 'list<int>' ,
],
],
2020-08-16 19:03:30 +02:00
'closureParamConstraintsMet' => [
' < ? php
class A {}
class B {}
$test = [ new A (), new B ()];
usort (
$test ,
/**
* @ param A | B $a
* @ param A | B $b
*/
function ( $a , $b ) : int
{
return $a === $b ? 1 : - 1 ;
}
); '
],
2020-01-31 19:58:02 +01:00
'specialCaseArrayFilterOnSingleEntry' => [
' < ? php
/** @psalm-return list<int> */
function makeAList ( int $ofThisInteger ) : array {
return array_filter ([ $ofThisInteger ]);
} '
],
'arrayMapWithEmptyArrayReturn' => [
' < ? php
/**
* @ param array < array < string >> $elements
* @ return list < string >
*/
function resolvePossibleFilePaths ( $elements ) : array
{
return array_values (
array_filter (
array_merge (
... array_map (
function ( array $element ) : array {
if ( rand ( 0 , 1 ) == 1 ) {
return [];
}
return $element ;
},
$elements
)
)
)
);
} '
],
'arrayFilterArrowFunction' => [
' < ? php
class A {}
class B {}
$a = \array_filter (
[ new A (), new B ()],
function ( $x ) {
return $x instanceof B ;
}
);
$b = \array_filter (
[ new A (), new B ()],
fn ( $x ) => $x instanceof B
); ' ,
'assertions' => [
'$a' => 'array<int, B>' ,
'$b' => 'array<int, B>' ,
],
2020-08-23 16:32:07 +02:00
'error_levels' => [],
'7.4' ,
2020-01-31 19:58:02 +01:00
],
'arrayMergeTwoExplicitLists' => [
' < ? php
/**
* @ param list < int > $foo
*/
function foo ( array $foo ) : void {}
$foo1 = [ 1 , 2 , 3 ];
$foo2 = [ 1 , 4 , 5 ];
foo ( array_merge ( $foo1 , $foo2 )); '
],
'arrayMergeTwoPossiblyFalse' => [
' < ? php
$a = array_merge (
glob ( __DIR__ . \ ' / stubs /*. php\ ' ),
glob ( __DIR__ . \ ' / stubs / DBAL /*. php\ ' ),
); ' ,
[
'$a' => 'list<string>'
],
],
2021-10-15 12:06:19 +02:00
'arrayReplaceTwoExplicitLists' => [
' < ? php
/**
* @ param list < int > $foo
*/
function foo ( array $foo ) : void {}
$foo1 = [ 1 , 2 , 3 ];
$foo2 = [ 1 , 4 , 5 ];
foo ( array_replace ( $foo1 , $foo2 )); '
],
'arrayReplaceTwoPossiblyFalse' => [
' < ? php
$a = array_replace (
glob ( __DIR__ . \ ' / stubs /*. php\ ' ),
glob ( __DIR__ . \ ' / stubs / DBAL /*. php\ ' ),
); ' ,
[
'$a' => 'list<string>'
],
],
2020-01-31 19:58:56 +01:00
'arrayMapPossiblyFalseIgnored' => [
' < ? php
function takesString ( string $string ) : void {}
$date = new DateTime ();
$a = [ $date -> format ( " Y-m-d " )];
takesString ( $a [ 0 ]);
array_map ( " takesString " , $a ); ' ,
],
2020-02-08 18:17:57 +01:00
'arrayMapExplicitZip' => [
' < ? php
$as = [ " key " ];
$bs = [ " value " ];
2020-08-23 16:32:07 +02:00
return array_map ( fn ( $a , $b ) => [ $a => $b ], $as , $bs ); ' ,
'assertions' => [],
'error_levels' => [],
'7.4' ,
2020-02-08 18:17:57 +01:00
],
2020-03-09 06:15:52 +01:00
'spliceTurnsintKeyedInputToList' => [
' < ? php
/**
* @ psalm - param list < string > $elements
* @ return list < string >
*/
function bar ( array $elements , int $index , string $element ) : array {
array_splice ( $elements , $index , 0 , [ $element ]);
return $elements ;
} '
],
2020-04-28 04:49:07 +02:00
'arrayChangeKeyCaseWithNonStringKeys' => [
' < ? php
$a = [ 42 , " A " => 42 ];
echo array_change_key_case ( $a , CASE_LOWER )[ 0 ]; '
],
2020-05-08 20:52:53 +02:00
'mapInterfaceMethod' => [
' < ? php
interface MapperInterface {
public function map ( string $s ) : int ;
}
/**
* @ param list < string > $strings
* @ return list < int >
*/
function mapList ( MapperInterface $m , array $strings ) : array {
return array_map ([ $m , " map " ], $strings );
} '
],
2020-08-05 18:43:27 +02:00
'arrayShiftComplexArray' => [
' < ? php
/**
* @ param list < string > $slugParts
*/
function foo ( array $slugParts ) : void {
if ( ! $slugParts ) {
$slugParts = [ " " ];
}
array_shift ( $slugParts );
if ( ! empty ( $slugParts )) {}
} '
],
2020-09-14 19:06:15 +02:00
'arrayMergeKeepLastKeysAndType' => [
' < ? php
/**
* @ param array { A : int } $a
* @ param array < string , string > $b
*
* @ return array { A : int }
*/
function merger ( array $a , array $b ) : array {
return array_merge ( $b , $a );
} '
],
'arrayMergeKeepFirstKeysSameType' => [
' < ? php
/**
* @ param array { A : int } $a
* @ param array < string , int > $b
*
* @ return array { A : int }
*/
function merger ( array $a , array $b ) : array {
return array_merge ( $a , $b );
} '
],
2021-10-15 12:06:19 +02:00
'arrayReplaceKeepLastKeysAndType' => [
' < ? php
/**
* @ param array { A : int } $a
* @ param array < string , string > $b
*
* @ return array { A : int }
*/
function merger ( array $a , array $b ) : array {
return array_replace ( $b , $a );
} '
],
'arrayReplaceKeepFirstKeysSameType' => [
' < ? php
/**
* @ param array { A : int } $a
* @ param array < string , int > $b
*
* @ return array { A : int }
*/
function merger ( array $a , array $b ) : array {
return array_replace ( $a , $b );
} '
],
2020-09-19 21:46:54 +02:00
'filteredArrayCanBeEmpty' => [
' < ? php
/**
* @ return string | null
*/
function thing () {
if ( rand ( 0 , 1 ) === 1 ) {
return " data " ;
} else {
return null ;
}
}
$list = [ thing (), thing (), thing ()];
$list = array_filter ( $list );
if ( ! empty ( $list )) {} '
],
2020-10-12 21:16:47 +02:00
'arrayShiftOnMixedOrEmptyArray' => [
' < ? php
/**
* @ param mixed | array < empty , empty > $lengths
*/
function doStuff ( $lengths ) : void {
/** @psalm-suppress MixedArgument, MixedAssignment */
$length = array_shift ( $lengths );
if ( $length !== null ) {}
} '
],
2020-10-28 18:32:55 +01:00
'countOnListIntoTuple' => [
' < ? php
/** @param array{string, string} $tuple */
function foo ( array $tuple ) : void {}
/** @param list<string> $list */
function bar ( array $list ) : void {
if ( count ( $list ) === 2 ) {
foo ( $list );
}
} '
],
2020-11-27 23:05:54 +01:00
'arrayColumnwithKeyedArrayWithoutRedundantUnion' => [
' < ? php
/**
* @ param array < string , array { x ? : int , y ? : int , width ? : int , height ? : int } > $foos
*/
function foo ( array $foos ) : void {
array_multisort ( $formLayoutFields , SORT_ASC , array_column ( $foos , " y " ));
} '
],
2021-02-08 15:29:41 +01:00
'arrayMapGenericObject' => [
' < ? php
/**
* @ template T
*/
interface Container
{
/**
* @ return T
*/
public function get ( string $name );
}
/**
* @ param Container < stdClass > $container
* @ param array < string > $data
* @ return array < stdClass >
*/
function bar ( Container $container , array $data ) : array {
return array_map (
[ $container , " get " ],
$data
);
} '
],
2021-07-03 05:24:18 +02:00
'arrayMapShapeAndGenericArray' => [
' < ? php
/** @return string[] */
function getLine () : array { return [ " a " , " b " ]; }
$line = getLine ();
if ( empty ( $line [ 0 ])) { // converts array<string> to array{0:string}<string>
throw new InvalidArgumentException ;
}
$line = array_map ( // should not destroy <string> part
function ( $val ) { return ( int ) $val ; },
$line
);
' ,
'assertions' => [
'$line===' => 'array{0: int}<array-key, int>' ,
],
],
2021-10-07 11:04:51 +02:00
'arrayUnshiftOnEmptyArrayMeansNonEmptyList' => [
' < ? php
/**
* @ return non - empty - list < string >
*/
function foo () : array
{
$a = [];
array_unshift ( $a , " string " );
return $a ;
} ' ,
],
2020-01-31 19:58:02 +01:00
];
}
/**
2021-03-20 02:44:44 +01:00
* @ return iterable < string , array { string , error_message : string , 1 ? : string [], 2 ? : bool , 3 ? : string } >
2020-01-31 19:58:02 +01:00
*/
2020-09-12 17:24:05 +02:00
public function providerInvalidCodeParse () : iterable
2020-01-31 19:58:02 +01:00
{
return [
'arrayFilterWithoutTypes' => [
' < ? php
$e = array_filter (
[ " a " => 5 , " b " => 12 , " c " => null ],
function ( ? int $i ) {
return $_GET [ " a " ];
}
); ' ,
'error_message' => 'MixedArgumentTypeCoercion' ,
'error_levels' => [ 'MissingClosureParamType' , 'MissingClosureReturnType' ],
],
'arrayFilterUseMethodOnInferrableInt' => [
' < ? php
$a = array_filter ([ 1 , 2 , 3 , 4 ], function ( $i ) { return $i -> foo (); }); ' ,
'error_message' => 'InvalidMethodCall' ,
],
'arrayMapUseMethodOnInferrableInt' => [
' < ? php
$a = array_map ( function ( $i ) { return $i -> foo (); }, [ 1 , 2 , 3 , 4 ]); ' ,
'error_message' => 'InvalidMethodCall' ,
],
'arrayMapWithNonCallableStringArray' => [
' < ? php
$foo = [ " one " , " two " ];
array_map ( $foo , [ " hello " ]); ' ,
'error_message' => 'InvalidArgument' ,
],
'arrayMapWithNonCallableIntArray' => [
' < ? php
$foo = [ 1 , 2 ];
array_map ( $foo , [ " hello " ]); ' ,
'error_message' => 'InvalidArgument' ,
],
'arrayFilterBadArgs' => [
' < ? php
function foo ( int $i ) : bool {
return true ;
}
array_filter ([ " hello " ], " foo " ); ' ,
'error_message' => 'InvalidScalarArgument' ,
],
2021-05-15 02:12:39 +02:00
'arrayFillPositiveConstantLength' => [
' < ? php
count ( array_fill ( 0 , 1 , 0 )) === 0 ; ' ,
'error_message' => 'TypeDoesNotContainType'
],
2020-01-31 19:58:02 +01:00
'arrayFilterTooFewArgs' => [
' < ? php
function foo ( int $i , string $s ) : bool {
return true ;
}
array_filter ([ 1 , 2 , 3 ], " foo " ); ' ,
'error_message' => 'TooFewArguments' ,
],
'arrayMapBadArgs' => [
' < ? php
function foo ( int $i ) : bool {
return true ;
}
array_map ( " foo " , [ " hello " ]); ' ,
'error_message' => 'InvalidScalarArgument' ,
],
'arrayMapTooFewArgs' => [
' < ? php
function foo ( int $i , string $s ) : bool {
return true ;
}
array_map ( " foo " , [ 1 , 2 , 3 ]); ' ,
'error_message' => 'TooFewArguments' ,
],
'arrayMapTooManyArgs' => [
' < ? php
function foo () : bool {
return true ;
}
array_map ( " foo " , [ 1 , 2 , 3 ]); ' ,
'error_message' => 'TooManyArguments' ,
],
'arrayReduceInvalidClosureTooFewArgs' => [
' < ? php
$arr = [ 2 , 3 , 4 , 5 ];
$direct_closure_result = array_reduce (
$arr ,
2021-01-06 18:59:51 +01:00
function () : int {
2020-01-31 19:58:02 +01:00
return 5 ;
},
1
); ' ,
'error_message' => 'InvalidArgument' ,
'error_levels' => [ 'MixedTypeCoercion' ],
],
'arrayReduceInvalidItemType' => [
' < ? php
$arr = [ 2 , 3 , 4 , 5 ];
$direct_closure_result = array_reduce (
$arr ,
function ( int $carry , stdClass $item ) {
return $_GET [ " boo " ];
},
1
); ' ,
'error_message' => 'InvalidArgument' ,
'error_levels' => [ 'MissingClosureReturnType' ],
],
'arrayReduceInvalidCarryType' => [
' < ? php
$arr = [ 2 , 3 , 4 , 5 ];
$direct_closure_result = array_reduce (
$arr ,
function ( stdClass $carry , int $item ) {
return $_GET [ " boo " ];
},
1
); ' ,
'error_message' => 'InvalidArgument' ,
'error_levels' => [ 'MissingClosureReturnType' ],
],
'arrayReduceInvalidCarryOutputType' => [
' < ? php
$arr = [ 2 , 3 , 4 , 5 ];
$direct_closure_result = array_reduce (
$arr ,
function ( int $carry , int $item ) : stdClass {
return new stdClass ;
},
1
); ' ,
'error_message' => 'InvalidArgument' ,
],
'arrayPopNotNull' => [
' < ? php
function expectsInt ( int $a ) : void {}
/**
* @ param array < array - key , array { item : int } > $list
*/
function test ( array $list ) : void
{
while ( ! empty ( $list )) {
$tmp = array_pop ( $list );
if ( $tmp === null ) {}
}
} ' ,
'error_message' => 'DocblockTypeContradiction' ,
],
'usortInvalidCallableString' => [
' < ? php
$a = [[ 1 ], [ 2 ], [ 3 ]];
usort ( $a , " strcmp " ); ' ,
'error_message' => 'InvalidArgument' ,
],
'arrayShiftUndefinedVariable' => [
' < ? php
/** @psalm-suppress MissingParamType */
function foo ( $data ) : void {
/** @psalm-suppress MixedArgument */
array_unshift ( $data , $a );
} ' ,
'error_message' => 'UndefinedVariable' ,
],
2020-08-30 17:44:14 +02:00
'arrayFilterTKeyedArray' => [
2020-01-31 19:58:02 +01:00
' < ? php
/** @param list<int> $ints */
function ints ( array $ints ) : void {}
$brr = array_filter ([ 2 , 3 , 0 , 4 , 5 ]);
ints ( $brr ); ' ,
2020-09-14 19:31:53 +02:00
'error_message' => 'InvalidArgument' ,
2020-01-31 19:58:02 +01:00
],
2020-04-29 20:57:57 +02:00
'usortOneParamInvalid' => [
' < ? php
$list = [ 3 , 2 , 5 , 9 ];
usort ( $list , fn ( int $a , string $b ) : int => ( int ) ( $a > $b )); ' ,
2020-08-23 16:32:07 +02:00
'error_message' => 'InvalidScalarArgument' ,
2021-03-20 02:44:44 +01:00
[],
false ,
'7.4' ,
2020-04-29 20:57:57 +02:00
],
2020-08-16 19:03:30 +02:00
'usortInvalidComparison' => [
' < ? php
$arr = [[ " one " ], [ " two " ], [ " three " ]];
usort (
$arr ,
function ( string $a , string $b ) : int {
return strcmp ( $a , $b );
}
); ' ,
'error_message' => 'InvalidArgument' ,
],
2020-09-14 19:06:15 +02:00
'arrayMergeKeepFirstKeysButNotType' => [
' < ? php
/**
* @ param array { A : int } $a
* @ param array < string , string > $b
*
* @ return array { A : int }
*/
function merger ( array $a , array $b ) : array {
return array_merge ( $a , $b );
} ' ,
2020-09-20 19:00:30 +02:00
'error_message' => 'LessSpecificReturnStatement - src' . DIRECTORY_SEPARATOR . 'somefile.php:9:32 - The type \'array{A: int|string}<string, string>\' is more general' ,
2020-09-14 19:06:15 +02:00
],
2021-10-15 12:06:19 +02:00
'arrayReplaceKeepFirstKeysButNotType' => [
' < ? php
/**
* @ param array { A : int } $a
* @ param array < string , string > $b
*
* @ return array { A : int }
*/
function merger ( array $a , array $b ) : array {
return array_replace ( $a , $b );
} ' ,
'error_message' => 'LessSpecificReturnStatement - src' . DIRECTORY_SEPARATOR . 'somefile.php:9:32 - The type \'array{A: int|string}<string, string>\' is more general' ,
],
2021-08-20 00:43:49 +02:00
'arrayWalkOverObject' => [
' < ? php
$o = new stdClass ();
array_walk ( $o , " var_dump " );
' ,
'error_message' => 'RawObjectIteration' ,
],
2021-08-31 22:18:20 +02:00
'arrayWalkRecursiveOverObject' => [
' < ? php
$o = new stdClass ();
array_walk_recursive ( $o , " var_dump " );
' ,
'error_message' => 'RawObjectIteration' ,
],
2020-01-31 19:58:02 +01:00
];
}
}