Skip to content

Commit 0682279

Browse files
committed
NarrowReturnType: preg_* narrowing only for constant patterns
1 parent 5d49dbb commit 0682279

3 files changed

Lines changed: 24 additions & 5 deletions

File tree

extension-narrowReturnType.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ services:
44
# PHP still returns |false, but it's trivial — indicates a programming error or unrealistic state
55
# ---------
66

7-
# Regex (false = invalid pattern)
7+
# Regex (false = invalid pattern; only stripped when pattern is a constant string)
88
preg_match
99
preg_split
1010
preg_grep

src/Type/NarrowReturnTypeResolver.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use PHPStan\Type\ExpressionTypeResolverExtension;
1818
use PHPStan\Type\Type;
1919
use PHPStan\Type\TypeCombinator;
20-
use function array_merge, explode, str_contains, strtolower;
20+
use function array_merge, explode, str_contains, str_starts_with, strtolower;
2121

2222

2323
/**
@@ -81,10 +81,20 @@ private function resolveFuncCall(FuncCall $expr, Scope $scope): ?Type
8181
}
8282

8383
$functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope);
84-
if (!isset($this->functions[$functionReflection->getName()])) {
84+
$functionName = $functionReflection->getName();
85+
86+
if (!isset($this->functions[$functionName])) {
8587
return null;
8688
}
8789

90+
// preg_* functions return false only for invalid patterns, so skip narrowing for non-constant patterns
91+
if (str_starts_with($functionName, 'preg_')) {
92+
$args = $expr->getArgs();
93+
if ($args === [] || $scope->getType($args[0]->value)->getConstantStrings() === []) {
94+
return null;
95+
}
96+
}
97+
8898
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
8999
$scope,
90100
$expr->getArgs(),

tests/data/narrow-return-type.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,24 @@
2727
// JSON
2828
assertType('non-empty-string', json_encode('data'));
2929

30-
// Regex
31-
function testRegex(string $s): void
30+
// Regex (constant pattern — |false stripped)
31+
function testRegexConstant(string $s): void
3232
{
3333
assertType('0|1', preg_match('/a/', $s));
3434
assertType('list<string>', preg_split('/a/', $s));
3535
assertType('array', preg_grep('/a/', [$s]));
3636
}
3737

3838

39+
// Regex (non-constant pattern — |false preserved)
40+
function testRegexDynamic(string $pattern, string $s): void
41+
{
42+
assertType('0|1|false', preg_match($pattern, $s));
43+
assertType('list<string>|false', preg_split($pattern, $s));
44+
assertType('array|false', preg_grep($pattern, [$s]));
45+
}
46+
47+
3948
// mb_string
4049
assertType('string', mb_chr(65));
4150
assertType('int', mb_ord('A'));

0 commit comments

Comments
 (0)