diff --git a/src/Type/Php/RegexGroupParser.php b/src/Type/Php/RegexGroupParser.php index c6782c16be..f63959e78d 100644 --- a/src/Type/Php/RegexGroupParser.php +++ b/src/Type/Php/RegexGroupParser.php @@ -21,6 +21,7 @@ use PHPStan\Type\TypeCombinator; use function array_key_exists; use function count; +use function implode; use function in_array; use function is_int; use function rtrim; @@ -89,6 +90,7 @@ public function parseGroups(string $regex): ?array $groupCombinations, $markVerbs, $captureOnlyNamed, + false, ); return [$capturingGroups, $groupCombinations, $markVerbs]; @@ -111,20 +113,31 @@ private function walkRegexAst( array &$groupCombinations, array &$markVerbs, bool $captureOnlyNamed, + bool $repeatedMoreThanOnce, ): 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), + $this->createGroupType($ast, $maybeConstant), ); $parentGroup = $group; } elseif ($ast->getId() === '#namedcapturing') { + $maybeConstant = !$repeatedMoreThanOnce; + if ($parentGroup !== null && $parentGroup->resetsGroupCounter()) { + $maybeConstant = false; + } + $name = $ast->getChild(0)->getValueValue(); $group = new RegexCapturingGroup( $captureGroupId++, @@ -132,7 +145,7 @@ private function walkRegexAst( $inAlternation ? $alternationId : null, $inOptionalQuantification, $parentGroup, - $this->createGroupType($ast), + $this->createGroupType($ast, $maybeConstant), ); $parentGroup = $group; } elseif ($ast->getId() === '#noncapturing') { @@ -155,11 +168,15 @@ private function walkRegexAst( $inOptionalQuantification = false; if ($ast->getId() === '#quantification') { - [$min] = $this->getQuantificationRange($ast); + [$min, $max] = $this->getQuantificationRange($ast); if ($min === 0) { $inOptionalQuantification = true; } + + if ($max === null || $max > 1) { + $repeatedMoreThanOnce = true; + } } if ($ast->getId() === '#alternation') { @@ -200,6 +217,7 @@ private function walkRegexAst( $groupCombinations, $markVerbs, $captureOnlyNamed, + $repeatedMoreThanOnce, ); if ($ast->getId() !== '#alternation') { @@ -259,13 +277,18 @@ private function getQuantificationRange(TreeNode $node): array return [$min, $max]; } - private function createGroupType(TreeNode $group): Type + private function createGroupType(TreeNode $group, bool $maybeConstant): Type { $isNonEmpty = TrinaryLogic::createMaybe(); $isNumeric = TrinaryLogic::createMaybe(); $inOptionalQuantification = false; + $onlyLiterals = []; + + $this->walkGroupAst($group, $isNonEmpty, $isNumeric, $inOptionalQuantification, $onlyLiterals); - $this->walkGroupAst($group, $isNonEmpty, $isNumeric, $inOptionalQuantification); + if ($maybeConstant && $onlyLiterals !== null && $onlyLiterals !== []) { + return new ConstantStringType(implode('', $onlyLiterals)); + } if ($isNumeric->yes()) { $result = new IntersectionType([new StringType(), new AccessoryNumericStringType()]); @@ -280,7 +303,10 @@ private function createGroupType(TreeNode $group): Type return new StringType(); } - private function walkGroupAst(TreeNode $ast, TrinaryLogic &$isNonEmpty, TrinaryLogic &$isNumeric, bool &$inOptionalQuantification): void + /** + * @param array|null $onlyLiterals + */ + private function walkGroupAst(TreeNode $ast, TrinaryLogic &$isNonEmpty, TrinaryLogic &$isNumeric, bool &$inOptionalQuantification, ?array &$onlyLiterals): void { $children = $ast->getChildren(); @@ -289,9 +315,7 @@ private function walkGroupAst(TreeNode $ast, TrinaryLogic &$isNonEmpty, TrinaryL && count($children) > 0 ) { $isNonEmpty = TrinaryLogic::createYes(); - } - - if ($ast->getId() === '#quantification') { + } elseif ($ast->getId() === '#quantification') { [$min] = $this->getQuantificationRange($ast); if ($min === 0) { @@ -301,10 +325,10 @@ private function walkGroupAst(TreeNode $ast, TrinaryLogic &$isNonEmpty, TrinaryL $isNonEmpty = TrinaryLogic::createYes(); $inOptionalQuantification = false; } - } - if ($ast->getId() === 'token') { - $literalValue = $this->getLiteralValue($ast); + $onlyLiterals = null; + } elseif ($ast->getId() === 'token') { + $literalValue = $this->getLiteralValue($ast, $onlyLiterals); if ($literalValue !== null) { if (Strings::match($literalValue, '/^\d+$/') === null) { $isNumeric = TrinaryLogic::createNo(); @@ -315,7 +339,11 @@ private function walkGroupAst(TreeNode $ast, TrinaryLogic &$isNonEmpty, TrinaryL if (!$inOptionalQuantification) { $isNonEmpty = TrinaryLogic::createYes(); } + } elseif (!in_array($ast->getValueToken(), ['capturing_name'], true)) { + $onlyLiterals = null; } + } elseif (!in_array($ast->getId(), ['#capturing', '#namedcapturing'], true)) { + $onlyLiterals = null; } // [^0-9] should not parse as numeric-string, and [^list-everything-but-numbers] is technically @@ -331,11 +359,15 @@ private function walkGroupAst(TreeNode $ast, TrinaryLogic &$isNonEmpty, TrinaryL $isNonEmpty, $isNumeric, $inOptionalQuantification, + $onlyLiterals, ); } } - private function getLiteralValue(TreeNode $node): ?string + /** + * @param array|null $onlyLiterals + */ + private function getLiteralValue(TreeNode $node, ?array &$onlyLiterals): ?string { if ($node->getId() !== 'token') { return null; @@ -346,8 +378,14 @@ private function getLiteralValue(TreeNode $node): ?string $value = $node->getValueValue(); if (in_array($token, ['literal', 'escaped_end_class'], true)) { - if (strlen($node->getValueValue()) > 1 && $value[0] === '\\') { + if (strlen($value) > 1 && $value[0] === '\\') { return substr($value, 1); + } elseif ( + $token === 'literal' + && $onlyLiterals !== null + && !in_array($value, ['.'], true) + ) { + $onlyLiterals[] = $value; } return $value; diff --git a/tests/PHPStan/Analyser/nsrt/bug-11311-php72.php b/tests/PHPStan/Analyser/nsrt/bug-11311-php72.php index 3b60727e84..51fc1a05be 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11311-php72.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11311-php72.php @@ -14,17 +14,17 @@ function doFoo(string $s) { function doUnmatchedAsNull(string $s): void { if (preg_match('/(foo)?(bar)?(baz)?/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType('array{0: string, 1?: non-empty-string, 2?: non-empty-string, 3?: non-empty-string}', $matches); + assertType("array{0: string, 1?: 'foo', 2?: 'bar', 3?: 'baz'}", $matches); } - assertType('array{}|array{0: string, 1?: non-empty-string, 2?: non-empty-string, 3?: non-empty-string}', $matches); + assertType("array{}|array{0: string, 1?: 'foo', 2?: 'bar', 3?: 'baz'}", $matches); } // see https://3v4l.org/VeDob#veol function unmatchedAsNullWithOptionalGroup(string $s): void { if (preg_match('/Price: (£|€)?\d+/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType('array{0: string, 1?: non-empty-string}', $matches); + assertType("array{0: string, 1?: non-empty-string}", $matches); } else { assertType('array{}', $matches); } - assertType('array{}|array{0: string, 1?: non-empty-string}', $matches); + assertType("array{}|array{0: string, 1?: non-empty-string}", $matches); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11311.php b/tests/PHPStan/Analyser/nsrt/bug-11311.php index 40cbb49557..0dfb7cc4b4 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11311.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11311.php @@ -14,9 +14,9 @@ function doFoo(string $s) { function doUnmatchedAsNull(string $s): void { if (preg_match('/(foo)?(bar)?(baz)?/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType('array{string, non-empty-string|null, non-empty-string|null, non-empty-string|null}', $matches); + assertType("array{string, 'foo'|null, 'bar'|null, 'baz'|null}", $matches); } - assertType('array{}|array{string, non-empty-string|null, non-empty-string|null, non-empty-string|null}', $matches); + assertType("array{}|array{string, 'foo'|null, 'bar'|null, 'baz'|null}", $matches); } // see https://3v4l.org/VeDob @@ -70,13 +70,13 @@ function bug11331c(string $url):void { class UnmatchedAsNullWithTopLevelAlternation { function doFoo(string $s): void { if (preg_match('/Price: (?:(£)|(€))\d+/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType('array{string, non-empty-string|null, non-empty-string|null}', $matches); // could be array{0: string, 1: null, 2: non-empty-string}|array{0: string, 1: non-empty-string, 2: null} + assertType("array{string, '£'|null, '€'|null}", $matches); // could be tagged union } } function doBar(string $s): void { if (preg_match('/Price: (?:(£)|(€))?\d+/', $s, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType('array{string, non-empty-string|null, non-empty-string|null}', $matches); + assertType("array{string, '£'|null, '€'|null}", $matches); // could be tagged union } } } diff --git a/tests/PHPStan/Analyser/nsrt/bug11384.php b/tests/PHPStan/Analyser/nsrt/bug11384.php index 3996587786..12020de0b9 100644 --- a/tests/PHPStan/Analyser/nsrt/bug11384.php +++ b/tests/PHPStan/Analyser/nsrt/bug11384.php @@ -14,7 +14,7 @@ class HelloWorld public function sayHello(string $s): void { if (preg_match('{(' . Bar::VAL . ')}', $s, $m)) { - assertType('array{string, numeric-string}', $m); + assertType("array{string, '3'}", $m); } } } diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_all_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_all_shapes.php index 626f519a4c..9575402af7 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_all_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_all_shapes.php @@ -71,65 +71,65 @@ function (string $size): void { function (string $size): void { preg_match_all('/a(b)(\d+)?/', $size, $matches, PREG_SET_ORDER); - assertType("list", $matches); + assertType("list", $matches); }; function (string $size): void { if (preg_match_all('/ab(?P\d+)(?Pab)?/', $size, $matches)) { - assertType("array{0: list, num: list, 1: list, suffix: list, 2: list}", $matches); + assertType("array{0: list, num: list, 1: list, suffix: list<''|'ab'>, 2: list<''|'ab'>}", $matches); } }; function (string $size): void { if (preg_match_all('/ab(?P\d+)(?Pab)?/', $size, $matches, PREG_UNMATCHED_AS_NULL)) { - assertType("array{0: list, num: list, 1: list, suffix: list, 2: list}", $matches); + assertType("array{0: list, num: list, 1: list, suffix: list<'ab'|null>, 2: list<'ab'|null>}", $matches); } }; function (string $size): void { if (preg_match_all('/ab(?P\d+)(?Pab)?/', $size, $matches, PREG_SET_ORDER)) { - assertType("list", $matches); + assertType("list", $matches); } }; function (string $size): void { if (preg_match_all('/ab(?P\d+)(?Pab)?/', $size, $matches, PREG_PATTERN_ORDER)) { - assertType("array{0: list, num: list, 1: list, suffix: list, 2: list}", $matches); + assertType("array{0: list, num: list, 1: list, suffix: list<''|'ab'>, 2: list<''|'ab'>}", $matches); } }; function (string $size): void { if (preg_match_all('/ab(?P\d+)(?Pab)?/', $size, $matches, PREG_UNMATCHED_AS_NULL|PREG_SET_ORDER)) { - assertType("list", $matches); + assertType("list", $matches); } }; function (string $size): void { if (preg_match_all('/ab(?P\d+)(?Pab)?/', $size, $matches, PREG_UNMATCHED_AS_NULL|PREG_PATTERN_ORDER)) { - assertType("array{0: list, num: list, 1: list, suffix: list, 2: list}", $matches); + assertType("array{0: list, num: list, 1: list, suffix: list<'ab'|null>, 2: list<'ab'|null>}", $matches); } }; function (string $size): void { if (preg_match_all('/ab(?P\d+)(?Pab)?/', $size, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) { - assertType("list}, num: array{numeric-string, int<0, max>}, 1: array{numeric-string, int<0, max>}, suffix?: array{non-empty-string, int<0, max>}, 2?: array{non-empty-string, int<0, max>}}>", $matches); + assertType("list}, num: array{numeric-string, int<0, max>}, 1: array{numeric-string, int<0, max>}, suffix?: array{'ab', int<0, max>}, 2?: array{'ab', int<0, max>}}>", $matches); } }; function (string $size): void { if (preg_match_all('/ab(?P\d+)(?Pab)?/', $size, $matches, PREG_PATTERN_ORDER|PREG_OFFSET_CAPTURE)) { - assertType("array{0: list}>, num: list}>, 1: list}>, suffix: list<''|array{non-empty-string, int<0, max>}>, 2: list<''|array{non-empty-string, int<0, max>}>}", $matches); + assertType("array{0: list}>, num: list}>, 1: list}>, suffix: list<''|array{'ab', int<0, max>}>, 2: list<''|array{'ab', int<0, max>}>}", $matches); } }; function (string $size): void { if (preg_match_all('/ab(?P\d+)(?Pab)?/', $size, $matches, PREG_UNMATCHED_AS_NULL|PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) { - assertType("list}, num: array{numeric-string|null, int<-1, max>}, 1: array{numeric-string|null, int<-1, max>}, suffix: array{non-empty-string|null, int<-1, max>}, 2: array{non-empty-string|null, int<-1, max>}}>", $matches); + assertType("list}, num: array{numeric-string|null, int<-1, max>}, 1: array{numeric-string|null, int<-1, max>}, suffix: array{'ab'|null, int<-1, max>}, 2: array{'ab'|null, int<-1, max>}}>", $matches); } }; function (string $size): void { if (preg_match_all('/ab(?P\d+)(?Pab)?/', $size, $matches, PREG_UNMATCHED_AS_NULL|PREG_PATTERN_ORDER|PREG_OFFSET_CAPTURE)) { - assertType("array{0: list}>, num: list}>, 1: list}>, suffix: list}>, 2: list}>}", $matches); + assertType("array{0: list}>, num: list}>, 1: list}>, suffix: list}>, 2: list}>}", $matches); } }; diff --git a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php index af57075078..a940e62ff4 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_match_shapes.php @@ -39,19 +39,19 @@ function doMatch(string $s): void { assertType('array{}|array{string, non-empty-string}', $matches); if (preg_match('/(a)(b)*(c)(d)*/', $s, $matches)) { - assertType('array{0: string, 1: non-empty-string, 2: string, 3: non-empty-string, 4?: non-empty-string}', $matches); + assertType("array{0: string, 1: 'a', 2: string, 3: 'c', 4?: non-empty-string}", $matches); } - assertType('array{}|array{0: string, 1: non-empty-string, 2: string, 3: non-empty-string, 4?: non-empty-string}', $matches); + assertType("array{}|array{0: string, 1: 'a', 2: string, 3: 'c', 4?: non-empty-string}", $matches); if (preg_match('/(a)(?b)*(c)(d)*/', $s, $matches)) { - assertType('array{0: string, 1: non-empty-string, name: string, 2: string, 3: non-empty-string, 4?: non-empty-string}', $matches); + assertType("array{0: string, 1: 'a', name: string, 2: string, 3: 'c', 4?: non-empty-string}", $matches); } - assertType('array{}|array{0: string, 1: non-empty-string, name: string, 2: string, 3: non-empty-string, 4?: non-empty-string}', $matches); + assertType("array{}|array{0: string, 1: 'a', name: string, 2: string, 3: 'c', 4?: non-empty-string}", $matches); if (preg_match('/(a)(b)*(c)(?d)*/', $s, $matches)) { - assertType('array{0: string, 1: non-empty-string, 2: string, 3: non-empty-string, name?: non-empty-string, 4?: non-empty-string}', $matches); + assertType("array{0: string, 1: 'a', 2: string, 3: 'c', name?: non-empty-string, 4?: non-empty-string}", $matches); } - assertType('array{}|array{0: string, 1: non-empty-string, 2: string, 3: non-empty-string, name?: non-empty-string, 4?: non-empty-string}', $matches); + assertType("array{}|array{0: string, 1: 'a', 2: string, 3: 'c', name?: non-empty-string, 4?: non-empty-string}", $matches); if (preg_match('/(a|b)|(?:c)/', $s, $matches)) { assertType('array{0: string, 1?: non-empty-string}', $matches); @@ -59,34 +59,34 @@ function doMatch(string $s): void { assertType('array{}|array{0: string, 1?: non-empty-string}', $matches); if (preg_match('/(foo)(bar)(baz)+/', $s, $matches)) { - assertType('array{string, non-empty-string, non-empty-string, non-empty-string}', $matches); + assertType("array{string, 'foo', 'bar', non-empty-string}", $matches); } - assertType('array{}|array{string, non-empty-string, non-empty-string, non-empty-string}', $matches); + assertType("array{}|array{string, 'foo', 'bar', non-empty-string}", $matches); if (preg_match('/(foo)(bar)(baz)*/', $s, $matches)) { - assertType('array{0: string, 1: non-empty-string, 2: non-empty-string, 3?: non-empty-string}', $matches); + assertType("array{0: string, 1: 'foo', 2: 'bar', 3?: non-empty-string}", $matches); } - assertType('array{}|array{0: string, 1: non-empty-string, 2: non-empty-string, 3?: non-empty-string}', $matches); + assertType("array{}|array{0: string, 1: 'foo', 2: 'bar', 3?: non-empty-string}", $matches); if (preg_match('/(foo)(bar)(baz)?/', $s, $matches)) { - assertType('array{0: string, 1: non-empty-string, 2: non-empty-string, 3?: non-empty-string}', $matches); + assertType("array{0: string, 1: 'foo', 2: 'bar', 3?: 'baz'}", $matches); } - assertType('array{}|array{0: string, 1: non-empty-string, 2: non-empty-string, 3?: non-empty-string}', $matches); + assertType("array{}|array{0: string, 1: 'foo', 2: 'bar', 3?: 'baz'}", $matches); if (preg_match('/(foo)(bar)(baz){0,3}/', $s, $matches)) { - assertType('array{0: string, 1: non-empty-string, 2: non-empty-string, 3?: non-empty-string}', $matches); + assertType("array{0: string, 1: 'foo', 2: 'bar', 3?: non-empty-string}", $matches); } - assertType('array{}|array{0: string, 1: non-empty-string, 2: non-empty-string, 3?: non-empty-string}', $matches); + assertType("array{}|array{0: string, 1: 'foo', 2: 'bar', 3?: non-empty-string}", $matches); if (preg_match('/(foo)(bar)(baz){2,3}/', $s, $matches)) { - assertType('array{string, non-empty-string, non-empty-string, non-empty-string}', $matches); + assertType("array{string, 'foo', 'bar', non-empty-string}", $matches); } - assertType('array{}|array{string, non-empty-string, non-empty-string, non-empty-string}', $matches); + assertType("array{}|array{string, 'foo', 'bar', non-empty-string}", $matches); if (preg_match('/(foo)(bar)(baz){2}/', $s, $matches)) { - assertType('array{string, non-empty-string, non-empty-string, non-empty-string}', $matches); + assertType("array{string, 'foo', 'bar', non-empty-string}", $matches); } - assertType('array{}|array{string, non-empty-string, non-empty-string, non-empty-string}', $matches); + assertType("array{}|array{string, 'foo', 'bar', non-empty-string}", $matches); } function doNonCapturingGroup(string $s): void { @@ -115,9 +115,9 @@ function doNamedSubpattern(string $s): void { function doOffsetCapture(string $s): void { if (preg_match('/(foo)(bar)(baz)/', $s, $matches, PREG_OFFSET_CAPTURE)) { - assertType('array{array{string, int<0, max>}, array{non-empty-string, int<0, max>}, array{non-empty-string, int<0, max>}, array{non-empty-string, int<0, max>}}', $matches); + assertType("array{array{string, int<0, max>}, array{'foo', int<0, max>}, array{'bar', int<0, max>}, array{'baz', int<0, max>}}", $matches); } - assertType('array{}|array{array{string, int<0, max>}, array{non-empty-string, int<0, max>}, array{non-empty-string, int<0, max>}, array{non-empty-string, int<0, max>}}', $matches); + assertType("array{}|array{array{string, int<0, max>}, array{'foo', int<0, max>}, array{'bar', int<0, max>}, array{'baz', int<0, max>}}", $matches); } function doUnknownFlags(string $s, int $flags): void { @@ -233,13 +233,13 @@ function testUnionPattern(string $s): void function doFoo(string $row): void { if (preg_match('~^(a(b))$~', $row, $matches) === 1) { - assertType('array{string, non-empty-string, non-empty-string}', $matches); + assertType("array{string, 'ab', 'b'}", $matches); } if (preg_match('~^(a(b)?)$~', $row, $matches) === 1) { - assertType('array{0: string, 1: non-empty-string, 2?: non-empty-string}', $matches); + assertType("array{0: string, 1: non-empty-string, 2?: 'b'}", $matches); } if (preg_match('~^(a(b)?)?$~', $row, $matches) === 1) { - assertType('array{0: string, 1?: non-empty-string, 2?: non-empty-string}', $matches); + assertType("array{0: string, 1?: non-empty-string, 2?: 'b'}", $matches); } } @@ -300,7 +300,7 @@ function (string $size): void { if (preg_match('~^a\.(b)?(c)?d~', $size, $matches) !== 1) { throw new InvalidArgumentException(sprintf('Invalid size "%s"', $size)); } - assertType('array{0: string, 1?: non-empty-string, 2?: non-empty-string}', $matches); + assertType("array{0: string, 1?: 'b', 2?: 'c'}", $matches); }; function (string $size): void { @@ -321,7 +321,7 @@ function (string $size): void { if (preg_match('~\{(?:(include)\\s+(?:[$]?\\w+(?}, array{non-empty-string|null, int<-1, max>}, array{non-empty-string|null, int<-1, max>}, array{non-empty-string|null, int<-1, max>}}', $matches); + assertType("array{array{string|null, int<-1, max>}, array{'foo'|null, int<-1, max>}, array{'bar'|null, int<-1, max>}, array{'baz'|null, int<-1, max>}}", $matches); } - assertType('array{}|array{array{string|null, int<-1, max>}, array{non-empty-string|null, int<-1, max>}, array{non-empty-string|null, int<-1, max>}, array{non-empty-string|null, int<-1, max>}}', $matches); + assertType("array{}|array{array{string|null, int<-1, max>}, array{'foo'|null, int<-1, max>}, array{'bar'|null, int<-1, max>}, array{'baz'|null, int<-1, max>}}", $matches); } function doNonAutoCapturingModifier(string $s): void { diff --git a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php index 574be40769..621051467e 100644 --- a/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php +++ b/tests/PHPStan/Analyser/nsrt/preg_replace_callback_shapes.php @@ -8,7 +8,7 @@ function (string $s): void { preg_replace_callback( '/(foo)?(bar)?(baz)?/', function ($matches) { - assertType('array{string, non-empty-string|null, non-empty-string|null, non-empty-string|null}', $matches); + assertType("array{string, 'foo'|null, 'bar'|null, 'baz'|null}", $matches); return ''; }, $s, @@ -22,7 +22,7 @@ function (string $s): void { preg_replace_callback( '/(foo)?(bar)?(baz)?/', function ($matches) { - assertType('array{0: array{string, int<0, max>}, 1?: array{non-empty-string, int<0, max>}, 2?: array{non-empty-string, int<0, max>}, 3?: array{non-empty-string, int<0, max>}}', $matches); + assertType("array{0: array{string, int<0, max>}, 1?: array{'foo', int<0, max>}, 2?: array{'bar', int<0, max>}, 3?: array{'baz', int<0, max>}}", $matches); return ''; }, $s, @@ -36,7 +36,7 @@ function (string $s): void { preg_replace_callback( '/(foo)?(bar)?(baz)?/', function ($matches) { - assertType('array{array{string|null, int<-1, max>}, array{non-empty-string|null, int<-1, max>}, array{non-empty-string|null, int<-1, max>}, array{non-empty-string|null, int<-1, max>}}', $matches); + assertType("array{array{string|null, int<-1, max>}, array{'foo'|null, int<-1, max>}, array{'bar'|null, int<-1, max>}, array{'baz'|null, int<-1, max>}}", $matches); return ''; }, $s,