Skip to content

Commit 1e8c978

Browse files
committed
RemoveFalseReturnTypeExtension: preg_* narrowing only for constant patterns
1 parent a2a86c4 commit 1e8c978

3 files changed

Lines changed: 24 additions & 5 deletions

File tree

extension-php.neon

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

11-
# Regex (false = invalid pattern)
11+
# Regex (false = invalid pattern; only stripped when pattern is a constant string)
1212
preg_match
1313
preg_split
1414
preg_grep

src/Php/RemoveFalseReturnTypeExtension.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
@@ -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)