Skip to content

Commit 024a65c

Browse files
authored
Fix phpstan/phpstan#14270: Regression: regex matches after checking non-empty array (#5209)
1 parent 63cb407 commit 024a65c

File tree

12 files changed

+66
-18
lines changed

12 files changed

+66
-18
lines changed

src/Type/Php/PregMatchParameterOutTypeExtension.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
use PHPStan\Reflection\FunctionReflection;
99
use PHPStan\Reflection\ParameterReflection;
1010
use PHPStan\TrinaryLogic;
11+
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
1112
use PHPStan\Type\FunctionParameterOutTypeExtension;
1213
use PHPStan\Type\Type;
14+
use PHPStan\Type\TypeCombinator;
1315
use function in_array;
1416
use function strtolower;
1517

@@ -49,8 +51,17 @@ public function getParameterOutTypeFromFunctionCall(FunctionReflection $function
4951
}
5052

5153
if ($functionReflection->getName() === 'preg_match') {
52-
return $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope);
54+
$matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope);
55+
if ($matchedType === null) {
56+
return null;
57+
}
58+
59+
return TypeCombinator::union(
60+
ConstantArrayTypeBuilder::createEmpty()->getArray(),
61+
$matchedType,
62+
);
5363
}
64+
5465
return $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope);
5566
}
5667

tests/PHPStan/Analyser/data/param-out.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,10 +494,10 @@ function fooMatch(string $input): void {
494494
assertType('list<array{string}>', $matches);
495495

496496
preg_match('/@[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/', $input, $matches, PREG_UNMATCHED_AS_NULL);
497-
assertType("array{0?: string}", $matches);
497+
assertType("array{}|array{non-falsy-string}", $matches);
498498
}
499499

500500
function testMatch() {
501501
preg_match('#.*#', 'foo', $matches);
502-
assertType('array{0?: string}', $matches);
502+
assertType('array{}|array{string}', $matches);
503503
}

