🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

Negating PHP_INT_MIN is inferred as PHP_INT_MIN instead of a float #14947

Description

@zonuexe

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions