Skip to content

Commit c5f4a47

Browse files
staabmondrejmirtes
authored andcommitted
Refactor RegexArrayShapeMatcher
extract getQuantificationRange() to ease re-use
1 parent 082f0a3 commit c5f4a47

File tree

2 files changed

+47
-8
lines changed

2 files changed

+47
-8
lines changed

src/Type/Php/RegexArrayShapeMatcher.php

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PhpParser\Node\Name;
1212
use PHPStan\Analyser\Scope;
1313
use PHPStan\Php\PhpVersion;
14+
use PHPStan\ShouldNotHappenException;
1415
use PHPStan\TrinaryLogic;
1516
use PHPStan\Type\Constant\ConstantArrayType;
1617
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
@@ -24,8 +25,9 @@
2425
use function array_reverse;
2526
use function count;
2627
use function in_array;
28+
use function is_int;
2729
use function is_string;
28-
use function str_contains;
30+
use function sscanf;
2931
use const PREG_OFFSET_CAPTURE;
3032
use const PREG_UNMATCHED_AS_NULL;
3133

@@ -450,14 +452,9 @@ private function walkRegexAst(
450452

451453
$inOptionalQuantification = false;
452454
if ($ast->getId() === '#quantification') {
453-
$lastChild = $ast->getChild($ast->getChildrenNumber() - 1);
454-
$value = $lastChild->getValue();
455+
[$min] = $this->getQuantificationRange($ast);
455456

456-
if ($value['token'] === 'n_to_m' && str_contains($value['value'], '{0,')) {
457-
$inOptionalQuantification = true;
458-
} elseif ($value['token'] === 'zero_or_one') {
459-
$inOptionalQuantification = true;
460-
} elseif ($value['token'] === 'zero_or_more') {
457+
if ($min === 0) {
461458
$inOptionalQuantification = true;
462459
}
463460
}
@@ -500,6 +497,43 @@ private function walkRegexAst(
500497
}
501498
}
502499

500+
/** @return array{?int, ?int} */
501+
private function getQuantificationRange(TreeNode $node): array
502+
{
503+
if ($node->getId() !== '#quantification') {
504+
throw new ShouldNotHappenException();
505+
}
506+
507+
$min = null;
508+
$max = null;
509+
510+
$lastChild = $node->getChild($node->getChildrenNumber() - 1);
511+
$value = $lastChild->getValue();
512+
513+
if ($value['token'] === 'n_to_m') {
514+
if (sscanf($value['value'], '{%d,%d}', $n, $m) !== 2 || !is_int($n) || !is_int($m)) {
515+
throw new ShouldNotHappenException();
516+
}
517+
518+
$min = $n;
519+
$max = $m;
520+
} elseif ($value['token'] === 'exactly_n') {
521+
if (sscanf($value['value'], '{%d}', $n) !== 1 || !is_int($n)) {
522+
throw new ShouldNotHappenException();
523+
}
524+
525+
$min = $n;
526+
$max = $n;
527+
} elseif ($value['token'] === 'zero_or_one') {
528+
$min = 0;
529+
$max = 1;
530+
} elseif ($value['token'] === 'zero_or_more') {
531+
$min = 0;
532+
}
533+
534+
return [$min, $max];
535+
}
536+
503537
private function getPatternType(Expr $patternExpr, Scope $scope): Type
504538
{
505539
if ($patternExpr instanceof Expr\BinaryOp\Concat) {

tests/PHPStan/Analyser/nsrt/preg_match_shapes.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ function doMatch(string $s): void {
8282
assertType('array{string, string, string, string}', $matches);
8383
}
8484
assertType('array{}|array{string, string, string, string}', $matches);
85+
86+
if (preg_match('/(foo)(bar)(baz){2}/', $s, $matches)) {
87+
assertType('array{string, string, string, string}', $matches);
88+
}
89+
assertType('array{}|array{string, string, string, string}', $matches);
8590
}
8691

8792
function doNonCapturingGroup(string $s): void {

0 commit comments

Comments
 (0)