Skip to content

Commit

Permalink
Understand the exact array shape coming from `Nette\Utils\Strings::ma…
Browse files Browse the repository at this point in the history
…tchAll()` based on pattern
  • Loading branch information
ondrejmirtes committed Aug 1, 2024
1 parent 5f97dfa commit 14a68a9
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 1 deletion.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
],
"require": {
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.11.8"
"phpstan/phpstan": "^1.11.9"
},
"conflict": {
"nette/application": "<2.3.0",
Expand Down
5 changes: 5 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ parameters:
conditionalTags:
PHPStan\Type\Nette\StringsMatchDynamicReturnTypeExtension:
phpstan.broker.dynamicStaticMethodReturnTypeExtension: %featureToggles.narrowPregMatches%
PHPStan\Type\Nette\StringsMatchAllDynamicReturnTypeExtension:
phpstan.broker.dynamicStaticMethodReturnTypeExtension: %featureToggles.narrowPregMatches%

services:
-
Expand Down Expand Up @@ -119,5 +121,8 @@ services:
tags:
- phpstan.properties.readWriteExtension

-
class: PHPStan\Type\Nette\StringsMatchAllDynamicReturnTypeExtension

-
class: PHPStan\Type\Nette\StringsMatchDynamicReturnTypeExtension
59 changes: 59 additions & 0 deletions src/Type/Nette/StringsMatchAllDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Nette;

use Nette\Utils\Strings;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
use PHPStan\Type\NullType;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

class StringsMatchAllDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
{

/** @var RegexArrayShapeMatcher */
private $regexArrayShapeMatcher;

public function __construct(RegexArrayShapeMatcher $regexArrayShapeMatcher)
{
$this->regexArrayShapeMatcher = $regexArrayShapeMatcher;
}

public function getClass(): string
{
return Strings::class;
}

public function isStaticMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'matchAll';
}

public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type
{
$args = $methodCall->getArgs();
$patternArg = $args[1] ?? null;
if ($patternArg === null) {
return null;
}

$flagsArg = $args[2] ?? null;
$flagsType = null;
if ($flagsArg !== null) {
$flagsType = $scope->getType($flagsArg->value);
}

$arrayShape = $this->regexArrayShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope);
if ($arrayShape === null) {
return null;
}

return TypeCombinator::union($arrayShape, new NullType());
}

}
11 changes: 11 additions & 0 deletions tests/Type/Nette/data/strings-match.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Nette\Utils\Strings;
use function PHPStan\Testing\assertType;
use const PREG_OFFSET_CAPTURE;
use const PREG_SET_ORDER;

function (string $s): void {
$result = Strings::match($s, '/%env\((.*)\:.*\)%/U');
Expand All @@ -22,3 +23,13 @@ function (string $s): void {
$result = Strings::match($s, '/(foo)(bar)'. preg_quote($s) .'(baz)/');
assertType('array{string, non-empty-string, non-empty-string, non-empty-string}|null', $result);
};

function (string $s): void {
$result = Strings::matchAll($s, '/ab(?P<num>\d+)(?P<suffix>ab)?/', PREG_SET_ORDER);
assertType("list<array{0: string, num: numeric-string, 1: numeric-string, suffix?: non-empty-string, 2?: non-empty-string}>|null", $result);
};

function (string $s): void {
$result = Strings::matchAll($s, '/ab(?P<num>\d+)(?P<suffix>ab)?/', PREG_PATTERN_ORDER);
assertType("array{0: list<string>, num: list<numeric-string>, 1: list<numeric-string>, suffix: list<string>, 2: list<string>}|null", $result);
};

2 comments on commit 14a68a9

@kukulich
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ondrejmirtes https://github.com/nette/utils/blob/master/src/Utils/Strings.php#L602

Ta metoda by měla vždy vracet pole, ale teď PHPStan reportuje, že může vracet null.

@ondrejmirtes
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kukulich Díky, zatemnění mysli :) Oprava vydána.

Please # to comment.