psalm
psalm copied to clipboard
Type narrowing doesn't work after string to int conversion
https://psalm.dev/r/bf49f34907
A real-world example, quite easy, but it's a hard one for a static analyzer.
I'd be nice to see int<2022, max>
there someday :)
I found these snippets:
https://psalm.dev/r/bf49f34907
<?php
$year = substr('foobar2000', -4);
if (!ctype_digit($year) || (int) $year < 2022) {
throw new \RuntimeException();
}
/** @psalm-trace $year */; // even not numeric-string
$year = intval($year);
/** @psalm-trace $year */; // int<2022, max> expected
Psalm output (using commit 42462b8):
INFO: Trace - 7:26 - $year: string
INFO: Trace - 9:26 - $year: int
You just need to change the assertion layout a bit: https://psalm.dev/r/e9953d77a7
I found these snippets:
https://psalm.dev/r/e9953d77a7
<?php
$year = substr('foobar2000', -4);
assert(is_numeric($year));
assert(ctype_digit($year));
$year = (int) $year;
assert($year >= 2022);
/** @psalm-trace $year */; // int<2022, max> expected
Psalm output (using commit 42462b8):
INFO: Trace - 8:26 - $year: int<2022, max>
Yeah, a few things:
- substr on literal strings is not computed right now, so it result in a generic string by default. You could totally contribute to that change if that's your personal itch.
- ctype_digit is not recognized by psalm currently. Likewise, it would be easy to add here: https://github.com/vimeo/psalm/blob/b69e22a33be8520bba9052d0bbabd5e6d525719c/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php#L1900
- Psalm is currently unable to detect assertions (understand type narrowing) when the variable is not directly checked (here you check the result of the cast and not the variable directly). This would be more difficult to implement I think (because it would mean retroactively changing the type of $a because a specific operation on $a gives you a specific result)
Likewise, it would be easy to add here
Not so easy as it would be. ctype_*
functions return true not only for asserted strings but also for some ASCII codes passed as int. That "but" can be ignored though, because passing ASCII codes is deprecated in PHP 8.1.
But I'd like to stub it properly. Now looks like #8421 prevents me from doing it.
ctype_* functions return true not only for asserted strings but also for some ASCII codes passed as int.
doesn't matter a lot, all of that will be considered numeric. Psalm reconciliation should do the rest