From 61c35ce9288516ccdccd7838018c94f56f100a7b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 8 Nov 2024 13:52:29 +0100 Subject: [PATCH 1/3] More precise types for `preg_match` greater than `0` --- src/Analyser/TypeSpecifier.php | 20 ++++++++- tests/PHPStan/Analyser/nsrt/bug-11293.php | 51 +++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11293.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index fb8b5985e7..23579833f1 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..91f7ab5c1f --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11293.php @@ -0,0 +1,51 @@ += 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); + } +} From fb014ee12330c82e5a75a8cae026e63d63c32058 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 8 Nov 2024 13:57:27 +0100 Subject: [PATCH 2/3] test yoda notation --- tests/PHPStan/Analyser/nsrt/bug-11293.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-11293.php b/tests/PHPStan/Analyser/nsrt/bug-11293.php index 91f7ab5c1f..0c190b23fc 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11293.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11293.php @@ -48,4 +48,15 @@ public function sayHello5(string $s): void 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); + } } From a7a7645b550a5e4de0616eede5b428710992bc09 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 8 Nov 2024 14:01:23 +0100 Subject: [PATCH 3/3] Update TypeSpecifier.php --- src/Analyser/TypeSpecifier.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 23579833f1..68864c18b6 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -339,7 +339,7 @@ public function specifyTypesInCondition( if ( !$context->null() && $expr->right instanceof FuncCall - && count($expr->right->getArgs()) === 3 + && 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()