Bug report
Negating the smallest integer is inferred as the smallest integer again, instead of the float PHP actually produces.
\PHPStan\dumpType(-(-9223372036854775807 - 1)); // Dumped type: -9223372036854775808
\PHPStan\dumpType(-PHP_INT_MIN); // Dumped type: -9223372036854775808|-2147483648
$min = -9223372036854775807 - 1;
\PHPStan\dumpType(-$min); // Dumped type: -9223372036854775808
At runtime, -PHP_INT_MIN overflows, because 9223372036854775808 is not representable as an int:
var_dump(-PHP_INT_MIN); // float(9.2233720368548E+18)
InitializerExprTypeResolver::getUnaryMinusTypeFromType() notices the overflow, but then bails out to the un-negated type:
https://github.com/phpstan/phpstan-src/blob/2.2.x/src/Reflection/InitializerExprTypeResolver.php#L2622-L2628
if (is_int($scalarValue)) {
/** @var int|float $newValue */
$newValue = -$scalarValue;
if (!is_int($newValue)) {
return $type; // <- the original type, negation not applied
}
$newTypes[] = new ConstantIntegerType($newValue);
} elseif (is_float($scalarValue)) {
$newTypes[] = new ConstantFloatType(-$scalarValue);
}
Interestingly, the multiplication path already handles this correctly, so $min * -1 gives the right answer while -$min does not:
\PHPStan\dumpType(PHP_INT_MIN * -1); // 2147483648|9.223372036854776E+18
\PHPStan\dumpType(-PHP_INT_MIN); // -9223372036854775808|-2147483648
Integer ranges are also fine, since getUnaryMinusType() routes them through that multiplication:
/** @var int<min, -1> $i */
\PHPStan\dumpType(-$i); // int<1, max>
So only the constant-integer path is affected.
Code snippet that reproduces the problem
https://phpstan.org/r/ee87d652-a1b3-46e1-bce8-2f870375b39b
Expected output
\PHPStan\dumpType(-(-9223372036854775807 - 1)); // 9.223372036854776E+18
\PHPStan\dumpType(-PHP_INT_MIN); // 2147483648|9.223372036854776E+18
i.e. the same as what * -1 already infers.
This is the same root cause as #14946 (abs(PHP_INT_MIN)), but it fails silently rather than with an internal error. I have a fix ready and will open a PR right away.
Did PHPStan help you today? Did it make you happy in any way?
Yes. I only noticed this because PHPStan's inferred types are precise enough that a wrong one stands out.
Bug report
Negating the smallest integer is inferred as the smallest integer again, instead of the float PHP actually produces.
At runtime,
-PHP_INT_MINoverflows, because9223372036854775808is not representable as anint:InitializerExprTypeResolver::getUnaryMinusTypeFromType()notices the overflow, but then bails out to the un-negated type:https://github.com/phpstan/phpstan-src/blob/2.2.x/src/Reflection/InitializerExprTypeResolver.php#L2622-L2628
Interestingly, the multiplication path already handles this correctly, so
$min * -1gives the right answer while-$mindoes not:Integer ranges are also fine, since
getUnaryMinusType()routes them through that multiplication:So only the constant-integer path is affected.
Code snippet that reproduces the problem
https://phpstan.org/r/ee87d652-a1b3-46e1-bce8-2f870375b39b
Expected output
i.e. the same as what
* -1already infers.This is the same root cause as #14946 (
abs(PHP_INT_MIN)), but it fails silently rather than with an internal error. I have a fix ready and will open a PR right away.Did PHPStan help you today? Did it make you happy in any way?
Yes. I only noticed this because PHPStan's inferred types are precise enough that a wrong one stands out.