Skip to content

Commit 05070e5

Browse files
committed
Support more functions
1 parent f7d01e1 commit 05070e5

11 files changed

+123
-53
lines changed

conf/config.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1717,7 +1717,7 @@ services:
17171717
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
17181718

17191719
-
1720-
class: PHPStan\Type\Php\PregGrepDynamicReturnTypeExtension
1720+
class: PHPStan\Type\Php\PregMatchingDynamicReturnTypeExtension
17211721
tags:
17221722
- phpstan.broker.dynamicFunctionReturnTypeExtension
17231723

src/Type/Php/PregGrepDynamicReturnTypeExtension.php renamed to src/Type/Php/PregMatchingDynamicReturnTypeExtension.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
use PHPStan\Type\Type;
1313
use PHPStan\Type\TypeCombinator;
1414
use function count;
15+
use function in_array;
1516
use function strtolower;
1617

17-
class PregGrepDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
18+
class PregMatchingDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
1819
{
1920

2021
public function __construct(
@@ -25,7 +26,7 @@ public function __construct(
2526

2627
public function isFunctionSupported(FunctionReflection $functionReflection): bool
2728
{
28-
return strtolower($functionReflection->getName()) === 'preg_grep';
29+
return in_array(strtolower($functionReflection->getName()), ['preg_grep', 'preg_match', 'preg_match_all'], true);
2930
}
3031

3132
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type

src/Type/Php/PregSplitDynamicReturnTypeExtension.php

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Reflection\FunctionReflection;
88
use PHPStan\Reflection\ParametersAcceptorSelector;
9+
use PHPStan\Rules\Regexp\RegularExpressionHelper;
910
use PHPStan\TrinaryLogic;
1011
use PHPStan\Type\Accessory\AccessoryArrayListType;
1112
use PHPStan\Type\ArrayType;
@@ -19,13 +20,15 @@
1920
use PHPStan\Type\StringType;
2021
use PHPStan\Type\Type;
2122
use PHPStan\Type\TypeCombinator;
23+
use function count;
2224
use function strtolower;
2325

2426
class PregSplitDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
2527
{
2628

2729
public function __construct(
2830
private BitwiseFlagHelper $bitwiseFlagAnalyser,
31+
private RegularExpressionHelper $regularExpressionHelper,
2932
)
3033
{
3134
}
@@ -44,10 +47,32 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
4447
new IntegerType(),
4548
new ConstantArrayType([new ConstantIntegerType(0), new ConstantIntegerType(1)], [new StringType(), IntegerRangeType::fromInterval(0, null)], [2], [], TrinaryLogic::createYes()),
4649
);
47-
return TypeCombinator::union(AccessoryArrayListType::intersectWith($type), new ConstantBooleanType(false));
50+
$returnType = TypeCombinator::union(AccessoryArrayListType::intersectWith($type), new ConstantBooleanType(false));
51+
} else {
52+
$returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
4853
}
4954

50-
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
55+
$patternArg = $functionCall->getArgs()[0] ?? null;
56+
if ($patternArg === null) {
57+
return $returnType;
58+
}
59+
60+
$patternType = $scope->getType($patternArg->value);
61+
$constantStrings = $patternType->getConstantStrings();
62+
if (count($constantStrings) === 0) {
63+
return $returnType;
64+
}
65+
66+
foreach ($constantStrings as $constantString) {
67+
if ($this->regularExpressionHelper->validatePattern($constantString->getValue()) !== null) {
68+
return $returnType;
69+
}
70+
}
71+
72+
return TypeCombinator::remove(
73+
$returnType,
74+
new ConstantBooleanType(false),
75+
);
5176
}
5277

5378
}

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use PHPStan\Type\Constant\ConstantStringType;
1616
use function extension_loaded;
1717
use function restore_error_handler;
18-
use function sprintf;
1918
use const PHP_VERSION_ID;
2019