tests/PHPStan/Analyser/nsrt/bug-11293.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function sayHello3(string $s): void
3030
public function sayHello4(string $s): void
3131
{
3232
if (preg_match('/data-(\d{6})\.json$/', $s, $matches) <= 0) {
33-
assertType('list{0?: string, 1?: non-falsy-string&numeric-string}', $matches);
33+
assertType('array{}|array{non-falsy-string, non-falsy-string&numeric-string}', $matches);
3434

3535
return;
3636
}
@@ -41,7 +41,7 @@ public function sayHello4(string $s): void
4141
public function sayHello5(string $s): void
4242
{
4343
if (preg_match('/data-(\d{6})\.json$/', $s, $matches) < 1) {
44-
assertType('list{0?: string, 1?: non-falsy-string&numeric-string}', $matches);
44+
assertType('array{}|array{non-falsy-string, non-falsy-string&numeric-string}', $matches);
4545

4646
return;
4747
}
@@ -52,7 +52,7 @@ public function sayHello5(string $s): void
5252
public function sayHello6(string $s): void
5353
{
5454
if (1 > preg_match('/data-(\d{6})\.json$/', $s, $matches)) {
55-
assertType('list{0?: string, 1?: non-falsy-string&numeric-string}', $matches);
55+
assertType('array{}|array{non-falsy-string, non-falsy-string&numeric-string}', $matches);
5656

5757
return;
5858
}

tests/PHPStan/Analyser/nsrt/bug-11311.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,12 @@ function (string $s): void {
191191

192192
function (string $s): void {
193193
preg_match('/%a(\d*)/', $s, $matches, PREG_UNMATCHED_AS_NULL);
194-
assertType("list{0?: string, 1?: ''|numeric-string|null}", $matches); // could be array{0?: string, 1?: ''|numeric-string}
194+
assertType("array{}|array{non-falsy-string, ''|numeric-string}", $matches);
195195
};
196196

197197
function (string $s): void {
198198
preg_match('/%a(\d*)?/', $s, $matches, PREG_UNMATCHED_AS_NULL);
199-
assertType("list{0?: string, 1?: ''|numeric-string|null}", $matches); // could be array{0?: string, 1?: ''|numeric-string}
199+
assertType("array{}|array{non-falsy-string, ''|numeric-string|null}", $matches);
200200
};
201201

202202
function (string $s): void {
@@ -222,5 +222,5 @@ function (string $s): void {
222222

223223
function (string $s): void {
224224
preg_match('~a|(\d)|(\s)~', $s, $matches, PREG_UNMATCHED_AS_NULL);
225-
assertType("list{0?: string, 1?: numeric-string|null, 2?: non-empty-string|null}", $matches);
225+
assertType("array{}|array{non-empty-string, numeric-string|null, non-empty-string|null}", $matches);
226226
};

tests/PHPStan/Analyser/nsrt/bug-11580.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function bad2(string $in): void
2626
public function bad3(string $in): void
2727
{
2828
$result = preg_match('~^/xxx/([\w\-]+)/?([\w\-]+)?/?$~', $in, $matches);
29-
assertType('list{0?: string, 1?: non-empty-string, 2?: non-empty-string}', $matches);
29+
assertType('array{}|array{0: non-falsy-string, 1: non-empty-string, 2?: non-empty-string}', $matches);
3030
if ($result) {
3131
assertType('array{0: non-falsy-string, 1: non-empty-string, 2?: non-empty-string}', $matches);
3232
}

tests/PHPStan/Analyser/nsrt/count-type.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public function constantArrayWhichCanBecomeList(string $h): void
9494
return;
9595
}
9696

97-
assertType('array{string, non-empty-string}', $matches);
97+
assertType('array{non-falsy-string, non-empty-string}', $matches);
9898
}
9999

100100
}

tests/PHPStan/Analyser/nsrt/if.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ function () {
392392
assertVariableCertainty(TrinaryLogic::createYes(), $anotherF);
393393
assertType('int<1, max>', $anotherF);
394394
assertVariableCertainty(TrinaryLogic::createYes(), $matches);
395-
assertType('array{0?: string}', $matches);
395+
assertType('array{}|array{string}', $matches);
396396
assertVariableCertainty(TrinaryLogic::createYes(), $anotherArray);
397397
assertType('array{test: array{\'another\'}}', $anotherArray);
398398
assertVariableCertainty(TrinaryLogic::createYes(), $ifVar);
@@ -405,7 +405,7 @@ function () {
405405
assertType('1|2|3', $ifNotNestedVar);
406406
assertVariableCertainty(TrinaryLogic::createNo(), $variableOnlyInEarlyTerminatingElse);
407407
assertVariableCertainty(TrinaryLogic::createMaybe(), $matches2);
408-
assertType('array{0?: string}', $matches2);
408+
assertType('array{}|array{string}', $matches2);
409409
assertVariableCertainty(TrinaryLogic::createYes(), $inTry);
410410
assertType('1', $inTry);
411411
assertVariableCertainty(TrinaryLogic::createYes(), $matches3);

tests/PHPStan/Analyser/nsrt/preg_match_shapes.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -525,15 +525,15 @@ function bug11323(string $s): void {
525525

526526
function (string $s): void {
527527
preg_match('/%a(\d*)/', $s, $matches);
528-
assertType("list{0?: string, 1?: ''|numeric-string}", $matches);
528+
assertType("array{}|array{non-falsy-string, ''|numeric-string}", $matches);
529529
};
530530

531531
class Bug11376
532532
{
533533
public function test(string $str): void
534534
{
535535
preg_match('~^(?:(\w+)::)?(\w+)$~', $str, $matches);
536-
assertType('list{0?: string, 1?: string, 2?: non-empty-string}', $matches);
536+
assertType("array{}|array{non-empty-string, string, non-empty-string}", $matches);
537537
}
538538

539539
public function test2(string $str): void
@@ -706,7 +706,7 @@ static public function sayHello(string $source): void
706706
// 2 => '1',
707707
//)
708708

709-
assertType("array{0?: string, dateFrom?: ''|numeric-string, 1?: ''|numeric-string, dateTo?: numeric-string, 2?: numeric-string}", $matches);
709+
assertType("array{}|array{0: string, dateFrom?: ''|numeric-string, 1?: ''|numeric-string, dateTo?: numeric-string, 2?: numeric-string}", $matches);
710710
}
711711
}
712712

@@ -730,7 +730,7 @@ function (string $s): void {
730730

731731
function (string $s): void {
732732
preg_match('~a|(\d)|(\s)~', $s, $matches);
733-
assertType("list{0?: string, 1?: '', 2?: non-empty-string}|list{0?: string, 1?: numeric-string}", $matches);
733+
assertType("array{}|array{0: non-empty-string, 1?: numeric-string}|array{non-empty-string, '', non-empty-string}", $matches);
734734
};
735735

736736
function bug11490 (string $expression): void {
@@ -1013,7 +1013,7 @@ function bug12749f(string $str): void
10131013

10141014
function bug12397(string $string): void {
10151015
$m = preg_match('#\b([A-Z]{2,})-(\d+)#', $string, $match);
1016-
assertType('list{0?: string, 1?: non-falsy-string, 2?: numeric-string}', $match);
1016+
assertType("array{}|array{non-falsy-string, non-falsy-string, numeric-string}", $match);
10171017
}
10181018

10191019
function bug12792(string $string): void {

tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ public function testRule(): void
4949
]);
5050
}
5151

52+
public function testBug14270(): void
53+
{
54+
$this->analyse([__DIR__ . '/data/bug-14270.php'], []);
55+
}
56+
5257
#[RequiresPhp('>= 8.0')]
5358
public function testRuleWithNullsafeVariant(): void
5459
{
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14270;
4+
5+
class Foo
6+
{
7+
public function getDuration(string $path): void {
8+
preg_match('~^([a-z]+)\:\/\/(.+)~', $path, $matches);
9+
$scheme = null;
10+
if ($matches !== []) {
11+
[, $scheme, $path] = $matches;
12+
}
13+
}
14+
}

0 commit comments

Comments
 (0)