Skip to content

Commit 43964bb

Browse files
committed
cleanup
1 parent 9adf2dd commit 43964bb

File tree

3 files changed

+143
-20
lines changed

3 files changed

+143
-20
lines changed

src/Type/Php/RegexArrayShapeMatcher.php

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
use PHPStan\Type\Regex\RegexAlternation;
1919
use PHPStan\Type\Regex\RegexCapturingGroup;
2020
use PHPStan\Type\Regex\RegexExpressionHelper;
21+
use PHPStan\Type\Regex\RegexGroupList;
2122
use PHPStan\Type\Regex\RegexGroupParser;
2223
use PHPStan\Type\StringType;
2324
use PHPStan\Type\Type;
2425
use PHPStan\Type\TypeCombinator;
25-
use function array_reverse;
2626
use function count;
2727
use function in_array;
2828
use function is_string;
@@ -115,13 +115,8 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
115115
}
116116
[$groupList, $markVerbs] = $parseResult;
117117

118-
$trailingOptionals = 0;
119-
foreach (array_reverse($groupList) as $captureGroup) {
120-
if (!$captureGroup->isOptional()) {
121-
break;
122-
}
123-
$trailingOptionals++;
124-
}
118+
$regexGroupList = new RegexGroupList($groupList);
119+
$trailingOptionals = $regexGroupList->countTrailingOptionals();
125120

