From d6988a084c6acd6b77e76f625a09c3592604de74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bronis=C5=82aw=20Bia=C5=82ek?= Date: Mon, 6 Jun 2022 19:02:45 +0200 Subject: [PATCH] Fix phpstan removing null on iterable value (#58) --- .github/workflows/main.yml | 5 ++- composer.json | 5 ++- .../IterableValueNotEmptyExtension.php | 15 ++++++-- src/Stringable/ToStringValue.php | 3 +- tests/GenericCases/NotEmpty.php | 2 +- tests/GenericCases/NotEmptyIterable.php | 34 +++++++++++++++++++ 6 files changed, 57 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index af44b43..3e5800a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,10 @@ jobs: run: ./bin/phpspec run - name: "PHPStan" - run: ./bin/phpstan analyze -l 8 src examples tests/GenericCases + run: ./bin/phpstan analyze -l 8 src + + - name: "PHPStan" + run: ./bin/phpstan analyze -l 9 tests/GenericCases examples - name: "PHPUnit" run: ./bin/phpunit tests diff --git a/composer.json b/composer.json index 57a3469..2dc2280 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,10 @@ } }, "config": { - "bin-dir": "bin" + "bin-dir": "bin", + "allow-plugins": { + "phpstan/extension-installer": true + } }, "extra": { "phpstan": { diff --git a/src/PHPStan/IterableValueNotEmptyExtension.php b/src/PHPStan/IterableValueNotEmptyExtension.php index 87a9e63..aaa4b0c 100644 --- a/src/PHPStan/IterableValueNotEmptyExtension.php +++ b/src/PHPStan/IterableValueNotEmptyExtension.php @@ -7,12 +7,12 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicMethodReturnTypeExtension; +use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; final class IterableValueNotEmptyExtension implements DynamicMethodReturnTypeExtension { - use NotEmptyTypeRemover; - public function getClass(): string { return IterableValue::class; @@ -30,4 +30,15 @@ public function getTypeFromMethodCall( ): Type { return $this->removeNull($scope->getType($methodCall->var)); } + + private function removeNull(Type $type): Type + { + if (!$type instanceof GenericObjectType) { + return $type; + } + + $types = $type->getTypes(); + + return new GenericObjectType($type->getClassName(), [$types[0], TypeCombinator::removeNull($types[1])]); + } } diff --git a/src/Stringable/ToStringValue.php b/src/Stringable/ToStringValue.php index e19081c..c0a405a 100644 --- a/src/Stringable/ToStringValue.php +++ b/src/Stringable/ToStringValue.php @@ -9,8 +9,7 @@ final class ToStringValue { - /** @param mixed $string */ - public function __invoke($string): StringValue + public function __invoke(mixed $string): StringValue { if (is_scalar($string)) { return Wrap::string((string)$string); diff --git a/tests/GenericCases/NotEmpty.php b/tests/GenericCases/NotEmpty.php index 4cddab7..fac020c 100644 --- a/tests/GenericCases/NotEmpty.php +++ b/tests/GenericCases/NotEmpty.php @@ -51,7 +51,7 @@ function iterableValue3(): iterable ->notEmpty(); } -/** @return iterable */ +/** @return iterable */ function iterableValue4(): iterable { $numbersOrNulls = Wrap::iterable(['a' => 1, 'b' => 2, 'c' => 3, 'd' => null]); diff --git a/tests/GenericCases/NotEmptyIterable.php b/tests/GenericCases/NotEmptyIterable.php index 97e28a8..8265a00 100644 --- a/tests/GenericCases/NotEmptyIterable.php +++ b/tests/GenericCases/NotEmptyIterable.php @@ -2,7 +2,9 @@ namespace tests\GW\Value\GenericCases; +use GW\Value\IterableValue; use GW\Value\Wrap; +use function random_int; class Id {} @@ -39,3 +41,35 @@ function iterableValue5(): void iterate($chunks); } + +class RequireIterable +{ + /** @param iterable $it */ + public function functionRequireIterable(iterable $it): void + { + } + /** @param iterable $it */ + public function functionRequireIterable2(iterable $it): void + { + } + /** @param IterableValue $it */ + public function functionRequireIterable3(IterableValue $it): void + { + } + /** @param array $it */ + public function functionRequireArray(array $it): void + { + } +} + +$ri = new RequireIterable(); +$ri->functionRequireIterable(Wrap::iterable([null, 1])->filterEmpty()); +$ri->functionRequireIterable2(Wrap::iterable([null, 1])->filterEmpty()); +$ri->functionRequireIterable3(Wrap::iterable([null, 1])->filterEmpty()); + +$ri->functionRequireIterable(Wrap::iterable([2, 1, 3])->map(fn(int $x): ?int => random_int(0, 2) ?: null)->filterEmpty()); +$ri->functionRequireIterable2(Wrap::iterable([null, 1])->filterEmpty()); +$ri->functionRequireIterable3(Wrap::iterable([null, 1])->filterEmpty()); + +$ia = Wrap::iterable([2, 1, 3])->map(fn(int $x): ?Foo => random_int(0, 2) ? new Foo() : null)->filterEmpty()->toArray(); +$ri->functionRequireArray($ia);