Skip to content

Commit 080280f

Browse files
authored
Refactor RegexGroupParser - extract RegexAlternation class
1 parent 285ca8b commit 080280f

11 files changed

+102
-52
lines changed

conf/config.neon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,10 +1506,10 @@ services:
15061506
class: PHPStan\Type\Php\RegexArrayShapeMatcher
15071507

15081508
-
1509-
class: PHPStan\Type\Php\RegexGroupParser
1509+
class: PHPStan\Type\Regex\RegexGroupParser
15101510

15111511
-
1512-
class: PHPStan\Type\Php\RegexExpressionHelper
1512+
class: PHPStan\Type\Regex\RegexExpressionHelper
15131513

15141514
-
15151515
class: PHPStan\Type\Php\ReflectionClassConstructorThrowTypeExtension

src/Rules/Regexp/RegularExpressionPatternRule.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
use PHPStan\Analyser\Scope;
1010
use PHPStan\Rules\Rule;
1111
use PHPStan\Rules\RuleErrorBuilder;
12-
use PHPStan\Type\Php\RegexExpressionHelper;
12+
use PHPStan\Type\Regex\RegexExpressionHelper;
1313
use function in_array;
1414
use function sprintf;
1515
use function str_starts_with;

src/Rules/Regexp/RegularExpressionQuotingRule.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use PHPStan\Rules\Rule;
1616
use PHPStan\Rules\RuleErrorBuilder;
1717
use PHPStan\ShouldNotHappenException;
18-
use PHPStan\Type\Php\RegexExpressionHelper;
18+
use PHPStan\Type\Regex\RegexExpressionHelper;
1919
use function array_filter;
2020
use function array_merge;
2121
use function array_values;

src/Type/Php/RegexArrayShapeMatcher.php

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
use PHPStan\Type\Constant\ConstantStringType;
1515
use PHPStan\Type\IntegerRangeType;
1616
use PHPStan\Type\IntegerType;
17+
use PHPStan\Type\Regex\RegexAlternation;
18+
use PHPStan\Type\Regex\RegexCapturingGroup;
19+
use PHPStan\Type\Regex\RegexExpressionHelper;
20+
use PHPStan\Type\Regex\RegexGroupParser;
1721
use PHPStan\Type\StringType;
1822
use PHPStan\Type\Type;
1923
use PHPStan\Type\TypeCombinator;
20-
use function array_key_exists;
2124
use function array_reverse;
2225
use function count;
2326
use function in_array;
@@ -117,7 +120,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
117120
// regex could not be parsed by Hoa/Regex
118121
return null;
119122
}
120-
[$groupList, $groupCombinations, $markVerbs] = $parseResult;
123+
[$groupList, $markVerbs] = $parseResult;
121124

122125
$trailingOptionals = 0;
123126
foreach (array_reverse($groupList) as $captureGroup) {
@@ -128,7 +131,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
128131
}
129132

130133
$onlyOptionalTopLevelGroup = $this->getOnlyOptionalTopLevelGroup($groupList);
131-
$onlyTopLevelAlternationId = $this->getOnlyTopLevelAlternationId($groupList);
134+
$onlyTopLevelAlternation = $this->getOnlyTopLevelAlternation($groupList);
132135

