mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
Merge pull request #7511 from orklah/literal-inequality
improve literal inequality with ranges and rework GreaterThan/LessThan assertions
This commit is contained in:
commit
048025b1d6
@ -51,9 +51,11 @@ use Psalm\Storage\Assertion\IsClassNotEqual;
|
||||
use Psalm\Storage\Assertion\IsCountable;
|
||||
use Psalm\Storage\Assertion\IsEqualIsset;
|
||||
use Psalm\Storage\Assertion\IsGreaterThan;
|
||||
use Psalm\Storage\Assertion\IsGreaterThanOrEqualTo;
|
||||
use Psalm\Storage\Assertion\IsIdentical;
|
||||
use Psalm\Storage\Assertion\IsIsset;
|
||||
use Psalm\Storage\Assertion\IsLessThan;
|
||||
use Psalm\Storage\Assertion\IsLessThanOrEqualTo;
|
||||
use Psalm\Storage\Assertion\IsLooselyEqual;
|
||||
use Psalm\Storage\Assertion\IsNotIdentical;
|
||||
use Psalm\Storage\Assertion\IsNotLooselyEqual;
|
||||
@ -1644,8 +1646,7 @@ class AssertionFinder
|
||||
protected static function hasSuperiorNumberCheck(
|
||||
FileSource $source,
|
||||
PhpParser\Node\Expr\BinaryOp $conditional,
|
||||
?int &$literal_value_comparison,
|
||||
bool &$isset_assert
|
||||
?int &$literal_value_comparison
|
||||
) {
|
||||
$right_assignment = false;
|
||||
$value_right = null;
|
||||
@ -1666,10 +1667,7 @@ class AssertionFinder
|
||||
$value_right = $conditional->right->expr->value;
|
||||
}
|
||||
if ($right_assignment === true && $value_right !== null) {
|
||||
$isset_assert = $value_right === 0 && $conditional instanceof Greater;
|
||||
|
||||
$literal_value_comparison = $value_right +
|
||||
($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 1 : 0);
|
||||
$literal_value_comparison = $value_right;
|
||||
|
||||
return self::ASSIGNMENT_TO_RIGHT;
|
||||
}
|
||||
@ -1693,10 +1691,7 @@ class AssertionFinder
|
||||
$value_left = $conditional->left->expr->value;
|
||||
}
|
||||
if ($left_assignment === true && $value_left !== null) {
|
||||
$isset_assert = $value_left === 0 && $conditional instanceof Greater;
|
||||
|
||||
$literal_value_comparison = $value_left +
|
||||
($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? -1 : 0);
|
||||
$literal_value_comparison = $value_left;
|
||||
|
||||
return self::ASSIGNMENT_TO_LEFT;
|
||||
}
|
||||
@ -1711,8 +1706,7 @@ class AssertionFinder
|
||||
protected static function hasInferiorNumberCheck(
|
||||
FileSource $source,
|
||||
PhpParser\Node\Expr\BinaryOp $conditional,
|
||||
?int &$literal_value_comparison,
|
||||
bool &$isset_assert
|
||||
?int &$literal_value_comparison
|
||||
) {
|
||||
$right_assignment = false;
|
||||
$value_right = null;
|
||||
@ -1733,10 +1727,8 @@ class AssertionFinder
|
||||
$value_right = $conditional->right->expr->value;
|
||||
}
|
||||
if ($right_assignment === true && $value_right !== null) {
|
||||
$isset_assert = $value_right === 0 && $conditional instanceof Smaller;
|
||||
$literal_value_comparison = $value_right;
|
||||
|
||||
$literal_value_comparison = $value_right +
|
||||
($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? -1 : 0);
|
||||
return self::ASSIGNMENT_TO_RIGHT;
|
||||
}
|
||||
|
||||
@ -1759,10 +1751,7 @@ class AssertionFinder
|
||||
$value_left = $conditional->left->expr->value;
|
||||
}
|
||||
if ($left_assignment === true && $value_left !== null) {
|
||||
$isset_assert = $value_left === 0 && $conditional instanceof Smaller;
|
||||
|
||||
$literal_value_comparison = $value_left +
|
||||
($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 1 : 0);
|
||||
$literal_value_comparison = $value_left;
|
||||
|
||||
return self::ASSIGNMENT_TO_LEFT;
|
||||
}
|
||||
@ -3788,13 +3777,11 @@ class AssertionFinder
|
||||
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
|
||||
$max_count = null;
|
||||
$count_inequality_position = self::hasLessThanCountEqualityCheck($conditional, $max_count);
|
||||
$isset_assert = false;
|
||||
$superior_value_comparison = null;
|
||||
$superior_value_position = self::hasSuperiorNumberCheck(
|
||||
$source,
|
||||
$conditional,
|
||||
$superior_value_comparison,
|
||||
$isset_assert
|
||||
$superior_value_comparison
|
||||
);
|
||||
|
||||
if ($count_equality_position) {
|
||||
@ -3851,7 +3838,7 @@ class AssertionFinder
|
||||
return $if_types ? [$if_types] : [];
|
||||
}
|
||||
|
||||
if ($superior_value_position) {
|
||||
if ($superior_value_position && $superior_value_comparison !== null) {
|
||||
if ($superior_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
||||
$var_name = ExpressionIdentifier::getArrayVarId(
|
||||
$conditional->left,
|
||||
@ -3868,13 +3855,17 @@ class AssertionFinder
|
||||
|
||||
if ($var_name !== null) {
|
||||
if ($superior_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
||||
$if_types[$var_name] = [[new IsGreaterThan($superior_value_comparison)]];
|
||||
if ($conditional instanceof GreaterOrEqual) {
|
||||
$if_types[$var_name] = [[new IsGreaterThanOrEqualTo($superior_value_comparison)]];
|
||||
} else {
|
||||
$if_types[$var_name] = [[new IsGreaterThan($superior_value_comparison)]];
|
||||
}
|
||||
} else {
|
||||
$if_types[$var_name] = [[new IsLessThan($superior_value_comparison)]];
|
||||
}
|
||||
|
||||
if ($isset_assert) {
|
||||
$if_types[$var_name][] = [new IsEqualIsset()];
|
||||
if ($conditional instanceof GreaterOrEqual) {
|
||||
$if_types[$var_name] = [[new IsLessThanOrEqualTo($superior_value_comparison)]];
|
||||
} else {
|
||||
$if_types[$var_name] = [[new IsLessThan($superior_value_comparison)]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3898,13 +3889,11 @@ class AssertionFinder
|
||||
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
|
||||
$max_count = null;
|
||||
$count_inequality_position = self::hasLessThanCountEqualityCheck($conditional, $max_count);
|
||||
$isset_assert = false;
|
||||
$inferior_value_comparison = null;
|
||||
$inferior_value_position = self::hasInferiorNumberCheck(
|
||||
$source,
|
||||
$conditional,
|
||||
$inferior_value_comparison,
|
||||
$isset_assert
|
||||
$inferior_value_comparison
|
||||
);
|
||||
|
||||
if ($count_equality_position) {
|
||||
@ -3973,15 +3962,19 @@ class AssertionFinder
|
||||
}
|
||||
|
||||
|
||||
if ($var_name !== null) {
|
||||
if ($var_name !== null && $inferior_value_comparison !== null) {
|
||||
if ($inferior_value_position === self::ASSIGNMENT_TO_RIGHT) {
|
||||
$if_types[$var_name] = [[new IsLessThan($inferior_value_comparison)]];
|
||||
if ($conditional instanceof SmallerOrEqual) {
|
||||
$if_types[$var_name] = [[new IsLessThanOrEqualTo($inferior_value_comparison)]];
|
||||
} else {
|
||||
$if_types[$var_name] = [[new IsLessThan($inferior_value_comparison)]];
|
||||
}
|
||||
} else {
|
||||
$if_types[$var_name] = [[new IsGreaterThan($inferior_value_comparison)]];
|
||||
}
|
||||
|
||||
if ($isset_assert) {
|
||||
$if_types[$var_name][] = [new IsEqualIsset()];
|
||||
if ($conditional instanceof SmallerOrEqual) {
|
||||
$if_types[$var_name] = [[new IsGreaterThanOrEqualTo($inferior_value_comparison)]];
|
||||
} else {
|
||||
$if_types[$var_name] = [[new IsGreaterThan($inferior_value_comparison)]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -547,7 +547,6 @@ class ArrayFunctionArgumentsAnalyzer
|
||||
]
|
||||
);
|
||||
} else {
|
||||
/** @psalm-suppress InvalidPropertyAssignmentValue */
|
||||
$array_atomic_type->count--;
|
||||
}
|
||||
} else {
|
||||
@ -565,7 +564,6 @@ class ArrayFunctionArgumentsAnalyzer
|
||||
]
|
||||
);
|
||||
} else {
|
||||
/** @psalm-suppress InvalidPropertyAssignmentValue */
|
||||
$array_atomic_type->count--;
|
||||
}
|
||||
} else {
|
||||
|
@ -328,6 +328,44 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
$did_remove_type = true;
|
||||
}
|
||||
}
|
||||
|
||||
$existing_range_types = $existing_var_type->getRangeInts();
|
||||
|
||||
if ($existing_range_types) {
|
||||
foreach ($existing_range_types as $int_key => $literal_type) {
|
||||
if ($literal_type->contains($assertion_type->value)) {
|
||||
$did_remove_type = true;
|
||||
$existing_var_type->removeType($int_key);
|
||||
if ($literal_type->min_bound === null
|
||||
|| $literal_type->min_bound <= $assertion_type->value - 1
|
||||
) {
|
||||
$existing_var_type->addType(new Type\Atomic\TIntRange(
|
||||
$literal_type->min_bound,
|
||||
$assertion_type->value - 1
|
||||
));
|
||||
}
|
||||
if ($literal_type->max_bound === null
|
||||
|| $literal_type->max_bound >= $assertion_type->value + 1
|
||||
) {
|
||||
$existing_var_type->addType(new Type\Atomic\TIntRange(
|
||||
$assertion_type->value + 1,
|
||||
$literal_type->max_bound
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($existing_var_type->getAtomicTypes()['int'])
|
||||
&& get_class($existing_var_type->getAtomicTypes()['int']) === Type\Atomic\TInt::class
|
||||
) {
|
||||
$did_remove_type = true;
|
||||
//this may be used to generate a range containing any int except the one that was asserted against
|
||||
//but this is failing some tests
|
||||
/*$existing_var_type->removeType('int');
|
||||
$existing_var_type->addType(new Type\Atomic\TIntRange(null, $assertion_type->value - 1));
|
||||
$existing_var_type->addType(new Type\Atomic\TIntRange($assertion_type->value + 1, null));*/
|
||||
}
|
||||
} else {
|
||||
$scalar_var_type = clone $assertion_type;
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
}
|
||||
|
||||
if ($assertion instanceof IsGreaterThan) {
|
||||
return self::reconcileSuperiorTo(
|
||||
return self::reconcileIsGreaterThan(
|
||||
$assertion,
|
||||
$existing_var_type,
|
||||
$inside_loop,
|
||||
@ -166,7 +166,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
}
|
||||
|
||||
if ($assertion instanceof IsLessThan) {
|
||||
return self::reconcileInferiorTo(
|
||||
return self::reconcileIsLessThan(
|
||||
$assertion,
|
||||
$existing_var_type,
|
||||
$inside_loop,
|
||||
@ -1612,7 +1612,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
/**
|
||||
* @param string[] $suppressed_issues
|
||||
*/
|
||||
private static function reconcileSuperiorTo(
|
||||
private static function reconcileIsGreaterThan(
|
||||
IsGreaterThan $assertion,
|
||||
Union $existing_var_type,
|
||||
bool $inside_loop,
|
||||
@ -1622,19 +1622,21 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
?CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
): Union {
|
||||
$assertion_value = $assertion->value;
|
||||
//we add 1 from the assertion value because we're on a strict operator
|
||||
$assertion_value = $assertion->value + 1;
|
||||
|
||||
$did_remove_type = false;
|
||||
|
||||
if ($existing_var_type->hasType('null') && $assertion->doesFilterNull()) {
|
||||
$did_remove_type = true;
|
||||
$existing_var_type->removeType('null');
|
||||
}
|
||||
|
||||
foreach ($existing_var_type->getAtomicTypes() as $atomic_type) {
|
||||
if ($inside_loop) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($assertion_value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($atomic_type instanceof TIntRange) {
|
||||
if ($atomic_type->contains($assertion_value)) {
|
||||
// if the range contains the assertion, the range must be adapted
|
||||
@ -1715,7 +1717,7 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
/**
|
||||
* @param string[] $suppressed_issues
|
||||
*/
|
||||
private static function reconcileInferiorTo(
|
||||
private static function reconcileIsLessThan(
|
||||
IsLessThan $assertion,
|
||||
Union $existing_var_type,
|
||||
bool $inside_loop,
|
||||
@ -1725,19 +1727,21 @@ class SimpleAssertionReconciler extends Reconciler
|
||||
?CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
): Union {
|
||||
$assertion_value = $assertion->value;
|
||||
//we remove 1 from the assertion value because we're on a strict operator
|
||||
$assertion_value = $assertion->value - 1;
|
||||
|
||||
$did_remove_type = false;
|
||||
|
||||
if ($existing_var_type->hasType('null') && $assertion->doesFilterNull()) {
|
||||
$did_remove_type = true;
|
||||
$existing_var_type->removeType('null');
|
||||
}
|
||||
|
||||
foreach ($existing_var_type->getAtomicTypes() as $atomic_type) {
|
||||
if ($inside_loop) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($assertion_value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($atomic_type instanceof TIntRange) {
|
||||
if ($atomic_type->contains($assertion_value)) {
|
||||
// if the range contains the assertion, the range must be adapted
|
||||
|
@ -1653,22 +1653,23 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
*/
|
||||
private static function reconcileIsLessThanOrEqualTo(
|
||||
IsLessThanOrEqualTo $assertion,
|
||||
Union $existing_var_type,
|
||||
bool $inside_loop,
|
||||
string $old_var_type_string,
|
||||
?string $var_id,
|
||||
bool $negated,
|
||||
?CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
Union $existing_var_type,
|
||||
bool $inside_loop,
|
||||
string $old_var_type_string,
|
||||
?string $var_id,
|
||||
bool $negated,
|
||||
?CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
): Union {
|
||||
if ($assertion->value === null) {
|
||||
return $existing_var_type;
|
||||
}
|
||||
|
||||
$assertion_value = $assertion->value - 1;
|
||||
$assertion_value = $assertion->value;
|
||||
|
||||
$did_remove_type = false;
|
||||
|
||||
if ($existing_var_type->hasType('null') && $assertion->doesFilterNull()) {
|
||||
$did_remove_type = true;
|
||||
$existing_var_type->removeType('null');
|
||||
}
|
||||
|
||||
foreach ($existing_var_type->getAtomicTypes() as $atomic_type) {
|
||||
if ($inside_loop) {
|
||||
continue;
|
||||
@ -1756,22 +1757,23 @@ class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
*/
|
||||
private static function reconcileIsGreaterThanOrEqualTo(
|
||||
IsGreaterThanOrEqualTo $assertion,
|
||||
Union $existing_var_type,
|
||||
bool $inside_loop,
|
||||
string $old_var_type_string,
|
||||
?string $var_id,
|
||||
bool $negated,
|
||||
?CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
Union $existing_var_type,
|
||||
bool $inside_loop,
|
||||
string $old_var_type_string,
|
||||
?string $var_id,
|
||||
bool $negated,
|
||||
?CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
): Union {
|
||||
if ($assertion->value === null) {
|
||||
return $existing_var_type;
|
||||
}
|
||||
|
||||
$assertion_value = $assertion->value + 1;
|
||||
$assertion_value = $assertion->value;
|
||||
|
||||
$did_remove_type = false;
|
||||
|
||||
if ($existing_var_type->hasType('null') && $assertion->doesFilterNull()) {
|
||||
$did_remove_type = true;
|
||||
$existing_var_type->removeType('null');
|
||||
}
|
||||
|
||||
foreach ($existing_var_type->getAtomicTypes() as $atomic_type) {
|
||||
if ($inside_loop) {
|
||||
continue;
|
||||
|
@ -6,9 +6,9 @@ use Psalm\Storage\Assertion;
|
||||
|
||||
class IsGreaterThan extends Assertion
|
||||
{
|
||||
public ?int $value;
|
||||
public int $value;
|
||||
|
||||
public function __construct(?int $value)
|
||||
public function __construct(int $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
@ -29,4 +29,9 @@ class IsGreaterThan extends Assertion
|
||||
{
|
||||
return $assertion instanceof IsLessThanOrEqualTo && $this->value === $assertion->value;
|
||||
}
|
||||
|
||||
public function doesFilterNull(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ use Psalm\Storage\Assertion;
|
||||
|
||||
class IsGreaterThanOrEqualTo extends Assertion
|
||||
{
|
||||
public ?int $value;
|
||||
public int $value;
|
||||
|
||||
public function __construct(?int $value)
|
||||
public function __construct(int $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
@ -34,4 +34,9 @@ class IsGreaterThanOrEqualTo extends Assertion
|
||||
{
|
||||
return $assertion instanceof IsLessThan && $this->value === $assertion->value;
|
||||
}
|
||||
|
||||
public function doesFilterNull(): bool
|
||||
{
|
||||
return $this->value !== 0;
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ use Psalm\Storage\Assertion;
|
||||
|
||||
class IsLessThan extends Assertion
|
||||
{
|
||||
public ?int $value;
|
||||
public int $value;
|
||||
|
||||
public function __construct(?int $value)
|
||||
public function __construct(int $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
@ -29,4 +29,9 @@ class IsLessThan extends Assertion
|
||||
{
|
||||
return $assertion instanceof IsGreaterThanOrEqualTo && $this->value === $assertion->value;
|
||||
}
|
||||
|
||||
public function doesFilterNull(): bool
|
||||
{
|
||||
return $this->value === 0;
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ use Psalm\Storage\Assertion;
|
||||
|
||||
class IsLessThanOrEqualTo extends Assertion
|
||||
{
|
||||
public ?int $value;
|
||||
public int $value;
|
||||
|
||||
public function __construct(?int $value)
|
||||
public function __construct(int $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
@ -34,4 +34,9 @@ class IsLessThanOrEqualTo extends Assertion
|
||||
{
|
||||
return $assertion instanceof IsGreaterThan && $this->value === $assertion->value;
|
||||
}
|
||||
|
||||
public function doesFilterNull(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -837,6 +837,21 @@ class BinaryOperationTest extends TestCase
|
||||
return ($foo instanceof FooInterface ? $foo->toString() : null) ?? "Not a stringable foo";
|
||||
}',
|
||||
],
|
||||
'handleLiteralInequalityWithInts' => [
|
||||
'code' => '<?php
|
||||
|
||||
/**
|
||||
* @param int<0, max> $i
|
||||
* @return int<1, max>
|
||||
*/
|
||||
function toPositiveInt(int $i): int
|
||||
{
|
||||
if ($i !== 0) {
|
||||
return $i;
|
||||
}
|
||||
return 1;
|
||||
}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -716,6 +716,90 @@ class IntRangeTest extends TestCase
|
||||
'assertions' => [
|
||||
],
|
||||
],
|
||||
'assertionsAndNegationsOnRanges' => [
|
||||
'code' => '<?php
|
||||
/** @var int $int */
|
||||
$int = 1;
|
||||
$a = $b = $c = $d = $e = $f = $g = $h = $int;
|
||||
|
||||
if ($a < 1) {
|
||||
$res1 = $a; //should be int<min, 0>
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$res2 = $a; //should be int<1, max>
|
||||
|
||||
if ($b > 1) {
|
||||
$res3 = $b; //should be int<2, max>
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$res4 = $b; //should be int<min, 1>
|
||||
|
||||
if ($c <= 1) {
|
||||
$res5 = $c; //should be int<min, 1>
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$res6 = $c; //should be int<2, max>
|
||||
|
||||
if ($d >= 1) {
|
||||
$res7 = $d; //should be int<1, max>
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$res8 = $d; //should be int<min, 0>
|
||||
|
||||
|
||||
|
||||
if (1 < $e) {
|
||||
$res9 = $e; //should be int<2, max>
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$res10 = $e; //should be int<min, 1>
|
||||
|
||||
if (1 > $f) {
|
||||
$res11 = $f; //should be int<min, 0>
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$res12 = $f; //should be int<1, max>
|
||||
|
||||
if (1 <= $g) {
|
||||
$res13 = $g; //should be int<1, max>
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$res14 = $g; //should be int<min, 0>
|
||||
|
||||
if (1 >= $h) {
|
||||
$res15 = $h; //should be int<min, 1>
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$res16 = $h; //should be int<2, max>',
|
||||
'assertions' => [
|
||||
//'$res1' => 'int<min, 0>',
|
||||
'$res2' => 'int<1, max>',
|
||||
//'$res3' => 'int<2, max>',
|
||||
'$res4' => 'int<min, 1>',
|
||||
//'$res5' => 'int<min, 1>',
|
||||
'$res6' => 'int<2, max>',
|
||||
//'$res7' => 'int<1, max>',
|
||||
'$res8' => 'int<min, 0>',
|
||||
|
||||
//'$res9' => 'int<2, max>',
|
||||
'$res10' => 'int<min, 1>',
|
||||
//'$res11' => 'int<min, 0>',
|
||||
'$res12' => 'int<1, max>',
|
||||
//'$res13' => 'int<1, max>',
|
||||
'$res14' => 'int<min, 0>',
|
||||
//'$res15' => 'int<min, 1>',
|
||||
'$res16' => 'int<2, max>',
|
||||
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user