[ 'code' => ' rand(0, 10), "b" => rand(0, 10), "c" => null]); $e = array_filter( ["a" => rand(0, 10), "b" => rand(0, 10), "c" => null], function(?int $i): bool { return true; } );', 'assertions' => [ '$d' => 'array{a?: int<1, 10>, b?: int<1, 10>}', '$e' => 'array|null>', ], ], 'positiveIntArrayFilter' => [ 'code' => ' $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]); }', ], 'arrayFilterAdvanced' => [ 'code' => ' 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', '$g' => 'array', ], ], 'arrayFilterIgnoreNullable' => [ 'code' => ' */ 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' => [], 'ignored_issues' => ['PossiblyInvalidArgument'], ], 'arrayFilterAllowTrim' => [ 'code' => ' [ 'code' => ' [ 'code' => ' $a * @return array */ function foo(array $a) : array { return array_filter($a, "is_object"); }', ], 'arrayFilterFleshOutType' => [ 'code' => ' $statusList */ $statusList = [Baz::STATUS_FOO, Baz::STATUS_QUX]; $statusList = array_filter($statusList, [Baz::class, "isStatus"]);', ], 'arrayKeysNonEmpty' => [ 'code' => ' 1, "b" => 2]);', 'assertions' => [ '$a' => 'non-empty-list', ], ], 'arrayKeysMixed' => [ 'code' => ' 5]; $a = array_keys($b);', 'assertions' => [ '$a' => 'list', ], 'ignored_issues' => ['MixedArgument'], ], 'arrayValues' => [ 'code' => ' 1, "b" => 2]); $c = array_values(["a" => "hello", "b" => "jello"]);', 'assertions' => [ '$b' => 'non-empty-list', '$c' => 'non-empty-list', ], ], 'arrayCombine' => [ 'code' => ' [ '$c===' => 'array{a: 1, b: 2, c: 3}', ], 'ignored_issues' => [], 'php_version' => '7.4', ], 'arrayCombineDynamicParams' => [ 'code' => ' */ function getStrings(): array { return []; } /** @return array */ function getInts(): array { return []; } $c = array_combine(getStrings(), getInts());', 'assertions' => [ '$c' => 'array|false', ], ], 'arrayCombineDynamicParamsNonEmpty' => [ 'code' => ' */ function getStrings(): array { return ["test"]; } /** @return non-empty-array */ function getInts(): array { return [123, 321]; } $c = array_combine(getStrings(), getInts());', 'assertions' => [ '$c' => 'false|non-empty-array', ], ], 'arrayCombineDynamicParamsPHP8' => [ 'code' => ' */ function getStrings(): array { return ["test"]; } /** @return non-empty-array */ function getInts(): array { return [123]; } $c = array_combine(getStrings(), getInts());', 'assertions' => [ '$c' => 'non-empty-array', ], 'ignored_issues' => [], 'php_version' => '8.0', ], 'arrayMergeOverWrite' => [ 'code' => ' "a1"]; $a2 = ["a" => "a2"]; $result = array_merge($a1, $a2); ', 'assertions' => [ '$result===' => "array{a: 'a2'}", ], ], 'arrayMergeListOfShapes' => [ 'code' => ' */ $a = []; $b = array_merge(...$a); /** @var non-empty-list */ $c = []; $d = array_merge(...$c); ', 'assertions' => [ '$b' => 'array{a?: int}', '$d' => 'array{a: int}', ], ], 'arrayMergeIntArrays' => [ 'code' => ' [ '$d===' => "list{'a', 'b', 'c', 'd', 1, 2, 3}", ], ], 'arrayMergePossiblyUndefined' => [ 'code' => ' 5], $opts); }', ], 'arrayMergeListResultWithArray' => [ 'code' => ' $list * @return list */ function bar(array $list) : array { return array_merge($list, ["test"]); }', ], 'arrayMergeListResultWithList' => [ 'code' => ' $list * @return list */ function foo(array $list) : array { return array_merge($list, ["test"]); }', ], 'arrayMergeTypes' => [ 'code' => ' [ 'code' => ' */ $a = []; /** @var non-empty-list */ $b = []; $c = array_merge($a, $b); $d = array_merge($b, $a);', 'assertions' => [ // todo: this first type is not entirely correct //'$c===' => "list{int|string, ..., int|string>}", '$c===' => "list{string, ..., int|string>}", '$d===' => "list{string, ..., int|string>}", ], ], 'arrayMergeEmpty' => [ 'code' => ' 0]]; $b = array_merge(...$test); ', 'assertions' => [ '$a===' => 'array', '$b===' => 'array{test: 0}', ], ], 'arrayReplaceIntArrays' => [ 'code' => ' [ '$d===' => "list{1, 2, 3, 'd'}", ], ], 'arrayReplacePossiblyUndefined' => [ 'code' => ' 5], $opts); }', ], 'arrayReplaceListResultWithArray' => [ 'code' => ' $list * @return list */ function bar(array $list) : array { return array_replace($list, ["test"]); }', ], 'arrayReplaceListResultWithList' => [ 'code' => ' $list * @return list */ function foo(array $list) : array { return array_replace($list, ["test"]); }', ], 'arrayReplaceTypes' => [ 'code' => ' [ 'code' => ' 4]);', 'assertions' => [ '$d' => 'non-empty-array', ], ], 'arrayReverseDontPreserveKeyExplicitArg' => [ 'code' => ' 4], false);', 'assertions' => [ '$d' => 'non-empty-array', ], ], 'arrayReversePreserveKey' => [ 'code' => ' [ '$d' => 'array{0: string, 1: string, 2: int}', ], ], 'arrayDiff' => [ 'code' => ' 5, "b" => 12], [5]);', 'assertions' => [ '$d' => 'array', ], ], 'arrayDiffIsVariadic' => [ 'code' => ' [], ], 'arrayDiffKeyIsVariadic' => [ 'code' => ' [], ], 'arrayDiffAssoc' => [ 'code' => ' $a * @var array $b * @var array $c */ $r = array_diff_assoc($a, $b, $c);', 'assertions' => [ '$r' => 'array', ], ], 'arrayPopMixed' => [ 'code' => ' 5, "c" => 6]; $a = array_pop($b);', 'assertions' => [ '$a' => 'mixed', '$b' => 'mixed', ], 'ignored_issues' => ['MixedAssignment', 'MixedArgument'], ], 'arrayPopNonEmpty' => [ 'code' => ' */ $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' => [ 'code' => ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (isset($a["a"])) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCount' => [ 'code' => ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (count($a)) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayShiftNonEmptyList' => [ 'code' => ' [ 'code' => '|array{null} $arr * @return array */ function foo(array $arr) { array_shift($arr); return $arr; }', ], 'arrayPopNonEmptyAfterCountEqualsOne' => [ 'code' => ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (count($a) === 1) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountSoftEqualsOne' => [ 'code' => ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (count($a) == 1) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountGreaterThanOne' => [ 'code' => ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (count($a) > 0) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountGreaterOrEqualsOne' => [ 'code' => ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (count($a) >= 1) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountEqualsOneReversed' => [ 'code' => ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (1 === count($a)) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountSoftEqualsOneReversed' => [ 'code' => ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (1 == count($a)) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountGreaterThanOneReversed' => [ 'code' => ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (0 < count($a)) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountGreatorOrEqualToOneReversed' => [ 'code' => ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (1 <= count($a)) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayNotEmptyArrayAfterCountBiggerThanEqualToOne' => [ 'code' => ' */ $leftCount = [1, 2, 3]; if (count($leftCount) >= 1) { echo $leftCount[0]; } /** @var list */ $rightCount = [1, 2, 3]; if (1 <= count($rightCount)) { echo $rightCount[0]; }', ], 'arrayNotEmptyArrayAfterCountBiggerThanTwo' => [ 'code' => ' */ $leftCount = [1, 2, 3]; if (count($leftCount) > 2) { echo $leftCount[0]; } /** @var list */ $rightCount = [1, 2, 3]; if (2 < count($rightCount)) { echo $rightCount[0]; }', ], 'arrayEmptyArrayAfterCountLessThanOne' => [ 'code' => ' */ $leftCount = [1, 2, 3]; assert (count($leftCount) < 1); /** @var list */ $rightCount = [1, 2, 3]; assert (1 > count($rightCount));', 'assertions' => [ '$leftCount' => 'array', '$rightCount' => 'array', ], ], 'arrayEmptyArrayAfterCountLessThanEqualToZero' => [ 'code' => ' */ $leftCount = [1, 2, 3]; assert (count($leftCount) <= 0); /** @var list */ $rightCount = [1, 2, 3]; assert (0 >= count($rightCount));', 'assertions' => [ '$leftCount' => 'array', '$rightCount' => 'array', ], ], 'arrayNotNonEmptyArrayAfterCountGreaterThanEqualToZero' => [ 'code' => ' */ $leftCount = [1, 2, 3]; assert(count($leftCount) >= 0); /** @var list */ $rightCount = [1, 2, 3]; assert(0 <= count($rightCount));', 'assertions' => [ '$leftCount' => 'list', '$rightCount' => 'list', ], ], 'arrayNotNonEmptyArrayAfterCountGreaterThanMinusOne' => [ 'code' => ' */ $leftCount = [1, 2, 3]; assert (count($leftCount) > -1); /** @var list */ $rightCount = [1, 2, 3]; assert (-1 < count($rightCount));', 'assertions' => [ '$leftCount' => 'list', '$rightCount' => 'list', ], ], 'arrayNonEmptyArrayAfterCountGreaterThanEqualToOne' => [ 'code' => ' */ $leftCount = [1, 2, 3]; assert(count($leftCount) >= 1); /** @var list */ $rightCount = [1, 2, 3]; assert(1 <= count($rightCount));', 'assertions' => [ '$leftCount' => 'non-empty-list', '$rightCount' => 'non-empty-list', ], ], 'arrayNonEmptyArrayAfterCountGreaterThanZero' => [ 'code' => ' */ $leftCount = [1, 2, 3]; assert (count($leftCount) > 0); /** @var list */ $rightCount = [1, 2, 3]; assert (0 < count($rightCount));', 'assertions' => [ '$leftCount' => 'non-empty-list', '$rightCount' => 'non-empty-list', ], ], 'arrayPopNonEmptyAfterArrayAddition' => [ 'code' => ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $a["foo"] = 10; $b = array_pop($a);', 'assertions' => [ '$b' => 'int', ], ], 'SKIPPED-arrayPopNonEmptyAfterMixedArrayAddition' => [ 'code' => ' 5, "b" => 6, "c" => 7]; $a[] = "hello"; $b = array_pop($a);', 'assertions' => [ '$b' => 'mixed|string', ], 'ignored_issues' => [ 'MixedAssignment', ], ], 'uasort' => [ 'code' => ' $b ? 1 : -1; } $manifest = ["a" => 1, "b" => 2]; uasort( $manifest, "foo" ); $emptyManifest = []; uasort( $emptyManifest, "foo" ); ', 'assertions' => [ '$manifest' => 'non-empty-array', '$emptyManifest' => 'array', ], ], 'uksort' => [ 'code' => ' $b; } $array = ["b" => 1, "a" => 2]; uksort( $array, "foo" ); $emptyArray = []; uksort( $emptyArray, "foo" );', 'assertions' => [ '$array' => 'non-empty-array', '$emptyArray' => 'array', ], ], 'arrayMergeTKeyedArray' => [ 'code' => ' $a * @return array */ function foo($a) { return $a; } $a1 = ["hi" => 3]; $a2 = ["bye" => 5]; $a3 = array_merge($a1, $a2); foo($a3);', 'assertions' => [ '$a3' => 'array{bye: int, hi: int}', ], ], 'arrayReplaceTKeyedArray' => [ 'code' => ' $a * @return array */ function foo($a) { return $a; } $a1 = ["hi" => 3]; $a2 = ["bye" => 5]; $a3 = array_replace($a1, $a2); foo($a3);', 'assertions' => [ '$a3' => 'array{bye: int, hi: int}', ], ], 'arrayRand' => [ 'code' => ' "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', '$more_vars' => 'list{string, string}', '$e' => 'int<0, 1>', ], ], 'arrayRandMultiple' => [ 'code' => ' "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', '$f' => 'list', ], ], 'arrayKeysNoEmpty' => [ 'code' => ' [], 'ignored_issues' => ['MixedAssignment', 'MixedArgument', 'MixedArgumentTypeCoercion', 'NoValue'], ], 'arrayPopNotNullable' => [ 'code' => ' $list */ function test(array $list) : void { while (!empty($list)) { $tmp = array_pop($list); expectsInt($tmp["item"]); } }', ], 'arrayFilterWithAssert' => [ 'code' => ' [ '$a' => 'array, string>', ], 'ignored_issues' => [ 'MissingClosureParamType', ], ], 'arrayFilterUseKey' => [ 'code' => ' function (): string { return "baz"; }, ]; $foo = array_filter( $foo, function (string $key): bool { return $key === "bar"; }, ARRAY_FILTER_USE_KEY );', 'assertions' => [ '$foo' => 'array', ], ], 'ignoreFalsableCurrent' => [ 'code' => ' [ 'code' => ' [ '$foo' => 'int', ], ], 'arraySumOnlyInt' => [ 'code' => ' [ '$foo' => 'int', ], ], 'arraySumOnlyFloat' => [ 'code' => ' [ '$foo' => 'float', ], ], 'arraySumNumeric' => [ 'code' => ' [ '$foo' => 'float|int', ], ], 'arraySumMix' => [ 'code' => ' [ '$foo' => 'float', ], ], 'arrayMapWithArrayAndCallable' => [ 'code' => ' */ function foo(array $v): array { $r = array_map("intval", $v); return $r; }', ], 'arrayMapTKeyedArrayAndCallable' => [ 'code' => ' 1, "key2"=> "2"]; $r = array_map("intval", $v); return $r; }', ], 'arrayMapTKeyedArrayListAndCallable' => [ 'code' => ' $list */ function takesList(array $list): void {} takesList( array_map( "intval", ["1", "2", "3"] ) );', ], 'arrayMapTKeyedArrayAndClosure' => [ 'code' => ' 1, "key2"=> "2"]; $r = array_map(function($i) : int { return intval($i);}, $v); return $r; }', 'assertions' => [], 'ignored_issues' => [ 'MissingClosureParamType', ], ], 'arrayMapTKeyedArrayListAndClosure' => [ 'code' => ' $list */ function takesList(array $list): void {} takesList( array_map( function (string $str): string { return $str . "x"; }, ["foo", "bar", "baz"] ) );', ], 'arrayMapUntypedCallable' => [ 'code' => ' $array */ $a = array_map($callable, $array); /** * @var callable $callable * @var array $array */ $b = array_map($callable, $array, $array); /** * @var callable $callable * @var list $list */ $c = array_map($callable, $list); /** * @var callable $callable * @var list $list */ $d = array_map($callable, $list, $list);', 'assertions' => [ '$a' => 'array', '$b' => 'list', '$c' => 'list', '$d' => 'list', ], ], 'arrayFilterGoodArgs' => [ 'code' => ' [ 'code' => ' [], 'ignored_issues' => ['UndefinedClass'], ], 'arrayFilterIgnoreMissingMethod' => [ 'code' => ' [], 'ignored_issues' => ['UndefinedMethod'], ], 'arrayMapParamDefault' => [ 'code' => ' [ 'code' => ' [ 'code' => ' [ '$a===' => 'list{0, 0, 0}', // Techinically this doesn't cover the case of running on 8.0 but nvm '$b===' => 'array{-1: 0, 0: 0, 1: 0}', '$c===' => 'array{-2: 0, 0: 0, 1: 0}', ], 'ignored_issues' => [], 'php_version' => '7.4', ], 'arrayFillLiteral80' => [ 'code' => ' [ '$a===' => 'list{0, 0, 0}', '$b===' => 'array{-1: 0, 0: 0, 1: 0}', '$c===' => 'array{-1: 0, -2: 0, 0: 0}', ], 'ignored_issues' => [], 'php_version' => '8.0', ], 'implodeMultiDimensionalArray' => [ 'code' => ' [ 'code' => ' [ '$a===' => 'non-empty-literal-string', '$b===' => 'non-empty-literal-string', ], ], 'implodeArrayOfNonEmptyStringAndEmptyString' => [ 'code' => ' [ '$a===' => 'non-empty-literal-string', '$b===' => 'non-empty-string', ], ], 'implodeEmptyArrayAndString' => [ 'code' => ' [ '$a===' => 'string', '$b===' => 'string', ], ], 'key' => [ 'code' => ' 1, "two" => 3]; $b = key($a);', 'assertions' => [ '$b' => 'string', ], ], 'keyEmptyArray' => [ 'code' => ' [ '$b' => 'null', ], ], 'keyNonEmptyArray' => [ 'code' => ' [ 'code' => ' 1, "two" => 3]; $b = current($a);', 'assertions' => [ '$b' => 'int', ], ], 'currentEmptyArray' => [ 'code' => ' [ '$b' => 'false', ], ], 'currentNonEmptyArray' => [ 'code' => ' $arr * @return int */ function foo(array $arr) { return current($arr); }', ], 'reset' => [ 'code' => ' 1, "two" => 3]; $b = reset($a);', 'assertions' => [ '$b' => 'int', ], ], 'resetEmptyArray' => [ 'code' => ' [ '$b' => 'false', ], ], 'resetNonEmptyArray' => [ 'code' => ' $arr * @return int */ function foo(array $arr) { return reset($arr); }', ], 'end' => [ 'code' => ' 1, "two" => 3]; $b = end($a);', 'assertions' => [ '$b' => 'int', ], ], 'endEmptyArray' => [ 'code' => ' [ '$b' => 'false', ], ], 'endNonEmptyArray' => [ 'code' => ' $arr * @return int */ function foo(array $arr) { return end($arr); }', ], 'arrayKeyFirst' => [ 'code' => ' */ 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' => [ 'code' => ' 1, "two" => 3]; $b = array_key_first($a); $c = $a[$b];', 'assertions' => [ '$b' => 'string', '$c' => 'int', ], ], 'arrayKeyFirstEmpty' => [ 'code' => ' [ '$b' => 'null', ], ], 'arrayKeyLast' => [ 'code' => ' */ 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' => [ 'code' => ' 1, "two" => 3]; $b = array_key_last($a); $c = $a[$b];', 'assertions' => [ '$b' => 'string', '$c' => 'int', ], ], 'arrayKeyLastEmpty' => [ 'code' => ' [ '$b' => 'null', ], ], 'arrayResetNonEmptyArray' => [ 'code' => ' */ function makeArray(): array { return ["one" => 1, "two" => 3]; } $a = makeArray(); $b = reset($a);', 'assertions' => [ '$b' => 'int', ], ], 'arrayResetNonEmptyList' => [ 'code' => ' */ function makeArray(): array { return [1, 3]; } $a = makeArray(); $b = reset($a);', 'assertions' => [ '$b' => 'int', ], ], 'arrayResetNonEmptyTKeyedArray' => [ 'code' => ' 1, "two" => 3]; $b = reset($a);', 'assertions' => [ '$b' => 'int', ], ], 'arrayResetEmptyArray' => [ 'code' => ' [ '$b' => 'false', ], ], 'arrayResetEmptyList' => [ 'code' => ' */ function makeArray(): array { return []; } $a = makeArray(); $b = reset($a);', 'assertions' => [ '$b' => 'false', ], ], 'arrayResetMaybeEmptyArray' => [ 'code' => ' */ function makeArray(): array { return ["one" => 1, "two" => 3]; } $a = makeArray(); $b = reset($a);', 'assertions' => [ '$b' => 'false|int', ], ], 'arrayResetMaybeEmptyList' => [ 'code' => ' */ function makeArray(): array { return []; } $a = makeArray(); $b = reset($a);', 'assertions' => [ '$b' => 'false|int', ], ], 'arrayResetMaybeEmptyTKeyedArray' => [ 'code' => ' [ '$b' => 'false|int', ], ], 'arrayEndNonEmptyArray' => [ 'code' => ' */ function makeArray(): array { return ["one" => 1, "two" => 3]; } $a = makeArray(); $b = end($a);', 'assertions' => [ '$b' => 'int', ], ], 'arrayEndNonEmptyList' => [ 'code' => ' */ function makeArray(): array { return [1, 3]; } $a = makeArray(); $b = end($a);', 'assertions' => [ '$b' => 'int', ], ], 'arrayEndNonEmptyTKeyedArray' => [ 'code' => ' 1, "two" => 3]; $b = end($a);', 'assertions' => [ '$b' => 'int', ], ], 'arrayEndEmptyArray' => [ 'code' => ' [ '$b' => 'false', ], ], 'arrayEndEmptyList' => [ 'code' => ' */ function makeArray(): array { return []; } $a = makeArray(); $b = end($a);', 'assertions' => [ '$b' => 'false', ], ], 'arrayEndMaybeEmptyArray' => [ 'code' => ' */ function makeArray(): array { return ["one" => 1, "two" => 3]; } $a = makeArray(); $b = end($a);', 'assertions' => [ '$b' => 'false|int', ], ], 'arrayEndMaybeEmptyList' => [ 'code' => ' */ function makeArray(): array { return []; } $a = makeArray(); $b = end($a);', 'assertions' => [ '$b' => 'false|int', ], ], 'arrayEndMaybeEmptyTKeyedArray' => [ 'code' => ' [ '$b' => 'false|int', ], ], 'arrayColumnInference' => [ 'code' => '> */ function makeGenericArray(): array { return []; } /** @return array */ function makeShapeArray(): array { return []; } /** @return array */ function makeUnionArray(): array { return []; } /** @return array */ function makeKeyedArray(): array { return []; } $a = array_column([[1], [2], [3]], 0); $b = array_column([["a" => 1], ["a" => 2], ["a" => 3]], "a"); $c = array_column([["a" => 1], ["a" => 2], ["a" => 3]], null, "a"); $d = array_column([["a" => 1], ["a" => 2], ["a" => 3]], null, "b"); $e = array_column([["a" => 1], ["a" => 2], ["a" => 3]], rand(0,1) ? "a" : "b", "b"); $f = array_column([["k" => "a", "v" => 1], ["k" => "b", "v" => 2]], "v", "k"); $g = array_column([], 0); $h = array_column(makeMixedArray(), 0); $i = array_column(makeMixedArray(), 0, "k"); $j = array_column(makeMixedArray(), 0, null); $k = array_column(makeGenericArray(), 0); $l = array_column(makeShapeArray(), 0); $m = array_column(makeUnionArray(), 0); $n = array_column([[0 => "test"]], 0); $o = array_column(makeKeyedArray(), "y"); $p_prepare = makeKeyedArray(); assert($p_prepare !== []); $p = array_column($p_prepare, "y"); ', 'assertions' => [ '$a===' => 'list{1, 2, 3}', '$b===' => 'list{1, 2, 3}', '$c' => 'array{1: array{a: int}, 2: array{a: int}, 3: array{a: int}}', '$d' => 'array', '$e' => 'array', '$f' => 'array{a: int, b: int}', '$g' => 'list', '$h' => 'list', '$i' => 'array', '$j' => 'list', '$k' => 'list', '$l' => 'list', '$m' => 'list', '$n' => 'list{string}', '$o' => 'list', '$p' => 'list', ], ], 'arrayColumnExactInference' => [ 'code' => ' "a"], ["v" => "b"], ["v" => "c"], ["v" => "d"], ], "v"); $b = array_column([ ["v" => "a"], [], ["v" => "c"], ["v" => "d"], ], "v"); $c = array_column([ ["v" => "a"], 123, ["v" => "c"], ["v" => "d"], ], "v"); $d = array_column([ ["v" => "a", "k" => "A"], ["v" => "b", "k" => "B"], ["v" => "c", "k" => "C"], ["v" => "d", "k" => "D"], ], "v", "k"); $e = array_column([ ["v" => "a", "k" => 0], ["v" => "b", "k" => 1], ["v" => "c", "k" => 2], ["v" => "d", "k" => 3], ], "v", "k"); $f = array_column([ ["v" => "a", "k" => 3], ["v" => "b", "k" => 2], ["v" => "c", "k" => 1], ["v" => "d", "k" => 0], ], "v", "k"); $g = array_column([ ["v" => "a", "k" => 0], ["v" => "b", "k" => 1], ["v" => "c", "k" => 2], ["v" => "d", "k" => 3], ], null, "k"); $h = array_column([ "a" => ["k" => 0], "b" => ["k" => 1], "c" => ["k" => 2], ], null, "k"); /** @var array{a: array{v: 0}, b?: array{v: 1}} */ $aa = []; $i = array_column($aa, "v"); /** @var array{a: array{v: "a", k: 0}, b?: array{v: "b", k: 1}, c: array{v: "c", k: 2}} */ $aa = []; $j = array_column($aa, null, "k"); /** @var array{a: array{v: "a", k: 0}, b: array{v: "b", k: 1}, c?: array{v: "c", k: 2}} */ $aa = []; $k = array_column($aa, null, "k"); $l = array_column(["test" => ["v" => "a"], "test2" => ["v" => "b"]], "v"); ', 'assertions' => [ '$a===' => "list{'a', 'b', 'c', 'd'}", '$b===' => "list{'a', 'c', 'd'}", '$c===' => "list{'a', 'c', 'd'}", '$d===' => "array{A: 'a', B: 'b', C: 'c', D: 'd'}", '$e===' => "list{'a', 'b', 'c', 'd'}", '$f===' => "array{0: 'd', 1: 'c', 2: 'b', 3: 'a'}", '$g===' => "list{array{k: 0, v: 'a'}, array{k: 1, v: 'b'}, array{k: 2, v: 'c'}, array{k: 3, v: 'd'}}", '$h===' => "list{array{k: 0}, array{k: 1}, array{k: 2}}", '$i===' => "list{0: 0, 1?: 1}", '$j===' => "array{0: array{k: 0, v: 'a'}, 1?: array{k: 1, v: 'b'}, 2: array{k: 2, v: 'c'}}", '$k===' => "list{0: array{k: 0, v: 'a'}, 1: array{k: 1, v: 'b'}, 2?: array{k: 2, v: 'c'}}", '$l===' => "list{'a', 'b'}", ], ], 'splatArrayIntersect' => [ 'code' => ' [ '$bar' => 'array, int>', ], ], 'arrayIntersectIsVariadic' => [ 'code' => ' [], ], 'arrayIntersectKeyIsVariadic' => [ 'code' => ' [], ], 'arrayIntersectKeyNoReturnType' => [ 'code' => ' "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' => [ 'code' => ' $a * @var array $b * @var array $c */ $r = array_intersect_assoc($a, $b, $c);', 'assertions' => [ '$r' => 'array', ], ], 'arrayReduce' => [ 'code' => ' [ '$direct_closure_result' => 'int', '$passed_closure_result' => 'int', '$function_call_result' => 'int', ], ], 'arrayReduceStaticMethods' => [ 'code' => ' [], ], 'arrayReduceMixedReturn' => [ 'code' => ' [], 'ignored_issues' => ['MissingClosureReturnType', 'MixedAssignment'], ], 'arraySpliceArray' => [ 'code' => ' [ '$a' => 'non-empty-list', '$b' => 'list{string, string, string}', '$c' => 'list{int, int, int}', ], ], 'arraySpliceReturn' => [ 'code' => ' [ '$e' => 'list', ], ], 'arraySpliceOtherType' => [ 'code' => ' [ '$d' => 'array', ], ], 'ksortPreserveShape' => [ 'code' => ' 3, "b" => 4]; ksort($a); acceptsAShape($a); /** * @param array{a:int,b:int} $a */ function acceptsAShape(array $a): void {}', ], 'arraySlicePreserveKeys' => [ 'code' => ' 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', '$c' => 'array', '$d' => 'array', ], ], 'arraySliceDontPreserveIntKeys' => [ 'code' => ' "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', '$c' => 'list', '$d' => 'list', ], ], 'arrayReversePreserveNonEmptiness' => [ 'code' => ' [ 'code' => ' */ function Foo(DateTime ...$dateTimes) : array { return array_map( function ($dateTime) { return ($dateTime->format("c")); }, $dateTimes ); }', ], 'inferArrayMapArrowFunctionReturnType' => [ 'code' => ' */ function Foo(DateTime ...$dateTimes) : array { return array_map( fn ($dateTime) => ($dateTime->format("c")), $dateTimes ); }', 'assertions' => [], 'ignored_issues' => [], 'php_version' => '7.4', ], 'arrayPad' => [ 'code' => ' 1, "bar" => 2], 10, 123); $b = array_pad(["a", "b", "c"], 10, "x"); /** @var list $list */ $c = array_pad($list, 10, 0); /** @var array $array */ $d = array_pad($array, 10, "");', 'assertions' => [ '$a' => 'non-empty-array', '$b' => 'non-empty-list', '$c' => 'non-empty-list', '$d' => 'non-empty-array', ], ], 'arrayPadDynamicSize' => [ 'code' => ' 1, "bar" => 2], getSize(), 123); $b = array_pad(["a", "b", "c"], getSize(), "x"); /** @var list $list */ $c = array_pad($list, getSize(), 0); /** @var array $array */ $d = array_pad($array, getSize(), "");', 'assertions' => [ '$a' => 'array', '$b' => 'list', '$c' => 'list', '$d' => 'array', ], ], 'arrayPadZeroSize' => [ 'code' => ' [ '$result' => 'array', ], ], 'arrayPadTypeCombination' => [ 'code' => ' 1, "bar" => "two"], 5, false); $b = array_pad(["a", 2, 3.14], 5, null); /** @var list $list */ $c = array_pad($list, 5, 0); /** @var array $array */ $d = array_pad($array, 5, null);', 'assertions' => [ '$a' => 'non-empty-array', '$b' => 'non-empty-list', '$c' => 'non-empty-list', '$d' => 'non-empty-array', ], ], 'arrayPadMixed' => [ 'code' => ' [ '$a' => 'non-empty-array', '$b' => 'non-empty-list', '$c' => 'non-empty-list', '$d' => 'non-empty-array', ], ], 'arrayPadFallback' => [ 'code' => ' [ '$result' => 'array', ], ], 'arrayChunk' => [ 'code' => ' $list */ $b = array_chunk($list, 2); /** @var array $arr */ $c = array_chunk($arr, 2); ', 'assertions' => [ '$a' => 'list>', '$b' => 'list>', '$c' => 'list>', ], ], 'arrayChunkPreservedKeys' => [ 'code' => ' $list */ $b = array_chunk($list, 2, true); /** @var array $arr */ $c = array_chunk($arr, 2, true);', 'assertions' => [ '$a' => 'list>', '$b' => 'list, string>>', '$c' => 'list>', ], ], 'arrayChunkPreservedKeysExplicitFalse' => [ 'code' => ' $arr */ $result = array_chunk($arr, 2, false);', 'assertions' => [ '$result' => 'list>', ], ], 'arrayChunkMixed' => [ 'code' => ' $list */ $b = array_chunk($list, 2); /** @var mixed[] $arr */ $c = array_chunk($arr, 2);', 'assertions' => [ '$a' => 'list>', '$b' => 'list>', '$c' => 'list>', ], ], 'arrayChunkFallback' => [ 'code' => ' [ '$result' => 'list>', ], ], 'arrayMapPreserveNonEmptiness' => [ 'code' => ' $strings * @psalm-return non-empty-list */ function foo(array $strings): array { return array_map("intval", $strings); }', ], 'SKIPPED-arrayMapZip' => [ 'code' => ' */ function getCharPairs(string $line) : array { $chars = str_split($line); return array_map( null, $chars, array_slice($chars, 1) ); }', ], 'arrayFillKeys' => [ 'code' => ' */ $keys = [1, 2, 3]; $a = array_fill_keys($keys, true); $keys = [1, 2, 3]; $b = array_fill_keys($keys, true); $keys = [0, 1, 2]; $c = array_fill_keys($keys, true); $keys = random_int(0, 1) ? [0] : [0, 1]; $d = array_fill_keys($keys, true); $keys = random_int(0, 1) ? ["a"] : ["a", "b"]; $e = array_fill_keys($keys, true); ', 'assertions' => [ '$a===' => 'array', '$b===' => 'array{1: true, 2: true, 3: true}', '$c===' => 'list{true, true, true}', '$d===' => 'list{0: true, 1?: true}', '$e===' => 'array{a: true, b?: true}', ], ], 'shuffle' => [ 'code' => ' 123, "bar" => 456]; shuffle($array); $emptyArray = []; shuffle($emptyArray);', 'assertions' => [ '$array' => 'non-empty-list', '$emptyArray' => 'list', ], ], 'sort' => [ 'code' => ' 123, "bar" => 456]; sort($array); $emptyArray = []; sort($emptyArray);', 'assertions' => [ '$array' => 'non-empty-list', '$emptyArray' => 'list', ], ], 'rsort' => [ 'code' => ' 123, "bar" => 456]; rsort($array); $emptyArray = []; rsort($emptyArray);', 'assertions' => [ '$array' => 'non-empty-list', '$emptyArray' => 'list', ], ], 'usort' => [ 'code' => ' $b; } $array = ["foo" => 123, "bar" => 456]; usort($array, "baz"); $emptyArray = []; usort($emptyArray, "baz");', 'assertions' => [ '$array' => 'non-empty-list', '$emptyArray' => 'list', ], ], 'closureParamConstraintsMet' => [ 'code' => ' [ 'code' => ' */ function makeAList(int $ofThisInteger): array { return array_filter([$ofThisInteger]); }', ], 'arrayMapWithEmptyArrayReturn' => [ 'code' => '> $elements * @return list */ 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' => [ 'code' => ' $x instanceof B );', 'assertions' => [ // TODO: improve key type '$a' => 'array, B>', '$b' => 'array, B>', ], 'ignored_issues' => [], 'php_version' => '7.4', ], 'arrayMergeTwoExplicitLists' => [ 'code' => ' $foo */ function foo(array $foo) : void {} $foo1 = [1, 2, 3]; $foo2 = [1, 4, 5]; foo(array_merge($foo1, $foo2));', ], 'arrayMergeTwoPossiblyFalse' => [ 'code' => ' [ '$a' => 'list', ], ], 'arrayReplaceTwoExplicitLists' => [ 'code' => ' $foo */ function foo(array $foo) : void {} $foo1 = [1, 2, 3]; $foo2 = [1, 4, 5]; foo(array_replace($foo1, $foo2));', ], 'arrayReplaceTwoPossiblyFalse' => [ 'code' => ' [ '$a' => 'list', ], ], 'arrayMapPossiblyFalseIgnored' => [ 'code' => 'format("Y-m-d")]; takesString($a[0]); array_map("takesString", $a);', ], 'arrayMapZip' => [ 'code' => ' [ '$d===' => "list{list{1, 'one', 'uno'}, list{2, 'two', 'dos'}, list{3, 'three', 'tres'}, list{4, 'four', 'cuatro'}, list{5, 'five', 'cinco'}, list{null, null, 'seis'}}", ], 'ignored_issues' => [], 'php_version' => '7.4', ], 'arrayMapMoreZip' => [ 'code' => ' 1]); $d = array_map(null, [], []); ', 'assertions' => [ '$a===' => 'array', '$b===' => 'list{1}', '$c===' => 'array{test: 1}', '$d===' => 'array', ], 'ignored_issues' => [], 'php_version' => '7.4', ], 'arrayMapExplicitZip' => [ 'code' => ' [$a => $b], $as, $bs);', 'assertions' => [], 'ignored_issues' => [], 'php_version' => '7.4', ], 'spliceTurnsintKeyedInputToList' => [ 'code' => ' $elements * @return list */ function bar(array $elements, int $index, string $element) : array { array_splice($elements, $index, 0, [$element]); return $elements; }', ], 'arrayChangeKeyCaseWithNonStringKeys' => [ 'code' => ' 42]; echo array_change_key_case($a, CASE_LOWER)[0];', ], 'mapInterfaceMethod' => [ 'code' => ' $strings * @return list */ function mapList(MapperInterface $m, array $strings): array { return array_map([$m, "map"], $strings); }', ], 'arrayShiftComplexArray' => [ 'code' => ' $slugParts */ function foo(array $slugParts) : void { if (!$slugParts) { $slugParts = [""]; } array_shift($slugParts); if (!empty($slugParts)) {} }', ], 'arrayMergeKeepLastKeysAndType' => [ 'code' => ' $b * * @return array{A: int, ...} */ function merger(array $a, array $b) : array { return array_merge($b, $a); }', ], 'arrayMergeKeepFirstKeysSameType' => [ 'code' => ' $b * * @return array{A: int, ...} */ function merger(array $a, array $b) : array { return array_merge($a, $b); }', ], 'arrayReplaceKeepLastKeysAndType' => [ 'code' => ' $b * * @return array{A: int, ...} */ function merger(array $a, array $b) : array { return array_replace($b, $a); }', ], 'arrayReplaceKeepFirstKeysSameType' => [ 'code' => ' $b * * @return array{A: int, ...} */ function merger(array $a, array $b) : array { return array_replace($a, $b); }', ], 'filteredArrayCanBeEmpty' => [ 'code' => ' [ 'code' => ' $lengths */ function doStuff($lengths): void { /** @psalm-suppress MixedArgument, MixedAssignment */ $length = array_shift($lengths); if ($length !== null) {} }', ], 'countOnListIntoTuple' => [ 'code' => ' $list */ function bar(array $list) : void { if (count($list) === 2) { foo($list); } }', ], 'arrayColumnwithKeyedArrayWithoutRedundantUnion' => [ 'code' => ' $foos */ function foo(array $foos): void { array_multisort($formLayoutFields, SORT_ASC, array_column($foos, "y")); }', ], 'arrayMapGenericObject' => [ 'code' => ' $container * @param array $data * @return array */ function bar(Container $container, array $data): array { return array_map( [$container, "get"], $data ); }', ], 'arrayMapShapeAndGenericArray' => [ 'code' => ' to array{0:string} throw new InvalidArgumentException; } $line = array_map( // should not destroy part function($val) { return (int)$val; }, $line ); ', 'assertions' => [ '$line===' => 'array{0: int, ...}', ], ], 'arrayUnshiftOnEmptyArrayMeansNonEmptyList' => [ 'code' => ' */ function foo(): array { $a = []; array_unshift($a, "string"); return $a; }', ], 'keepClassStringInOffsetThroughArrayMerge' => [ 'code' => ' */ private array $a; public function __construct() { $this->a = []; } public function handle(): void { $b = [A::class => "d"]; $this->a = array_merge($this->a, $b); } } ', ], 'mergeBetweenSealedArrayWithPossiblyUndefinedAndMixedArrayIsMixedArray' => [ 'code' => ' "a"]; } $b = $statics + $closure->getStaticVariables(); /** @psalm-check-type $b = array */ $_a = count($b); /** @psalm-check-type $_a = int<0, max> */ } ', ], ]; } public function providerInvalidCodeParse(): iterable { return [ 'arrayFilterWithoutTypes' => [ 'code' => ' 5, "b" => 12, "c" => null], function(?int $i) { return $GLOBALS["a"]; } );', 'error_message' => 'MixedArgumentTypeCoercion', 'ignored_issues' => ['MissingClosureParamType', 'MissingClosureReturnType'], ], 'arrayFilterUseMethodOnInferrableInt' => [ 'code' => 'foo(); });', 'error_message' => 'InvalidMethodCall', ], 'arrayMapUseMethodOnInferrableInt' => [ 'code' => 'foo(); }, [1, 2, 3, 4]);', 'error_message' => 'InvalidMethodCall', ], 'arrayMapWithNonCallableStringArray' => [ 'code' => ' 'InvalidArgument', ], 'arrayMapWithNonCallableIntArray' => [ 'code' => ' 'InvalidArgument', ], 'arrayFilterBadArgs' => [ 'code' => ' 'InvalidScalarArgument', ], 'arrayFillPositiveConstantLength' => [ 'code' => ' 'TypeDoesNotContainType', ], 'arrayFilterTooFewArgs' => [ 'code' => ' 'TooFewArguments', ], 'arrayMapBadArgs' => [ 'code' => ' 'InvalidScalarArgument', ], 'arrayMapTooFewArgs' => [ 'code' => ' 'TooFewArguments', ], 'arrayMapTooManyArgs' => [ 'code' => ' 'TooManyArguments', ], 'arrayReduceInvalidClosureTooFewArgs' => [ 'code' => ' 'InvalidArgument', 'ignored_issues' => ['MixedTypeCoercion'], ], 'arrayReduceInvalidItemType' => [ 'code' => ' 'InvalidArgument', 'ignored_issues' => ['MissingClosureReturnType'], ], 'arrayReduceInvalidCarryType' => [ 'code' => ' 'InvalidArgument', 'ignored_issues' => ['MissingClosureReturnType'], ], 'arrayReduceInvalidCarryOutputType' => [ 'code' => ' 'InvalidArgument', ], 'arrayPopNotNull' => [ 'code' => ' $list */ function test(array $list) : void { while (!empty($list)) { $tmp = array_pop($list); if ($tmp === null) {} } }', 'error_message' => 'DocblockTypeContradiction', ], 'usortInvalidCallableString' => [ 'code' => ' 'InvalidArgument', ], 'arrayShiftUndefinedVariable' => [ 'code' => ' 'UndefinedVariable', ], 'arrayFilterTKeyedArray' => [ 'code' => ' $ints */ function ints(array $ints) : void {} $brr = array_filter([2,3,0,4,5]); ints($brr);', 'error_message' => 'ArgumentTypeCoercion', ], 'usortOneParamInvalid' => [ 'code' => ' (int) ($a > $b));', 'error_message' => 'InvalidScalarArgument', 'ignored_issues' => [], 'php_version' => '7.4', ], 'usortInvalidComparison' => [ 'code' => ' 'InvalidArgument', ], 'arrayMergeKeepFirstKeysButNotType' => [ 'code' => ' $b * * @return array{A: int, ...} */ function merger(array $a, array $b) : array { return array_merge($a, $b); }', 'error_message' => 'LessSpecificReturnStatement - src' . DIRECTORY_SEPARATOR . 'somefile.php:9:32 - The type \'array{A: int|string, ...}\' is more general', ], 'arrayReplaceKeepFirstKeysButNotType' => [ 'code' => ' $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, ...}\' is more general', ], 'arrayWalkOverObject' => [ 'code' => ' 'RawObjectIteration', ], 'arrayWalkRecursiveOverObject' => [ 'code' => ' 'RawObjectIteration', ], 'implodeWithNonStringableArgs' => [ 'code' => ' 'InvalidArgument', ], 'arrayCombineNotMatching' => [ 'code' => ' 'InvalidArgument', ], 'arrayCombineNotMatchingPHP8' => [ 'code' => ' 'InvalidArgument', ], 'arrayMergeNoNamed' => [ 'code' => ' []]; array_merge(...$map); ', 'error_message' => 'NamedArgumentNotAllowed', ], 'arrayMergeRecursiveNoNamed' => [ 'code' => ' []]; array_merge_recursive(...$map); ', 'error_message' => 'NamedArgumentNotAllowed', ], ]; } }