2120
class AnalyserIntegrationTest extends PHPStanTestCase
@@ -835,13 +834,11 @@ public function testOffsetAccess(): void
835834
public function testUnresolvableParameter(): void
836835
{
837836
$errors = $this->runAnalyse(__DIR__ . '/data/unresolvable-parameter.php');
838-
$this->assertCount(3, $errors);
839-
$this->assertSame('Parameter #2 $array of function array_map expects array, array<int, string>|false given.', $errors[0]->getMessage());
840-
$this->assertSame(18, $errors[0]->getLine());
841-
$this->assertSame('Method UnresolvableParameter\Collection::pipeInto() has parameter $class with no type specified.', $errors[1]->getMessage());
837+
$this->assertCount(2, $errors);
838+
$this->assertSame('Method UnresolvableParameter\Collection::pipeInto() has parameter $class with no type specified.', $errors[0]->getMessage());
839+
$this->assertSame(30, $errors[0]->getLine());
840+
$this->assertSame('PHPDoc tag @param for parameter $class contains unresolvable type.', $errors[1]->getMessage());
842841
$this->assertSame(30, $errors[1]->getLine());
843-
$this->assertSame('PHPDoc tag @param for parameter $class contains unresolvable type.', $errors[2]->getMessage());
844-
$this->assertSame(30, $errors[2]->getLine());
845842
}
846843

847844
public function testBug7248(): void
@@ -883,13 +880,7 @@ public function testBug7500(): void
883880
public function testBug7554(): void
884881
{
885882
$errors = $this->runAnalyse(__DIR__ . '/data/bug-7554.php');
886-
$this->assertCount(2, $errors);
887-
888-
$this->assertSame(sprintf('Parameter #1 $%s of function count expects array|Countable, array<int, array<int, int|string>>|false given.', PHP_VERSION_ID < 80000 ? 'var' : 'value'), $errors[0]->getMessage());
889-
$this->assertSame(26, $errors[0]->getLine());
890-
891-
$this->assertSame('Cannot access offset int<1, max> on list<array{string, int<0, max>}>|false.', $errors[1]->getMessage());
892-
$this->assertSame(27, $errors[1]->getLine());
883+
$this->assertNoErrors($errors);
893884
}
894885

895886
public function testBug7637(): void

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1325,9 +1325,13 @@ public function dataFileAsserts(): iterable
13251325
yield from $this->gatherAssertTypes(__DIR__ . '/data/trigger-error-php7.php');
13261326
}
13271327

1328+
if (PHP_VERSION_ID >= 80000) {
1329+
yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_matching_php8.php');
1330+
} else {
1331+
yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_matching_php7.php');
1332+
}
13281333
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7915.php');
13291334
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9714.php');
1330-
yield from $this->gatherAssertTypes(__DIR__ . '/data/preg_grep.php');
13311335
}
13321336

