vimeo / psalm

A static analysis tool for finding errors in PHP applications
https://psalm.dev
MIT License
5.57k stars 659 forks source link

array_walk callback 2nd param type inference #10660

Open bkdotcom opened 9 months ago

bkdotcom commented 9 months ago

https://psalm.dev/r/93b224455b

array_walk($someArray, function ($value, $index) {
});

it seems to me that Psalm should be able to infer that $index is of typearray-key and not mixed?

psalm-github-bot[bot] commented 9 months ago

I found these snippets:

https://psalm.dev/r/93b224455b ```php $array * * @return void */ function bob(array $array) { \array_walk($array, function ($val, $index) { sprintf('index %s', $index); }); } ``` ``` Psalm output (using commit 4b2c698): INFO: MixedArgument - 11:29 - Argument 2 of sprintf cannot be mixed, expecting float|int|object{__tostring()}|string ERROR: UnusedFunctionCall - 11:9 - The call to sprintf is not used INFO: MissingClosureParamType - 10:35 - Parameter $val has no provided type INFO: MissingClosureParamType - 10:41 - Parameter $index has no provided type ```
kkmuffme commented 9 months ago

The callback for array_walk doesn't have more specific types in psalm's callmap.

Ideally the annotation would be callable(&value-of<T>, key-of<T>, mixed=) or even better to create a separate \2 annotation where the optional 3rd arg is only included when arg is set and otherwise not.

For \1 with object would be like callable(&value-of<properties-of<T>>, key-of<properties-of<T>>, mixed=)

Same for array_walk_recursive I guess.

bkdotcom commented 9 months ago

Related... psalm doesn't understand the assertions happening within array_walk

here, after array_walk, we should know that the array is array<array-key, int|string> vs array<array-key, mixed>

https://psalm.dev/r/e9cb9a41d0

psalm-github-bot[bot] commented 9 months ago

I found these snippets:

https://psalm.dev/r/e9cb9a41d0 ```php * * @throws InvalidArgumentException * * @psalm-suppress RedundantConditionGivenDocblockType * @psalm-suppress DocblockTypeContradiction */ private static function pathToArray($path) { if (\is_string($path)) { // return \array_filter(\preg_split('#[\./]#', (string) $path), 'strlen'); return array(); } if (\is_array($path) === false) { throw new InvalidArgumentException('Path must be string or list of string|int'); } // using array_walk vs foreach loop \array_walk($path, static function ($val) { if (\is_string($val) === false && \is_int($val) === false) { throw new InvalidArgumentException('Path array must consist only of string|int'); } }); return $path; } } ``` ``` Psalm output (using commit cb0e6a1): INFO: MissingClosureParamType - 27:45 - Parameter $val has no provided type INFO: MixedReturnTypeCoercion - 32:16 - The type 'array' is more general than the declared return type 'array' for Foo::pathToArray INFO: MixedReturnTypeCoercion - 10:16 - The declared return type 'array' for Foo::pathToArray is more specific than the inferred return type 'array' ```