psalm icon indicating copy to clipboard operation
psalm copied to clipboard

5.4: Issue with flow analysis causes NoValue false positive

Open pilif opened this issue 3 years ago • 11 comments

https://psalm.dev/r/8a407af77c

psalm seems to get confused that $v is updated in the loop after the condition.

This was fine in 5.3 and prior and is broken in 5.4

pilif avatar Dec 20 '22 08:12 pilif

I found these snippets:

https://psalm.dev/r/8a407af77c
<?php

$v = 0;
do {
    $foo = "a";
    if ($v > 0) {
        $foo .= sprintf('_%02d', $v);
    }
    $v++;
} while (rand(1,10) < 5);
echo $foo;
Psalm output (using commit 62db5d4):

ERROR: NoValue - 7:34 - All possible types for this argument were invalidated - This may be dead code

psalm-github-bot[bot] avatar Dec 20 '22 08:12 psalm-github-bot[bot]

Encountered this too when making a PR, possibly caused by https://github.com/vimeo/psalm/pull/8934

The issue is that the if statement is analyzed twice, once with the impossible values inferred on the first iteration (always never), then on future iterations which are not never: https://psalm.dev/r/3b6e234333

danog avatar Dec 20 '22 09:12 danog

I found these snippets:

https://psalm.dev/r/3b6e234333
<?php

$v = 0;
do {
    $foo = "a";
    if ($v > 0) {
        /** @psalm-trace $v */;
        $foo .= sprintf('_%02d', $v);
    }
    $v++;
} while (rand(1,10) < 5);
echo $foo;
Psalm output (using commit 62db5d4):

INFO: Trace - 7:31 - $v: never

ERROR: NoValue - 8:34 - All possible types for this argument were invalidated - This may be dead code

INFO: Trace - 7:31 - $v: int<1, max>

psalm-github-bot[bot] avatar Dec 20 '22 09:12 psalm-github-bot[bot]

in my case, the workaround is to put the increment before the if and change the condition to > 1. Which is also strange because that should IMHO fail the same way, but it doesn't.

pilif avatar Dec 20 '22 09:12 pilif

Yeah, in the case of https://psalm.dev/r/b33c14eaf6 there should be a paradox since $v is always > 0, there's already an open issue for this: https://github.com/vimeo/psalm/issues/8059.

Overall I think the current behavior for https://psalm.dev/r/8a407af77c is correct, as it detects that there might be an impossible code path at some point in the iteration, closing this unless someone else comes up with a more broken example.

danog avatar Dec 20 '22 09:12 danog

I found these snippets:

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

$v = 0;
do {
    $foo = "a";
    $v++;
    /** @psalm-trace $v */;
    if ($v > 0) {
        $foo .= sprintf('_%02d', $v);
    }
} while (rand(1,10) < 5);
echo $foo;
Psalm output (using commit 62db5d4):

INFO: Trace - 7:27 - $v: int<1, max>
https://psalm.dev/r/8a407af77c
<?php

$v = 0;
do {
    $foo = "a";
    if ($v > 0) {
        $foo .= sprintf('_%02d', $v);
    }
    $v++;
} while (rand(1,10) < 5);
echo $foo;
Psalm output (using commit 62db5d4):

ERROR: NoValue - 7:34 - All possible types for this argument were invalidated - This may be dead code

psalm-github-bot[bot] avatar Dec 20 '22 09:12 psalm-github-bot[bot]

cc @orklah just in case you want to comment on this too

danog avatar Dec 20 '22 14:12 danog

The issue is that the if statement is analyzed twice, once with the impossible values inferred on the first iteration (always never), then on future iterations which are not never: https://psalm.dev/r/3b6e234333

I agree with that, so it seems there is indeed a bug here. I would keep this open? Unless I missed something?

orklah avatar Dec 20 '22 17:12 orklah

I found these snippets:

https://psalm.dev/r/3b6e234333
<?php

$v = 0;
do {
    $foo = "a";
    if ($v > 0) {
        /** @psalm-trace $v */;
        $foo .= sprintf('_%02d', $v);
    }
    $v++;
} while (rand(1,10) < 5);
echo $foo;
Psalm output (using commit 62db5d4):

INFO: Trace - 7:31 - $v: never

ERROR: NoValue - 8:34 - All possible types for this argument were invalidated - This may be dead code

INFO: Trace - 7:31 - $v: int<1, max>

psalm-github-bot[bot] avatar Dec 20 '22 17:12 psalm-github-bot[bot]

@danog Does this count as a more broken example?

https://psalm.dev/r/47669b633f

$sum = 0;
$i = 0;
do {
    if ($i > 0) {
        $sum += $i;
        /** @psalm-trace $i */  // `never` AND `int<1, 9>`
        echo $i;  // NoValue
    }
    $i++;
} while ($i < 10);

The sum is silly, but it's the same issue as what I'm really trying to do: https://psalm.dev/r/dbcf7a3b01

mgiuffrida avatar Aug 23 '25 17:08 mgiuffrida

I found these snippets:

https://psalm.dev/r/47669b633f
<?php

$sum = 0;
$i = 0;
do {
    if ($i > 0) {
        $sum += $i;
        /** @psalm-trace $i */  // `never` AND `int<1, 9>`
        echo $i;  // NoValue
    }
    $i++;
} while ($i < 10);

echo $sum;
Psalm output (using commit cdceda0):

ERROR: NoValue - 9:14 - All possible types for this argument were invalidated - This may be dead code

INFO: Trace - 9:9 - $i: never

INFO: Trace - 9:9 - $i: int<1, 9>
https://psalm.dev/r/dbcf7a3b01
<?php

// Print the sum of the numbers 1 thru 9.
// (Yes, I know there's a closed form for this....)
$sum = 0;
$i = 0;
do {
    if ($i > 0) {
        $sum += $i;
		echo "$i\t$sum\n";  // `never` cannot be cast to string
    }
    $i++;
} while ($i < 10);
Psalm output (using commit cdceda0):

ERROR: InvalidCast - 10:9 - never cannot be cast to string

psalm-github-bot[bot] avatar Aug 23 '25 17:08 psalm-github-bot[bot]