psalm icon indicating copy to clipboard operation
psalm copied to clipboard

Type narrowing doesn't work after string to int conversion

Open fluffycondor opened this issue 2 years ago • 6 comments

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 :)

fluffycondor avatar Aug 17 '22 14:08 fluffycondor

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

psalm-github-bot[bot] avatar Aug 17 '22 14:08 psalm-github-bot[bot]

You just need to change the assertion layout a bit: https://psalm.dev/r/e9953d77a7

danog avatar Aug 17 '22 18:08 danog

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>

psalm-github-bot[bot] avatar Aug 17 '22 18:08 psalm-github-bot[bot]

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)

orklah avatar Aug 17 '22 18:08 orklah

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.

fluffycondor avatar Aug 18 '22 12:08 fluffycondor

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

orklah avatar Aug 18 '22 19:08 orklah