13331337
/**

tests/PHPStan/Analyser/data/preg_grep.php

Lines changed: 0 additions & 25 deletions
This file was deleted.

tests/PHPStan/Analyser/data/preg_match_php7.php

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

77
class Foo {
88
public function doFoo() {
9-
assertType('0|1|false', preg_match('{}', ''));
10-
assertType('int<0, max>|false|null', preg_match_all('{}', ''));
9+
assertType('0|1', preg_match('{}', ''));
10+
assertType('int<0, max>|null', preg_match_all('{}', ''));
1111
}
1212
}

tests/PHPStan/Analyser/data/preg_match_php8.php

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

77
class Foo {
88
public function doFoo() {
9-
assertType('0|1|false', preg_match('{}', ''));
10-
assertType('int<0, max>|false', preg_match_all('{}', ''));
9+
assertType('0|1', preg_match('{}', ''));
10+
assertType('int<0, max>', preg_match_all('{}', ''));
1111

1212
}
1313
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace PregMatching;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function compilablePattern(array $a) {
8+
assertType('array', preg_grep('/^[0-9]+$/', $a));
9+
assertType('0|1', preg_match('/^[0-9]+$/', $a));
10+
assertType('int<0, max>|null', preg_match_all('/^[0-9]+$/', $a));
11+
assertType('list<string>', preg_split('/^[0-9]+$/', $a));
12+
}
13+
14+
function bogusPattern(array $a) {
15+
assertType('array|false', preg_grep('/bogus-pattern-[0^-9]$/', $a));
16+
assertType('0|1|false', preg_match('/bogus-pattern-[0^-9]$/', $a));
17+
assertType('int<0, max>|false|null', preg_match_all('/bogus-pattern-[0^-9]$/', $a));
18+
assertType('list<string>|false', preg_split('/bogus-pattern-[0^-9]$/', $a));
19+
}
20+
21+
function unknownPattern(string $p, array $a) {
22+
assertType('array|false', preg_grep($p, $a));
23+
assertType('0|1|false', preg_match($p, $a));
24+
assertType('int<0, max>|false|null', preg_match_all($p, $a));
25+
assertType('list<string>|false', preg_split($p, $a));
26+
}
27+
28+
function sometimesCompilablePattern(array $a) {
29+
$p = '/^[0-9]+$/';
30+
if (rand(0,1)) {
31+
$p = '/bogus-pattern-[0^-9]$/';
32+
}
33+
assertType('array|false', preg_grep($p, $a));
34+
assertType('0|1|false', preg_match($p, $a));
35+
assertType('int<0, max>|false|null', preg_match_all($p, $a));
36+
assertType('list<string>|false', preg_split($p, $a));
37+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace PregMatching;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function compilablePattern(array $a) {
8+
assertType('array', preg_grep('/^[0-9]+$/', $a));
9+
assertType('0|1', preg_match('/^[0-9]+$/', $a));
10+
assertType('int<0, max>', preg_match_all('/^[0-9]+$/', $a));
11+
assertType('list<string>', preg_split('/^[0-9]+$/', $a));
12+
}
13+
14+
function bogusPattern(array $a) {
15+
assertType('array|false', preg_grep('/bogus-pattern-[0^-9]$/', $a));
16+
assertType('0|1|false', preg_match('/bogus-pattern-[0^-9]$/', $a));
17+
assertType('int<0, max>|false', preg_match_all('/bogus-pattern-[0^-9]$/', $a));
18+
assertType('list<string>|false', preg_split('/bogus-pattern-[0^-9]$/', $a));
19+
}
20+
21+
function unknownPattern(string $p, array $a) {
22+
assertType('array|false', preg_grep($p, $a));
23+
assertType('0|1|false', preg_match($p, $a));
24+
assertType('int<0, max>|false', preg_match_all($p, $a));
25+
assertType('list<string>|false', preg_split($p, $a));
26+
}
27+
28+
function sometimesCompilablePattern(array $a) {
29+
$p = '/^[0-9]+$/';
30+
if (rand(0,1)) {
31+
$p = '/bogus-pattern-[0^-9]$/';
32+
}
33+
assertType('array|false', preg_grep($p, $a));
34+
assertType('0|1|false', preg_match($p, $a));
35+
assertType('int<0, max>|false', preg_match_all($p, $a));
36+
assertType('list<string>|false', preg_split($p, $a));
37+
}

tests/PHPStan/Analyser/data/preg_split.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ class HelloWorld
88
{
99
public function doFoo()
1010
{
11-
assertType('list<string>|false', preg_split('/-/', '1-2-3'));
12-
assertType('list<string>|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY));
13-
assertType('list<array{string, int<0, max>}>|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_OFFSET_CAPTURE));
14-
assertType('list<array{string, int<0, max>}>|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE));
11+
assertType('list<string>', preg_split('/-/', '1-2-3'));
12+
assertType('list<string>', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY));
13+
assertType('list<array{string, int<0, max>}>', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_OFFSET_CAPTURE));
14+
assertType('list<array{string, int<0, max>}>', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE));
1515
}
1616

1717
/**

0 commit comments

Comments
 (0)