Skip to content

Commit

Permalink
remove delimiters and modifiers before parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm committed Jul 28, 2024
1 parent 842d35f commit e2c94bb
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 17 deletions.
3 changes: 2 additions & 1 deletion src/Type/Php/RegexArrayShapeMatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,9 @@ private function parseGroups(string $regex): ?array
return null;
}

$rawRegex = $this->regexExpressionHelper->removeDelimitersAndModifiers($regex);
try {
$ast = self::$parser->parse($regex);
$ast = self::$parser->parse($rawRegex);
} catch (Exception) {
return null;
}
Expand Down
46 changes: 32 additions & 14 deletions src/Type/Php/RegexExpressionHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,30 +72,48 @@ public function resolve(Expr $expr): Type

public function getPatternModifiers(string $pattern): ?string
{
$delimiter = $this->getDelimiterFromString(new ConstantStringType($pattern));
if ($delimiter === null) {
$endDelimiterPos = $this->getEndDelimiterPos($pattern);

if ($endDelimiterPos === false) {
return null;
}

return substr($pattern, $endDelimiterPos + 1);
}

public function removeDelimitersAndModifiers(string $pattern): string
{
$endDelimiterPos = $this->getEndDelimiterPos($pattern);

if ($endDelimiterPos === false) {
return $pattern;
}

return substr($pattern, 1, $endDelimiterPos - 1);
}

private function getEndDelimiterPos(string $pattern): false|int
{
$startDelimiter = $this->getPatternDelimiter($pattern);
if ($startDelimiter === null) {
return false;
}

// delimiter variants, see https://www.php.net/manual/en/regexp.reference.delimiters.php
$bracketStyleDelimiters = [
'{' => '}',
'(' => ')',
'[' => ']',
'<' => '>',
];
if (array_key_exists($delimiter, $bracketStyleDelimiters)) {
$endDelimiterPos = strrpos($pattern, $bracketStyleDelimiters[$delimiter]);
if (array_key_exists($startDelimiter, $bracketStyleDelimiters)) {
$endDelimiterPos = strrpos($pattern, $bracketStyleDelimiters[$startDelimiter]);
} else {
// same start and end delimiter
$endDelimiterPos = strrpos($pattern, $delimiter);
$endDelimiterPos = strrpos($pattern, $startDelimiter);
}

if ($endDelimiterPos === false) {
return null;
}

return substr($pattern, $endDelimiterPos + 1);
return $endDelimiterPos;
}

/**
Expand All @@ -113,7 +131,7 @@ public function getPatternDelimiters(Concat $concat, Scope $scope): array

$delimiters = [];
foreach ($left->getConstantStrings() as $leftString) {
$delimiter = $this->getDelimiterFromString($leftString);
$delimiter = $this->getPatternDelimiter($leftString->getValue());
if ($delimiter === null) {
continue;
}
Expand All @@ -123,13 +141,13 @@ public function getPatternDelimiters(Concat $concat, Scope $scope): array
return $delimiters;
}

private function getDelimiterFromString(ConstantStringType $string): ?string
private function getPatternDelimiter(string $regex): ?string
{
if ($string->getValue() === '') {
if ($regex === '') {
return null;
}

return substr($string->getValue(), 0, 1);
return substr($regex, 0, 1);
}

}
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/nsrt/preg_match_shapes.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ function doMatch(string $s): void {
assertType('array{}|array{string, non-empty-string}', $matches);

if (preg_match('(Price: (£|€))i', $s, $matches)) {
assertType('array{string, non-empty-string, non-empty-string}', $matches);
assertType('array{string, non-empty-string}', $matches);
}
assertType('array{}|array{string, non-empty-string, non-empty-string}', $matches);
assertType('array{}|array{string, non-empty-string}', $matches);

if (preg_match('_foo(.)\_i_i', $s, $matches)) {
assertType('array{string, non-empty-string}', $matches);
Expand Down
5 changes: 5 additions & 0 deletions tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,8 @@ function (string $s): void {
assertType('array{0: string, num: numeric-string, 1: numeric-string}', $matches);
}
};
function (string $s): void {
if (preg_match('[(\d+)(?P<num>\d+)]n', $s, $matches)) {
assertType('array{0: string, num: numeric-string, 1: numeric-string}', $matches);
}
};

0 comments on commit e2c94bb

Please sign in to comment.