Skip to content

Commit cba69ae

Browse files
committed
use extension
1 parent 010eb1d commit cba69ae

7 files changed

Lines changed: 149 additions & 19 deletions

File tree

extension.neon

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ parameters:
2929
- stubs/Utils/Html.stub
3030
- stubs/Utils/Paginator.stub
3131
- stubs/Utils/Random.stub
32-
- stubs/Utils/Strings.stub
3332
universalObjectCratesClasses:
3433
- Nette\Application\UI\ITemplate
3534
- Nette\Application\UI\Template
@@ -138,3 +137,13 @@ services:
138137
class: PHPStan\Type\Nette\StringsReplaceCallbackClosureTypeExtension
139138
tags:
140139
- phpstan.staticMethodParameterClosureTypeExtension
140+
141+
-
142+
class: PHPStan\Type\Nette\StringsLengthTypeSpecifiyingExtension
143+
tags:
144+
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
145+
146+
-
147+
class: PHPStan\Type\Nette\StringsLengthDynamicReturnTypeExtension
148+
tags:
149+
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Nette;
4+
5+
use Nette\Utils\Strings;
6+
use PhpParser\Node\Arg;
7+
use PhpParser\Node\Expr\StaticCall;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Analyser\SpecifiedTypes;
10+
use PHPStan\Reflection\MethodReflection;
11+
use PHPStan\TrinaryLogic;
12+
use PHPStan\Type\Constant\ConstantBooleanType;
13+
use PHPStan\Type\Constant\ConstantIntegerType;
14+
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
15+
use PHPStan\Type\IntegerRangeType;
16+
use PHPStan\Type\NullType;
17+
use PHPStan\Type\Php\RegexArrayShapeMatcher;
18+
use PHPStan\Type\Type;
19+
use PHPStan\Type\TypeCombinator;
20+
use function array_key_exists;
21+
use const PREG_OFFSET_CAPTURE;
22+
use const PREG_UNMATCHED_AS_NULL;
23+
24+
class StringsLengthDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
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() === 'length';
35+
}
36+
37+
public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type
38+
{
39+
$args = $methodCall->getArgs();
40+
$stringArg = $args[0] ?? null;
41+
42+
if ($stringArg === null) {
43+
return new SpecifiedTypes();
44+
}
45+
46+
$type = $scope->getType($stringArg->value);
47+
if ($type->isNonEmptyString()->yes()) {
48+
return IntegerRangeType::fromInterval(1, null);
49+
}
50+
51+
return null;
52+
}
53+
54+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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\Analyser\SpecifiedTypes;
9+
use PHPStan\Analyser\TypeSpecifier;
10+
use PHPStan\Analyser\TypeSpecifierAwareExtension;
11+
use PHPStan\Analyser\TypeSpecifierContext;
12+
use PHPStan\Reflection\MethodReflection;
13+
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
14+
use PHPStan\Type\IntersectionType;
15+
use PHPStan\Type\StaticMethodTypeSpecifyingExtension;
16+
use PHPStan\Type\StringType;
17+
18+
class StringsLengthTypeSpecifiyingExtension implements StaticMethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
19+
{
20+
21+
private TypeSpecifier $typeSpecifier;
22+
23+
public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
24+
{
25+
$this->typeSpecifier = $typeSpecifier;
26+
}
27+
28+
public function getClass(): string
29+
{
30+
return Strings::class;
31+
}
32+
33+
public function isStaticMethodSupported(MethodReflection $staticMethodReflection, StaticCall $node, TypeSpecifierContext $context): bool
34+
{
35+
return $context->true() && $staticMethodReflection->getName() === 'length';
36+
}
37+
38+
public function specifyTypes(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
39+
{
40+
$args = $node->getArgs();
41+
$stringArg = $args[0] ?? null;
42+
43+
if ($stringArg === null) {
44+
return new SpecifiedTypes();
45+
}
46+
47+
$type = $scope->getType($stringArg->value);
48+
if (!$type->isString()->yes()) {
49+
return new SpecifiedTypes();
50+
}
51+
52+
return $this->typeSpecifier->create(
53+
$stringArg->value,
54+
new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]),
55+
$context,
56+
$scope,
57+
);
58+
}
59+
60+
}

stubs/Utils/Strings.stub

Lines changed: 0 additions & 17 deletions
This file was deleted.

tests/Type/Nette/TypeInferenceTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function dataFileAsserts(): iterable
2727
yield from self::gatherAssertTypes(__DIR__ . '/data/multiplier.php');
2828
}
2929

30-
yield from $this->gatherAssertTypes(__DIR__ . '/data/strings.php');
30+
yield from $this->gatherAssertTypes(__DIR__ . '/data/strings-length.php');
3131
}
3232

3333
/**
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace StringsTypesNarrowing;
4+
5+
use Nette\Utils\Strings;
6+
use function PHPStan\Testing\assertType;
7+
8+
function doFoo(string $string) {
9+
assertType('string', $string);
10+
if (Strings::length($string)) {
11+
assertType('non-empty-string', $string);
12+
assertType('int<1, max>', Strings::length($string));
13+
} else {
14+
assertType('string', $string);
15+
assertType('0', Strings::length($string));
16+
}
17+
assertType('string', $string);
18+
assertType('int', Strings::length($string));
19+
20+
if (Strings::length($string) === 0) {
21+
assertType('string', $string);
22+
}
23+
assertType('string', $string);
24+
}

0 commit comments

Comments
 (0)