Skip to content

Commit

Permalink
Merge branch refs/heads/1.11.x into 1.12.x
Browse files Browse the repository at this point in the history
  • Loading branch information
phpstan-bot authored Jul 27, 2024
2 parents ee66b3a + c0537db commit 676ae9f
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 11 deletions.
16 changes: 9 additions & 7 deletions resources/RegexGrammar.pp
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@
// @license New BSD License
//


// Skip.
%skip nl \n

// Character classes.
%token negative_class_ \[\^ -> class
%token class_ \[ -> class
Expand All @@ -58,7 +54,7 @@
%token class:character \\([aefnrtb]|c[\x00-\x7f])
%token class:dynamic_character \\([0-7]{3}|x[0-9a-zA-Z]{2}|x{[0-9a-zA-Z]+})
%token class:character_type \\([CdDhHNRsSvVwWX]|[pP]{[^}]+})
%token class:literal \\.|.
%token class:literal \\.|.|\n

// Internal options.
// See https://www.regular-expressions.info/refmodifiers.html
Expand All @@ -82,6 +78,11 @@
%token co:_comment \) -> default
%token co:comment .*?(?=(?<!\\)\))

// Marker verbs
%token marker_ \(\*: -> mark
%token mark:name [^)]+
%token mark:_marker \) -> default

// Capturing group.
%token named_capturing_ \(\?P?< -> nc
%token nc:_named_capturing > -> default
Expand Down Expand Up @@ -122,7 +123,7 @@
%token character_type \\([CdDhHNRsSvVwWX]|[pP]{[^}]+})
%token anchor \\([bBAZzG])|\^|\$
%token match_point_reset \\K
%token literal \\.|.
%token literal \\.|.|\n


// Rules.
Expand Down Expand Up @@ -190,7 +191,8 @@
| literal()

#capturing:
::comment_:: <comment>? ::_comment:: #comment
::marker_:: <name> ::_marker:: #mark
| ::comment_:: <comment>? ::_comment:: #comment
| (
::named_capturing_:: <capturing_name> ::_named_capturing:: #namedcapturing
| ::non_capturing_:: #noncapturing
Expand Down
35 changes: 31 additions & 4 deletions src/Type/Php/RegexArrayShapeMatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
// regex could not be parsed by Hoa/Regex
return null;
}
[$groupList, $groupCombinations] = $parseResult;
[$groupList, $groupCombinations, $markVerbs] = $parseResult;

$trailingOptionals = 0;
foreach (array_reverse($groupList) as $captureGroup) {
Expand All @@ -152,6 +152,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
$wasMatched,
$trailingOptionals,
$flags ?? 0,
$markVerbs,
);

if (!$this->containsUnmatchedAsNull($flags ?? 0)) {
Expand Down Expand Up @@ -189,6 +190,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
$wasMatched,
$trailingOptionals,
$flags ?? 0,
$markVerbs,
);

$combiTypes[] = $combiType;
Expand All @@ -211,6 +213,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
$wasMatched,
$trailingOptionals,
$flags ?? 0,
$markVerbs,
);
}

Expand Down Expand Up @@ -266,12 +269,14 @@ private function getOnlyTopLevelAlternationId(array $captureGroups): ?int

/**
* @param array<RegexCapturingGroup> $captureGroups
* @param list<string> $markVerbs
*/
private function buildArrayType(
array $captureGroups,
TrinaryLogic $wasMatched,
int $trailingOptionals,
int $flags,
array $markVerbs,
): Type
{
$builder = ConstantArrayTypeBuilder::createEmpty();
Expand Down Expand Up @@ -325,6 +330,18 @@ private function buildArrayType(
$i++;
}

if (count($markVerbs) > 0) {
$markTypes = [];
foreach ($markVerbs as $mark) {
$markTypes[] = new ConstantStringType($mark);
}
$builder->setOffsetValueType(
$this->getKeyType('MARK'),
TypeCombinator::union(...$markTypes),
true,
);
}

return $builder->getArray();
}

Expand Down Expand Up @@ -372,7 +389,7 @@ private function getValueType(Type $baseType, int $flags): Type
}

/**
* @return array{array<int, RegexCapturingGroup>, array<int, array<int, int[]>>}|null
* @return array{array<int, RegexCapturingGroup>, array<int, array<int, int[]>>, list<string>}|null
*/
private function parseGroups(string $regex): ?array
{
Expand All @@ -398,6 +415,7 @@ private function parseGroups(string $regex): ?array
$groupCombinations = [];
$alternationId = -1;
$captureGroupId = 100;
$markVerbs = [];
$this->walkRegexAst(
$ast,
false,
Expand All @@ -408,14 +426,16 @@ private function parseGroups(string $regex): ?array
$captureGroupId,
$capturingGroups,
$groupCombinations,
$markVerbs,
);

return [$capturingGroups, $groupCombinations];
return [$capturingGroups, $groupCombinations, $markVerbs];
}

/**
* @param array<int, RegexCapturingGroup> $capturingGroups
* @param array<int, array<int, int[]>> $groupCombinations
* @param list<string> $markVerbs
*/
private function walkRegexAst(
TreeNode $ast,
Expand All @@ -427,6 +447,7 @@ private function walkRegexAst(
int &$captureGroupId,
array &$capturingGroups,
array &$groupCombinations,
array &$markVerbs,
): void
{
$group = null;
Expand All @@ -441,7 +462,7 @@ private function walkRegexAst(
);
$parentGroup = $group;
} elseif ($ast->getId() === '#namedcapturing') {
$name = $ast->getChild(0)->getValue()['value'];
$name = $ast->getChild(0)->getValueValue();
$group = new RegexCapturingGroup(
$captureGroupId++,
$name,
Expand Down Expand Up @@ -483,6 +504,11 @@ private function walkRegexAst(
$inAlternation = true;
}

if ($ast->getId() === '#mark') {
$markVerbs[] = $ast->getChild(0)->getValueValue();
return;
}

if ($group instanceof RegexCapturingGroup) {
$capturingGroups[$group->getId()] = $group;

Expand All @@ -506,6 +532,7 @@ private function walkRegexAst(
$captureGroupId,
$capturingGroups,
$groupCombinations,
$markVerbs,
);

if ($ast->getId() !== '#alternation') {
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/nsrt/preg_match_shapes.php
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,12 @@ function bug11323(string $s): void {
if (preg_match('{([^1-4])}', $s, $matches)) {
assertType('array{string, non-empty-string}', $matches);
}
if (preg_match("{([\r\n]+)(\n)([\n])}", $s, $matches)) {
assertType('array{string, non-empty-string, non-empty-string, non-empty-string}', $matches);
}
if (preg_match('/foo(*:first)|bar(*:second)([x])/', $s, $matches)) {
assertType("array{0: string, 1?: non-empty-string, MARK?: 'first'|'second'}", $matches);
}
}

function (string $s): void {
Expand Down

0 comments on commit 676ae9f

Please sign in to comment.