Skip to content

Commit 1968aa9

Browse files
staabmondrejmirtes
authored andcommittedAug 31, 2024·
RegexArrayShapeMatcher - improve type inference in alternations
1 parent 95f82af commit 1968aa9

File tree

2 files changed

+52
-21
lines changed

2 files changed

+52
-21
lines changed
 

‎src/Type/Regex/RegexGroupParser.php

+32-19
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
use PHPStan\Type\StringType;
2121
use PHPStan\Type\Type;
2222
use PHPStan\Type\TypeCombinator;
23-
use function array_merge;
2423
use function count;
2524
use function in_array;
2625
use function is_int;
@@ -295,6 +294,16 @@ private function getQuantificationRange(TreeNode $node): array
295294

296295
private function createGroupType(TreeNode $group, bool $maybeConstant, string $patternModifiers): Type
297296
{
297+
$rootAlternation = $this->getRootAlternation($group);
298+
if ($rootAlternation !== null) {
299+
$types = [];
300+
foreach ($rootAlternation->getChildren() as $alternative) {
301+
$types[] = $this->createGroupType($alternative, $maybeConstant, $patternModifiers);
302+
}
303+
304+
return TypeCombinator::union(...$types);
305+
}
306+
298307
$isNonEmpty = TrinaryLogic::createMaybe();
299308
$isNonFalsy = TrinaryLogic::createMaybe();
300309
$isNumeric = TrinaryLogic::createMaybe();
@@ -345,6 +354,28 @@ private function createGroupType(TreeNode $group, bool $maybeConstant, string $p
345354
return new StringType();
346355
}
347356

357+
private function getRootAlternation(TreeNode $group): ?TreeNode
358+
{
359+
if (
360+
$group->getId() === '#capturing'
361+
&& count($group->getChildren()) === 1
362+
&& $group->getChild(0)->getId() === '#alternation'
363+
) {
364+
return $group->getChild(0);
365+
}
366+
367+
// 1st token within a named capturing group is a token holding the group-name
368+
if (
369+
$group->getId() === '#namedcapturing'
370+
&& count($group->getChildren()) === 2
371+
&& $group->getChild(1)->getId() === '#alternation'
372+
) {
373+
return $group->getChild(1);
374+
}
375+
376+
return null;
377+
}
378+
348379
/**
349380
* @param array<string>|null $onlyLiterals
350381
*/
@@ -448,7 +479,6 @@ private function walkGroupAst(
448479
$isNumeric = TrinaryLogic::createNo();
449480
}
450481

451-
$alternativeLiterals = [];
452482
foreach ($children as $child) {
453483
$this->walkGroupAst(
454484
$child,
@@ -461,24 +491,7 @@ private function walkGroupAst(
461491
$inClass,
462492
$patternModifiers,
463493
);
464-
465-
if ($ast->getId() !== '#alternation') {
466-
continue;
467-
}
468-
469-
if ($onlyLiterals !== null && $alternativeLiterals !== null) {
470-
$alternativeLiterals = array_merge($alternativeLiterals, $onlyLiterals);
471-
$onlyLiterals = [];
472-
} else {
473-
$alternativeLiterals = null;
474-
}
475-
}
476-
477-
if ($alternativeLiterals === null || $alternativeLiterals === []) {
478-
return;
479494
}
480-
481-
$onlyLiterals = $alternativeLiterals;
482495
}
483496

484497
private function isMaybeEmptyNode(TreeNode $node, string $patternModifiers, bool &$isNonFalsy): bool

‎tests/PHPStan/Analyser/nsrt/preg_match_shapes.php

+20-2
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ function (string $s): void {
561561
}
562562

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

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

610610
function (string $s): void {
611611
if (preg_match('/Price: (a|bc?)/', $s, $matches)) {
612+
assertType("array{string, non-falsy-string}", $matches);
613+
}
614+
};
615+
616+
function (string $s): void {
617+
if (preg_match('/Price: (?<named>a|bc?)/', $s, $matches)) {
618+
assertType("array{0: string, named: non-falsy-string, 1: non-falsy-string}", $matches);
619+
}
620+
};
621+
622+
function (string $s): void {
623+
if (preg_match('/Price: (a|0c?)/', $s, $matches)) {
612624
assertType("array{string, non-empty-string}", $matches);
613625
}
614626
};
615627

616628
function (string $s): void {
617629
if (preg_match('/Price: (a|\d)/', $s, $matches)) {
618-
assertType("array{string, non-empty-string}", $matches);
630+
assertType("array{string, 'a'|numeric-string}", $matches);
631+
}
632+
};
633+
634+
function (string $s): void {
635+
if (preg_match('/Price: (?<named>a|\d)/', $s, $matches)) {
636+
assertType("array{0: string, named: 'a'|numeric-string, 1: 'a'|numeric-string}", $matches);
619637
}
620638
};
621639

0 commit comments

Comments
 (0)
Please sign in to comment.