Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 30 additions & 14 deletions src/Type/Php/RegexGroupParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ public function parseGroups(string $regex): ?array
}

$captureOnlyNamed = false;
$modifiers = $this->regexExpressionHelper->getPatternModifiers($regex) ?? '';
if ($this->phpVersion->supportsPregCaptureOnlyNamedGroups()) {
$modifiers = $this->regexExpressionHelper->getPatternModifiers($regex);
$captureOnlyNamed = str_contains($modifiers ?? '', 'n');
$captureOnlyNamed = str_contains($modifiers, 'n');
}

$capturingGroups = [];
Expand All @@ -90,6 +90,7 @@ public function parseGroups(string $regex): ?array
$markVerbs,
$captureOnlyNamed,
false,
$modifiers,
);

return [$capturingGroups, $groupCombinations, $markVerbs];
Expand All @@ -113,38 +114,29 @@ private function walkRegexAst(
array &$markVerbs,
bool $captureOnlyNamed,
bool $repeatedMoreThanOnce,
string $patternModifiers,
): void
{
$group = null;
if ($ast->getId() === '#capturing') {
$maybeConstant = !$repeatedMoreThanOnce;
if ($parentGroup !== null && $parentGroup->resetsGroupCounter()) {
$maybeConstant = false;
}

$group = new RegexCapturingGroup(
$captureGroupId++,
null,
$inAlternation ? $alternationId : null,
$inOptionalQuantification,
$parentGroup,
$this->createGroupType($ast, $maybeConstant),
$this->createGroupType($ast, $this->allowConstantTypes($patternModifiers, $repeatedMoreThanOnce, $parentGroup)),
);
$parentGroup = $group;
} elseif ($ast->getId() === '#namedcapturing') {
$maybeConstant = !$repeatedMoreThanOnce;
if ($parentGroup !== null && $parentGroup->resetsGroupCounter()) {
$maybeConstant = false;
}

$name = $ast->getChild(0)->getValueValue();
$group = new RegexCapturingGroup(
$captureGroupId++,
$name,
$inAlternation ? $alternationId : null,
$inOptionalQuantification,
$parentGroup,
$this->createGroupType($ast, $maybeConstant),
$this->createGroupType($ast, $this->allowConstantTypes($patternModifiers, $repeatedMoreThanOnce, $parentGroup)),
);
$parentGroup = $group;
} elseif ($ast->getId() === '#noncapturing') {
Expand Down Expand Up @@ -217,6 +209,7 @@ private function walkRegexAst(
$markVerbs,
$captureOnlyNamed,
$repeatedMoreThanOnce,
$patternModifiers,
);

if ($ast->getId() !== '#alternation') {
Expand All @@ -227,6 +220,29 @@ private function walkRegexAst(
}
}

private function allowConstantTypes(
string $patternModifiers,
bool $repeatedMoreThanOnce,
RegexCapturingGroup|RegexNonCapturingGroup|null $parentGroup,
): bool
{
if (str_contains($patternModifiers, 'i')) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most helpful would be a issue report with a reproducing snippet.

but I am already working on this one, so thanks for the heads up.

// if caseless, we don't use constant types
// because it likely yields too many combinations
return false;
}

if ($repeatedMoreThanOnce) {
return false;
}

if ($parentGroup !== null && $parentGroup->resetsGroupCounter()) {
return false;
}

return true;
}

/** @return array{?int, ?int} */
private function getQuantificationRange(TreeNode $node): array
{
Expand Down
18 changes: 12 additions & 6 deletions tests/PHPStan/Analyser/nsrt/preg_match_shapes.php
Original file line number Diff line number Diff line change
Expand Up @@ -549,9 +549,9 @@ public function test2(string $str): void

function (string $s): void {
if (rand(0,1)) {
$p = '/Price: (£)(abc)/i';
$p = '/Price: (£)(abc)/';
} else {
$p = '/Price: (\d)(b)/i';
$p = '/Price: (\d)(b)/';
}

if (preg_match($p, $s, $matches)) {
Expand All @@ -561,9 +561,9 @@ function (string $s): void {

function (string $s): void {
if (rand(0,1)) {
$p = '/Price: (£)/i';
$p = '/Price: (£)/';
} else {
$p = '/Price: (£|(\d)|(x))/i';
$p = '/Price: (£|(\d)|(x))/';
}

if (preg_match($p, $s, $matches)) {
Expand All @@ -585,18 +585,24 @@ function (string $s): void {

function (string $s): void {
if (preg_match('/Price: ([xXa])/i', $s, $matches)) {
assertType("array{string, non-empty-string}", $matches);
}
};

function (string $s): void {
if (preg_match('/Price: ([xXa])/', $s, $matches)) {
assertType("array{string, 'a'|'X'|'x'}", $matches);
}
};

function (string $s): void {
if (preg_match('/Price: (ba[rz])/i', $s, $matches)) {
if (preg_match('/Price: (ba[rz])/', $s, $matches)) {
assertType("array{string, 'bar'|'baz'}", $matches);
}
};

function (string $s): void {
if (preg_match('/Price: (b[ao][mn])/i', $s, $matches)) {
if (preg_match('/Price: (b[ao][mn])/', $s, $matches)) {
assertType("array{string, 'bam'|'ban'|'bom'|'bon'}", $matches);
}
};
Loading