diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index 143889f3023..50bab6e6cc7 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -1705,9 +1705,12 @@ private static function reconcileIsGreaterThan( $did_remove_type = false; - if ($existing_var_type->hasType('null') && $assertion->doesFilterNull()) { + if ($assertion->doesFilterNullOrFalse() && + ($existing_var_type->hasType('null') || $existing_var_type->hasType('false')) + ) { $did_remove_type = true; $existing_var_type->removeType('null'); + $existing_var_type->removeType('false'); } foreach ($existing_var_type->getAtomicTypes() as $atomic_type) { @@ -1815,9 +1818,12 @@ private static function reconcileIsLessThan( $did_remove_type = false; - if ($existing_var_type->hasType('null') && $assertion->doesFilterNull()) { + if ($assertion->doesFilterNullOrFalse() && + ($existing_var_type->hasType('null') || $existing_var_type->hasType('false')) + ) { $did_remove_type = true; $existing_var_type->removeType('null'); + $existing_var_type->removeType('false'); } foreach ($existing_var_type->getAtomicTypes() as $atomic_type) { diff --git a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php index b5806f12f0e..05c787eb2e5 100644 --- a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php @@ -1719,9 +1719,12 @@ private static function reconcileIsLessThanOrEqualTo( $did_remove_type = false; - if ($existing_var_type->hasType('null') && $assertion->doesFilterNull()) { + if ($assertion->doesFilterNullOrFalse() && + ($existing_var_type->hasType('null') || $existing_var_type->hasType('false')) + ) { $did_remove_type = true; $existing_var_type->removeType('null'); + $existing_var_type->removeType('false'); } foreach ($existing_var_type->getAtomicTypes() as $atomic_type) { @@ -1827,9 +1830,12 @@ private static function reconcileIsGreaterThanOrEqualTo( $did_remove_type = false; - if ($existing_var_type->hasType('null') && $assertion->doesFilterNull()) { + if ($assertion->doesFilterNullOrFalse() && + ($existing_var_type->hasType('null') || $existing_var_type->hasType('false')) + ) { $did_remove_type = true; $existing_var_type->removeType('null'); + $existing_var_type->removeType('false'); } foreach ($existing_var_type->getAtomicTypes() as $atomic_type) { diff --git a/src/Psalm/Storage/Assertion/IsGreaterThan.php b/src/Psalm/Storage/Assertion/IsGreaterThan.php index ed58ecabc99..49d0bc6b8b0 100644 --- a/src/Psalm/Storage/Assertion/IsGreaterThan.php +++ b/src/Psalm/Storage/Assertion/IsGreaterThan.php @@ -31,7 +31,7 @@ public function isNegationOf(Assertion $assertion): bool return $assertion instanceof IsLessThanOrEqualTo && $this->value === $assertion->value; } - public function doesFilterNull(): bool + public function doesFilterNullOrFalse(): bool { return true; } diff --git a/src/Psalm/Storage/Assertion/IsGreaterThanOrEqualTo.php b/src/Psalm/Storage/Assertion/IsGreaterThanOrEqualTo.php index 20a3c05d189..9fc81110d12 100644 --- a/src/Psalm/Storage/Assertion/IsGreaterThanOrEqualTo.php +++ b/src/Psalm/Storage/Assertion/IsGreaterThanOrEqualTo.php @@ -36,7 +36,7 @@ public function isNegationOf(Assertion $assertion): bool return $assertion instanceof IsLessThan && $this->value === $assertion->value; } - public function doesFilterNull(): bool + public function doesFilterNullOrFalse(): bool { return $this->value !== 0; } diff --git a/src/Psalm/Storage/Assertion/IsLessThan.php b/src/Psalm/Storage/Assertion/IsLessThan.php index 8d78d4d4669..3d9191b276b 100644 --- a/src/Psalm/Storage/Assertion/IsLessThan.php +++ b/src/Psalm/Storage/Assertion/IsLessThan.php @@ -31,7 +31,7 @@ public function isNegationOf(Assertion $assertion): bool return $assertion instanceof IsGreaterThanOrEqualTo && $this->value === $assertion->value; } - public function doesFilterNull(): bool + public function doesFilterNullOrFalse(): bool { return $this->value === 0; } diff --git a/src/Psalm/Storage/Assertion/IsLessThanOrEqualTo.php b/src/Psalm/Storage/Assertion/IsLessThanOrEqualTo.php index 2dd565915a1..83d681e0bdf 100644 --- a/src/Psalm/Storage/Assertion/IsLessThanOrEqualTo.php +++ b/src/Psalm/Storage/Assertion/IsLessThanOrEqualTo.php @@ -36,7 +36,7 @@ public function isNegationOf(Assertion $assertion): bool return $assertion instanceof IsGreaterThan && $this->value === $assertion->value; } - public function doesFilterNull(): bool + public function doesFilterNullOrFalse(): bool { return false; } diff --git a/tests/TypeReconciliation/ConditionalTest.php b/tests/TypeReconciliation/ConditionalTest.php index 5d33c49b486..10caa709a29 100644 --- a/tests/TypeReconciliation/ConditionalTest.php +++ b/tests/TypeReconciliation/ConditionalTest.php @@ -2756,6 +2756,49 @@ function getIntOrNull(): ?int{return null;} } ', ], + 'falseErasureWithSmallerAndGreater' => [ + 'code' => ' 0) { + echo $a + 3; + } + + if ($a >= 0) { + /** @psalm-suppress PossiblyFalseOperand */ + echo $a + 3; + } + + if (0 < $a) { + echo $a + 3; + } + + if (0 <= $a) { + /** @psalm-suppress PossiblyFalseOperand */ + echo $a + 3; + } + + if (0 > $a) { + echo $a + 3; + } + + if (0 >= $a) { + /** @psalm-suppress PossiblyFalseOperand */ + echo $a + 3; + } + ', + ], 'SimpleXMLElementNotAlwaysTruthy' => [ 'code' => '