Skip to content

Commit f7d01e1

Browse files
committed
Implement PregGrepDynamicReturnTypeExtension
1 parent 7c80750 commit f7d01e1

File tree

7 files changed

+122
-15
lines changed

7 files changed

+122
-15
lines changed

conf/config.neon

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,6 +1716,14 @@ services:
17161716
tags:
17171717
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
17181718

1719+
-
1720+
class: PHPStan\Type\Php\PregGrepDynamicReturnTypeExtension
1721+
tags:
1722+
- phpstan.broker.dynamicFunctionReturnTypeExtension
1723+
1724+
-
1725+
class: PHPStan\Rules\Regexp\RegularExpressionHelper
1726+
17191727
-
17201728
class: PHPStan\Type\ClosureTypeFactory
17211729
arguments:
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Regexp;
4+
5+
use Nette\Utils\RegexpException;
6+
use Nette\Utils\Strings;
7+
8+
final class RegularExpressionHelper
9+
{
10+
11+
public function validatePattern(string $pattern): ?string
12+
{
13+
try {
14+
Strings::match('', $pattern);
15+
} catch (RegexpException $e) {
16+
return $e->getMessage();
17+
}
18+
19+
return null;
20+
}
21+
22+
}

src/Rules/Regexp/RegularExpressionPatternRule.php

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
namespace PHPStan\Rules\Regexp;
44

5-
use Nette\Utils\RegexpException;
6-
use Nette\Utils\Strings;
75
use PhpParser\Node;
86
use PhpParser\Node\Expr\FuncCall;
97
use PHPStan\Analyser\Scope;
@@ -20,6 +18,12 @@
2018
class RegularExpressionPatternRule implements Rule
2119
{
2220

21+
public function __construct(
22+
private RegularExpressionHelper $regularExpressionHelper,
23+
)
24+
{
25+
}
26+
2327
public function getNodeType(): string
2428
{
2529
return FuncCall::class;
@@ -31,7 +35,7 @@ public function processNode(Node $node, Scope $scope): array
3135

3236
$errors = [];
3337
foreach ($patterns as $pattern) {
34-
$errorMessage = $this->validatePattern($pattern);
38+
$errorMessage = $this->regularExpressionHelper->validatePattern($pattern);
3539
if ($errorMessage === null) {
3640
continue;
3741
}
@@ -110,15 +114,4 @@ private function extractPatterns(FuncCall $functionCall, Scope $scope): array
110114
return $patternStrings;
111115
}
112116

113-
private function validatePattern(string $pattern): ?string
114-
{
115-
try {
116-
Strings::match('', $pattern);
117-
} catch (RegexpException $e) {
118-
return $e->getMessage();
119-
}
120-
121-
return null;
122-
}
123-
124117
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PhpParser\Node\Expr\FuncCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\FunctionReflection;
8+
use PHPStan\Reflection\ParametersAcceptorSelector;
9+
use PHPStan\Rules\Regexp\RegularExpressionHelper;
10+
use PHPStan\Type\Constant\ConstantBooleanType;
11+
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
12+
use PHPStan\Type\Type;
13+
use PHPStan\Type\TypeCombinator;
14+
use function count;
15+
use function strtolower;
16+
17+
class PregGrepDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
18+
{
19+
20+
public function __construct(
21+
private RegularExpressionHelper $regularExpressionHelper,
22+
)
23+
{
24+
}
25+
26+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
27+
{
28+
return strtolower($functionReflection->getName()) === 'preg_grep';
29+
}
30+
31+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
32+
{
33+
$args = $functionCall->getArgs();
34+
if (count($args) < 1) {
35+
return null;
36+
}
37+
38+
$patternType = $scope->getType($args[0]->value);
39+
$constantStrings = $patternType->getConstantStrings();
40+
if (count($constantStrings) === 0) {
41+
return null;
42+
}
43+
44+
foreach ($constantStrings as $constantString) {
45+
if ($this->regularExpressionHelper->validatePattern($constantString->getValue()) !== null) {
46+
return null;
47+
}
48+
}
49+
50+
$defaultReturn = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
51+
52+
return TypeCombinator::remove(
53+
$defaultReturn,
54+
new ConstantBooleanType(false),
55+
);
56+
}
57+
58+
}

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,6 +1327,7 @@ public function dataFileAsserts(): iterable
13271327

13281328
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7915.php');
13291329
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9714.php');
1330+
yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_grep.php');
13301331
}
13311332

13321333
/**
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace PregGrep;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function compilablePattern(array $a) {
8+
assertType('array', preg_grep('/^[0-9]+$/', $a));
9+
}
10+
11+
function bogusPattern(array $a) {
12+
assertType('array|false', preg_grep('/bogus-pattern-[0^-9]$/', $a));
13+
}
14+
15+
function unknownPattern(string $p, array $a) {
16+
assertType('array|false', preg_grep($p, $a));
17+
}
18+
19+
function sometimesCompilablePattern(array $a) {
20+
$p = '/^[0-9]+$/';
21+
if (rand(0,1)) {
22+
$p = '/bogus-pattern-[0^-9]$/';
23+
}
24+
assertType('array|false', preg_grep($p, $a));
25+
}

tests/PHPStan/Rules/Regexp/RegularExpressionPatternRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class RegularExpressionPatternRuleTest extends RuleTestCase
1515

1616
protected function getRule(): Rule
1717
{
18-
return new RegularExpressionPatternRule();
18+
return new RegularExpressionPatternRule(new RegularExpressionHelper());
1919
}
2020

2121
public function testValidRegexPatternBefore73(): void

0 commit comments

Comments
 (0)