Skip to content

Commit 7c61b03

Browse files
staabmondrejmirtes
authored andcommitted
RegexArrayShapeMatcher - support resetting non-capturing groups
1 parent d931c89 commit 7c61b03

File tree

4 files changed

+32
-1
lines changed

4 files changed

+32
-1
lines changed

src/Type/Php/RegexArrayShapeMatcher.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ private function matchRegex(string $regex, ?int $flags, TrinaryLogic $wasMatched
133133
$isOptionalAlternation = $group->inOptionalAlternation();
134134
$group->forceNonOptional();
135135
$beforeCurrentCombo = false;
136-
} elseif ($beforeCurrentCombo) {
136+
} elseif ($beforeCurrentCombo && !$group->resetsGroupCounter()) {
137137
$group->forceNonOptional();
138138
} elseif ($group->getAlternationId() === $onlyTopLevelAlternationId) {
139139
unset($comboList[$groupId]);
@@ -385,6 +385,15 @@ private function walkRegexAst(
385385
$inAlternation ? $alternationId : null,
386386
$inOptionalQuantification,
387387
$parentGroup,
388+
false,
389+
);
390+
$parentGroup = $group;
391+
} elseif ($ast->getId() === '#noncapturingreset') {
392+
$group = new RegexNonCapturingGroup(
393+
$inAlternation ? $alternationId : null,
394+
$inOptionalQuantification,
395+
$parentGroup,
396+
true,
388397
);
389398
$parentGroup = $group;
390399
}

src/Type/Php/RegexCapturingGroup.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ public function restoreNonOptional(): void
3232
$this->forceNonOptional = false;
3333
}
3434

35+
public function resetsGroupCounter(): bool
36+
{
37+
return $this->parent instanceof RegexNonCapturingGroup && $this->parent->resetsGroupCounter();
38+
}
39+
3540
/** @phpstan-assert-if-true !null $this->getAlternationId() */
3641
public function inAlternation(): bool
3742
{

src/Type/Php/RegexNonCapturingGroup.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public function __construct(
99
private ?int $alternationId,
1010
private bool $inOptionalQuantification,
1111
private RegexCapturingGroup|RegexNonCapturingGroup|null $parent,
12+
private bool $resetGroupCounter,
1213
)
1314
{
1415
}
@@ -42,4 +43,9 @@ public function getParent(): RegexCapturingGroup|RegexNonCapturingGroup|null
4243
return $this->parent;
4344
}
4445

46+
public function resetsGroupCounter(): bool
47+
{
48+
return $this->resetGroupCounter;
49+
}
50+
4551
}

tests/PHPStan/Analyser/nsrt/preg_match_shapes.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,3 +360,14 @@ function bug11277b(string $value): void
360360
}
361361
}
362362
}
363+
364+
// https://www.pcre.org/current/doc/html/pcre2pattern.html#dupgroupnumber
365+
// https://3v4l.org/09qdT
366+
function bug11291(string $s): void {
367+
if (preg_match('/(?|(a)|(b)(c)|(d)(e)(f))/', $s, $matches)) {
368+
assertType('array{0: string, 1: string, 2?: string, 3?: string}', $matches);
369+
} else {
370+
assertType('array{}', $matches);
371+
}
372+
assertType('array{}|array{0: string, 1: string, 2?: string, 3?: string}', $matches);
373+
}

0 commit comments

Comments
 (0)