Skip to content

Commit 14a68a9

Browse files
committed
Understand the exact array shape coming from Nette\Utils\Strings::matchAll() based on pattern
1 parent 5f97dfa commit 14a68a9

File tree

4 files changed

+76
-1
lines changed

4 files changed

+76
-1
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
],
88
"require": {
99
"php": "^7.2 || ^8.0",
10-
"phpstan/phpstan": "^1.11.8"
10+
"phpstan/phpstan": "^1.11.9"
1111
},
1212
"conflict": {
1313
"nette/application": "<2.3.0",

extension.neon

+5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ parameters:
5555
conditionalTags:
5656
PHPStan\Type\Nette\StringsMatchDynamicReturnTypeExtension:
5757
phpstan.broker.dynamicStaticMethodReturnTypeExtension: %featureToggles.narrowPregMatches%
58+
PHPStan\Type\Nette\StringsMatchAllDynamicReturnTypeExtension:
59+
phpstan.broker.dynamicStaticMethodReturnTypeExtension: %featureToggles.narrowPregMatches%
5860

5961
services:
6062
-
@@ -119,5 +121,8 @@ services:
119121
tags:
120122
- phpstan.properties.readWriteExtension
121123

124+
-
125+
class: PHPStan\Type\Nette\StringsMatchAllDynamicReturnTypeExtension
126+
122127
-
123128
class: PHPStan\Type\Nette\StringsMatchDynamicReturnTypeExtension
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Nette;
4+
5+
use Nette\Utils\Strings;
6+
use PhpParser\Node\Expr\StaticCall;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Reflection\MethodReflection;
9+
use PHPStan\TrinaryLogic;
10+
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
11+
use PHPStan\Type\NullType;
12+
use PHPStan\Type\Php\RegexArrayShapeMatcher;
13+
use PHPStan\Type\Type;
14+
use PHPStan\Type\TypeCombinator;
15+
16+
class StringsMatchAllDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
17+
{
18+
19+
/** @var RegexArrayShapeMatcher */
20+
private $regexArrayShapeMatcher;
21+
22+
public function __construct(RegexArrayShapeMatcher $regexArrayShapeMatcher)
23+
{
24+
$this->regexArrayShapeMatcher = $regexArrayShapeMatcher;
25+
}
26+
27+
public function getClass(): string
28+
{
29+
return Strings::class;
30+
}
31+
32+
public function isStaticMethodSupported(MethodReflection $methodReflection): bool
33+
{
34+
return $methodReflection->getName() === 'matchAll';
35+
}
36+
37+
public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type
38+
{
39+
$args = $methodCall->getArgs();
40+
$patternArg = $args[1] ?? null;
41+
if ($patternArg === null) {
42+
return null;
43+
}
44+
45+
$flagsArg = $args[2] ?? null;
46+
$flagsType = null;
47+
if ($flagsArg !== null) {
48+
$flagsType = $scope->getType($flagsArg->value);
49+
}
50+
51+
$arrayShape = $this->regexArrayShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope);
52+
if ($arrayShape === null) {
53+
return null;
54+
}
55+
56+
return TypeCombinator::union($arrayShape, new NullType());
57+
}
58+
59+
}

tests/Type/Nette/data/strings-match.php

+11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Nette\Utils\Strings;
66
use function PHPStan\Testing\assertType;
77
use const PREG_OFFSET_CAPTURE;
8+
use const PREG_SET_ORDER;
89

910
function (string $s): void {
1011
$result = Strings::match($s, '/%env\((.*)\:.*\)%/U');
@@ -22,3 +23,13 @@ function (string $s): void {
2223
$result = Strings::match($s, '/(foo)(bar)'. preg_quote($s) .'(baz)/');
2324
assertType('array{string, non-empty-string, non-empty-string, non-empty-string}|null', $result);
2425
};
26+
27+
function (string $s): void {
28+
$result = Strings::matchAll($s, '/ab(?P<num>\d+)(?P<suffix>ab)?/', PREG_SET_ORDER);
29+
assertType("list<array{0: string, num: numeric-string, 1: numeric-string, suffix?: non-empty-string, 2?: non-empty-string}>|null", $result);
30+
};
31+
32+
function (string $s): void {
33+
$result = Strings::matchAll($s, '/ab(?P<num>\d+)(?P<suffix>ab)?/', PREG_PATTERN_ORDER);
34+
assertType("array{0: list<string>, num: list<numeric-string>, 1: list<numeric-string>, suffix: list<string>, 2: list<string>}|null", $result);
35+
};

0 commit comments

Comments
 (0)