|
11 | 11 | use PhpParser\Node\Name;
|
12 | 12 | use PHPStan\Analyser\Scope;
|
13 | 13 | use PHPStan\Php\PhpVersion;
|
| 14 | +use PHPStan\ShouldNotHappenException; |
14 | 15 | use PHPStan\TrinaryLogic;
|
15 | 16 | use PHPStan\Type\Constant\ConstantArrayType;
|
16 | 17 | use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
|
|
24 | 25 | use function array_reverse;
|
25 | 26 | use function count;
|
26 | 27 | use function in_array;
|
| 28 | +use function is_int; |
27 | 29 | use function is_string;
|
28 |
| -use function str_contains; |
| 30 | +use function sscanf; |
29 | 31 | use const PREG_OFFSET_CAPTURE;
|
30 | 32 | use const PREG_UNMATCHED_AS_NULL;
|
31 | 33 |
|
@@ -450,14 +452,9 @@ private function walkRegexAst(
|
450 | 452 |
|
451 | 453 | $inOptionalQuantification = false;
|
452 | 454 | if ($ast->getId() === '#quantification') {
|
453 |
| - $lastChild = $ast->getChild($ast->getChildrenNumber() - 1); |
454 |
| - $value = $lastChild->getValue(); |
| 455 | + [$min] = $this->getQuantificationRange($ast); |
455 | 456 |
|
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) { |
461 | 458 | $inOptionalQuantification = true;
|
462 | 459 | }
|
463 | 460 | }
|
@@ -500,6 +497,43 @@ private function walkRegexAst(
|
500 | 497 | }
|
501 | 498 | }
|
502 | 499 |
|
| 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 | + |
503 | 537 | private function getPatternType(Expr $patternExpr, Scope $scope): Type
|
504 | 538 | {
|
505 | 539 | if ($patternExpr instanceof Expr\BinaryOp\Concat) {
|
|
0 commit comments