1
0
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:
orklah 2021-09-05 18:02:42 +02:00
parent b3a62c76dd
commit b5b2677ee3
5 changed files with 237 additions and 22 deletions

View File

@ -123,7 +123,8 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
$negated,
$code_location,
$suppressed_issues,
$failed_reconciliation
$failed_reconciliation,
$inside_loop
);
}

View File

@ -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) {

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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;}