133136
if (
134137
!$matchesAll
@@ -160,12 +163,11 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
160163
} elseif (
161164
!$matchesAll
162165
&& $wasMatched->yes()
163-
&& $onlyTopLevelAlternationId !== null
164-
&& array_key_exists($onlyTopLevelAlternationId, $groupCombinations)
166+
&& $onlyTopLevelAlternation !== null
165167
) {
166168
$combiTypes = [];
167169
$isOptionalAlternation = false;
168-
foreach ($groupCombinations[$onlyTopLevelAlternationId] as $groupCombo) {
170+
foreach ($onlyTopLevelAlternation->getGroupCombinations() as $groupCombo) {
169171
$comboList = $groupList;
170172

171173
$beforeCurrentCombo = true;
@@ -176,7 +178,10 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
176178
$beforeCurrentCombo = false;
177179
} elseif ($beforeCurrentCombo && !$group->resetsGroupCounter()) {
178180
$group->forceNonOptional();
179-
} elseif ($group->getAlternationId() === $onlyTopLevelAlternationId && !$this->containsUnmatchedAsNull($flags ?? 0, $matchesAll)) {
181+
} elseif (
182+
$group->getAlternationId() === $onlyTopLevelAlternation->getId()
183+
&& !$this->containsUnmatchedAsNull($flags ?? 0, $matchesAll)
184+
) {
180185
unset($comboList[$groupId]);
181186
}
182187
}
@@ -243,9 +248,9 @@ private function getOnlyOptionalTopLevelGroup(array $captureGroups): ?RegexCaptu
243248
/**
244249
* @param array<int, RegexCapturingGroup> $captureGroups
245250
*/
246-
private function getOnlyTopLevelAlternationId(array $captureGroups): ?int
251+
private function getOnlyTopLevelAlternation(array $captureGroups): ?RegexAlternation
247252
{
248-
$alternationId = null;
253+
$alternation = null;
249254
foreach ($captureGroups as $captureGroup) {
250255
if (!$captureGroup->isTopLevel()) {
251256
continue;
@@ -255,14 +260,14 @@ private function getOnlyTopLevelAlternationId(array $captureGroups): ?int
255260
return null;
256261
}
257262

258-
if ($alternationId === null) {
259-
$alternationId = $captureGroup->getAlternationId();
260-
} elseif ($alternationId !== $captureGroup->getAlternationId()) {
263+
if ($alternation === null) {
264+
$alternation = $captureGroup->getAlternation();
265+
} elseif ($alternation->getId() !== $captureGroup->getAlternation()->getId()) {
261266
return null;
262267
}
263268
}
264269

265-
return $alternationId;
270+
return $alternation;
266271
}
267272

268273
/**

src/Type/Regex/RegexAlternation.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Regex;
4+
5+
use function array_key_exists;
6+
7+
final class RegexAlternation
8+
{
9+
10+
/** @var array<int, list<int>> */
11+
private array $groupCombinations = [];
12+
13+
public function __construct(private int $alternationId)
14+
{
15+
}
16+
17+
public function getId(): int
18+
{
19+
return $this->alternationId;
20+
}
21+
22+
public function pushGroup(int $combinationIndex, RegexCapturingGroup $group): void
23+
{
24+
if (!array_key_exists($combinationIndex, $this->groupCombinations)) {
25+
$this->groupCombinations[$combinationIndex] = [];
26+
}
27+
28+
$this->groupCombinations[$combinationIndex][] = $group->getId();
29+
}
30+
31+
/**
32+
* @return array<int, list<int>>
33+
*/
34+
public function getGroupCombinations(): array
35+
{
36+
return $this->groupCombinations;
37+
}
38+
39+
}

src/Type/Php/RegexCapturingGroup.php renamed to src/Type/Regex/RegexCapturingGroup.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php declare(strict_types = 1);
22

3-
namespace PHPStan\Type\Php;
3+
namespace PHPStan\Type\Regex;
44

55
use PHPStan\Type\Type;
66

@@ -12,7 +12,7 @@ class RegexCapturingGroup
1212
public function __construct(
1313
private int $id,
1414
private ?string $name,
15-
private ?int $alternationId,
15+
private ?RegexAlternation $alternation,
1616
private bool $inOptionalQuantification,
1717
private RegexCapturingGroup|RegexNonCapturingGroup|null $parent,
1818
private Type $type,
@@ -40,15 +40,27 @@ public function resetsGroupCounter(): bool
4040
return $this->parent instanceof RegexNonCapturingGroup && $this->parent->resetsGroupCounter();
4141
}
4242

43-
/** @phpstan-assert-if-true !null $this->getAlternationId() */
43+
/**
44+
* @phpstan-assert-if-true !null $this->getAlternationId()
45+
* @phpstan-assert-if-true !null $this->getAlternation()
46+
*/
4447
public function inAlternation(): bool
4548
{
46-
return $this->alternationId !== null;
49+
return $this->alternation !== null;
50+
}
51+
52+
public function getAlternation(): ?RegexAlternation
53+
{
54+
return $this->alternation;
4755
}
4856

4957
public function getAlternationId(): ?int
5058
{
51-
return $this->alternationId;
59+
if ($this->alternation === null) {
60+
return null;
61+
}
62+
63+
return $this->alternation->getId();
5264
}
5365

5466
public function isOptional(): bool

src/Type/Php/RegexExpressionHelper.php renamed to src/Type/Regex/RegexExpressionHelper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php declare(strict_types = 1);
22

3-
namespace PHPStan\Type\Php;
3+
namespace PHPStan\Type\Regex;
44

55
use PhpParser\Node\Expr;
66
use PhpParser\Node\Expr\BinaryOp\Concat;

src/Type/Php/RegexGroupParser.php renamed to src/Type/Regex/RegexGroupParser.php

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php declare(strict_types = 1);
22

3-
namespace PHPStan\Type\Php;
3+
namespace PHPStan\Type\Regex;
44

55
use Hoa\Compiler\Llk\Llk;
66
use Hoa\Compiler\Llk\Parser;
@@ -20,7 +20,6 @@
2020
use PHPStan\Type\StringType;
2121
use PHPStan\Type\Type;
2222
use PHPStan\Type\TypeCombinator;
23-
use function array_key_exists;
2423
use function count;
2524
use function in_array;
2625
use function is_int;
@@ -45,7 +44,7 @@ public function __construct(
4544
}
4645

4746
/**
48-
* @return array{array<int, RegexCapturingGroup>, array<int, array<int, int[]>>, list<string>}|null
47+
* @return array{array<int, RegexCapturingGroup>, list<string>}|null
4948
*/
5049
public function parseGroups(string $regex): ?array
5150
{
@@ -75,44 +74,40 @@ public function parseGroups(string $regex): ?array
7574
}
7675

7776
$capturingGroups = [];
78-
$groupCombinations = [];
7977
$alternationId = -1;
8078
$captureGroupId = 100;
8179
$markVerbs = [];
8280
$this->walkRegexAst(
8381
$ast,
84-
false,
82+
null,
8583
$alternationId,
8684
0,
8785
false,
8886
null,
8987
$captureGroupId,
9088
$capturingGroups,
91-
$groupCombinations,
9289
$markVerbs,
9390
$captureOnlyNamed,
9491
false,
9592
$modifiers,
9693
);
9794

98-
return [$capturingGroups, $groupCombinations, $markVerbs];
95+
return [$capturingGroups, $markVerbs];
9996
}
10097

