Skip to content

Commit f3b9034

Browse files
committed
Add sequenceT function
1 parent 6b7287d commit f3b9034

File tree

3 files changed

+65
-4
lines changed

3 files changed

+65
-4
lines changed

src/Fp/Functions/Collection/Sequence.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,20 @@ function sequenceEither(iterable $collection): Either
7070
return TraverseEitherOperation::id($collection)->map(asArray(...));
7171
}
7272

73+
/**
74+
* Varargs version of {@see sequenceEither()}.
75+
*
76+
* @template E
77+
* @template TVI
78+
*
79+
* @param Either<E, TVI> | Closure(): Either<E, TVI> ...$items
80+
* @return Either<E, list<TVI>>
81+
*/
82+
function sequenceEitherT(Either|Closure ...$items): Either
83+
{
84+
return TraverseEitherOperation::id($items)->map(asList(...));
85+
}
86+
7387
/**
7488
* Same as {@see sequenceEither()} but accumulates all left errors.
7589
*

src/Fp/Psalm/Hook/FunctionReturnTypeProvider/SequenceEitherFunctionReturnTypeProvider.php

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44

55
namespace Fp\Psalm\Hook\FunctionReturnTypeProvider;
66

7+
use Fp\Collections\ArrayList;
8+
use Fp\Collections\NonEmptyArrayList;
79
use Fp\Collections\NonEmptyHashMap;
810
use Fp\Functional\Either\Either;
11+
use Fp\Functional\Option\Option;
912
use Fp\Psalm\Util\Sequence\GetEitherTypeParam;
1013
use Fp\PsalmToolkit\Toolkit\CallArg;
1114
use Fp\PsalmToolkit\Toolkit\PsalmApi;
@@ -18,21 +21,23 @@
1821

1922
use function Fp\Callable\ctor;
2023
use function Fp\Collection\sequenceOptionT;
24+
use function Fp\Evidence\of;
25+
use function Fp\Evidence\proveTrue;
2126

2227
final class SequenceEitherFunctionReturnTypeProvider implements FunctionReturnTypeProviderInterface
2328
{
2429
public static function getFunctionIds(): array
2530
{
2631
return [
2732
strtolower('Fp\Collection\sequenceEither'),
33+
strtolower('Fp\Collection\sequenceEitherT'),
2834
];
2935
}
3036

3137
public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Union
3238
{
33-
return PsalmApi::$args->getCallArgs($event)
34-
->flatMap(fn($args) => $args->head())
35-
->flatMap(fn(CallArg $arg) => PsalmApi::$types->asSingleAtomicOf(TKeyedArray::class, $arg->type))
39+
return self::getInputTypeFromSequenceEither($event)
40+
->orElse(fn() => self::getInputTypeFromSequenceEitherT($event))
3641
->flatMap(fn(TKeyedArray $types) => sequenceOptionT(
3742
fn() => NonEmptyHashMap::collectNonEmpty($types->properties)
3843
->traverseOption(GetEitherTypeParam::left(...))
@@ -56,4 +61,29 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev
5661
->map(ctor(Union::class))
5762
->get();
5863
}
64+
65+
/**
66+
* @return Option<TKeyedArray>
67+
*/
68+
private static function getInputTypeFromSequenceEither(FunctionReturnTypeProviderEvent $event): Option
69+
{
70+
return proveTrue(strtolower('Fp\Collection\sequenceEither') === $event->getFunctionId())
71+
->flatMap(fn() => PsalmApi::$args->getCallArgs($event))
72+
->flatMap(fn($args) => $args->head())
73+
->flatMap(fn(CallArg $arg) => PsalmApi::$types->asSingleAtomic($arg->type))
74+
->flatMap(of(TKeyedArray::class));
75+
}
76+
77+
/**
78+
* @return Option<TKeyedArray>
79+
*/
80+
private static function getInputTypeFromSequenceEitherT(FunctionReturnTypeProviderEvent $event): Option
81+
{
82+
return proveTrue(strtolower('Fp\Collection\sequenceEitherT') === $event->getFunctionId())
83+
->flatMap(fn() => PsalmApi::$args->getCallArgs($event))
84+
->flatMap(fn(ArrayList $args) => $args->toNonEmptyArrayList())
85+
->map(fn(NonEmptyArrayList $args) => new TKeyedArray(
86+
$args->map(fn(CallArg $arg) => $arg->type)->toNonEmptyList(),
87+
));
88+
}
5989
}

tests/Static/Plugin/SequenceEitherPluginStaticTest.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use function Fp\Collection\at;
1212
use function Fp\Collection\sequenceEither;
1313
use function Fp\Collection\sequenceEitherAcc;
14+
use function Fp\Collection\sequenceEitherT;
1415
use function Fp\Evidence\proveInt;
1516
use function Fp\Evidence\proveNonEmptyString;
1617
use function Fp\Evidence\proveString;
@@ -133,6 +134,22 @@ public function sequenceAccWithNonEmptyArray(array $list): Either
133134
return sequenceEitherAcc($list);
134135
}
135136

137+
/**
138+
* @param array<string, mixed> $data
139+
* @return Either<InvalidArgumentException, array{non-empty-string, int}>
140+
*/
141+
public function sequenceEitherT(array $data): Either
142+
{
143+
return sequenceEitherT(
144+
at($data, 'name')
145+
->flatMap(proveNonEmptyString(...))
146+
->toRight(fn() => new InvalidArgumentException()),
147+
at($data, 'age')
148+
->flatMap(proveInt(...))
149+
->toRight(fn() => new InvalidArgumentException()),
150+
);
151+
}
152+
136153
/**
137154
* @return Either<
138155
* array{
@@ -146,7 +163,7 @@ public function sequenceAccWithNonEmptyArray(array $list): Either
146163
* array{
147164
* name: non-empty-string,
148165
* age: int,
149-
* address?: array{
166+
* address: array{
150167
* postcode: int,
151168
* city: non-empty-string
152169
* }

0 commit comments

Comments
 (0)