Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ services:
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension

-
class: PHPStan\Type\Nette\StringsMatchTypeSpecifiyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension

-
class: PHPStan\Type\Nette\StringsReplaceCallbackClosureTypeExtension
tags:
Expand Down
70 changes: 70 additions & 0 deletions src/Type/Nette/StringsMatchTypeSpecifiyingExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Nette;

use Nette\Utils\Strings;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierAwareExtension;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\StaticMethodTypeSpecifyingExtension;
use function in_array;

class StringsMatchTypeSpecifiyingExtension implements StaticMethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
{

private RegexArrayShapeMatcher $regexArrayShapeMatcher;

private TypeSpecifier $typeSpecifier;

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

public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
{
$this->typeSpecifier = $typeSpecifier;
}

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

public function isStaticMethodSupported(MethodReflection $staticMethodReflection, StaticCall $node, TypeSpecifierContext $context): bool
{
return $context->true() && in_array($staticMethodReflection->getName(), ['match', 'matchAll'], true);
}

public function specifyTypes(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$args = $node->getArgs();
$subjectArg = $args[0] ?? null;
$patternArg = $args[1] ?? null;

$subjectTypes = new SpecifiedTypes();
if ($patternArg === null || $subjectArg === null) {
return $subjectTypes;
}

if ($scope->getType($subjectArg->value)->isString()->yes()) {
$subjectType = $this->regexArrayShapeMatcher->matchSubjectExpr($patternArg->value, $scope);
if ($subjectType !== null) {
$subjectTypes = $this->typeSpecifier->create(
$subjectArg->value,
$subjectType,
$context,
$scope,
)->setRootExpr($node);
}
}

return $subjectTypes;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@

use PHPStan\Testing\TypeInferenceTestCase;

class StringsMatchDynamicReturnTypeExtensionTest extends TypeInferenceTestCase
class StringsMatchTypeInferenceExtensionTest extends TypeInferenceTestCase
{

public function dataFileAsserts(): iterable
{
yield from self::gatherAssertTypes(__DIR__ . '/data/strings-match.php');
yield from self::gatherAssertTypes(__DIR__ . '/data/strings-match-74.php');
yield from self::gatherAssertTypes(__DIR__ . '/data/strings-match-subject.php');
}

/**
Expand Down
23 changes: 23 additions & 0 deletions tests/Type/Nette/data/strings-match-subject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace StringsMatchSubject;

use Nette\Utils\Strings;
use function PHPStan\Testing\assertType;

function (string $s): void {
if (Strings::match($s, '/foo/')) {
assertType("non-falsy-string", $s);
} else {
assertType("string", $s);
}
assertType("string", $s);

$matches = Strings::matchAll($s, '/foo/');
if (count($matches) !== 0) {
assertType("non-falsy-string", $s);
} else {
assertType("string", $s);
}
assertType("string", $s);
};
Loading