10198
/**
10299
* @param array<int, RegexCapturingGroup> $capturingGroups
103-
* @param array<int, array<int, int[]>> $groupCombinations
104100
* @param list<string> $markVerbs
105101
*/
106102
private function walkRegexAst(
107103
TreeNode $ast,
108-
bool $inAlternation,
104+
?RegexAlternation $alternation,
109105
int &$alternationId,
110106
int $combinationIndex,
111107
bool $inOptionalQuantification,
112108
RegexCapturingGroup|RegexNonCapturingGroup|null $parentGroup,
113109
int &$captureGroupId,
114110
array &$capturingGroups,
115-
array &$groupCombinations,
116111
array &$markVerbs,
117112
bool $captureOnlyNamed,
118113
bool $repeatedMoreThanOnce,
@@ -124,7 +119,7 @@ private function walkRegexAst(
124119
$group = new RegexCapturingGroup(
125120
$captureGroupId++,
126121
null,
127-
$inAlternation ? $alternationId : null,
122+
$alternation,
128123
$inOptionalQuantification,
129124
$parentGroup,
130125
$this->createGroupType(
@@ -139,7 +134,7 @@ private function walkRegexAst(
139134
$group = new RegexCapturingGroup(
140135
$captureGroupId++,
141136
$name,
142-
$inAlternation ? $alternationId : null,
137+
$alternation,
143138
$inOptionalQuantification,
144139
$parentGroup,
145140
$this->createGroupType(
@@ -151,15 +146,15 @@ private function walkRegexAst(
151146
$parentGroup = $group;
152147
} elseif ($ast->getId() === '#noncapturing') {
153148
$group = new RegexNonCapturingGroup(
154-
$inAlternation ? $alternationId : null,
149+
$alternation,
155150
$inOptionalQuantification,
156151
$parentGroup,
157152
false,
158153
);
159154
$parentGroup = $group;
160155
} elseif ($ast->getId() === '#noncapturingreset') {
161156
$group = new RegexNonCapturingGroup(
162-
$inAlternation ? $alternationId : null,
157+
$alternation,
163158
$inOptionalQuantification,
164159
$parentGroup,
165160
true,
@@ -182,7 +177,7 @@ private function walkRegexAst(
182177

183178
if ($ast->getId() === '#alternation') {
184179
$alternationId++;
185-
$inAlternation = true;
180+
$alternation = new RegexAlternation($alternationId);
186181
}
187182

188183
if ($ast->getId() === '#mark') {
@@ -196,26 +191,21 @@ private function walkRegexAst(
196191
) {
197192
$capturingGroups[$group->getId()] = $group;
198193

199-
if (!array_key_exists($alternationId, $groupCombinations)) {
200-
$groupCombinations[$alternationId] = [];
201-
}
202-
if (!array_key_exists($combinationIndex, $groupCombinations[$alternationId])) {
203-
$groupCombinations[$alternationId][$combinationIndex] = [];
194+
if ($alternation !== null) {
195+
$alternation->pushGroup($combinationIndex, $group);
204196
}
205-
$groupCombinations[$alternationId][$combinationIndex][] = $group->getId();
206197
}
207198

208199
foreach ($ast->getChildren() as $child) {
209200
$this->walkRegexAst(
210201
$child,
211-
$inAlternation,
202+
$alternation,
212203
$alternationId,
213204
$combinationIndex,
214205
$inOptionalQuantification,
215206
$parentGroup,
216207
$captureGroupId,
217208
$capturingGroups,
218-
$groupCombinations,
219209
$markVerbs,
220210
$captureOnlyNamed,
221211
$repeatedMoreThanOnce,

src/Type/Php/RegexNonCapturingGroup.php renamed to src/Type/Regex/RegexNonCapturingGroup.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<?php declare(strict_types = 1);
22

3-
namespace PHPStan\Type\Php;
3+
namespace PHPStan\Type\Regex;
44

55
class RegexNonCapturingGroup
66
{
77

88
public function __construct(
9-
private ?int $alternationId,
9+
private ?RegexAlternation $alternation,
1010
private bool $inOptionalQuantification,
1111
private RegexCapturingGroup|RegexNonCapturingGroup|null $parent,
1212
private bool $resetGroupCounter,
@@ -17,12 +17,16 @@ public function __construct(
1717
/** @phpstan-assert-if-true !null $this->getAlternationId() */
1818
public function inAlternation(): bool
1919
{
20-
return $this->alternationId !== null;
20+
return $this->alternation !== null;
2121
}
2222

2323
public function getAlternationId(): ?int
2424
{
25-
return $this->alternationId;
25+
if ($this->alternation === null) {
26+
return null;
27+
}
28+
29+
return $this->alternation->getId();
2630
}
2731

2832
public function isOptional(): bool

0 commit comments

Comments
 (0)