Skip to content

Commit

Permalink
Merge branch refs/heads/1.11.x into 1.12.x
Browse files Browse the repository at this point in the history
  • Loading branch information
phpstan-bot authored Aug 8, 2024
2 parents 6d58150 + 427a319 commit 06bd1ca
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 16 deletions.
19 changes: 11 additions & 8 deletions src/Type/Php/RegexArrayShapeMatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -394,15 +394,19 @@ private function isGroupOptional(RegexCapturingGroup $captureGroup, TrinaryLogic
private function createGroupValueType(RegexCapturingGroup $captureGroup, TrinaryLogic $wasMatched, int $flags, bool $isTrailingOptional, bool $isLastGroup, bool $matchesAll): Type
{
if ($matchesAll) {
$groupValueType = $this->getValueType($captureGroup->getType(), $flags, $matchesAll);

if (!$isTrailingOptional && $this->containsUnmatchedAsNull($flags, $matchesAll) && !$captureGroup->isOptional()) {
if (!$this->containsSetOrder($flags) && !$this->containsUnmatchedAsNull($flags, $matchesAll) && $captureGroup->isOptional()) {
$groupValueType = $this->getValueType(
TypeCombinator::union($captureGroup->getType(), new ConstantStringType('')),
$flags,
$matchesAll,
);
$groupValueType = TypeCombinator::removeNull($groupValueType);
} else {
$groupValueType = $this->getValueType($captureGroup->getType(), $flags, $matchesAll);
}

if (!$this->containsSetOrder($flags) && !$this->containsUnmatchedAsNull($flags, $matchesAll) && $captureGroup->isOptional()) {
if (!$isTrailingOptional && $this->containsUnmatchedAsNull($flags, $matchesAll) && !$captureGroup->isOptional()) {
$groupValueType = TypeCombinator::removeNull($groupValueType);
$groupValueType = TypeCombinator::union($groupValueType, new ConstantStringType(''));
}

if ($this->containsPatternOrder($flags)) {
Expand Down Expand Up @@ -471,11 +475,10 @@ private function getValueType(Type $baseType, int $flags, bool $matchesAll): Typ
{
$valueType = $baseType;

$offsetType = IntegerRangeType::fromInterval(0, null);
// unmatched groups return -1 as offset
$offsetType = IntegerRangeType::fromInterval(-1, null);
if ($this->containsUnmatchedAsNull($flags, $matchesAll)) {
$valueType = TypeCombinator::addNull($valueType);
// unmatched groups return -1 as offset
$offsetType = IntegerRangeType::fromInterval(-1, null);
}

if ($this->containsOffsetCapture($flags)) {
Expand Down
18 changes: 15 additions & 3 deletions tests/PHPStan/Analyser/nsrt/preg_match_all_shapes.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ function (string $size): void {

function (string $size): void {
if (preg_match_all('/ab(?P<num>\d+)(?P<suffix>ab)?/', $size, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
assertType("list<array{0: array{string, int<0, max>}, 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);
assertType("list<array{0: array{string, int<-1, max>}, num: array{numeric-string, int<-1, max>}, 1: array{numeric-string, int<-1, max>}, suffix?: array{'ab', int<-1, max>}, 2?: array{'ab', int<-1, max>}}>", $matches);
}
};

function (string $size): void {
if (preg_match_all('/ab(?P<num>\d+)(?P<suffix>ab)?/', $size, $matches, PREG_PATTERN_ORDER|PREG_OFFSET_CAPTURE)) {
assertType("array{0: list<array{string, int<0, max>}>, num: list<array{numeric-string, int<0, max>}>, 1: list<array{numeric-string, int<0, max>}>, suffix: list<''|array{'ab', int<0, max>}>, 2: list<''|array{'ab', int<0, max>}>}", $matches);
assertType("array{0: list<array{string, int<-1, max>}>, num: list<array{numeric-string, int<-1, max>}>, 1: list<array{numeric-string, int<-1, max>}>, suffix: list<array{''|'ab', int<-1, max>}>, 2: list<array{''|'ab', int<-1, max>}>}", $matches);
}
};

Expand All @@ -142,7 +142,7 @@ public function sayHello(string $content): void
return;
}

assertType('array{list<array{string, int<0, max>}>}', $matches);
assertType('array{list<array{string, int<-1, max>}>}', $matches);
}

public function sayFoo(string $content): void
Expand All @@ -162,4 +162,16 @@ public function sayBar(string $content): void

assertType('array{list<string>}', $matches);
}

function doFoobar(string $s): void {
if (preg_match_all('/(foo)?(bar)?(baz)?/', $s, $matches, PREG_OFFSET_CAPTURE)) {
assertType("array{list<array{string, int<-1, max>}>, list<array{''|'foo', int<-1, max>}>, list<array{''|'bar', int<-1, max>}>, list<array{''|'baz', int<-1, max>}>}", $matches);
}
}

function doFoobarNull(string $s): void {
if (preg_match_all('/(foo)?(bar)?(baz)?/', $s, $matches, PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL)) {
assertType("array{list<array{string|null, int<-1, max>}>, list<array{'foo'|null, int<-1, max>}>, list<array{'bar'|null, int<-1, max>}>, list<array{'baz'|null, int<-1, max>}>}", $matches);
}
}
}
8 changes: 4 additions & 4 deletions tests/PHPStan/Analyser/nsrt/preg_match_shapes.php
Original file line number Diff line number Diff line change
Expand Up @@ -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{'foo', int<0, max>}, array{'bar', int<0, max>}, array{'baz', int<0, max>}}", $matches);
assertType("array{array{string, int<-1, max>}, array{'foo', int<-1, max>}, array{'bar', int<-1, max>}, array{'baz', int<-1, 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);
assertType("array{}|array{array{string, int<-1, max>}, array{'foo', int<-1, max>}, array{'bar', int<-1, max>}, array{'baz', int<-1, max>}}", $matches);
}

function doUnknownFlags(string $s, int $flags): void {
Expand Down Expand Up @@ -652,14 +652,14 @@ function (string $s): void {
function (string $value): void
{
if (preg_match('/^(x)*$/', $value, $matches, PREG_OFFSET_CAPTURE)) {
assertType("array{0: array{string, int<0, max>}, 1?: array{non-empty-string, int<0, max>}}", $matches);
assertType("array{0: array{string, int<-1, max>}, 1?: array{non-empty-string, int<-1, max>}}", $matches);
}
};

function (string $value): void
{
if (preg_match('/^(?:(x)|(y))*$/', $value, $matches, PREG_OFFSET_CAPTURE)) {
assertType("array{0: array{string, int<0, max>}, 1?: array{non-empty-string, int<0, max>}, 2?: array{non-empty-string, int<0, max>}}", $matches);
assertType("array{0: array{string, int<-1, max>}, 1?: array{non-empty-string, int<-1, max>}, 2?: array{non-empty-string, int<-1, max>}}", $matches);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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{''|'foo', int<0, max>}, 2?: array{''|'bar', int<0, max>}, 3?: array{'baz', int<0, max>}}", $matches);
assertType("array{0: array{string, int<-1, max>}, 1?: array{''|'foo', int<-1, max>}, 2?: array{''|'bar', int<-1, max>}, 3?: array{'baz', int<-1, max>}}", $matches);
return '';
},
$s,
Expand Down

0 comments on commit 06bd1ca

Please sign in to comment.