psalm icon indicating copy to clipboard operation
psalm copied to clipboard

False positive `ParadoxicalCondition` when using null-coalescing assignment

Open blackwolf12333 opened this issue 2 years ago • 1 comments

https://psalm.dev/r/dfb092ce4c

If you remove the ?? [] on line 16 the snipped above will error out with an Undefined offset: 1 error.

So this is seen as a ParadoxicalCondition while it really is required.

blackwolf12333 avatar Jul 21 '22 10:07 blackwolf12333

I found these snippets:

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

/** @var array<int, array> $list */
$list = [
1 => ['b', 'c']
];
/** @var array<int, array> $list2 */
$list2 = [
	1 => [1, 2],
    2 => [3, 4],
];
$map = [];

foreach ($list as $key => $value) {
	$map[$key] ??= array_merge(
		$map[$key] ?? [],
		...array_map(
			fn(int $a) => $list2[$a],
			$value
		)
	);
}
Psalm output (using commit 85fe7e8):

ERROR: ParadoxicalCondition - 16:3 - Condition ($map[$key] is isset) contradicts a previously-established condition ($map[$key] is not isset)

psalm-github-bot[bot] avatar Jul 21 '22 10:07 psalm-github-bot[bot]

Somewhat simplified: https://psalm.dev/r/3703120ab5

I would say this error is correct. If the the array_merge is running, then it means $map[$key] is unset or null, so the empty array [] will always be used. This code is equivalent and causes no error: https://psalm.dev/r/3703120ab5 (In fact with this change, the array_merge is now unnecessary.)

The thing that seems like a bug to me, however, is that we don't get the ParadoxicalCondition when we change the array to a variable, eg: https://psalm.dev/r/f6745c6ba3

edsrzf avatar Feb 18 '24 01:02 edsrzf

I found these snippets:

https://psalm.dev/r/3703120ab5
<?php

/**
 * @param list<int> $list1
 */
function f(array $list1, array $list2): void
{
    $map = [];

    foreach ($list1 as $key) {
        $map[$key] ??= array_merge(
            $map[$key] ?? [],
            ...$list2
        );
    }
}
Psalm output (using commit c488d40):

ERROR: ParadoxicalCondition - 12:13 - Condition ($map[$key] is isset) contradicts a previously-established condition ($map[$key] is not isset)
https://psalm.dev/r/f6745c6ba3
<?php

/**
 * @param list<int> $list1
 */
function f(array $list1, array $list2): void
{
    foreach ($list1 as $_key) {
        $v ??= array_merge(
            $v ?? [],
            ...$list2
        );
    }
}
Psalm output (using commit c488d40):

INFO: MixedArgument - 10:13 - Argument 1 of array_merge cannot be array<never, never>|mixed, expecting array<array-key, mixed>

psalm-github-bot[bot] avatar Feb 18 '24 01:02 psalm-github-bot[bot]

That's because $v is undefined (=mixed). Fix that and you'll get TypeDoesNotContainType (=paradox): https://psalm.dev/r/55695988d8

weirdan avatar Feb 19 '24 02:02 weirdan

I found these snippets:

https://psalm.dev/r/55695988d8
<?php

/**
 * @param list<int> $list1
 */
function f(array $list1, array $list2): void
{
    $v = null;
    foreach ($list1 as $_key) {
        $v ??= array_merge(
            $v ?? [],
            ...$list2
        );
    }
}
Psalm output (using commit c488d40):

ERROR: TypeDoesNotContainType - 11:13 - Type null for $v is always !null

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

Overall, I don't see any bugs here. cc: @AndrolGenhald

weirdan avatar Feb 19 '24 02:02 weirdan