Skip to content

Commit

Permalink
RegexArrayShapeMatcher - improve type inference in alternations
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm authored and ondrejmirtes committed Aug 31, 2024
1 parent 95f82af commit 1968aa9
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 21 deletions.
51 changes: 32 additions & 19 deletions src/Type/Regex/RegexGroupParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use function array_merge;
use function count;
use function in_array;
use function is_int;
Expand Down Expand Up @@ -295,6 +294,16 @@ private function getQuantificationRange(TreeNode $node): array

private function createGroupType(TreeNode $group, bool $maybeConstant, string $patternModifiers): Type
{
$rootAlternation = $this->getRootAlternation($group);
if ($rootAlternation !== null) {
$types = [];
foreach ($rootAlternation->getChildren() as $alternative) {
$types[] = $this->createGroupType($alternative, $maybeConstant, $patternModifiers);
}

return TypeCombinator::union(...$types);
}

$isNonEmpty = TrinaryLogic::createMaybe();
$isNonFalsy = TrinaryLogic::createMaybe();
$isNumeric = TrinaryLogic::createMaybe();
Expand Down Expand Up @@ -345,6 +354,28 @@ private function createGroupType(TreeNode $group, bool $maybeConstant, string $p
return new StringType();
}

private function getRootAlternation(TreeNode $group): ?TreeNode
{
if (
$group->getId() === '#capturing'
&& count($group->getChildren()) === 1
&& $group->getChild(0)->getId() === '#alternation'
) {
return $group->getChild(0);
}

// 1st token within a named capturing group is a token holding the group-name
if (
$group->getId() === '#namedcapturing'
&& count($group->getChildren()) === 2
&& $group->getChild(1)->getId() === '#alternation'
) {
return $group->getChild(1);
}

return null;
}

/**
* @param array<string>|null $onlyLiterals
*/
Expand Down Expand Up @@ -448,7 +479,6 @@ private function walkGroupAst(
$isNumeric = TrinaryLogic::createNo();
}

$alternativeLiterals = [];
foreach ($children as $child) {
$this->walkGroupAst(
$child,
Expand All @@ -461,24 +491,7 @@ private function walkGroupAst(
$inClass,
$patternModifiers,
);

if ($ast->getId() !== '#alternation') {
continue;
}

if ($onlyLiterals !== null && $alternativeLiterals !== null) {
$alternativeLiterals = array_merge($alternativeLiterals, $onlyLiterals);
$onlyLiterals = [];
} else {
$alternativeLiterals = null;
}
}

if ($alternativeLiterals === null || $alternativeLiterals === []) {
return;
}

$onlyLiterals = $alternativeLiterals;
}

private function isMaybeEmptyNode(TreeNode $node, string $patternModifiers, bool &$isNonFalsy): bool
Expand Down
22 changes: 20 additions & 2 deletions tests/PHPStan/Analyser/nsrt/preg_match_shapes.php
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ function (string $s): void {
}

if (preg_match($p, $s, $matches)) {
assertType("array{0: string, 1: non-empty-string, 2?: ''|numeric-string, 3?: 'x'}", $matches);
assertType("array{0: string, 1: 'x'|'£'|numeric-string, 2?: ''|numeric-string, 3?: 'x'}", $matches);
}
};

Expand Down Expand Up @@ -609,13 +609,31 @@ function (string $s): void {

function (string $s): void {
if (preg_match('/Price: (a|bc?)/', $s, $matches)) {
assertType("array{string, non-falsy-string}", $matches);
}
};

function (string $s): void {
if (preg_match('/Price: (?<named>a|bc?)/', $s, $matches)) {
assertType("array{0: string, named: non-falsy-string, 1: non-falsy-string}", $matches);
}
};

function (string $s): void {
if (preg_match('/Price: (a|0c?)/', $s, $matches)) {
assertType("array{string, non-empty-string}", $matches);
}
};

function (string $s): void {
if (preg_match('/Price: (a|\d)/', $s, $matches)) {
assertType("array{string, non-empty-string}", $matches);
assertType("array{string, 'a'|numeric-string}", $matches);
}
};

function (string $s): void {
if (preg_match('/Price: (?<named>a|\d)/', $s, $matches)) {
assertType("array{0: string, named: 'a'|numeric-string, 1: 'a'|numeric-string}", $matches);
}
};

Expand Down

0 comments on commit 1968aa9

Please sign in to comment.