Skip to content

Commit

Permalink
implemented str_contains FunctionTypeSpecifyingExtension
Browse files Browse the repository at this point in the history
  • Loading branch information
clxmstaab committed Mar 13, 2022
1 parent 1911a92 commit a6a4fed
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 0 deletions.
5 changes: 5 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,11 @@ services:
tags:
- phpstan.dynamicStaticMethodThrowTypeExtension

-
class: PHPStan\Type\Php\StrContainingTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension

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

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierAwareExtension;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\Accessory\AccessoryLiteralStringType;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\Accessory\AccessoryNumericStringType;
use PHPStan\Type\FunctionTypeSpecifyingExtension;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\StringType;
use function count;
use function in_array;
use function strtolower;

final class StrContainingTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension
{

/** @var string[] */
private array $strContainingFunctions = [
'str_contains',
'str_starts_with',
'str_ends_with',
];

private TypeSpecifier $typeSpecifier;

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

public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool
{
return in_array(strtolower($functionReflection->getName()), $this->strContainingFunctions, true)
&& $context->true();
}

public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
$args = $node->getArgs();

if (count($args) >= 2) {
$haystackType = $scope->getType($args[0]->value);
$needleType = $scope->getType($args[1]->value);

if ($needleType->isNonEmptyString()->yes() && $haystackType->isString()->yes()) {
$accessories = [
new StringType(),
new AccessoryNonEmptyStringType(),
];

if ($haystackType->isLiteralString()->yes()) {
$accessories[] = new AccessoryLiteralStringType();
}
if ($haystackType->isNumericString()->yes()) {
$accessories[] = new AccessoryNumericStringType();
}

return $this->typeSpecifier->create(
$args[0]->value,
new IntersectionType($accessories),
$context,
false,
$scope,
);
}
}

return new SpecifiedTypes();
}

}
2 changes: 2 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,8 @@ public function dataFileAsserts(): iterable
}

yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6584.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-string-str-contains.php');
}

/**
Expand Down
60 changes: 60 additions & 0 deletions tests/PHPStan/Analyser/data/non-empty-string-str-contains.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace NonEmptyStringStrContains;

use function PHPStan\Testing\assertType;

class Foo {
/**
* @param non-empty-string $nonES
* @param numeric-string $numS
* @param literal-string $literalS
* @param non-empty-string&numeric-string $nonEAndNumericS
*/
public function sayHello(string $s, string $s2, $nonES, $numS, $literalS, $nonEAndNumericS, int $i): void
{
if (str_contains($s, ':')) {
assertType('non-empty-string', $s);
}
assertType('string', $s);

if (str_contains($s, $s2)) {
assertType('string', $s);
}

if (str_contains($s, $nonES)) {
assertType('non-empty-string', $s);
}

if (str_contains($s, $numS)) {
assertType('non-empty-string', $s);
}

if (str_contains($s, $literalS)) {
assertType('string', $s);
}

if (str_contains($s, $nonEAndNumericS)) {
assertType('non-empty-string', $s);
}
if (str_contains($numS, $nonEAndNumericS)) {
assertType('non-empty-string&numeric-string', $numS);
}

if (str_contains($i, $s2)) {
assertType('int', $i);
}
}

public function variants(string $s) {
if (str_starts_with($s, ':')) {
assertType('non-empty-string', $s);
}
assertType('string', $s);

if (str_ends_with($s, ':')) {
assertType('non-empty-string', $s);
}
assertType('string', $s);
}
}

0 comments on commit a6a4fed

Please sign in to comment.