,error_levels?:string[]}> */ public function providerValidCodeParse() { return [ 'arrayFilter' => [ ' 5, "b" => 12, "c" => null]); $e = array_filter( ["a" => 5, "b" => 12, "c" => null], function(?int $i): bool { return true; } );', 'assertions' => [ '$d' => 'array', '$e' => 'array', ], ], 'arrayFilterAdvanced' => [ ' 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' => [ ' */ 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' => [ ' [ ' [ ' $a * @return array */ function foo(array $a) : array { return array_filter($a, "is_object"); }', ], 'arrayKeysNonEmpty' => [ ' 1, "b" => 2]);', 'assertions' => [ '$a' => 'non-empty-list', ], ], 'arrayKeysMixed' => [ ' 5]; $a = array_keys($b);', 'assertions' => [ '$a' => 'list', ], 'error_levels' => ['MixedArgument'], ], 'arrayValues' => [ ' 1, "b" => 2]); $c = array_values(["a" => "hello", "b" => "jello"]);', 'assertions' => [ '$b' => 'non-empty-list', '$c' => 'non-empty-list', ], ], 'arrayCombine' => [ ' [ '$c' => 'array|false', ], ], 'arrayCombineFalse' => [ ' [ '$c' => 'array|false', ], ], 'arrayMerge' => [ ' [ '$d' => 'array{0: string, 1: string, 2: string, 3: int, 4: int, 5: int}', ], ], 'arrayMergeListResult' => [ ' $list * @return list */ function foo(array $list) : array { return array_merge($list, ["test"]); } /** * @param array $list * @return list */ function bar(array $list) : array { return array_merge($list, ["test"]); }', ], 'arrayReverseDontPreserveKey' => [ ' 4]);', 'assertions' => [ '$d' => 'non-empty-array', ], ], 'arrayReverseDontPreserveKeyExplicitArg' => [ ' 4], false);', 'assertions' => [ '$d' => 'non-empty-array', ], ], 'arrayReversePreserveKey' => [ ' [ '$d' => 'non-empty-array', ], ], 'arrayDiff' => [ ' 5, "b" => 12], [5]);', 'assertions' => [ '$d' => 'array', ], ], 'arrayDiffIsVariadic' => [ ' [], ], 'arrayDiffKeyIsVariadic' => [ ' [], ], 'arrayDiffAssoc' => [ ' $a * @var array $b * @var array $c */ $r = array_diff_assoc($a, $b, $c);', 'assertions' => [ '$r' => 'array', ], ], 'arrayPopMixed' => [ ' 5, "c" => 6]; $a = array_pop($b);', 'assertions' => [ '$a' => 'mixed', '$b' => 'mixed', ], 'error_levels' => ['MixedAssignment', 'MixedArgument'], ], 'arrayPopNonEmpty' => [ ' */ $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' => [ ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (isset($a["a"])) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCount' => [ ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (count($a)) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayShiftNonEmptyList' => [ ' [ ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (count($a) === 1) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountSoftEqualsOne' => [ ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (count($a) == 1) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountGreaterThanOne' => [ ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (count($a) > 0) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountGreaterOrEqualsOne' => [ ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (count($a) >= 1) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountEqualsOneReversed' => [ ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (1 === count($a)) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountSoftEqualsOneReversed' => [ ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (1 == count($a)) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountGreaterThanOneReversed' => [ ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (0 < count($a)) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterCountGreatorOrEqualToOneReversed' => [ ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $b = 5; if (1 <= count($a)) { $b = array_pop($a); }', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterArrayAddition' => [ ' */ $a = ["a" => 5, "b" => 6, "c" => 7]; $a["foo"] = 10; $b = array_pop($a);', 'assertions' => [ '$b' => 'int', ], ], 'arrayPopNonEmptyAfterMixedArrayAddition' => [ ' 5, "b" => 6, "c" => 7]; $a[] = "hello"; $b = array_pop($a);', 'assertions' => [ '$b' => 'mixed|string', ], 'error_levels' => [ 'MixedAssignment', ], ], 'uasort' => [ ' 1, "b" => 2]; uasort( $manifest, function (int $a, int $b) { return $a > $b ? 1 : -1; } );', 'assertions' => [ '$manifest' => 'array' ], ], 'uksort' => [ ' 1, "a" => 2]; uksort( $array, function (string $a, string $b) { return $a <=> $b; } );', 'assertions' => [ '$array' => 'array', ], ], 'arrayMergeObjectLike' => [ ' $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}', ], ], 'arrayRand' => [ ' "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' => 'array{string, string}', '$e' => 'int', ], ], 'arrayRandMultiple' => [ ' "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|string', ], ], 'arrayKeysNoEmpty' => [ ' [], 'error_levels' => ['MixedAssignment', 'MixedArgument', 'MixedArgumentTypeCoercion'], ], 'arrayPopNotNullable' => [ ' $list */ function test(array $list) : void { while (!empty($list)) { $tmp = array_pop($list); expectsInt($tmp["item"]); } }', ], 'arrayFilterWithAssert' => [ ' [ '$a' => 'array', ], 'error_levels' => [ 'MissingClosureParamType', ], ], 'arrayFilterUseKey' => [ ' function (): string { return "baz"; }, ]; $foo = array_filter( $foo, function (string $key): bool { return $key === "bar"; }, ARRAY_FILTER_USE_KEY );', 'assertions' => [ '$foo' => 'array', ], ], 'ignoreFalsableCurrent' => [ ' [ ' [ '$foo' => 'float|int', ], ], 'arrayMapWithArrayAndCallable' => [ ' */ function foo(array $v): array { $r = array_map("intval", $v); return $r; }', ], 'arrayMapObjectLikeAndCallable' => [ ' 1, "key2"=> "2"]; $r = array_map("intval", $v); return $r; }', ], 'arrayMapObjectLikeListAndCallable' => [ ' $list */ function takesList(array $list): void {} takesList( array_map( "intval", ["1", "2", "3"] ) );', ], 'arrayMapObjectLikeAndClosure' => [ ' 1, "key2"=> "2"]; $r = array_map(function($i) : int { return intval($i);}, $v); return $r; }', 'assertions' => [], 'error_levels' => [ 'MissingClosureParamType', 'MixedTypeCoercion', ], ], 'arrayMapObjectLikeListAndClosure' => [ ' $list */ function takesList(array $list): void {} takesList( array_map( function (string $str): string { return $str . "x"; }, ["foo", "bar", "baz"] ) );', ], 'arrayMapUntypedCallable' => [ ' $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' => [ ' [ ' [], 'error_levels' => ['UndefinedClass'], ], 'arrayFilterIgnoreMissingMethod' => [ ' [], 'error_levels' => ['UndefinedMethod'], ], 'arrayMapParamDefault' => [ ' [ ' [ ' */ $l = ["a", "b"]; $a = implode(":", $l);', [ '$a===' => 'non-empty-string', ] ], 'key' => [ ' 1, "two" => 3]; $b = key($a); $c = $a[$b];', 'assertions' => [ '$b' => 'null|string', '$c' => 'int', ], ], 'array_key_first' => [ ' 1, "two" => 3]; $b = array_key_first($a); $c = $a[$b];', 'assertions' => [ '$b' => 'null|string', '$c' => 'int', ], ], 'array_key_last' => [ ' 1, "two" => 3]; $b = array_key_last($a); $c = $a[$b];', 'assertions' => [ '$b' => 'null|string', '$c' => 'int', ], ], 'arrayColumnInference' => [ '> */ function makeGenericArray(): array { return []; } /** @return array */ function makeShapeArray(): array { return []; } /** @return array */ function makeUnionArray(): array { return []; } $a = array_column([[1], [2], [3]], 0); $b = array_column([["a" => 1], ["a" => 2], ["a" => 3]], "a"); $c = array_column([["k" => "a", "v" => 1], ["k" => "b", "v" => 2]], "v", "k"); $d = array_column([], 0); $e = array_column(makeMixedArray(), 0); $f = array_column(makeMixedArray(), 0, "k"); $g = array_column(makeMixedArray(), 0, null); $h = array_column(makeGenericArray(), 0); $i = array_column(makeShapeArray(), 0); $j = array_column(makeUnionArray(), 0); ', 'assertions' => [ '$a' => 'list', '$b' => 'list', '$c' => 'array', '$d' => 'list', '$e' => 'list', '$f' => 'array', '$g' => 'list', '$h' => 'list', '$i' => 'list', '$j' => 'list', ], ], 'splatArrayIntersect' => [ ' [ '$bar' => 'array', ], ], 'arrayIntersectIsVariadic' => [ ' [], ], 'arrayIntersectKeyIsVariadic' => [ ' [], ], 'arrayIntersectKeyNoReturnType' => [ ' "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' => [ ' $a * @var array $b * @var array $c */ $r = array_intersect_assoc($a, $b, $c);', 'assertions' => [ '$r' => 'array', ], ], 'arrayReduce' => [ ' [ '$direct_closure_result' => 'int', '$passed_closure_result' => 'int', '$function_call_result' => 'int', ], ], 'arrayReduceMixedReturn' => [ ' [], 'error_levels' => ['MissingClosureReturnType', 'MixedAssignment'], ], 'arraySplice' => [ ' [ '$a' => 'non-empty-list', '$b' => 'array{string, string, string}', '$c' => 'array{int, int, int}', '$e' => 'array' ], ], 'arraySpliceOtherType' => [ ' [ '$d' => 'array', ], ], 'ksortPreserveShape' => [ ' 3, "b" => 4]; ksort($a); acceptsAShape($a); /** * @param array{a:int,b:int} $a */ function acceptsAShape(array $a): void {}', ], 'arraySlicePreserveKeys' => [ ' 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' => [ ' "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' => [ ' [ ' */ function Foo(DateTime ...$dateTimes) : array { return array_map( function ($dateTime) { return (string) ($dateTime->format("c")); }, $dateTimes ); }', ], 'inferArrayMapArrowFunctionReturnType' => [ ' */ function Foo(DateTime ...$dateTimes) : array { return array_map( fn ($dateTime) => (string) ($dateTime->format("c")), $dateTimes ); }', ], 'arrayPad' => [ ' 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' => [ ' 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' => [ ' [ '$result' => 'array', ], ], 'arrayPadTypeCombination' => [ ' 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' => [ ' [ '$a' => 'non-empty-array', '$b' => 'non-empty-list', '$c' => 'non-empty-list', '$d' => 'non-empty-array', ], ], 'arrayPadFallback' => [ ' [ '$result' => 'array', ], ], 'arrayChunk' => [ ' $list */ $b = array_chunk($list, 2); /** @var array $arr */ $c = array_chunk($arr, 2); ', 'assertions' => [ '$a' => 'list>', '$b' => 'list>', '$c' => 'list>', ], ], 'arrayChunkPreservedKeys' => [ ' $list */ $b = array_chunk($list, 2, true); /** @var array $arr */ $c = array_chunk($arr, 2, true);', 'assertions' => [ '$a' => 'list>', '$b' => 'list>', '$c' => 'list>', ], ], 'arrayChunkPreservedKeysExplicitFalse' => [ ' $arr */ $result = array_chunk($arr, 2, false);', 'assertions' => [ '$result' => 'list>', ], ], 'arrayChunkMixed' => [ ' $list */ $b = array_chunk($list, 2); /** @var mixed[] $arr */ $c = array_chunk($arr, 2);', 'assertions' => [ '$a' => 'list>', '$b' => 'list>', '$c' => 'list>', ], ], 'arrayChunkFallback' => [ ' [ '$result' => 'list>', ], ], 'arrayMapPreserveNonEmptiness' => [ ' $strings * @psalm-return non-empty-list */ function foo(array $strings): array { return array_map("intval", $strings); }' ], 'arrayMapZip' => [ ' [ ' [ '$result' => 'array', ], ], 'shuffle' => [ ' 123, "bar" => 456]; shuffle($array);', 'assertions' => [ '$array' => 'list', ], ], 'sort' => [ ' 123, "bar" => 456]; sort($array);', 'assertions' => [ '$array' => 'list', ], ], 'rsort' => [ ' 123, "bar" => 456]; sort($array);', 'assertions' => [ '$array' => 'list', ], ], 'usort' => [ ' 123, "bar" => 456]; usort($array, function (int $a, int $b) { return $a <=> $b; });', 'assertions' => [ '$array' => 'list', ], ], 'specialCaseArrayFilterOnSingleEntry' => [ ' */ function makeAList(int $ofThisInteger): array { return array_filter([$ofThisInteger]); }' ], 'arrayMapWithEmptyArrayReturn' => [ '> $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' => [ ' $x instanceof B );', 'assertions' => [ '$a' => 'array', '$b' => 'array', ], ], 'arrayMergeTwoExplicitLists' => [ ' $foo */ function foo(array $foo) : void {} $foo1 = [1, 2, 3]; $foo2 = [1, 4, 5]; foo(array_merge($foo1, $foo2));' ], 'arrayMergeTwoPossiblyFalse' => [ ' 'list' ], ], 'arrayMapPossiblyFalseIgnored' => [ 'format("Y-m-d")]; takesString($a[0]); array_map("takesString", $a);', ], 'arrayMapExplicitZip' => [ ' [$a => $b], $as, $bs);' ], 'allowUnpackWithArrayKey' => [ 'one(), ...$this->two()]; } }' ], 'spliceTurnsintKeyedInputToList' => [ ' $elements * @return list */ function bar(array $elements, int $index, string $element) : array { array_splice($elements, $index, 0, [$element]); return $elements; }' ], 'arrayKeyExistsShoudldNotModifyIntType' => [ ' "a", 404 => "b", 500 => "c" ]; } function init(string $code) : string { if (array_key_exists($code, HttpError::ERRS)) { return $code; } return ""; }' ], 'arrayChangeKeyCaseWithNonStringKeys' => [ ' 42]; echo array_change_key_case($a, CASE_LOWER)[0];' ], 'mapInterfaceMethod' => [ ' $strings * @return list */ function mapList(MapperInterface $m, array $strings): array { return array_map([$m, "map"], $strings); }' ], ]; } /** * @return iterable */ public function providerInvalidCodeParse() { return [ 'arrayFilterWithoutTypes' => [ ' 5, "b" => 12, "c" => null], function(?int $i) { return $_GET["a"]; } );', 'error_message' => 'MixedArgumentTypeCoercion', 'error_levels' => ['MissingClosureParamType', 'MissingClosureReturnType'], ], 'arrayFilterUseMethodOnInferrableInt' => [ 'foo(); });', 'error_message' => 'InvalidMethodCall', ], 'arrayMapUseMethodOnInferrableInt' => [ 'foo(); }, [1, 2, 3, 4]);', 'error_message' => 'InvalidMethodCall', ], 'arrayMapWithNonCallableStringArray' => [ ' 'InvalidArgument', ], 'arrayMapWithNonCallableIntArray' => [ ' 'InvalidArgument', ], 'arrayFilterBadArgs' => [ ' 'InvalidScalarArgument', ], 'arrayFilterTooFewArgs' => [ ' 'TooFewArguments', ], 'arrayMapBadArgs' => [ ' 'InvalidScalarArgument', ], 'arrayMapTooFewArgs' => [ ' 'TooFewArguments', ], 'arrayMapTooManyArgs' => [ ' 'TooManyArguments', ], 'arrayReduceInvalidClosureTooFewArgs' => [ ' 'InvalidArgument', 'error_levels' => ['MixedTypeCoercion'], ], 'arrayReduceInvalidItemType' => [ ' 'InvalidArgument', 'error_levels' => ['MissingClosureReturnType'], ], 'arrayReduceInvalidCarryType' => [ ' 'InvalidArgument', 'error_levels' => ['MissingClosureReturnType'], ], 'arrayReduceInvalidCarryOutputType' => [ ' 'InvalidArgument', ], 'arrayPopNotNull' => [ ' $list */ function test(array $list) : void { while (!empty($list)) { $tmp = array_pop($list); if ($tmp === null) {} } }', 'error_message' => 'DocblockTypeContradiction', ], 'usortInvalidCallableString' => [ ' 'InvalidArgument', ], 'arrayShiftUndefinedVariable' => [ ' 'UndefinedVariable', ], 'arrayFilterObjectLike' => [ ' $ints */ function ints(array $ints) : void {} $brr = array_filter([2,3,0,4,5]); ints($brr);', 'error_message' => 'ArgumentTypeCoercion - src/somefile.php:5:26 - Argument 1 of ints expects list, parent type array provided', ], 'usortOneParamInvalid' => [ ' (int) ($a > $b));', 'error_message' => 'InvalidScalarArgument' ], ]; } }