mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
allow negating inferiorto/superiorto
This commit is contained in:
parent
b3a62c76dd
commit
b5b2677ee3
@ -123,7 +123,8 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
|
||||
$negated,
|
||||
$code_location,
|
||||
$suppressed_issues,
|
||||
$failed_reconciliation
|
||||
$failed_reconciliation,
|
||||
$inside_loop
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,8 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
bool $negated,
|
||||
?CodeLocation $code_location,
|
||||
array $suppressed_issues,
|
||||
int &$failed_reconciliation
|
||||
int &$failed_reconciliation,
|
||||
bool $inside_loop
|
||||
): Type\Union {
|
||||
$is_equality = $is_strict_equality || $is_loose_equality;
|
||||
|
||||
@ -211,7 +212,8 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
$suppressed_issues,
|
||||
$failed_reconciliation,
|
||||
$is_equality,
|
||||
$is_strict_equality
|
||||
$is_strict_equality,
|
||||
$inside_loop
|
||||
);
|
||||
|
||||
if ($simple_negated_type) {
|
||||
|
@ -1589,6 +1589,7 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
|
||||
string $assertion,
|
||||
bool $inside_loop
|
||||
) : Union {
|
||||
$assertion_value = (int)$assertion;
|
||||
foreach ($existing_var_type->getAtomicTypes() as $atomic_type) {
|
||||
if ($inside_loop) {
|
||||
continue;
|
||||
@ -1597,16 +1598,16 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
|
||||
if ($atomic_type instanceof Atomic\TIntRange) {
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
if ($atomic_type->min_bound === null) {
|
||||
$atomic_type->min_bound = (int)$assertion;
|
||||
$atomic_type->min_bound = $assertion_value;
|
||||
} else {
|
||||
$atomic_type->min_bound = Atomic\TIntRange::getNewHighestBound(
|
||||
(int)$assertion,
|
||||
$assertion_value,
|
||||
$atomic_type->min_bound
|
||||
);
|
||||
}
|
||||
$existing_var_type->addType($atomic_type);
|
||||
} elseif ($atomic_type instanceof Atomic\TLiteralInt) {
|
||||
$new_range = new Atomic\TIntRange((int)$assertion, null);
|
||||
$new_range = new Atomic\TIntRange($assertion_value, null);
|
||||
if (!$new_range->contains($atomic_type->value)) {
|
||||
//emit an issue here in the future about incompatible type
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
@ -1614,21 +1615,21 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
|
||||
} /*elseif ($inside_loop) {
|
||||
//when inside a loop, allow the range to extends the type
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
if ($atomic_type->value < (int)$assertion) {
|
||||
$existing_var_type->addType(new Atomic\TIntRange($atomic_type->value, (int)$assertion));
|
||||
if ($atomic_type->value < $assertion_value) {
|
||||
$existing_var_type->addType(new Atomic\TIntRange($atomic_type->value, $assertion_value));
|
||||
} else {
|
||||
$existing_var_type->addType(new Atomic\TIntRange((int)$assertion, $atomic_type->value));
|
||||
$existing_var_type->addType(new Atomic\TIntRange($assertion_value, $atomic_type->value));
|
||||
}
|
||||
}*/
|
||||
} elseif ($atomic_type instanceof Atomic\TPositiveInt) {
|
||||
if ((int)$assertion <= 0) {
|
||||
if ($assertion_value <= 0) {
|
||||
//emit an issue here in the future about incompatible type
|
||||
}
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
$existing_var_type->addType(new Atomic\TIntRange((int)$assertion, null));
|
||||
$existing_var_type->addType(new Atomic\TIntRange($assertion_value, null));
|
||||
} elseif ($atomic_type instanceof TInt) {
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
$existing_var_type->addType(new Atomic\TIntRange((int)$assertion, null));
|
||||
$existing_var_type->addType(new Atomic\TIntRange($assertion_value, null));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1640,6 +1641,7 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
|
||||
string $assertion,
|
||||
bool $inside_loop
|
||||
) : Union {
|
||||
$assertion_value = (int)$assertion;
|
||||
foreach ($existing_var_type->getAtomicTypes() as $atomic_type) {
|
||||
if ($inside_loop) {
|
||||
continue;
|
||||
@ -1648,13 +1650,13 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
|
||||
if ($atomic_type instanceof Atomic\TIntRange) {
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
if ($atomic_type->max_bound === null) {
|
||||
$atomic_type->max_bound = (int)$assertion;
|
||||
$atomic_type->max_bound = $assertion_value;
|
||||
} else {
|
||||
$atomic_type->max_bound = min($atomic_type->max_bound, (int)$assertion);
|
||||
$atomic_type->max_bound = min($atomic_type->max_bound, $assertion_value);
|
||||
}
|
||||
$existing_var_type->addType($atomic_type);
|
||||
} elseif ($atomic_type instanceof Atomic\TLiteralInt) {
|
||||
$new_range = new Atomic\TIntRange(null, (int)$assertion);
|
||||
$new_range = new Atomic\TIntRange(null, $assertion_value);
|
||||
if (!$new_range->contains($atomic_type->value)) {
|
||||
//emit an issue here in the future about incompatible type
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
@ -1662,21 +1664,21 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
|
||||
}/* elseif ($inside_loop) {
|
||||
//when inside a loop, allow the range to extends the type
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
if ($atomic_type->value < (int)$assertion) {
|
||||
$existing_var_type->addType(new Atomic\TIntRange($atomic_type->value, (int)$assertion));
|
||||
if ($atomic_type->value < $assertion_value) {
|
||||
$existing_var_type->addType(new Atomic\TIntRange($atomic_type->value, $assertion_value));
|
||||
} else {
|
||||
$existing_var_type->addType(new Atomic\TIntRange((int)$assertion, $atomic_type->value));
|
||||
$existing_var_type->addType(new Atomic\TIntRange($assertion_value, $atomic_type->value));
|
||||
}
|
||||
}*/
|
||||
} elseif ($atomic_type instanceof Atomic\TPositiveInt) {
|
||||
if ((int)$assertion <= 0) {
|
||||
if ($assertion_value <= 0) {
|
||||
//emit an issue here in the future about incompatible type
|
||||
}
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
$existing_var_type->addType(new Atomic\TIntRange(1, (int)$assertion));
|
||||
$existing_var_type->addType(new Atomic\TIntRange(1, $assertion_value));
|
||||
} elseif ($atomic_type instanceof TInt) {
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
$existing_var_type->addType(new Atomic\TIntRange(null, (int)$assertion));
|
||||
$existing_var_type->addType(new Atomic\TIntRange(null, $assertion_value));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,9 @@ use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Atomic\TTrue;
|
||||
use Psalm\Type\Reconciler;
|
||||
|
||||
use Psalm\Type\Union;
|
||||
use function get_class;
|
||||
use function max;
|
||||
use function substr;
|
||||
|
||||
class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
@ -46,7 +48,8 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
array $suppressed_issues = [],
|
||||
int &$failed_reconciliation = 0,
|
||||
bool $is_equality = false,
|
||||
bool $is_strict_equality = false
|
||||
bool $is_strict_equality = false,
|
||||
bool $inside_loop = false
|
||||
) : ?Type\Union {
|
||||
if ($assertion === 'object' && !$existing_var_type->hasMixed()) {
|
||||
return self::reconcileObject(
|
||||
@ -231,6 +234,22 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
return $existing_var_type;
|
||||
}
|
||||
|
||||
if ($assertion[0] === '>') {
|
||||
return self::reconcileSuperiorTo(
|
||||
$existing_var_type,
|
||||
substr($assertion, 1),
|
||||
$inside_loop
|
||||
);
|
||||
}
|
||||
|
||||
if ($assertion[0] === '<') {
|
||||
return self::reconcileInferiorTo(
|
||||
$existing_var_type,
|
||||
substr($assertion, 1),
|
||||
$inside_loop
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1651,4 +1670,99 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function reconcileSuperiorTo(Union $existing_var_type, string $assertion, bool $inside_loop): Union
|
||||
{
|
||||
$assertion_value = (int)$assertion - 1;
|
||||
foreach ($existing_var_type->getAtomicTypes() as $atomic_type) {
|
||||
if ($inside_loop) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($atomic_type instanceof Atomic\TIntRange) {
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
if ($atomic_type->max_bound === null) {
|
||||
$atomic_type->max_bound = $assertion_value;
|
||||
} else {
|
||||
$atomic_type->max_bound = Atomic\TIntRange::getNewLowestBound(
|
||||
$assertion_value,
|
||||
$atomic_type->max_bound
|
||||
);
|
||||
}
|
||||
$existing_var_type->addType($atomic_type);
|
||||
} elseif ($atomic_type instanceof Atomic\TLiteralInt) {
|
||||
$new_range = new Atomic\TIntRange(null, $assertion_value);
|
||||
if (!$new_range->contains($atomic_type->value)) {
|
||||
//emit an issue here in the future about incompatible type
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
$existing_var_type->addType($new_range);
|
||||
} /*elseif ($inside_loop) {
|
||||
//when inside a loop, allow the range to extends the type
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
if ($atomic_type->value < $assertion_value) {
|
||||
$existing_var_type->addType(new Atomic\TIntRange($atomic_type->value, $assertion_value));
|
||||
} else {
|
||||
$existing_var_type->addType(new Atomic\TIntRange($assertion_value, $atomic_type->value));
|
||||
}
|
||||
}*/
|
||||
} elseif ($atomic_type instanceof Atomic\TPositiveInt) {
|
||||
if ($assertion_value > 0) {
|
||||
//emit an issue here in the future about incompatible type
|
||||
}
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
$existing_var_type->addType(new Atomic\TIntRange(null, $assertion_value));
|
||||
} elseif ($atomic_type instanceof TInt) {
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
$existing_var_type->addType(new Atomic\TIntRange(null, $assertion_value));
|
||||
}
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
}
|
||||
|
||||
private static function reconcileInferiorTo(Union $existing_var_type, string $assertion, bool $inside_loop): Union
|
||||
{
|
||||
$assertion_value = (int)$assertion + 1;
|
||||
foreach ($existing_var_type->getAtomicTypes() as $atomic_type) {
|
||||
if ($inside_loop) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($atomic_type instanceof Atomic\TIntRange) {
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
if ($atomic_type->min_bound === null) {
|
||||
$atomic_type->min_bound = $assertion_value;
|
||||
} else {
|
||||
$atomic_type->min_bound = max($atomic_type->min_bound, $assertion_value);
|
||||
}
|
||||
$existing_var_type->addType($atomic_type);
|
||||
} elseif ($atomic_type instanceof Atomic\TLiteralInt) {
|
||||
$new_range = new Atomic\TIntRange($assertion_value, null);
|
||||
if (!$new_range->contains($atomic_type->value)) {
|
||||
//emit an issue here in the future about incompatible type
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
$existing_var_type->addType($new_range);
|
||||
}/* elseif ($inside_loop) {
|
||||
//when inside a loop, allow the range to extends the type
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
if ($atomic_type->value < $assertion_value) {
|
||||
$existing_var_type->addType(new Atomic\TIntRange($atomic_type->value, $assertion_value));
|
||||
} else {
|
||||
$existing_var_type->addType(new Atomic\TIntRange($assertion_value, $atomic_type->value));
|
||||
}
|
||||
}*/
|
||||
} elseif ($atomic_type instanceof Atomic\TPositiveInt) {
|
||||
if ($assertion_value > 0) {
|
||||
//emit an issue here in the future about incompatible type
|
||||
}
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
$existing_var_type->addType(new Atomic\TIntRange($assertion_value, 1));
|
||||
} elseif ($atomic_type instanceof TInt) {
|
||||
$existing_var_type->removeType($atomic_type->getKey());
|
||||
$existing_var_type->addType(new Atomic\TIntRange($assertion_value, null));
|
||||
}
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,102 @@ class IntRangeTest extends TestCase
|
||||
'$c===' => 'int<-499, -61>',
|
||||
]
|
||||
],
|
||||
'negatedAssertions' => [
|
||||
'<?php
|
||||
function getInt(): int{return 0;}
|
||||
$a = $b = $c = $d = $e = $f = $g = $h = $i = $j = $k = $l = $m = $n = $o = $p = getInt();
|
||||
//>
|
||||
if($a > 10){
|
||||
die();
|
||||
}
|
||||
|
||||
if($b > -10){
|
||||
die();
|
||||
}
|
||||
|
||||
//<
|
||||
if($c < 500){
|
||||
die();
|
||||
}
|
||||
|
||||
if($d < -500){
|
||||
die();
|
||||
}
|
||||
|
||||
//>=
|
||||
if($e >= 10){
|
||||
die();
|
||||
}
|
||||
|
||||
if($f >= -10){
|
||||
die();
|
||||
}
|
||||
|
||||
//<=
|
||||
if($g <= 500){
|
||||
die();
|
||||
}
|
||||
|
||||
if($h <= -500){
|
||||
die();
|
||||
}
|
||||
|
||||
//>
|
||||
if(10 > $i){
|
||||
die();
|
||||
}
|
||||
|
||||
if(-10 > $j){
|
||||
die();
|
||||
}
|
||||
|
||||
//<
|
||||
if(500 < $k){
|
||||
die();
|
||||
}
|
||||
|
||||
if(-500 < $l){
|
||||
die();
|
||||
}
|
||||
|
||||
//>=
|
||||
if(10 >= $m){
|
||||
die();
|
||||
}
|
||||
|
||||
if(-10 >= $n){
|
||||
die();
|
||||
}
|
||||
|
||||
//<=
|
||||
if(500 <= $o){
|
||||
die();
|
||||
}
|
||||
|
||||
if(-500 <= $p){
|
||||
die();
|
||||
}
|
||||
//inverse
|
||||
',
|
||||
'assertions' => [
|
||||
'$a===' => 'int<min, 10>',
|
||||
'$b===' => 'int<min, -10>',
|
||||
'$c===' => 'int<500, max>',
|
||||
'$d===' => 'int<-500, max>',
|
||||
'$e===' => 'int<min, 9>',
|
||||
'$f===' => 'int<min, -11>',
|
||||
'$g===' => 'int<501, max>',
|
||||
'$h===' => 'int<-499, max>',
|
||||
'$i===' => 'int<10, max>',
|
||||
'$j===' => 'int<-10, max>',
|
||||
'$k===' => 'int<min, 500>',
|
||||
'$l===' => 'int<min, -500>',
|
||||
'$m===' => 'int<11, max>',
|
||||
'$n===' => 'int<-9, max>',
|
||||
'$o===' => 'int<min, 499>',
|
||||
'$p===' => 'int<min, -501>',
|
||||
]
|
||||
],
|
||||
'intOperations' => [
|
||||
'<?php
|
||||
function getInt(): int{return 0;}
|
||||
|
Loading…
Reference in New Issue
Block a user