diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 5c806c3a44..e946229f7d 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -339,13 +339,15 @@ public function specifyTypesInCondition( && count($expr->right->getArgs()) >= 3 && $expr->right->name instanceof Name && in_array(strtolower((string) $expr->right->name), ['preg_match'], true) - && IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($leftType)->yes() + && ( + IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($leftType)->yes() + || ($expr instanceof Expr\BinaryOp\Smaller && IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($leftType)->yes()) + ) ) { - return $this->specifyTypesInCondition( - $scope, - new Expr\BinaryOp\NotIdentical($expr->right, new ConstFetch(new Name('false'))), - $context, - )->setRootExpr($expr); + // 0 < preg_match or 1 <= preg_match becomes 1 === preg_match + $newExpr = new Expr\BinaryOp\Identical($expr->right, new Node\Scalar\Int_(1)); + + return $this->specifyTypesInCondition($scope, $newExpr, $context)->setRootExpr($expr); } if ( diff --git a/src/Type/Php/PregMatchTypeSpecifyingExtension.php b/src/Type/Php/PregMatchTypeSpecifyingExtension.php index dca332f50e..399ee9126f 100644 --- a/src/Type/Php/PregMatchTypeSpecifyingExtension.php +++ b/src/Type/Php/PregMatchTypeSpecifyingExtension.php @@ -55,10 +55,18 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $flagsType = $scope->getType($flagsArg->value); } + if ($context->true() && $context->falsey()) { + $wasMatched = TrinaryLogic::createMaybe(); + } elseif ($context->true()) { + $wasMatched = TrinaryLogic::createYes(); + } else { + $wasMatched = TrinaryLogic::createNo(); + } + if ($functionReflection->getName() === 'preg_match') { - $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope); + $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, $wasMatched, $scope); } else { - $matchedType = $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope); + $matchedType = $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, $wasMatched, $scope); } if ($matchedType === null) { return new SpecifiedTypes(); diff --git a/tests/PHPStan/Analyser/nsrt/bug-11293.php b/tests/PHPStan/Analyser/nsrt/bug-11293.php index 19a9a1eb5c..caf95180ab 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11293.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11293.php @@ -30,7 +30,7 @@ public function sayHello3(string $s): void public function sayHello4(string $s): void { if (preg_match('/data-(\d{6})\.json$/', $s, $matches) <= 0) { - assertType('array{}', $matches); + assertType('list{0?: string, 1?: non-falsy-string&numeric-string}', $matches); return; } @@ -41,7 +41,7 @@ public function sayHello4(string $s): void public function sayHello5(string $s): void { if (preg_match('/data-(\d{6})\.json$/', $s, $matches) < 1) { - assertType('array{}', $matches); + assertType('list{0?: string, 1?: non-falsy-string&numeric-string}', $matches); return; } @@ -52,7 +52,7 @@ public function sayHello5(string $s): void public function sayHello6(string $s): void { if (1 > preg_match('/data-(\d{6})\.json$/', $s, $matches)) { - assertType('array{}', $matches); + assertType('list{0?: string, 1?: non-falsy-string&numeric-string}', $matches); return; } diff --git a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php index e8744f7f51..aa55dbe441 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php @@ -432,6 +432,13 @@ public function testBug5365(): void $this->analyse([__DIR__ . '/data/bug-5365.php'], []); } + public function testBug11908(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->reportAlwaysTrueInLastCondition = true; + $this->analyse([__DIR__ . '/data/bug-11908.php'], []); + } + public function testBug8555(): void { $this->treatPhpDocTypesAsCertain = true; diff --git a/tests/PHPStan/Rules/Comparison/data/bug-11908.php b/tests/PHPStan/Rules/Comparison/data/bug-11908.php new file mode 100644 index 0000000000..b583aaef55 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-11908.php @@ -0,0 +1,8 @@ +analyse([__DIR__ . '/data/bug-12771.php'], []); } + public function testBug11708(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/bug-11708.php'], []); + } + public function testIssetAfterRememberedConstructor(): void { $this->treatPhpDocTypesAsCertain = true; diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 15a04e3eb9..3c18294d1c 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -342,6 +342,13 @@ public function testBug10577(): void $this->analyse([__DIR__ . '/data/bug-10577.php'], []); } + public function testBug11708(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/bug-11708.php'], []); + } + public function testBug10610(): void { $this->treatPhpDocTypesAsCertain = true; diff --git a/tests/PHPStan/Rules/Variables/data/bug-11708.php b/tests/PHPStan/Rules/Variables/data/bug-11708.php new file mode 100644 index 0000000000..0a8487bbdb --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-11708.php @@ -0,0 +1,19 @@ +