From cafbcadf5f1a0845c34c4c895727b150af503d93 Mon Sep 17 00:00:00 2001 From: Jan Machala Date: Tue, 16 Nov 2021 10:15:32 +0100 Subject: [PATCH 1/4] Remove check of fromTraversable Since we droped it in favor of fromIterable --- composer.json | 2 +- extension.neon | 2 +- tests/Bonami/Collection/Phpstan/LazyListTest.php | 11 ----------- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index 2582bd0..341cc7c 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "phpunit/phpunit": "^9.4.2", "slevomat/coding-standard": "^6.4.1", "squizlabs/php_codesniffer": "^3.5.0", - "bonami/collections": "^0.3" + "bonami/collections": "dev-jm-phpstan-1.0" }, "config": { "bin-dir": "bin" diff --git a/extension.neon b/extension.neon index 50c9956..2c3d3c2 100644 --- a/extension.neon +++ b/extension.neon @@ -21,7 +21,7 @@ services: - phpstan.broker.dynamicMethodReturnTypeExtension - class: Bonami\Collection\Phpstan\LateStaticBindingStaticMethodReturnTypeExtension - factory: Bonami\Collection\Phpstan\LateStaticBindingStaticMethodReturnTypeExtension::forMethods('Bonami\Collection\LazyList', ['fill', 'fromEmpty', 'fromArray', 'fromTraversable', 'fromIterable', 'of']) + factory: Bonami\Collection\Phpstan\LateStaticBindingStaticMethodReturnTypeExtension::forMethods('Bonami\Collection\LazyList', ['fill', 'fromEmpty', 'fromArray', 'fromIterable', 'of']) tags: - phpstan.broker.dynamicStaticMethodReturnTypeExtension - diff --git a/tests/Bonami/Collection/Phpstan/LazyListTest.php b/tests/Bonami/Collection/Phpstan/LazyListTest.php index 16bfe8f..0e34a83 100644 --- a/tests/Bonami/Collection/Phpstan/LazyListTest.php +++ b/tests/Bonami/Collection/Phpstan/LazyListTest.php @@ -44,17 +44,6 @@ public function testFromArrayReturnType(): void self::assertInstanceOf(FooLazyList::class, $concreteList); } - public function testFromTraversableReturnType(): void - { - $genericList = LazyList::fromTraversable(new ArrayIterator([new Foo()])); - $this->requireLazyListOfFoo($genericList); - self::assertInstanceOf(LazyList::class, $genericList); - - $concreteList = FooLazyList::fromTraversable(new ArrayIterator([new Foo()])); - $this->requireFooList($concreteList); - self::assertInstanceOf(FooLazyList::class, $concreteList); - } - public function testFromIterableReturnType(): void { $genericList = LazyList::fromIterable([new Foo()]); From f41059f88e389c8fedfd722c1f72f9e465a6774e Mon Sep 17 00:00:00 2001 From: Jan Machala Date: Tue, 16 Nov 2021 10:16:01 +0100 Subject: [PATCH 2/4] Increase phpstan level --- phpstan.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index 43916cf..c6f920b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,7 +3,7 @@ includes: parameters: inferPrivatePropertyTypeFromConstructor: true reportUnmatchedIgnoredErrors: true - level: 8 + level: 9 paths: - src/ - tests/ From 5dfa8be9590be2b5c561e5bc4bba3e8a669241b9 Mon Sep 17 00:00:00 2001 From: Jan Machala Date: Tue, 16 Nov 2021 10:24:44 +0100 Subject: [PATCH 3/4] Typehint properties explicitely --- phpstan.neon | 1 - .../Collection/Phpstan/GroupByMethodReturnTypeExtension.php | 1 + .../Phpstan/LateStaticBindingMethodReturnTypeExtension.php | 1 + .../Phpstan/LateStaticBindingStaticMethodReturnTypeExtension.php | 1 + 4 files changed, 3 insertions(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index c6f920b..9cc4f44 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,7 +1,6 @@ includes: - extension.neon parameters: - inferPrivatePropertyTypeFromConstructor: true reportUnmatchedIgnoredErrors: true level: 9 paths: diff --git a/src/Bonami/Collection/Phpstan/GroupByMethodReturnTypeExtension.php b/src/Bonami/Collection/Phpstan/GroupByMethodReturnTypeExtension.php index 20bdae1..913e840 100644 --- a/src/Bonami/Collection/Phpstan/GroupByMethodReturnTypeExtension.php +++ b/src/Bonami/Collection/Phpstan/GroupByMethodReturnTypeExtension.php @@ -18,6 +18,7 @@ class GroupByMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension { + /** @var string */ private $class; public function __construct(string $class) diff --git a/src/Bonami/Collection/Phpstan/LateStaticBindingMethodReturnTypeExtension.php b/src/Bonami/Collection/Phpstan/LateStaticBindingMethodReturnTypeExtension.php index 98b2903..3162ce4 100644 --- a/src/Bonami/Collection/Phpstan/LateStaticBindingMethodReturnTypeExtension.php +++ b/src/Bonami/Collection/Phpstan/LateStaticBindingMethodReturnTypeExtension.php @@ -12,6 +12,7 @@ class LateStaticBindingMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension { + /** @var string */ private $class; /** @var array */ diff --git a/src/Bonami/Collection/Phpstan/LateStaticBindingStaticMethodReturnTypeExtension.php b/src/Bonami/Collection/Phpstan/LateStaticBindingStaticMethodReturnTypeExtension.php index 5c77507..de94871 100644 --- a/src/Bonami/Collection/Phpstan/LateStaticBindingStaticMethodReturnTypeExtension.php +++ b/src/Bonami/Collection/Phpstan/LateStaticBindingStaticMethodReturnTypeExtension.php @@ -15,6 +15,7 @@ class LateStaticBindingStaticMethodReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { + /** @var string */ private $class; /** @var array */ From 854a8eba694e43ecbd177499d0aee759f678818f Mon Sep 17 00:00:00 2001 From: Jan Machala Date: Fri, 19 Nov 2021 17:12:45 +0100 Subject: [PATCH 4/4] Remove nulls from type when withoutNulls is called This should help phpstan to detect, that when withoutNulls is called on ArrayList then resulting type is ArrayList --- extension.neon | 4 ++ ...rayListWithoutNullsReturnTypeExtension.php | 54 +++++++++++++++++++ .../Collection/Phpstan/ArrayListTest.php | 8 ++- 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 src/Bonami/Collection/Phpstan/ArrayListWithoutNullsReturnTypeExtension.php diff --git a/extension.neon b/extension.neon index 2c3d3c2..8b236ae 100644 --- a/extension.neon +++ b/extension.neon @@ -1,4 +1,8 @@ services: + - + class: Bonami\Collection\Phpstan\ArrayListWithoutNullsReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension - class: Bonami\Collection\Phpstan\LateStaticBindingStaticMethodReturnTypeExtension factory: Bonami\Collection\Phpstan\LateStaticBindingStaticMethodReturnTypeExtension::forMethods('Bonami\Collection\ArrayList', ['fromEmpty', 'of', 'fill', 'fromIterable']) diff --git a/src/Bonami/Collection/Phpstan/ArrayListWithoutNullsReturnTypeExtension.php b/src/Bonami/Collection/Phpstan/ArrayListWithoutNullsReturnTypeExtension.php new file mode 100644 index 0000000..ebee986 --- /dev/null +++ b/src/Bonami/Collection/Phpstan/ArrayListWithoutNullsReturnTypeExtension.php @@ -0,0 +1,54 @@ +getName() === 'withoutNulls'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type { + $type = $scope->getType($methodCall->var); + + $declaringClassReflection = $methodReflection->getDeclaringClass(); + $referencedClasses = $type->getReferencedClasses(); + + if (in_array(ArrayList::class, $referencedClasses, true)) { + $types = $declaringClassReflection->typeMapToList( + $declaringClassReflection->getTemplateTypeMap()->resolveToBounds() + ); + + return new GenericObjectType( + $declaringClassReflection->getName(), + array_map(static function (Type $type) { + return TypeCombinator::removeNull($type); + }, $types) + ); + } + + return $type; + } +} diff --git a/tests/Bonami/Collection/Phpstan/ArrayListTest.php b/tests/Bonami/Collection/Phpstan/ArrayListTest.php index b8ab0ed..da05b87 100644 --- a/tests/Bonami/Collection/Phpstan/ArrayListTest.php +++ b/tests/Bonami/Collection/Phpstan/ArrayListTest.php @@ -143,11 +143,15 @@ public function testSliceReturnType(): void public function testWithoutNullsReturnType(): void { - $genericList = ArrayList::fromIterable([new Foo()])->withoutNulls(); + /** @var iterable $iterable */ + $iterable = [new Foo(), null]; + /** @var ArrayList $list */ + $list = ArrayList::fromIterable($iterable); + $genericList = $list->withoutNulls(); $this->requireArrayListOfFoo($genericList->withoutNulls()); self::assertInstanceOf(ArrayList::class, $genericList); - $concreteList = FooArrayList::fromIterable([new Foo()])->withoutNulls(); + $concreteList = FooArrayList::fromIterable($iterable)->withoutNulls(); $this->requireFooList($concreteList->withoutNulls()); self::assertInstanceOf(FooArrayList::class, $concreteList); }