diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index fb8b5985e7..68864c18b6 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -242,11 +242,11 @@ public function specifyTypesInCondition( $expr->left instanceof FuncCall && count($expr->left->getArgs()) >= 1 && $expr->left->name instanceof Name - && in_array(strtolower((string) $expr->left->name), ['count', 'sizeof', 'strlen', 'mb_strlen'], true) + && in_array(strtolower((string) $expr->left->name), ['count', 'sizeof', 'strlen', 'mb_strlen', 'preg_match'], true) && ( !$expr->right instanceof FuncCall || !$expr->right->name instanceof Name - || !in_array(strtolower((string) $expr->right->name), ['count', 'sizeof', 'strlen', 'mb_strlen'], true) + || !in_array(strtolower((string) $expr->right->name), ['count', 'sizeof', 'strlen', 'mb_strlen', 'preg_match'], true) ) ) { $inverseOperator = $expr instanceof Node\Expr\BinaryOp\Smaller @@ -336,6 +336,22 @@ public function specifyTypesInCondition( } } + if ( + !$context->null() + && $expr->right instanceof FuncCall + && 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() + ) { + return $this->specifyTypesInCondition( + $scope, + new Expr\BinaryOp\NotIdentical($expr->right, new ConstFetch(new Name('false'))), + $context, + $rootExpr, + ); + } + if ( !$context->null() && $expr->right instanceof FuncCall diff --git a/tests/PHPStan/Analyser/nsrt/bug-11293.php b/tests/PHPStan/Analyser/nsrt/bug-11293.php new file mode 100644 index 0000000000..0c190b23fc --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11293.php @@ -0,0 +1,62 @@ += 7.4 + +namespace Bug11293; + +use function PHPStan\Testing\assertType; + +class HelloWorld +{ + public function sayHello(string $s): void + { + if (preg_match('/data-(\d{6})\.json$/', $s, $matches) > 0) { + assertType('array{string, non-falsy-string&numeric-string}', $matches); + } + } + + public function sayHello2(string $s): void + { + if (preg_match('/data-(\d{6})\.json$/', $s, $matches) === 1) { + assertType('array{string, non-falsy-string&numeric-string}', $matches); + } + } + + public function sayHello3(string $s): void + { + if (preg_match('/data-(\d{6})\.json$/', $s, $matches) >= 1) { + assertType('array{string, non-falsy-string&numeric-string}', $matches); + } + } + + public function sayHello4(string $s): void + { + if (preg_match('/data-(\d{6})\.json$/', $s, $matches) <= 0) { + assertType('array{}', $matches); + + return; + } + + assertType('array{string, non-falsy-string&numeric-string}', $matches); + } + + public function sayHello5(string $s): void + { + if (preg_match('/data-(\d{6})\.json$/', $s, $matches) < 1) { + assertType('array{}', $matches); + + return; + } + + assertType('array{string, non-falsy-string&numeric-string}', $matches); + } + + public function sayHello6(string $s): void + { + if (1 > preg_match('/data-(\d{6})\.json$/', $s, $matches)) { + assertType('array{}', $matches); + + return; + } + + assertType('array{string, non-falsy-string&numeric-string}', $matches); + } +}