psalm icon indicating copy to clipboard operation
psalm copied to clipboard

array_walk callback 2nd param type inference

Open bkdotcom opened this issue 1 year ago • 4 comments

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?

bkdotcom avatar Feb 05 '24 21:02 bkdotcom

I found these snippets:

https://psalm.dev/r/93b224455b
<?php

/**
 * @param array<array-key, mixed> $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

psalm-github-bot[bot] avatar Feb 05 '24 21:02 psalm-github-bot[bot]

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.

kkmuffme avatar Feb 05 '24 22:02 kkmuffme

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

bkdotcom avatar Feb 12 '24 05:02 bkdotcom

I found these snippets:

https://psalm.dev/r/e9cb9a41d0
<?php

class Foo
{
    /**
     * Cast path to array
     *
     * @param array|string $path path
     *
     * @return array<array-key, string|int>
     *
     * @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<array-key, mixed>' is more general than the declared return type 'array<array-key, int|string>' for Foo::pathToArray

INFO: MixedReturnTypeCoercion - 10:16 - The declared return type 'array<array-key, int|string>' for Foo::pathToArray is more specific than the inferred return type 'array<array-key, mixed>'

psalm-github-bot[bot] avatar Feb 12 '24 05:02 psalm-github-bot[bot]