diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 48a6b9e517..6938ff8000 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -431,8 +431,6 @@ private function walkGroupAst( if (!$inOptionalQuantification) { $isNonEmpty = TrinaryLogic::createYes(); } - } elseif (!in_array($ast->getValueToken(), ['capturing_name'], true)) { - $onlyLiterals = null; } } elseif (!in_array($ast->getId(), ['#capturing', '#namedcapturing'], true)) { $onlyLiterals = null; @@ -508,16 +506,26 @@ private function getLiteralValue(TreeNode $node, ?array &$onlyLiterals, bool $ap $token = $node->getValueToken(); $value = $node->getValueValue(); - if (in_array($token, ['literal', 'escaped_end_class'], true)) { + if ( + in_array($token, [ + 'literal', 'escaped_end_class', + // literal "-" in front/back of a character class like '[-a-z]' or '[abc-]', not forming a range + 'range', + // literal "[" or "]" inside character classes '[[]' or '[]]' + 'class_', '_class_literal', + ], true) + ) { if (str_contains($patternModifiers, 'x') && trim($value) === '') { return null; } if (strlen($value) > 1 && $value[0] === '\\') { - return substr($value, 1); - } elseif ( + $value = substr($value, 1) ?: ''; + } + + if ( $appendLiterals - && $token === 'literal' + && in_array($token, ['literal', 'range', 'class_', '_class_literal'], true) && $onlyLiterals !== null && !in_array($value, ['.'], true) ) { @@ -533,14 +541,8 @@ private function getLiteralValue(TreeNode $node, ?array &$onlyLiterals, bool $ap return $value; } - // literal "-" in front/back of a character class like '[-a-z]' or '[abc-]', not forming a range - if ($token === 'range') { - return $value; - } - - // literal "[" or "]" inside character classes '[[]' or '[]]' - if (in_array($token, ['class_', '_class_literal'], true)) { - return $value; + if (!in_array($token, ['capturing_name'], true)) { + $onlyLiterals = null; } // character escape sequences, just return a fixed string diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index d441a79fcd..be26adf8b8 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -699,3 +699,20 @@ function (string $s): void { preg_match('~a|(\d)|(\s)~', $s, $matches); assertType("array{0?: string, 1?: '', 2?: non-empty-string}|array{0?: string, 1?: numeric-string}", $matches); }; + +function bug11490 (string $expression): void { + $matches = []; + + if (preg_match('/([-+])?([\d]+)%/', $expression, $matches) === 1) { + assertType("array{string, ''|'+'|'-', numeric-string}", $matches); + } +} + +function bug11490b (string $expression): void { + $matches = []; + + if (preg_match('/([\\[+])?([\d]+)%/', $expression, $matches) === 1) { + assertType("array{string, ''|'+'|'[', numeric-string}", $matches); + } +} +