Skip to content

Commit c03b8de

Browse files
authored
Improve PHPStan type hints (#8)
Tighten type information across the helper utilities so key and value types are preserved more accurately through pipelines when analysed with PHPStan. Add type assertions for the affected helpers and move the most advanced higher-order signatures into a PHPStan stub file so the runtime code can stay small, predictable, and function-first. # Conflicts: # src/pipe.php
1 parent 6359582 commit c03b8de

6 files changed

Lines changed: 191 additions & 46 deletions

File tree

phpstan.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
parameters:
2+
stubFiles:
3+
- stubs/pipe.stub

src/pipe.php

Lines changed: 61 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ function apply(callable $callback): Closure
4646
/**
4747
* Return unary callable for array_all
4848
*
49-
* @param callable $callback
50-
* @return Closure(array<array-key, mixed>) : bool
49+
* @template TValue
50+
* @param callable(TValue, array-key): bool $callback
51+
* @return Closure(array<array-key, TValue>): bool
5152
*/
5253
function array_all(callable $callback): Closure
5354
{
@@ -59,8 +60,9 @@ function array_all(callable $callback): Closure
5960
/**
6061
* Return unary callable for array_any
6162
*
62-
* @param callable $callback
63-
* @return Closure(array<array-key, mixed>) : bool
63+
* @template TValue
64+
* @param callable(TValue, array-key): bool $callback
65+
* @return Closure(array<array-key, TValue>): bool
6466
*/
6567
function array_any(callable $callback): Closure
6668
{
@@ -107,7 +109,7 @@ function array_dissoc(string|int ...$keys): Closure
107109
*
108110
* @param callable $callback
109111
* @param int $mode
110-
* @return Closure(array<array-key, mixed>) : array<array-key, mixed>
112+
* @return Closure(array<array-key, mixed>): array<array-key, mixed>
111113
*/
112114
function array_filter(callable $callback, int $mode = 0): Closure
113115
{
@@ -136,8 +138,10 @@ function array_flatten(array $array): array
136138
/**
137139
* Return unary callable for array_map
138140
*
139-
* @param callable $callback
140-
* @return Closure(array<array-key, mixed>) : array<array-key, mixed>
141+
* @template TValue
142+
* @template TResult
143+
* @param callable(TValue): TResult $callback
144+
* @return Closure(array<array-key, TValue>): array<array-key, TResult>
141145
*/
142146
function array_map(callable $callback): Closure
143147
{
@@ -230,9 +234,6 @@ function array_nth(int $i): Closure
230234
/**
231235
* Return unary callable for array_reduce
232236
*
233-
* The reducer is called as: $callback($carry, $value) (no array key is provided).
234-
* For an empty array, the result is $initial (or null if omitted).
235-
*
236237
* @param callable $callback
237238
* @param mixed $initial
238239
* @return Closure(array<array-key, mixed>): (mixed|null)
@@ -298,8 +299,8 @@ function array_slice(int $offset, ?int $length = null, bool $preserve_keys = fal
298299
* Equivalent to:
299300
* fn(array $xs) => array_reduce($xs, fn($sum, $x) => $sum + $callback($x), 0)
300301
*
301-
* @param callable $callback
302-
* @return Closure
302+
* @param callable $callback Callback that returns a numeric value (int|float)
303+
* @return Closure(array<array-key, mixed>): (int|float)
303304
*/
304305
function array_sum(callable $callback): Closure
305306
{
@@ -398,7 +399,7 @@ function array_unique(int $flags = \SORT_STRING): Closure
398399
}
399400

400401
return function (array $array) use ($flags): array {
401-
/** @phpstan-ignore-next-line */
402+
/** @var array<array-key, string|float|int> $array */
402403
return \array_unique($array, $flags);
403404
};
404405
}
@@ -438,7 +439,7 @@ function explode(string $separator, int $limit = PHP_INT_MAX): Closure
438439
* Returns a predicate: $x === $value
439440
*
440441
* @param mixed $value
441-
* @return Closure(mixed) : bool
442+
* @return Closure(mixed): bool
442443
*/
443444
function equals(mixed $value): Closure
444445
{
@@ -473,7 +474,7 @@ function if_else(callable $predicate, callable $then, callable $else): Closure
473474
function implode(string $separator = ""): Closure
474475
{
475476
return function (array $array) use ($separator): string {
476-
/** @phpstan-ignore-next-line */
477+
/** @var array<array-key, string> $array */
477478
return \implode($separator, $array);
478479
};
479480
}
@@ -496,7 +497,7 @@ function increment(int|float $by = 1): Closure
496497
* Short-circuits: stops iterating as soon as false (or actually !== true) is found.
497498
*
498499
* @param callable|null $callback
499-
* @return Closure(iterable<array-key, mixed>) : bool
500+
* @return Closure(iterable<array-key, mixed>): bool
500501
*/
501502
function iterable_all(?callable $callback = null): Closure
502503
{
@@ -527,7 +528,7 @@ function iterable_all(?callable $callback = null): Closure
527528
* Short-circuits: stops iterating as soon as a match is found.
528529
*
529530
* @param callable|null $callback
530-
* @return Closure(iterable<array-key, mixed>) : bool
531+
* @return Closure(iterable<array-key, mixed>): bool
531532
*/
532533
function iterable_any(?callable $callback = null): Closure
533534
{
@@ -564,7 +565,7 @@ function iterable_any(?callable $callback = null): Closure
564565
* @param bool $preserve_keys
565566
* @return Closure(iterable<array-key, mixed>): Generator<int, array<array-key, mixed>>
566567
* @throws InvalidArgumentException when $size < 1
567-
*/
568+
*/
568569
function iterable_chunk(int $size, bool $preserve_keys = false): Closure
569570
{
570571
if ($size < 1) {
@@ -602,13 +603,15 @@ function iterable_chunk(int $size, bool $preserve_keys = false): Closure
602603
/**
603604
* Return unary callable for filtering over an iterable
604605
*
605-
* @param callable $callback
606-
* @return Closure(iterable<array-key, mixed>) : Generator
606+
* @template TValue
607+
* @param callable(TValue, array-key): bool $callback
608+
* @return Closure(iterable<array-key, TValue>): Generator<array-key, TValue>
607609
*/
608610
function iterable_filter(callable $callback): Closure
609611
{
610612
return function (iterable $iterable) use ($callback): Generator {
611613
foreach ($iterable as $key => $value) {
614+
/** @var array-key $key */
612615
if ($callback($value, $key)) {
613616
yield $key => $value;
614617
}
@@ -621,8 +624,9 @@ function iterable_filter(callable $callback): Closure
621624
*
622625
* Warning: for Generators/Iterators, this consumes one element.
623626
*
624-
* @param iterable<array-key, mixed> $iterable
625-
* @return mixed
627+
* @template TValue
628+
* @param iterable<array-key, TValue> $iterable
629+
* @return TValue|null
626630
*/
627631
function iterable_first(iterable $iterable): mixed
628632
{
@@ -658,24 +662,27 @@ function iterable_flatten(bool $preserve_keys = true): Closure
658662
/**
659663
* Return unary callable for mapping over an iterable
660664
*
661-
* @param callable $callback
662-
* @return Closure(iterable<array-key, mixed>) : Generator
665+
* @template TValue
666+
* @template TResult
667+
* @param callable(TValue): TResult $callback
668+
* @return Closure(iterable<array-key, TValue>): Generator<array-key, TResult>
663669
*/
664670
function iterable_map(callable $callback): Closure
665671
{
666672
return function (iterable $iterable) use ($callback): Generator {
667673
foreach ($iterable as $key => $value) {
674+
/** @var array-key $key */
668675
yield $key => $callback($value);
669676
}
670677
};
671678
}
672679

673680
/**
674681
* Return unary callable that returns the nth element (0-based) from an iterable.
675-
* Returns 0 if n is out of bounds
682+
* Returns null if n is out of bounds
676683
*
677684
* @param int $n 0-based index
678-
* @return Closure(iterable<array-key, mixed>) : (mixed|null)
685+
* @return Closure(iterable<array-key, mixed>): (mixed|null)
679686
* @throws InvalidArgumentException if $n < 0
680687
*/
681688
function iterable_nth(int $n): \Closure
@@ -704,12 +711,13 @@ function iterable_nth(int $n): \Closure
704711
*
705712
* @param callable $callback
706713
* @param mixed $initial
707-
* @return Closure(iterable<array-key, mixed>) : mixed
714+
* @return Closure(iterable<array-key, mixed>): mixed
708715
*/
709716
function iterable_reduce(callable $callback, mixed $initial = null): Closure
710717
{
711718
return function (iterable $iterable) use ($callback, $initial): mixed {
712719
$carry = $initial;
720+
/** @var array-key $key */
713721
foreach ($iterable as $key => $value) {
714722
$carry = $callback($carry, $value, $key);
715723
}
@@ -733,13 +741,14 @@ function iterable_reduce(callable $callback, mixed $initial = null): Closure
733741
* @param callable $callback
734742
* @param callable $until
735743
* @param mixed $initial
736-
* @return Closure(iterable<array-key, mixed>): array{0:mixed, 1:mixed, 2:mixed}
744+
* @return Closure(iterable<array-key, mixed>): array{0:mixed, 1:array-key|null, 2:mixed|null}
737745
*/
738746
function iterable_reduce_until(callable $callback, callable $until, mixed $initial = null): Closure
739747
{
740748
return function (iterable $iterable) use ($callback, $until, $initial): array {
741749
$carry = $initial;
742750

751+
/** @var array-key $key */
743752
foreach ($iterable as $key => $value) {
744753
$carry = $callback($carry, $value, $key);
745754

@@ -802,7 +811,6 @@ function iterable_string(int $size = 1): Closure
802811
}
803812

804813

805-
806814
/**
807815
* Return unary callable for taking $count items from an iterable
808816
*
@@ -981,14 +989,12 @@ function iterate(callable $callback, bool $include_seed = true): Closure
981989
}
982990

983991
/**
984-
* Return unary callable for preg_replace
992+
* Return unary callable for preg_match
985993
*
986-
* @template TFlags of 0|256|512|768
987-
* @template TFlags of int
988994
* @param string $pattern
989-
* @param TFlags $flags
995+
* @param int-mask<0, 256, 512> $flags Bitmask of `PREG_OFFSET_CAPTURE` (256), `PREG_UNMATCHED_AS_NULL` (512)
990996
* @param int $offset
991-
* @return Closure(string): array<array{string|null, int<-1, max>}|string|null>
997+
* @return Closure(string): array<int|string, string|null|array{string|null, int<-1, max>}>
992998
*/
993999
function preg_match(string $pattern, int $flags = 0, int $offset = 0): Closure
9941000
{
@@ -999,11 +1005,12 @@ function preg_match(string $pattern, int $flags = 0, int $offset = 0): Closure
9991005
}
10001006

10011007
/**
1002-
* @template TFlags of int
1008+
* Return unary callable for preg_match_all
1009+
*
10031010
* @param string $pattern
1004-
* @param TFlags $flags
1011+
* @param int-mask<0, 1, 2, 256, 512> $flags Combination of `PREG_PATTERN_ORDER` (1), `PREG_SET_ORDER` (2), `PREG_OFFSET_CAPTURE` (256), `PREG_UNMATCHED_AS_NULL` (512)
10051012
* @param int $offset
1006-
* @return Closure(string) : array<array-key, mixed>
1013+
* @return Closure(string): array<array-key, mixed>
10071014
*/
10081015
function preg_match_all(string $pattern, int $flags = 0, int $offset = 0): Closure
10091016
{
@@ -1018,10 +1025,13 @@ function preg_match_all(string $pattern, int $flags = 0, int $offset = 0): Closu
10181025
* Return unary callable for preg_replace
10191026
* $count is ignored
10201027
*
1028+
* When the subject is a `string`, the return is `string|null`.
1029+
* When the subject is an `array`, the return is `array<string>|null`.
1030+
*
10211031
* @param string|array<string> $pattern
10221032
* @param string|array<string> $replacement
10231033
* @param int $limit
1024-
* @return Closure(array<float|int|string>|string) : (array<string>|string|null)
1034+
* @return Closure<TSubject of string|array<string>>(TSubject): (TSubject|null)
10251035
*/
10261036
function preg_replace(string|array $pattern, string|array $replacement, int $limit = -1): Closure
10271037
{
@@ -1040,7 +1050,7 @@ function preg_replace(string|array $pattern, string|array $replacement, int $lim
10401050
function rsort(int $flags = SORT_REGULAR): Closure
10411051
{
10421052
return function (array $array) use ($flags): array {
1043-
/** @phpstan-ignore-next-line */
1053+
/** @var array<array-key, string|float|int> $array */
10441054
\rsort($array, $flags);
10451055
return $array;
10461056
};
@@ -1055,17 +1065,21 @@ function rsort(int $flags = SORT_REGULAR): Closure
10551065
function sort(int $flags = SORT_REGULAR): Closure
10561066
{
10571067
return function (array $array) use ($flags): array {
1058-
/** @phpstan-ignore-next-line */
1068+
/** @var array<array-key, string|float|int> $array */
10591069
\sort($array, $flags);
10601070
return $array;
10611071
};
10621072
}
10631073

10641074
/**
10651075
* Return unary callable for str_replace
1076+
*
1077+
* When the subject is a `string`, the return is a string.
1078+
* When the subject is an `array` of `strings`, the return is an `array` of `strings`.
1079+
*
10661080
* @param string|array<string> $search
10671081
* @param string|array<string> $replace
1068-
* @return Closure(array<string>|string): (string|array<string>)
1082+
* @return Closure<TSubject of string|array<string>>(TSubject): TSubject
10691083
*/
10701084
function str_replace(string|array $search, string|array $replace): Closure
10711085
{
@@ -1130,8 +1144,9 @@ function unless(callable $predicate, callable $callback): Closure
11301144
* Return unary callable for usort
11311145
* Reindexes keys
11321146
*
1133-
* @param callable $callback
1134-
* @return Closure(array<array-key, mixed>): list<mixed>
1147+
* @template TValue
1148+
* @param callable(TValue, TValue): int $callback
1149+
* @return Closure(array<array-key, TValue>): list<TValue>
11351150
*/
11361151
function usort(callable $callback): Closure
11371152
{
@@ -1145,8 +1160,9 @@ function usort(callable $callback): Closure
11451160
* Return unary callable for uasort
11461161
* Preserves keys
11471162
*
1148-
* @param callable(mixed, mixed): int $callback
1149-
* @return Closure(array<array-key, mixed>): array<array-key, mixed>
1163+
* @template TValue
1164+
* @param callable(TValue, TValue): int $callback
1165+
* @return Closure(array<array-key, TValue>): array<array-key, TValue>
11501166
*/
11511167
function uasort(callable $callback): Closure
11521168
{

stubs/pipe.stub

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Anarchitecture\pipe;
6+
7+
use Closure;
8+
use Generator;
9+
10+
/**
11+
* @template TValue
12+
* @template TResult
13+
* @param callable(TValue): TResult $callback
14+
* @return Closure<TKey of array-key>(array<TKey, TValue>): array<TKey, TResult>
15+
*/
16+
function array_map(callable $callback): Closure
17+
{
18+
}
19+
20+
/**
21+
* @template TKey of array-key = array-key
22+
* @template TValue = mixed
23+
* @param iterable<TKey, TValue> $iterable
24+
* @return array<TKey, TValue>
25+
*/
26+
function collect(iterable $iterable): array
27+
{
28+
}
29+
30+
/**
31+
* @template TValue = mixed
32+
* @template TKey of array-key = array-key
33+
* @param callable(TValue, TKey): bool $callback
34+
* @return Closure(iterable<TKey, TValue>): Generator<TKey, TValue, mixed, mixed>
35+
*/
36+
function iterable_filter(callable $callback): Closure
37+
{
38+
}
39+
40+
/**
41+
* @template TValue
42+
* @template TResult
43+
* @param callable(TValue): TResult $callback
44+
* @return Closure<TKey of array-key>(iterable<TKey, TValue>): Generator<TKey, TResult, mixed, mixed>
45+
*/
46+
function iterable_map(callable $callback): Closure
47+
{
48+
}
49+
50+
/**
51+
* @return Closure<TKey of array-key, TValue>(array<TKey, TValue>): (TValue|null)
52+
*/
53+
function array_nth(int $i): Closure
54+
{
55+
}
56+
57+
/**
58+
* @return Closure<TKey of array-key, TValue>(iterable<TKey, TValue>): (TValue|null)
59+
*/
60+
function iterable_nth(int $n): Closure
61+
{
62+
}

0 commit comments

Comments
 (0)