|
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