diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php index 828cf7f06e8..3c002791e68 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php @@ -58,7 +58,6 @@ use Psalm\Storage\Assertion\IsNotIdentical; use Psalm\Storage\Assertion\IsNotLooselyEqual; use Psalm\Storage\Assertion\IsNotType; -use Psalm\Storage\Assertion\IsPositiveNumeric; use Psalm\Storage\Assertion\IsType; use Psalm\Storage\Assertion\NestedAssertions; use Psalm\Storage\Assertion\NonEmptyCountable; @@ -3869,13 +3868,7 @@ private static function getGreaterAssertions( if ($var_name !== null) { if ($superior_value_position === self::ASSIGNMENT_TO_RIGHT) { - if ($superior_value_comparison === 0) { - $if_types[$var_name] = [[new IsPositiveNumeric(true), new IsIdentical(new TLiteralInt(0))]]; - } elseif ($superior_value_comparison === 1) { - $if_types[$var_name] = [[new IsPositiveNumeric(true)]]; - } else { - $if_types[$var_name] = [[new IsGreaterThan($superior_value_comparison)]]; - } + $if_types[$var_name] = [[new IsGreaterThan($superior_value_comparison)]]; } else { $if_types[$var_name] = [[new IsLessThan($superior_value_comparison)]]; } @@ -3984,13 +3977,7 @@ private static function getSmallerAssertions( if ($inferior_value_position === self::ASSIGNMENT_TO_RIGHT) { $if_types[$var_name] = [[new IsLessThan($inferior_value_comparison)]]; } else { - if ($inferior_value_comparison === 0) { - $if_types[$var_name] = [[new IsPositiveNumeric(false), new IsIdentical(new TLiteralInt(0))]]; - } elseif ($inferior_value_comparison === 1) { - $if_types[$var_name] = [[new IsPositiveNumeric(true)]]; - } else { - $if_types[$var_name] = [[new IsGreaterThan($inferior_value_comparison)]]; - } + $if_types[$var_name] = [[new IsGreaterThan($inferior_value_comparison)]]; } if ($isset_assert) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php index fe2ec8b2181..80ba8e84fe7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php @@ -28,6 +28,7 @@ use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TInt; +use Psalm\Type\Atomic\TIntRange; use Psalm\Type\Atomic\TLiteralInt; use Psalm\Type\Atomic\TLiteralString; use Psalm\Type\Atomic\TLowercaseString; @@ -202,8 +203,7 @@ public static function analyze( ); if ($left_is_numeric) { - $right_uint = Type::getPositiveInt(); - $right_uint->addType(new TLiteralInt(0)); + $right_uint = new Union([new TIntRange(0, null)]); $right_is_uint = UnionTypeComparator::isContainedBy( $codebase, $right_type, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php index c6870a4b968..73b2cddaa90 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php @@ -547,7 +547,7 @@ public static function handleByRefArrayAdjustment( ] ); } else { - /** @psalm-suppress PropertyTypeCoercion */ + /** @psalm-suppress InvalidPropertyAssignmentValue */ $array_atomic_type->count--; } } else { @@ -565,7 +565,7 @@ public static function handleByRefArrayAdjustment( ] ); } else { - /** @psalm-suppress PropertyTypeCoercion */ + /** @psalm-suppress InvalidPropertyAssignmentValue */ $array_atomic_type->count--; } } else { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php index 2d0d50e7bef..abe02428fd3 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php @@ -16,6 +16,7 @@ use Psalm\Issue\UndefinedConstant; use Psalm\IssueBuffer; use Psalm\Type; +use Psalm\Type\Atomic\TIntRange; use Psalm\Type\Union; use ReflectionProperty; @@ -174,7 +175,7 @@ public static function getGlobalConstType( case 'PHP_INT_SIZE': case 'PHP_MAXPATHLEN': case 'PHP_VERSION_ID': - return Type::getPositiveInt(); + return new Union([new TIntRange(1, null)]); case 'PHP_FLOAT_EPSILON': case 'PHP_FLOAT_MAX': diff --git a/src/Psalm/Internal/Type/NegatedAssertionReconciler.php b/src/Psalm/Internal/Type/NegatedAssertionReconciler.php index 0092b093a6b..4d5036c617e 100644 --- a/src/Psalm/Internal/Type/NegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/NegatedAssertionReconciler.php @@ -11,7 +11,6 @@ use Psalm\Storage\Assertion\IsClassNotEqual; use Psalm\Storage\Assertion\IsNotCountable; use Psalm\Storage\Assertion\IsNotIdentical; -use Psalm\Storage\Assertion\IsNotPositiveNumeric; use Psalm\Storage\Assertion\IsNotType; use Psalm\Type; use Psalm\Type\Atomic; @@ -87,10 +86,6 @@ public static function reconcile( ); } - if ($is_equality && $assertion instanceof IsNotPositiveNumeric) { - return $existing_var_type; - } - $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); if ($assertion_type instanceof TFalse && isset($existing_var_atomic_types['bool'])) { diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index aaf7ce5e3c2..e39cfa81932 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -22,7 +22,6 @@ use Psalm\Storage\Assertion\IsIsset; use Psalm\Storage\Assertion\IsLessThan; use Psalm\Storage\Assertion\IsLooselyEqual; -use Psalm\Storage\Assertion\IsPositiveNumeric; use Psalm\Storage\Assertion\IsType; use Psalm\Storage\Assertion\NonEmpty; use Psalm\Storage\Assertion\NonEmptyCountable; @@ -40,7 +39,6 @@ use Psalm\Type\Atomic\TCallableString; use Psalm\Type\Atomic\TClassConstant; use Psalm\Type\Atomic\TClassString; -use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TGenericObject; use Psalm\Type\Atomic\TInt; @@ -63,7 +61,6 @@ use Psalm\Type\Atomic\TNonEmptyString; use Psalm\Type\Atomic\TNonFalsyString; use Psalm\Type\Atomic\TNonspecificLiteralString; -use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TNumeric; use Psalm\Type\Atomic\TNumericString; use Psalm\Type\Atomic\TObject; @@ -81,7 +78,6 @@ use function count; use function explode; use function get_class; -use function max; use function min; use function strpos; @@ -238,19 +234,6 @@ public static function reconcile( ); } - if ($assertion instanceof IsPositiveNumeric) { - return self::reconcilePositiveNumeric( - $assertion, - $existing_var_type, - $key, - $negated, - $code_location, - $suppressed_issues, - $failed_reconciliation, - $is_equality - ); - } - if ($assertion instanceof NonEmptyCountable) { return self::reconcileNonEmptyCountable( $assertion, @@ -725,85 +708,6 @@ private static function reconcileExactlyCountable( return $existing_var_type; } - /** - * @param string[] $suppressed_issues - * @param Reconciler::RECONCILIATION_* $failed_reconciliation - */ - private static function reconcilePositiveNumeric( - Assertion $assertion, - Union $existing_var_type, - ?string $key, - bool $negated, - ?CodeLocation $code_location, - array $suppressed_issues, - int &$failed_reconciliation, - bool $is_equality - ): Union { - $old_var_type_string = $existing_var_type->getId(); - - $did_remove_type = false; - - $positive_types = []; - - foreach ($existing_var_type->getAtomicTypes() as $atomic_type) { - if ($atomic_type instanceof TLiteralInt) { - if ($atomic_type->value < 1) { - $did_remove_type = true; - } else { - $positive_types[] = $atomic_type; - } - } elseif ($atomic_type instanceof TPositiveInt) { - $positive_types[] = $atomic_type; - } elseif ($atomic_type instanceof TIntRange) { - if (!$atomic_type->isPositive()) { - $did_remove_type = true; - } - $positive_types[] = new TIntRange( - $atomic_type->min_bound === null ? 1 : max(1, $atomic_type->min_bound), - $atomic_type->max_bound === null ? null : max(1, $atomic_type->max_bound) - ); - } elseif (get_class($atomic_type) === TInt::class) { - $positive_types[] = new TPositiveInt(); - $did_remove_type = true; - } else { - // for now allow this check everywhere else - if (!$atomic_type instanceof TNull - && !$atomic_type instanceof TFalse - ) { - $positive_types[] = $atomic_type; - } - - $did_remove_type = true; - } - } - - if (!$is_equality - && !$existing_var_type->hasMixed() - && (!$did_remove_type || !$positive_types) - ) { - if ($key && $code_location) { - self::triggerIssueForImpossible( - $existing_var_type, - $old_var_type_string, - $key, - $assertion, - !$did_remove_type, - $negated, - $code_location, - $suppressed_issues - ); - } - } - - if ($positive_types) { - return new Union($positive_types); - } - - $failed_reconciliation = Reconciler::RECONCILIATION_EMPTY; - - return Type::getNever(); - } - /** * @param string[] $suppressed_issues * @param Reconciler::RECONCILIATION_* $failed_reconciliation diff --git a/src/Psalm/Storage/Assertion/IsNotPositiveNumeric.php b/src/Psalm/Storage/Assertion/IsNotPositiveNumeric.php deleted file mode 100644 index 02016b095e1..00000000000 --- a/src/Psalm/Storage/Assertion/IsNotPositiveNumeric.php +++ /dev/null @@ -1,30 +0,0 @@ -is_negatable = $is_negatable; - } - - /** @psalm-mutation-free */ - public function getNegation(): Assertion - { - return $this->is_negatable ? new IsNotPositiveNumeric() : new Any(); - } - - /** @psalm-mutation-free */ - public function hasEquality(): bool - { - return !$this->is_negatable; - } - - public function __toString(): string - { - return (!$this->is_negatable ? '=' : '') . 'positive-numeric'; - } - - /** @psalm-mutation-free */ - public function isNegationOf(Assertion $assertion): bool - { - return $assertion instanceof IsNotPositiveNumeric; - } -} diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 703279556e4..9932e9b0571 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -210,7 +210,7 @@ public static function create( return new TClosedResource(); case 'positive-int': - return new TPositiveInt(); + return new TIntRange(1, null); case 'numeric': return $analysis_php_version_id !== null ? new TNamedObject($value) : new TNumeric(); diff --git a/tests/ClosureTest.php b/tests/ClosureTest.php index 83e1365aa84..054233ec8d3 100644 --- a/tests/ClosureTest.php +++ b/tests/ClosureTest.php @@ -418,7 +418,7 @@ public function test() : Closure { $closure = Closure::fromCallable("strlen"); ', 'assertions' => [ - '$closure' => 'pure-Closure(string):(int|positive-int)', + '$closure' => 'pure-Closure(string):int<0, max>', ] ], 'allowClosureWithNarrowerReturn' => [ @@ -583,8 +583,8 @@ function maker(string $className) { $result = $closure("test"); ', 'assertions' => [ - '$closure' => 'pure-Closure(string):(int|positive-int)', - '$result' => 'int|positive-int', + '$closure' => 'pure-Closure(string):int<0, max>', + '$result' => 'int<0, max>', ], 'ignored_issues' => [], 'php_version' => '8.1' @@ -660,7 +660,7 @@ public function __invoke(string $param): int { $closure = $closure(...); ', 'assertions' => [ - '$closure' => 'pure-Closure(string):(int|positive-int)', + '$closure' => 'pure-Closure(string):int<0, max>', ], 'ignored_issues' => [], 'php_version' => '8.1' diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index df940d6faea..8e55a23af2c 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -48,9 +48,9 @@ function fooFoo(array $a = []): void { $c = $_GET["c"]; $c = is_numeric($c) ? abs($c) : null;', 'assertions' => [ - '$a' => 'int|positive-int', + '$a' => 'int<0, max>', '$b' => 'float', - '$c' => 'float|int|null|positive-int', + '$c' => 'float|int<0, max>|null', ], 'ignored_issues' => ['MixedAssignment', 'MixedArgument'], ], @@ -1362,7 +1362,7 @@ function takesInt(int $i) : void {} $r = preg_match("{foo}", "foo", $matches, PREG_OFFSET_CAPTURE);', 'assertions' => [ '$r===' => '0|1|false', - '$matches===' => 'array', + '$matches===' => 'array}>', ], ], 'pregMatchWithFlagUnmatchedAsNull' => [ @@ -1378,7 +1378,7 @@ function takesInt(int $i) : void {} $r = preg_match("{foo}", "foo", $matches, PREG_OFFSET_CAPTURE | PREG_UNMATCHED_AS_NULL);', 'assertions' => [ '$r===' => '0|1|false', - '$matches===' => 'array', + '$matches===' => 'array}>', ], ], 'pregReplaceCallback' => [ diff --git a/tests/IntRangeTest.php b/tests/IntRangeTest.php index e77db5a1309..eaba7f7fd36 100644 --- a/tests/IntRangeTest.php +++ b/tests/IntRangeTest.php @@ -505,7 +505,7 @@ function getInt(): int{return 0;} 'assertions' => [ '$b===' => 'int<-9, 9>', '$c===' => 'int<0, 9>', - '$d===' => '0|positive-int' + '$d===' => 'int<0, max>' ], ], 'minus' => [ @@ -582,7 +582,7 @@ function getInt(): int{return 0;} 'code' => ' */ function getInt(): int{ return rand(0, 10); } - + $a = getInt(); $b = -$a; $c = null; diff --git a/tests/Template/ClassTemplateExtendsTest.php b/tests/Template/ClassTemplateExtendsTest.php index f30f842a1e9..97883d69319 100644 --- a/tests/Template/ClassTemplateExtendsTest.php +++ b/tests/Template/ClassTemplateExtendsTest.php @@ -4145,7 +4145,7 @@ public function map(callable $function): Functor } } - /** @return Functor */ + /** @return Functor> */ function foo(string $s) : Functor { $foo = new FakeFunctor($s); $function = function (string $a): int { diff --git a/tests/TypeCombinationTest.php b/tests/TypeCombinationTest.php index dc73951f9f4..147ac2ab2be 100644 --- a/tests/TypeCombinationTest.php +++ b/tests/TypeCombinationTest.php @@ -630,28 +630,28 @@ public function providerTestValidTypeCombination(): array ], ], 'combineZeroAndPositiveInt' => [ - '0|positive-int', + 'int<0, max>', [ '0', 'positive-int', ], ], 'combinePositiveIntAndZero' => [ - '0|positive-int', + 'int<0, max>', [ 'positive-int', '0', ], ], 'combinePositiveIntAndMinusOne' => [ - 'int', + 'int<-1, max>', [ 'positive-int', '-1', ], ], 'combinePositiveIntZeroAndMinusOne' => [ - 'int', + 'int<-1, max>', [ '0', 'positive-int', @@ -659,14 +659,14 @@ public function providerTestValidTypeCombination(): array ], ], 'combineMinusOneAndPositiveInt' => [ - 'int', + 'int<-1, max>', [ '-1', 'positive-int', ], ], 'combineZeroMinusOneAndPositiveInt' => [ - 'int', + 'int<-1, max>', [ '0', '-1', @@ -674,7 +674,7 @@ public function providerTestValidTypeCombination(): array ], ], 'combineZeroOneAndPositiveInt' => [ - '0|positive-int', + 'int<0, max>', [ '0', '1', @@ -682,7 +682,7 @@ public function providerTestValidTypeCombination(): array ], ], 'combinePositiveIntOneAndZero' => [ - '0|positive-int', + 'int<0, max>', [ 'positive-int', '1', @@ -690,7 +690,7 @@ public function providerTestValidTypeCombination(): array ], ], 'combinePositiveInts' => [ - 'positive-int', + 'int<1, max>', [ 'positive-int', 'positive-int', diff --git a/tests/TypeReconciliation/ConditionalTest.php b/tests/TypeReconciliation/ConditionalTest.php index f9b18f2f8c5..2ab475b5fd5 100644 --- a/tests/TypeReconciliation/ConditionalTest.php +++ b/tests/TypeReconciliation/ConditionalTest.php @@ -2717,7 +2717,7 @@ function getIntOrNull(): ?int{return null;} } if ($a >= 0) { - /** @tmp-psalm-suppress PossiblyNullOperand this should be suppressed but assertions remove null for now */ + /** @psalm-suppress PossiblyNullOperand */ echo $a + 3; } @@ -2726,7 +2726,7 @@ function getIntOrNull(): ?int{return null;} } if (0 <= $a) { - /** @tmp-psalm-suppress PossiblyNullOperand this should be suppressed but assertions remove null for now */ + /** @psalm-suppress PossiblyNullOperand */ echo $a + 3; }