126121
$onlyOptionalTopLevelGroupId = $this->getOnlyOptionalTopLevelGroupId($groupList);
127122
$onlyTopLevelAlternation = $this->getOnlyTopLevelAlternation($groupList);
@@ -134,10 +129,10 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
134129
) {
135130
// if only one top level capturing optional group exists
136131
// we build a more precise tagged union of a empty-match and a match with the group
137-
$groupList[$onlyOptionalTopLevelGroupId] = $groupList[$onlyOptionalTopLevelGroupId]->forceNonOptional();
132+
$regexGroupList = $regexGroupList->forceGroupIdNonOptional($onlyOptionalTopLevelGroupId);
138133

139134
$combiType = $this->buildArrayType(
140-
$groupList,
135+
$regexGroupList,
141136
$wasMatched,
142137
$trailingOptionals,
143138
$flags,
@@ -165,25 +160,24 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
165160
$combiTypes = [];
166161
$isOptionalAlternation = false;
167162
foreach ($onlyTopLevelAlternation->getGroupCombinations() as $groupCombo) {
168-
$comboList = $groupList;
163+
$comboList = new RegexGroupList($groupList);
169164

170165
$beforeCurrentCombo = true;
171166
foreach ($comboList as $groupId => $group) {
172167
if (in_array($groupId, $groupCombo, true)) {
173168
$isOptionalAlternation = $group->inOptionalAlternation();
174-
$forcedGroup = $group->forceNonOptional();
169+
$comboList = $comboList->forceGroupIdNonOptional($group->getId());
175170
$beforeCurrentCombo = false;
176-
$comboList[$groupId] = $forcedGroup;
177171
} elseif ($beforeCurrentCombo && !$group->resetsGroupCounter()) {
178-
$forcedGroup = $group->forceNonOptional()->forceType(
172+
$comboList = $comboList->forceGroupIdTypeAndNonOptional(
173+
$group->getId(),
179174
$this->containsUnmatchedAsNull($flags, $matchesAll) ? new NullType() : new ConstantStringType(''),
180175
);
181-
$comboList[$groupId] = $forcedGroup;
182176
} elseif (
183177
$group->getAlternationId() === $onlyTopLevelAlternation->getId()
184178
&& !$this->containsUnmatchedAsNull($flags, $matchesAll)
185179
) {
186-
unset($comboList[$groupId]);
180+
$comboList = $comboList->removeGroup($groupId);
187181
}
188182
}
189183

@@ -216,7 +210,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
216210
// the general case, which should work in all cases but does not yield the most
217211
// precise result possible in some cases
218212
return $this->buildArrayType(
219-
$groupList,
213+
$regexGroupList,
220214
$wasMatched,
221215
$trailingOptionals,
222216
$flags,
@@ -280,11 +274,10 @@ private function getOnlyTopLevelAlternation(array $captureGroups): ?RegexAlterna
280274
}
281275

282276
/**
283-
* @param array<RegexCapturingGroup> $captureGroups
284277
* @param list<string> $markVerbs
285278
*/
286279
private function buildArrayType(
287-
array $captureGroups,
280+
RegexGroupList $captureGroups,
288281
TrinaryLogic $wasMatched,
289282
int $trailingOptionals,
290283
int $flags,

src/Type/Regex/RegexCapturingGroup.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public function __construct(
1616
private readonly ?string $name,
1717
private readonly ?RegexAlternation $alternation,
1818
private readonly bool $inOptionalQuantification,
19-
private readonly RegexCapturingGroup|RegexNonCapturingGroup|null $parent,
19+
private RegexCapturingGroup|RegexNonCapturingGroup|null $parent,
2020
private readonly Type $type,
2121
)
2222
{
@@ -41,6 +41,13 @@ public function forceType(Type $type): self
4141
return $new;
4242
}
4343

44+
public function withParent(RegexCapturingGroup|RegexNonCapturingGroup $parent): self
45+
{
46+
$new = clone $this;
47+
$new->parent = $parent;
48+
return $new;
49+
}
50+
4451
public function resetsGroupCounter(): bool
4552
{
4653
return $this->parent instanceof RegexNonCapturingGroup && $this->parent->resetsGroupCounter();
@@ -126,4 +133,9 @@ public function getType(): Type
126133
return $this->type;
127134
}
128135

136+
public function getParent(): RegexCapturingGroup|RegexNonCapturingGroup|null
137+
{
138+
return $this->parent;
139+
}
140+
129141
}

src/Type/Regex/RegexGroupList.php

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Regex;
4+
5+
use ArrayIterator;
6+
use Countable;
7+
use IteratorAggregate;
8+
use PHPStan\ShouldNotHappenException;
9+
use PHPStan\Type\Type;
10+
use function array_reverse;
11+
use function count;
12+
13+
/**
14+
* @implements IteratorAggregate<int, RegexCapturingGroup>
15+
*/
16+
final class RegexGroupList implements Countable, IteratorAggregate
17+
{
18+
19+
/**
20+
* @param array<int, RegexCapturingGroup> $groups
21+
*/
22+
public function __construct(
23+
private readonly array $groups,
24+
)
25+
{
26+
}
27+
28+
public function countTrailingOptionals(): int
29+
{
30+
$trailingOptionals = 0;
31+
foreach (array_reverse($this->groups) as $captureGroup) {
32+
if (!$captureGroup->isOptional()) {
33+
break;
34+
}
35+
$trailingOptionals++;
36+
}
37+
return $trailingOptionals;
38+
}
39+
40+
public function forceGroupIdNonOptional(int $id): self
41+
{
42+
return $this->cloneAndReParentList($id);
43+
}
44+
45+
public function forceGroupIdTypeAndNonOptional(int $id, Type $type): self
46+
{
47+
return $this->cloneAndReParentList($id, $type);
48+
}
49+
50+
private function cloneAndReParentList(int $id, ?Type $type = null): self
51+
{
52+
$groups = [];
53+
$forcedGroup = null;
54+
foreach ($this->groups as $i => $group) {
55+
if ($group->getId() === $id) {
56+
$forcedGroup = $group->forceNonOptional();
57+
if ($type !== null) {
58+
$forcedGroup = $forcedGroup->forceType($type);
59+
}
60+
$groups[$i] = $forcedGroup;
61+
62+
continue;
63+
}
64+
65+
$groups[$i] = $group;
66+
}
67+
68+
if ($forcedGroup === null) {
69+
throw new ShouldNotHappenException();
70+
}
71+
72+
foreach ($groups as $i => $group) {
73+
$parent = $group->getParent();
74+
75+
while ($parent !== null) {
76+
if ($parent instanceof RegexNonCapturingGroup) {
77+
$parent = $parent->getParent();
78+
continue;
79+
}
80+
81+
if ($parent->getId() === $id) {
82+
$groups[$i] = $groups[$i]->withParent($forcedGroup);
83+
}
84+
$parent = $parent->getParent();
85+
}
86+
}
87+
88+
return new self($groups);
89+
}
90+
91+
public function removeGroup(int $id): self
92+
{
93+
$groups = [];
94+
foreach ($this->groups as $i => $group) {
95+
if ($group->getId() === $id) {
96+
continue;
97+
}
98+
99+
$groups[$i] = $group;
100+
}
101+
102+
return new self($groups);
103+
}
104+
105+
public function count(): int
106+
{
107+
return count($this->groups);
108+
}
109+
110+
/**
111+
* @return ArrayIterator<int, RegexCapturingGroup>
112+
*/
113+
public function getIterator(): ArrayIterator
114+
{
115+
return new ArrayIterator($this->groups);
116+
}
117+
118+
}

0 commit comments

Comments
 (0)