diff --git a/bin/NarrowingGenerator.php b/bin/NarrowingGenerator.php index 679ae09..345ba9e 100644 --- a/bin/NarrowingGenerator.php +++ b/bin/NarrowingGenerator.php @@ -202,6 +202,7 @@ public static function generateSetNarrowing(string $collectionContent, array $bl return self::generateNarrowing($collectionContent, 'Collection', [ ': Collection' => ': Set', 'Collection' => 'Set', + 'Collection<(E is null ? never : E)>' => 'Set<(E is null ? never : E)>', 'Collection' => 'Set', 'Collection' => 'Set', 'Collection' => 'Set', @@ -231,6 +232,7 @@ public static function generateImmutableSetNarrowing(string $immutableCollection return self::generateNarrowing($immutableCollectionContent, 'ImmutableCollection', [ ': ImmutableCollection' => ': ImmutableSet', 'ImmutableCollection' => 'ImmutableSet', + 'ImmutableCollection<(E is null ? never : E)>' => 'ImmutableSet<(E is null ? never : E)>', 'ImmutableCollection' => 'ImmutableSet', 'ImmutableCollection' => 'ImmutableSet', 'ImmutableCollection' => 'ImmutableSet', @@ -285,6 +287,7 @@ public static function generateListNarrowing(string $collectionContent, array $b return self::generateNarrowing($collectionContent, 'Collection', [ ': Collection' => ': ListInterface', 'Collection' => 'ListInterface', + 'Collection<(E is null ? never : E)>' => 'ListInterface<(E is null ? never : E)>', 'Collection' => 'ListInterface', 'Collection' => 'ListInterface', 'Collection' => 'ListInterface', @@ -314,6 +317,7 @@ public static function generateImmutableCollectionNarrowing(string $collectionCo return self::generateNarrowing($collectionContent, 'Collection', [ ': Collection' => ': ImmutableCollection', 'Collection' => 'ImmutableCollection', + 'Collection<(E is null ? never : E)>' => 'ImmutableCollection<(E is null ? never : E)>', 'Collection' => 'ImmutableCollection', 'Collection' => 'ImmutableCollection', 'Collection' => 'ImmutableCollection', @@ -340,6 +344,7 @@ public static function generateWritableCollectionTransformationNarrowing(string return self::generateNarrowing($collectionContent, 'Collection', [ ': Collection' => ': ImmutableCollection', 'Collection' => 'ImmutableCollection', + 'Collection<(E is null ? never : E)>' => 'ImmutableCollection<(E is null ? never : E)>', 'Collection' => 'ImmutableCollection', 'Collection' => 'ImmutableCollection', 'Collection' => 'ImmutableCollection', @@ -366,6 +371,7 @@ public static function generateWritableListTransformationNarrowing(string $colle return self::generateNarrowing($collectionContent, 'Collection', [ ': Collection' => ': ImmutableList', 'Collection' => 'ImmutableList', + 'Collection<(E is null ? never : E)>' => 'ImmutableList<(E is null ? never : E)>', 'Collection' => 'ImmutableList', 'Collection' => 'ImmutableList', 'Collection' => 'ImmutableList', @@ -392,6 +398,7 @@ public static function generateWritableSetTransformationNarrowing(string $collec return self::generateNarrowing($collectionContent, 'Collection', [ ': Collection' => ': ImmutableSet', 'Collection' => 'ImmutableSet', + 'Collection<(E is null ? never : E)>' => 'ImmutableSet<(E is null ? never : E)>', 'Collection' => 'ImmutableSet', 'Collection' => 'ImmutableSet', 'Collection' => 'ImmutableSet', @@ -419,6 +426,7 @@ public static function generateWritableMapTransformationNarrowing(string $mapCon ': Map' => ': ImmutableMap', 'Map' => 'ImmutableMap', 'Map' => 'ImmutableMap', + 'Map' => 'ImmutableMap', 'Map' => 'ImmutableMap', 'Map' => 'ImmutableMap', 'Map' => 'ImmutableMap', @@ -441,6 +449,7 @@ public static function generateImmutableListNarrowing(string $immutableCollectio return self::generateNarrowing($immutableCollectionContent, 'ImmutableCollection', [ ': ImmutableCollection' => ': ImmutableList', 'ImmutableCollection' => 'ImmutableList', + 'ImmutableCollection<(E is null ? never : E)>' => 'ImmutableList<(E is null ? never : E)>', 'ImmutableCollection' => 'ImmutableList', 'ImmutableCollection' => 'ImmutableList', 'ImmutableCollection' => 'ImmutableList', @@ -544,6 +553,7 @@ public static function generateImmutableMapNarrowing(string $mapContent, array $ ': Map' => ': ImmutableMap', 'Map' => 'ImmutableMap', 'Map' => 'ImmutableMap', + 'Map' => 'ImmutableMap', 'Map' => 'ImmutableMap', 'Map' => 'ImmutableMap', 'Map' => 'ImmutableMap', diff --git a/src/Collection.php b/src/Collection.php index 242cb4d..216bb3f 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -360,7 +360,7 @@ public function filter(Closure $predicate): Collection; /** * Filter non-null elements. * - * @return Collection + * @return Collection<(E is null ? never : E)> */ #[NoDiscard] public function filterNotNull(): Collection; diff --git a/src/ImmutableCollection.php b/src/ImmutableCollection.php index 53bec77..26ffeff 100644 --- a/src/ImmutableCollection.php +++ b/src/ImmutableCollection.php @@ -143,7 +143,7 @@ public function filter(Closure $predicate): ImmutableCollection; /** * Filter non-null elements. * - * @return ImmutableCollection + * @return ImmutableCollection<(E is null ? never : E)> */ #[NoDiscard] public function filterNotNull(): ImmutableCollection; diff --git a/src/List/ImmutableList.php b/src/List/ImmutableList.php index a0c3835..b1be945 100644 --- a/src/List/ImmutableList.php +++ b/src/List/ImmutableList.php @@ -203,7 +203,7 @@ public function filter(Closure $predicate): ImmutableList; /** * Filter non-null elements. * - * @return ImmutableList + * @return ImmutableList<(E is null ? never : E)> */ #[NoDiscard] public function filterNotNull(): ImmutableList; diff --git a/src/List/ListInterface.php b/src/List/ListInterface.php index 4f6e513..e02a18e 100644 --- a/src/List/ListInterface.php +++ b/src/List/ListInterface.php @@ -121,7 +121,7 @@ public function filter(Closure $predicate): ListInterface; /** * Filter non-null elements. * - * @return ListInterface + * @return ListInterface<(E is null ? never : E)> */ #[NoDiscard] public function filterNotNull(): ListInterface; diff --git a/src/List/ListLogic.php b/src/List/ListLogic.php index 627f8e8..2e939e9 100644 --- a/src/List/ListLogic.php +++ b/src/List/ListLogic.php @@ -179,9 +179,9 @@ public function filter(Closure $predicate): ImmutableList /** * {@inheritDoc} * - * @return ImmutableList + * @return ImmutableList<(E is null ? never : E)> */ - #[NoDiscard] + #[NoDiscard] // @phpstan-ignore conditionalType.subjectNotFound (in classes with a concrete E the conditional subject is already substituted) public function filterNotNull(): ImmutableList { return $this->newCollectionOf(new FilterOperation($this->store)->byValue(fn ($v) => $v !== null)); diff --git a/src/List/WritableList.php b/src/List/WritableList.php index 1f91022..29bc540 100644 --- a/src/List/WritableList.php +++ b/src/List/WritableList.php @@ -178,7 +178,7 @@ public function filter(Closure $predicate): ImmutableList; /** * Filter non-null elements. * - * @return ImmutableList + * @return ImmutableList<(E is null ? never : E)> */ #[NoDiscard] public function filterNotNull(): ImmutableList; diff --git a/src/Map/ImmutableMap.php b/src/Map/ImmutableMap.php index f573e7a..9c01972 100644 --- a/src/Map/ImmutableMap.php +++ b/src/Map/ImmutableMap.php @@ -235,7 +235,7 @@ public function filterValues(Closure $predicate): ImmutableMap; /** * Creates a new map excluding entries with null values. * - * @return ImmutableMap New map without null values. + * @return ImmutableMap New map without null values. */ #[NoDiscard] public function filterValuesNotNull(): ImmutableMap; diff --git a/src/Map/Map.php b/src/Map/Map.php index f50544b..82c96a7 100644 --- a/src/Map/Map.php +++ b/src/Map/Map.php @@ -198,7 +198,7 @@ public function filterValues(Closure $predicate): Map; /** * Creates a new map excluding entries with null values. * - * @return Map New map without null values. + * @return Map New map without null values. */ #[NoDiscard] public function filterValuesNotNull(): Map; diff --git a/src/Map/MapLogic.php b/src/Map/MapLogic.php index 81524e5..2bf3733 100644 --- a/src/Map/MapLogic.php +++ b/src/Map/MapLogic.php @@ -223,9 +223,9 @@ public function filterValues(Closure $predicate): ImmutableMap /** * {@inheritDoc} * - * @return ImmutableMap + * @return ImmutableMap */ - #[NoDiscard] + #[NoDiscard] // @phpstan-ignore conditionalType.subjectNotFound (in classes with a concrete V the conditional subject is already substituted) public function filterValuesNotNull(): ImmutableMap { return $this->newMapOf( diff --git a/src/Map/WritableMap.php b/src/Map/WritableMap.php index 85d758c..75b838d 100644 --- a/src/Map/WritableMap.php +++ b/src/Map/WritableMap.php @@ -234,7 +234,7 @@ public function filterValues(Closure $predicate): ImmutableMap; /** * Creates a new map excluding entries with null values. * - * @return ImmutableMap New map without null values. + * @return ImmutableMap New map without null values. */ #[NoDiscard] public function filterValuesNotNull(): ImmutableMap; diff --git a/src/Set/ImmutableSet.php b/src/Set/ImmutableSet.php index 47958a7..8b84250 100644 --- a/src/Set/ImmutableSet.php +++ b/src/Set/ImmutableSet.php @@ -153,7 +153,7 @@ public function filter(Closure $predicate): ImmutableSet; /** * Filter non-null elements. * - * @return ImmutableSet + * @return ImmutableSet<(E is null ? never : E)> */ #[NoDiscard] public function filterNotNull(): ImmutableSet; diff --git a/src/Set/Set.php b/src/Set/Set.php index 65df160..9328610 100644 --- a/src/Set/Set.php +++ b/src/Set/Set.php @@ -37,7 +37,7 @@ public function filter(Closure $predicate): Set; /** * Filter non-null elements. * - * @return Set + * @return Set<(E is null ? never : E)> */ #[NoDiscard] public function filterNotNull(): Set; diff --git a/src/Set/SetLogic.php b/src/Set/SetLogic.php index b48d77f..041409e 100644 --- a/src/Set/SetLogic.php +++ b/src/Set/SetLogic.php @@ -58,9 +58,9 @@ public function filter(Closure $predicate): ImmutableSet /** * {@inheritDoc} * - * @return ImmutableSet + * @return ImmutableSet<(E is null ? never : E)> */ - #[NoDiscard] + #[NoDiscard] // @phpstan-ignore conditionalType.subjectNotFound, conditionalType.alwaysFalse (in classes with a concrete or non-nullable E the conditional is already decided) public function filterNotNull(): ImmutableSet { return $this->newCollectionOf(new FilterOperation($this->store)->byValue(fn ($v) => $v !== null)); diff --git a/src/Set/WritableSet.php b/src/Set/WritableSet.php index 9dd8976..1000827 100644 --- a/src/Set/WritableSet.php +++ b/src/Set/WritableSet.php @@ -136,7 +136,7 @@ public function filter(Closure $predicate): ImmutableSet; /** * Filter non-null elements. * - * @return ImmutableSet + * @return ImmutableSet<(E is null ? never : E)> */ #[NoDiscard] public function filterNotNull(): ImmutableSet; diff --git a/src/WritableCollection.php b/src/WritableCollection.php index c3fbacf..b6ce9fc 100644 --- a/src/WritableCollection.php +++ b/src/WritableCollection.php @@ -136,7 +136,7 @@ public function filter(Closure $predicate): ImmutableCollection; /** * Filter non-null elements. * - * @return ImmutableCollection + * @return ImmutableCollection<(E is null ? never : E)> */ #[NoDiscard] public function filterNotNull(): ImmutableCollection; diff --git a/tests/Type/FilterNotNullTypeTest.php b/tests/Type/FilterNotNullTypeTest.php new file mode 100644 index 0000000..9b3b8f1 --- /dev/null +++ b/tests/Type/FilterNotNullTypeTest.php @@ -0,0 +1,38 @@ +', listOf([1, null])->filterNotNull()); +assertType('Noctud\Collection\List\ImmutableList', mutableListOf([1, null])->filterNotNull()); +assertType('Noctud\Collection\Set\ImmutableSet', setOf([1, null])->filterNotNull()); +assertType('Noctud\Collection\Set\ImmutableSet', mutableSetOf([1, null])->filterNotNull()); + +// Also through the base Collection interface. +/** @var Collection $c */ +$c = listOf(['a', null]); +assertType('Noctud\Collection\Collection', $c->filterNotNull()); + +// A collection without null keeps its element type unchanged. +assertType('Noctud\Collection\List\ImmutableList', listOf([1, 2])->filterNotNull()); + +// filterValuesNotNull() removes null from the value type, keys are preserved. +assertType('Noctud\Collection\Map\ImmutableMap', stringMapOf(['a' => 1, 'b' => null])->filterValuesNotNull()); +assertType('Noctud\Collection\Map\ImmutableMap', mutableStringMapOf(['a' => 1, 'b' => null])->filterValuesNotNull());