Skip to content

Commit e556e5d

Browse files
committed
Keep mark verbs to narrow down the type of MARK
1 parent 76eae8b commit e556e5d

File tree

2 files changed

+22
-16
lines changed

2 files changed

+22
-16
lines changed

src/Type/Php/RegexArrayShapeMatcher.php

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
125125
// regex could not be parsed by Hoa/Regex
126126
return null;
127127
}
128-
[$groupList, $groupCombinations, $hasMark] = $parseResult;
128+
[$groupList, $groupCombinations, $markVerbs] = $parseResult;
129129

130130
$trailingOptionals = 0;
131131
foreach (array_reverse($groupList) as $captureGroup) {
@@ -152,7 +152,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
152152
$wasMatched,
153153
$trailingOptionals,
154154
$flags ?? 0,
155-
$hasMark,
155+
$markVerbs,
156156
);
157157

158158
if (!$this->containsUnmatchedAsNull($flags ?? 0)) {
@@ -190,7 +190,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
190190
$wasMatched,
191191
$trailingOptionals,
192192
$flags ?? 0,
193-
$hasMark,
193+
$markVerbs,
194194
);
195195

196196
$combiTypes[] = $combiType;
@@ -213,7 +213,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
213213
$wasMatched,
214214
$trailingOptionals,
215215
$flags ?? 0,
216-
$hasMark,
216+
$markVerbs,
217217
);
218218
}
219219

@@ -269,13 +269,14 @@ private function getOnlyTopLevelAlternationId(array $captureGroups): ?int
269269

270270
/**
271271
* @param array<RegexCapturingGroup> $captureGroups
272+
* @param list<string> $markVerbs
272273
*/
273274
private function buildArrayType(
274275
array $captureGroups,
275276
TrinaryLogic $wasMatched,
276277
int $trailingOptionals,
277278
int $flags,
278-
bool $hasMark,
279+
array $markVerbs,
279280
): Type
280281
{
281282
$builder = ConstantArrayTypeBuilder::createEmpty();
@@ -329,10 +330,14 @@ private function buildArrayType(
329330
$i++;
330331
}
331332

332-
if ($hasMark) {
333+
if (count($markVerbs) > 0) {
334+
$markTypes = [];
335+
foreach ($markVerbs as $mark) {
336+
$markTypes[] = new ConstantStringType($mark);
337+
}
333338
$builder->setOffsetValueType(
334339
$this->getKeyType('MARK'),
335-
new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]),
340+
TypeCombinator::union(...$markTypes),
336341
true,
337342
);
338343
}
@@ -384,7 +389,7 @@ private function getValueType(Type $baseType, int $flags): Type
384389
}
385390

386391
/**
387-
* @return array{array<int, RegexCapturingGroup>, array<int, array<int, int[]>>, bool}|null
392+
* @return array{array<int, RegexCapturingGroup>, array<int, array<int, int[]>>, list<string>}|null
388393
*/
389394
private function parseGroups(string $regex): ?array
390395
{
@@ -410,7 +415,7 @@ private function parseGroups(string $regex): ?array
410415
$groupCombinations = [];
411416
$alternationId = -1;
412417
$captureGroupId = 100;
413-
$hasMark = false;
418+
$markVerbs = [];
414419
$this->walkRegexAst(
415420
$ast,
416421
false,
@@ -421,15 +426,16 @@ private function parseGroups(string $regex): ?array
421426
$captureGroupId,
422427
$capturingGroups,
423428
$groupCombinations,
424-
$hasMark,
429+
$markVerbs,
425430
);
426431

427-
return [$capturingGroups, $groupCombinations, $hasMark];
432+
return [$capturingGroups, $groupCombinations, $markVerbs];
428433
}
429434

430435
/**
431436
* @param array<int, RegexCapturingGroup> $capturingGroups
432437
* @param array<int, array<int, int[]>> $groupCombinations
438+
* @param list<string> $markVerbs
433439
*/
434440
private function walkRegexAst(
435441
TreeNode $ast,
@@ -441,7 +447,7 @@ private function walkRegexAst(
441447
int &$captureGroupId,
442448
array &$capturingGroups,
443449
array &$groupCombinations,
444-
bool &$hasMark,
450+
array &$markVerbs,
445451
): void
446452
{
447453
$group = null;
@@ -456,7 +462,7 @@ private function walkRegexAst(
456462
);
457463
$parentGroup = $group;
458464
} elseif ($ast->getId() === '#namedcapturing') {
459-
$name = $ast->getChild(0)->getValue()['value'];
465+
$name = $ast->getChild(0)->getValueValue();
460466
$group = new RegexCapturingGroup(
461467
$captureGroupId++,
462468
$name,
@@ -499,7 +505,7 @@ private function walkRegexAst(
499505
}
500506

501507
if ($ast->getId() === '#mark') {
502-
$hasMark = true;
508+
$markVerbs[] = $ast->getChild(0)->getValueValue();
503509
return;
504510
}
505511

@@ -526,7 +532,7 @@ private function walkRegexAst(
526532
$captureGroupId,
527533
$capturingGroups,
528534
$groupCombinations,
529-
$hasMark,
535+
$markVerbs,
530536
);
531537

532538
if ($ast->getId() !== '#alternation') {

tests/PHPStan/Analyser/nsrt/preg_match_shapes.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ function bug11323(string $s): void {
522522
assertType('array{string, non-empty-string, non-empty-string, non-empty-string}', $matches);
523523
}
524524
if (preg_match('/foo(*:first)|bar(*:second)([x])/', $s, $matches)) {
525-
assertType('array{0: string, 1?: non-empty-string, MARK?: non-empty-string}', $matches);
525+
assertType("array{0: string, 1?: non-empty-string, MARK?: 'first'|'second'}", $matches);
526526
}
527527
}
528528

0 commit comments

Comments
 (0)