Skip to content

Commit 59c248f

Browse files
committed
NarrowReturnType: preg_* narrowing only for constant patterns
1 parent 7d3f9d8 commit 59c248f

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
@@ -19,7 +19,7 @@
1919
use PHPStan\Type\ExpressionTypeResolverExtension;
2020
use PHPStan\Type\Type;
2121
use PHPStan\Type\TypeCombinator;
22-
use function array_merge, explode, str_contains, strtolower;
22+
use function array_merge, explode, str_contains, str_starts_with, strtolower;
2323

2424

2525
/**
@@ -83,10 +83,20 @@ private function resolveFuncCall(FuncCall $expr, Scope $scope): ?Type
8383
}
8484

8585
$functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope);
86-
if (!isset($this->functions[$functionReflection->getName()])) {
86+
$functionName = $functionReflection->getName();
87+
88+
if (!isset($this->functions[$functionName])) {
8789
return null;
8890
}
8991

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

tests/data/narrow-return-type.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,24 @@
3131
// JSON
3232
assertType('non-empty-string', json_encode('data'));
3333

34-
// Regex
35-
function testRegex(string $s): void
34+
// Regex (constant pattern — |false stripped)
35+
function testRegexConstant(string $s): void
3636
{
3737
assertType('0|1', preg_match('/a/', $s));
3838
assertType('list<string>', preg_split('/a/', $s));
3939
assertType('array', preg_grep('/a/', [$s]));
4040
}
4141

4242

43+
// Regex (non-constant pattern — |false preserved)
44+
function testRegexDynamic(string $pattern, string $s): void
45+
{
46+
assertType('0|1|false', preg_match($pattern, $s));
47+
assertType('list<string>|false', preg_split($pattern, $s));
48+
assertType('array|false', preg_grep($pattern, [$s]));
49+
}
50+
51+
4352
// mb_string
4453
assertType('string', mb_chr(65));
4554
assertType('int', mb_ord('A'));

0 commit comments

Comments
 (0)