diff --git a/README.md b/README.md
index 54da7949..b8b7a261 100644
--- a/README.md
+++ b/README.md
@@ -16,19 +16,15 @@ PHP Functional Programming library. Monads and common use functions.
### Composer
+Supported installation method is via [composer](https://getcomposer.org):
+
```console
$ composer require fp4php/functional
```
-### Enable psalm plugin (optional)
-
-Read more about [plugin](doc/Psalm.md).
+### Psalm integration
-```console
-$ composer require --dev fp4php/psalm-toolkit
-$ vendor/bin/psalm-plugin enable fp4php/psalm-toolkit
-$ vendor/bin/psalm-plugin enable fp4php/functional
-```
+Please refer to the [fp4php/functional-psalm-plugin](https://github.com/fp4php/functional-psalm-plugin) repository.
## Overview
Typesafe and concise.
diff --git a/UPGRADING-v6.md b/UPGRADING-v6.md
new file mode 100644
index 00000000..f35120c2
--- /dev/null
+++ b/UPGRADING-v6.md
@@ -0,0 +1,32 @@
+## Update psalm to v5
+
+Since fp4php/functional v6 Psalm integration ships as separate package.
+Including the Psalm integration in the main fp4php/functional repository was a mistake.
+
+For migration:
+
+1. Remove old toolkit:
+
+```shell
+composer remove fp4php/psalm-toolkit
+```
+
+2. Install new psalm plugin:
+
+```shell
+composer require --dev fp4php/functional-psalm-plugin
+```
+
+3. Update psalm.xml:
+
+```diff
+
+
+ ...
+
+-
+-
++
+
+
+```
diff --git a/composer.json b/composer.json
index 739006a4..539a0659 100644
--- a/composer.json
+++ b/composer.json
@@ -22,25 +22,26 @@
"ext-simplexml": "*"
},
"require-dev": {
- "vimeo/psalm": "^4.23",
+ "vimeo/psalm": "^5.7",
"phpunit/phpunit": "^9",
"symfony/process": "^5.2",
"rregeer/phpunit-coverage-check": "^0.3.1",
"php-coveralls/php-coveralls": "~2.4.0",
- "fp4php/psalm-toolkit": "dev-main"
+ "fp4php/psalm-toolkit": "dev-psalm-v5",
+ "fp4php/functional-psalm-plugin": "^1.0"
},
"autoload": {
"psr-4": {
"Fp\\Operations\\": "src/Fp/Operations",
"Fp\\Collections\\": "src/Fp/Collections",
"Fp\\Streams\\": "src/Fp/Streams",
- "Fp\\Functional\\": "src/Fp/Functional",
- "Fp\\Psalm\\": "src/Fp/Psalm"
+ "Fp\\Functional\\": "src/Fp/Functional"
},
"files": [
"src/Fp/Functions/Callable/Ctor.php",
"src/Fp/Functions/Callable/ToSafeClosure.php",
"src/Fp/Functions/Callable/DropFirstArg.php",
+ "src/Fp/Functions/Callable/Pipe.php",
"src/Fp/Functions/Callable/Compose.php",
"src/Fp/Functions/Callable/Partial.php",
"src/Fp/Functions/Callable/PartialLeft.php",
@@ -98,6 +99,7 @@
"src/Fp/Functions/Evidence/ProveObject.php",
"src/Fp/Functions/Evidence/ProveOf.php",
"src/Fp/Functions/Evidence/ProveString.php",
+ "src/Fp/Functions/Util/ToString.php",
"src/Fp/Functions/Util/JsonDecode.php",
"src/Fp/Functions/Util/RegExp.php",
"src/Fp/Functions/Util/Writeln.php",
diff --git a/psalm.xml b/psalm.xml
index e7ee65a1..441cbc99 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -1,11 +1,11 @@
-
+
@@ -13,57 +13,15 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/src/Doc/Md/Psalm/1_PsalmSupport.md b/src/Doc/Md/Psalm/1_PsalmSupport.md
deleted file mode 100644
index 42c90d54..00000000
--- a/src/Doc/Md/Psalm/1_PsalmSupport.md
+++ /dev/null
@@ -1,368 +0,0 @@
-# Static analysis
-
-Highly recommended to use this library in tandem with [Psalm](https://github.com/vimeo/psalm).
-
-Psalm is awesome library for static analysis of PHP code.
-It opens the road to typed functional programming.
-
-# Psalm plugin
-
-Psalm cannot check everything. But the [plugin system](https://psalm.dev/docs/running_psalm/plugins/authoring_plugins/) allows to improve type inference and implement other custom diagnostics.
-
-To enable plugin shipped with library:
-
-```console
-$ composer require --dev fp4php/psalm-toolkit
-$ vendor/bin/psalm-plugin enable fp4php/functional
-```
-
-# Features
-
-- #### filter
-
- Plugin add type narrowing for filtering.
-
- `Fp\Functional\Option\Option::filter`:
-
- ```php
-
- */
- function getOption(): Option
- {
- // ...
- }
-
- // Narrowed to Option
-
- /** @psalm-trace $result */
- $result = getOption()->filter(fn($value) => is_string($value));
- ```
-
- `Fp\Collections\ArrayList::filter` (and other collections with `filter` method):
-
- ```php
-
- */
- function getArrayList(): ArrayList
- {
- // ...
- }
-
- // Narrowed to ArrayList
-
- /** @psalm-trace $result */
- $result = getArrayList()->filter(fn($value) => is_string($value));
- ```
-
- `Fp\Functional\Either\Either::filterOrElse`:
-
- ```php
-
- */
- function getEither(): Either
- {
- // ...
- }
-
- // Narrowed to Either
- getEither()->filterOrElse(
- fn($value) => is_string($value),
- fn() => new TypeError('Is not string'),
- );
- ```
-
- `Fp\Collection\filter`:
-
- ```php
-
- */
- function getList(): array
- {
- // ...
- }
-
- // Narrowed to list
- filter(getList(), fn($value) => is_string($value));
- ```
-
- `Fp\Collection\first` and `Fp\Collection\last`:
-
- ```php
-
- */
- function getList(): array
- {
- // ...
- }
-
- // Narrowed to Option
- first(getList(), fn($value) => is_string($value));
-
- // Narrowed to Option
- last(getList(), fn($value) => is_int($value));
- ```
-
- For all cases above you can use [first-class callable](https://wiki.php.net/rfc/first_class_callable_syntax) syntax:
-
- ```php
-
- */
- function getList(): array
- {
- // ...
- }
-
- // Narrowed to list
- filter(getList(), is_string(...));
- ```
-
-- #### fold
-
- Is too difficult to make the fold function using type system of psalm.
- Without plugin `Fp\Collection\fold` and collections fold method has some edge cases. For example: https://psalm.dev/r/b0a99c4912
-
- Plugin can fix that problem.
-
-- #### ctor
-
- PHP 8.1 brings feature called [first-class callable](https://wiki.php.net/rfc/first_class_callable_syntax).
- But that feature cannot be used for class constructor. `Fp\Callable\ctor` can simulate this feature for class constructors, but requires plugin for static analysis.
-
- ```php
-
- */
- function sequenceOptionShapeExample(int $id): Option
- {
- // Inferred type is: Option not Option>
- return sequenceOption([
- 'foo' => getFoo($id),
- 'bar' => getBar($id),
- ]);
- }
-
- /**
- * @return Option
- */
- function sequenceOptionTupleExample(int $id): Option
- {
- // Inferred type is: Option not Option>
- return sequenceOptionT(getFoo($id), getBar($id));
- }
- ```
-
-- #### assertion
-
- Unfortunately `@psalm-assert-if-true`/`@psalm-assert-if-false` works incorrectly for Option/Either assertion methods: https://psalm.dev/r/408248f46f
-
- Plugin implements workaround for this bug.
-
-- #### N-combinators
-
- Psalm plugin will prevent calling *N combinator in non-valid cases:
-
- ```php
- $maybeData
- * @return Option
- */
- function test(Option $maybeData): Option
- {
- /*
- * ERROR: IfThisIsMismatch
- * Object must be type of Option, actual type Option
- */
- return $maybeData->mapN(fn(int $a, bool $b, bool $c) => new Foo($a, $b, $c));
- }
- ```
-
-- #### proveTrue
-
- Implementation assertion effect for `Fp\Evidence\proveTrue` (like for builtin `assert` function):
-
- ```php
- , ArrayList> $separated
- * @return Either, ArrayList>
- */
- function separatedArrayListToEither(Separated $separated): Either
- {
- return $separated->toEither();
- }
-
- /**
- * @param Separated, HashSet> $separated
- * @return Either, HashSet>
- */
- function separatedHashSetToEither(Separated $separated): Either
- {
- return $separated->toEither();
- }
- ```
-
-- #### partitionT
-
- Plugin infers each `list` type from predicates of `partitionT`:
-
- ```php
- $list
- * @return array{list, list, list}
- */
- function testExhaustiveInference(array $list): array
- {
- return partitionT($list, fn($i) => $i instanceof Foo, fn($i) => $i instanceof Bar);
- }
- ```
-
-- #### filterNotNull
-
- Plugin turns all nullable keys to possibly undefined keys:
- ```php
- , TVO> $callback
+ * @return Either, ArrayList>
+ */
+ public function traverseEitherMerged(callable $callback): Either
+ {
+ return Ops\TraverseEitherMergedOperation::of($this)(dropFirstArg($callback))->map(ArrayList::collect(...));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(mixed...): Either, TVO> $callback
+ * @return Either, ArrayList>
+ *
+ * @see MapTapNMethodReturnTypeProvider
+ */
+ public function traverseEitherMergedN(callable $callback): Either
+ {
+ return $this->traverseEitherMerged(function($tuple) use ($callback) {
+ /** @var array $tuple */;
+ return toSafeClosure($callback)(...$tuple);
+ });
+ }
+
/**
* {@inheritDoc}
*
@@ -810,6 +843,22 @@ public function sequenceEither(): Either
return Ops\TraverseEitherOperation::id($this)->map(ArrayList::collect(...));
}
+ /**
+ * {@inheritDoc}
+ *
+ * Same as {@see Seq::sequenceEither()} but merge all left errors into non-empty-list.
+ *
+ * @template E
+ * @template TVO
+ * @psalm-if-this-is ArrayList, TVO>>
+ *
+ * @return Either, ArrayList>
+ */
+ public function sequenceEitherMerged(): Either
+ {
+ return Ops\TraverseEitherMergedOperation::id($this)->map(ArrayList::collect(...));
+ }
+
/**
* {@inheritDoc}
*
diff --git a/src/Fp/Collections/Collection.php b/src/Fp/Collections/Collection.php
index 4e9a590e..c17abcec 100644
--- a/src/Fp/Collections/Collection.php
+++ b/src/Fp/Collections/Collection.php
@@ -5,7 +5,6 @@
namespace Fp\Collections;
use Countable;
-use Iterator;
use IteratorAggregate;
/**
diff --git a/src/Fp/Collections/HashMap.php b/src/Fp/Collections/HashMap.php
index 6f2fd80a..3f785619 100644
--- a/src/Fp/Collections/HashMap.php
+++ b/src/Fp/Collections/HashMap.php
@@ -839,6 +839,51 @@ public function traverseEitherKV(callable $callback): Either
return Ops\TraverseEitherOperation::of($this)($callback)->map(fn($gen) => HashMap::collect($gen));
}
+ /**
+ * {@inheritDoc}
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(TV): Either, TVO> $callback
+ * @return Either, HashMap>
+ */
+ public function traverseEitherMerged(callable $callback): Either
+ {
+ return $this->traverseEitherMergedKV(dropFirstArg($callback));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(TK, TV): Either, TVO> $callback
+ * @return Either, HashMap>
+ */
+ public function traverseEitherMergedKV(callable $callback): Either
+ {
+ return Ops\TraverseEitherMergedOperation::of($this)($callback)->map(HashMap::collect(...));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(mixed...): Either, TVO> $callback
+ * @return Either, HashMap>
+ */
+ public function traverseEitherMergedN(callable $callback): Either
+ {
+ return $this->traverseEitherMerged(function($tuple) use ($callback) {
+ /** @var array $tuple */;
+ return toSafeClosure($callback)(...$tuple);
+ });
+ }
+
/**
* {@inheritDoc}
*
@@ -853,6 +898,20 @@ public function sequenceEither(): Either
return Ops\TraverseEitherOperation::id($this)->map(fn($gen) => HashMap::collect($gen));
}
+ /**
+ * {@inheritDoc}
+ *
+ * @template E
+ * @template TVO
+ * @psalm-if-this-is HashMap, TVO>>
+ *
+ * @return Either, HashMap>
+ */
+ public function sequenceEitherMerged(): Either
+ {
+ return Ops\TraverseEitherMergedOperation::id($this)->map(HashMap::collect(...));
+ }
+
/**
* {@inheritDoc}
*
diff --git a/src/Fp/Collections/HashSet.php b/src/Fp/Collections/HashSet.php
index efa38b0e..9f4db172 100644
--- a/src/Fp/Collections/HashSet.php
+++ b/src/Fp/Collections/HashSet.php
@@ -607,6 +607,37 @@ public function traverseEitherN(callable $callback): Either
});
}
+ /**
+ * {@inheritDoc}
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(TV): Either, TVO> $callback
+ * @return Either, HashSet>
+ */
+ public function traverseEitherMerged(callable $callback): Either
+ {
+ return Ops\TraverseEitherMergedOperation::of($this)(dropFirstArg($callback))->map(HashSet::collect(...));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(mixed...): Either, TVO> $callback
+ * @return Either, HashSet>
+ */
+ public function traverseEitherMergedN(callable $callback): Either
+ {
+ return $this->traverseEitherMerged(function($tuple) use ($callback) {
+ /** @var array $tuple */;
+ return toSafeClosure($callback)(...$tuple);
+ });
+ }
+
/**
* {@inheritDoc}
*
@@ -618,8 +649,21 @@ public function traverseEitherN(callable $callback): Either
*/
public function sequenceEither(): Either
{
- return Ops\TraverseEitherOperation::id($this->getIterator())
- ->map(fn($gen) => HashSet::collect($gen));
+ return Ops\TraverseEitherOperation::id($this->getIterator())->map(HashSet::collect(...));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @template E
+ * @template TVO
+ * @psalm-if-this-is HashSet, TVO>>
+ *
+ * @return Either, HashSet>
+ */
+ public function sequenceEitherMerged(): Either
+ {
+ return Ops\TraverseEitherMergedOperation::id($this)->map(HashSet::collect(...));
}
/**
diff --git a/src/Fp/Collections/LinkedList.php b/src/Fp/Collections/LinkedList.php
index ece93fad..635cb107 100644
--- a/src/Fp/Collections/LinkedList.php
+++ b/src/Fp/Collections/LinkedList.php
@@ -659,6 +659,7 @@ public function uniqueBy(callable $callback): LinkedList
*/
public function reverse(): LinkedList
{
+ /** @var LinkedList */
$list = Nil::getInstance();
foreach ($this as $elem) {
@@ -788,6 +789,39 @@ public function traverseEitherN(callable $callback): Either
});
}
+ /**
+ * {@inheritDoc}
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(TV): Either, TVO> $callback
+ * @return Either, LinkedList>
+ */
+ public function traverseEitherMerged(callable $callback): Either
+ {
+ return Ops\TraverseEitherMergedOperation::of($this)(dropFirstArg($callback))->map(LinkedList::collect(...));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(mixed...): Either, TVO> $callback
+ * @return Either, LinkedList>
+ *
+ * @see MapTapNMethodReturnTypeProvider
+ */
+ public function traverseEitherMergedN(callable $callback): Either
+ {
+ return $this->traverseEitherMerged(function($tuple) use ($callback) {
+ /** @var array $tuple */;
+ return toSafeClosure($callback)(...$tuple);
+ });
+ }
+
/**
* {@inheritDoc}
*
@@ -799,8 +833,23 @@ public function traverseEitherN(callable $callback): Either
*/
public function sequenceEither(): Either
{
- return Ops\TraverseEitherOperation::id($this->getIterator())
- ->map(fn($gen) => LinkedList::collect($gen));
+ return Ops\TraverseEitherOperation::id($this->getIterator())->map(LinkedList::collect(...));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Same as {@see Seq::sequenceEither()} but merge all left errors into non-empty-list.
+ *
+ * @template E
+ * @template TVO
+ * @psalm-if-this-is LinkedList, TVO>>
+ *
+ * @return Either, LinkedList>
+ */
+ public function sequenceEitherMerged(): Either
+ {
+ return Ops\TraverseEitherMergedOperation::id($this->getIterator())->map(LinkedList::collect(...));
}
/**
diff --git a/src/Fp/Collections/LinkedListBuffer.php b/src/Fp/Collections/LinkedListBuffer.php
index d5d1410d..14961d43 100644
--- a/src/Fp/Collections/LinkedListBuffer.php
+++ b/src/Fp/Collections/LinkedListBuffer.php
@@ -7,7 +7,6 @@
/**
* Provides constant time append to list
*
- * @internal
* @template TV
*/
final class LinkedListBuffer
diff --git a/src/Fp/Collections/Map.php b/src/Fp/Collections/Map.php
index 5d45ef67..9d5b64ad 100644
--- a/src/Fp/Collections/Map.php
+++ b/src/Fp/Collections/Map.php
@@ -4,8 +4,6 @@
namespace Fp\Collections;
-use Iterator;
-
/**
* @template-covariant TK
* @template-covariant TV
diff --git a/src/Fp/Collections/MapCastableOps.php b/src/Fp/Collections/MapCastableOps.php
index 21cb30b8..6bb798b1 100644
--- a/src/Fp/Collections/MapCastableOps.php
+++ b/src/Fp/Collections/MapCastableOps.php
@@ -8,7 +8,7 @@
use Fp\Streams\Stream;
/**
- * @template TK
+ * @template-covariant TK
* @template-covariant TV
*/
interface MapCastableOps
diff --git a/src/Fp/Collections/MapChainableOps.php b/src/Fp/Collections/MapChainableOps.php
index 19d42348..6d4f9ff5 100644
--- a/src/Fp/Collections/MapChainableOps.php
+++ b/src/Fp/Collections/MapChainableOps.php
@@ -9,7 +9,7 @@
use Fp\Psalm\Hook\MethodReturnTypeProvider\MapTapNMethodReturnTypeProvider;
/**
- * @template TK
+ * @template-covariant TK
* @template-covariant TV
*
* @psalm-suppress InvalidTemplateParam
diff --git a/src/Fp/Collections/MapCollector.php b/src/Fp/Collections/MapCollector.php
index 098462c3..756ef4c9 100644
--- a/src/Fp/Collections/MapCollector.php
+++ b/src/Fp/Collections/MapCollector.php
@@ -5,7 +5,7 @@
namespace Fp\Collections;
/**
- * @template TK
+ * @template-covariant TK
* @template-covariant TV
*/
interface MapCollector
diff --git a/src/Fp/Collections/MapOps.php b/src/Fp/Collections/MapOps.php
index 5e1b7402..65173b3c 100644
--- a/src/Fp/Collections/MapOps.php
+++ b/src/Fp/Collections/MapOps.php
@@ -5,7 +5,7 @@
namespace Fp\Collections;
/**
- * @template TK
+ * @template-covariant TK
* @template-covariant TV
* @extends MapChainableOps
* @extends MapTerminalOps
diff --git a/src/Fp/Collections/MapTerminalOps.php b/src/Fp/Collections/MapTerminalOps.php
index 70e9176e..703cd02a 100644
--- a/src/Fp/Collections/MapTerminalOps.php
+++ b/src/Fp/Collections/MapTerminalOps.php
@@ -11,7 +11,7 @@
use Fp\Psalm\Hook\MethodReturnTypeProvider\FoldMethodReturnTypeProvider;
/**
- * @template TK
+ * @template-covariant TK
* @template-covariant TV
*
* @psalm-suppress InvalidTemplateParam
@@ -218,6 +218,41 @@ public function traverseEitherN(callable $callback): Either;
*/
public function traverseEitherKV(callable $callback): Either;
+ /**
+ * Same as {@see MapTerminalOps::traverseEither()}, but collects all errors to non-empty-list.
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(TV): Either, TVO> $callback
+ * @return Either, Map>
+ */
+ public function traverseEitherMerged(callable $callback): Either;
+
+ /**
+ * Same as {@see MapTerminalOps::traverseEitherMerged()}, but passing also the key to the $callback function.
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(TK, TV): Either, TVO> $callback
+ * @return Either, Map>
+ */
+ public function traverseEitherMergedKV(callable $callback): Either;
+
+ /**
+ * Same as {@see MapTerminalOps::traverseEitherMerged()}, but deconstruct input tuple and pass it to the $callback function.
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(mixed...): Either, TVO> $callback
+ * @return Either, Map>
+ *
+ * @see MapTapNMethodReturnTypeProvider
+ */
+ public function traverseEitherMergedN(callable $callback): Either;
+
/**
* Same as {@see MapTerminalOps::traverseEither()} but use {@see id()} implicitly for $callback.
*
@@ -245,6 +280,17 @@ public function traverseEitherKV(callable $callback): Either;
*/
public function sequenceEither(): Either;
+ /**
+ * Same as {@see Set::sequenceEither()} but merge all left errors into non-empty-list.
+ *
+ * @template E
+ * @template TVO
+ * @psalm-if-this-is Map, TVO>>
+ *
+ * @return Either, Map>
+ */
+ public function sequenceEitherMerged(): Either;
+
/**
* Split collection to two parts by predicate function.
* If $predicate returns true then item gonna to right.
diff --git a/src/Fp/Collections/NonEmptyMapCastableOps.php b/src/Fp/Collections/NonEmptyMapCastableOps.php
index 741ed5a7..87282daa 100644
--- a/src/Fp/Collections/NonEmptyMapCastableOps.php
+++ b/src/Fp/Collections/NonEmptyMapCastableOps.php
@@ -7,7 +7,7 @@
use Fp\Streams\Stream;
/**
- * @template TK
+ * @template-covariant TK
* @template-covariant TV
*/
interface NonEmptyMapCastableOps
diff --git a/src/Fp/Collections/NonEmptyMapChainableOps.php b/src/Fp/Collections/NonEmptyMapChainableOps.php
index f7cc5254..cfeaefbd 100644
--- a/src/Fp/Collections/NonEmptyMapChainableOps.php
+++ b/src/Fp/Collections/NonEmptyMapChainableOps.php
@@ -7,7 +7,7 @@
use Fp\Psalm\Hook\MethodReturnTypeProvider\MapTapNMethodReturnTypeProvider;
/**
- * @template TK
+ * @template-covariant TK
* @template-covariant TV
*
* @psalm-suppress InvalidTemplateParam
diff --git a/src/Fp/Collections/NonEmptyMapCollector.php b/src/Fp/Collections/NonEmptyMapCollector.php
index 8bf5f08c..97deef9b 100644
--- a/src/Fp/Collections/NonEmptyMapCollector.php
+++ b/src/Fp/Collections/NonEmptyMapCollector.php
@@ -7,7 +7,7 @@
use Fp\Functional\Option\Option;
/**
- * @template TK
+ * @template-covariant TK
* @template-covariant TV
*/
interface NonEmptyMapCollector
diff --git a/src/Fp/Collections/NonEmptyMapOps.php b/src/Fp/Collections/NonEmptyMapOps.php
index a542a19b..b895e8e2 100644
--- a/src/Fp/Collections/NonEmptyMapOps.php
+++ b/src/Fp/Collections/NonEmptyMapOps.php
@@ -5,7 +5,7 @@
namespace Fp\Collections;
/**
- * @template TK
+ * @template-covariant TK
* @template-covariant TV
* @extends NonEmptyMapChainableOps
* @extends NonEmptyMapTerminalOps
diff --git a/src/Fp/Collections/NonEmptyMapTerminalOps.php b/src/Fp/Collections/NonEmptyMapTerminalOps.php
index 1327d29d..b3cbfd87 100644
--- a/src/Fp/Collections/NonEmptyMapTerminalOps.php
+++ b/src/Fp/Collections/NonEmptyMapTerminalOps.php
@@ -11,7 +11,7 @@
use Fp\Psalm\Hook\MethodReturnTypeProvider\FoldMethodReturnTypeProvider;
/**
- * @template TK
+ * @template-covariant TK
* @template-covariant TV
*
* @psalm-suppress InvalidTemplateParam
diff --git a/src/Fp/Collections/NonEmptySetChainableOps.php b/src/Fp/Collections/NonEmptySetChainableOps.php
index f8a93888..c31f1616 100644
--- a/src/Fp/Collections/NonEmptySetChainableOps.php
+++ b/src/Fp/Collections/NonEmptySetChainableOps.php
@@ -4,8 +4,6 @@
namespace Fp\Collections;
-use Fp\Functional\Option\Option;
-use Fp\Psalm\Hook\MethodReturnTypeProvider\CollectionFilterMethodReturnTypeProvider;
use Fp\Psalm\Hook\MethodReturnTypeProvider\MapTapNMethodReturnTypeProvider;
/**
diff --git a/src/Fp/Collections/SeqTerminalOps.php b/src/Fp/Collections/SeqTerminalOps.php
index 6eb52e31..e42ca66e 100644
--- a/src/Fp/Collections/SeqTerminalOps.php
+++ b/src/Fp/Collections/SeqTerminalOps.php
@@ -156,6 +156,30 @@ public function traverseEither(callable $callback): Either;
*/
public function traverseEitherN(callable $callback): Either;
+ /**
+ * Same as {@see SeqTerminalOps::traverseEither()}, but collects all errors to non-empty-list.
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(TV): Either, TVO> $callback
+ * @return Either, Seq>
+ */
+ public function traverseEitherMerged(callable $callback): Either;
+
+ /**
+ * Same as {@see SeqTerminalOps::traverseEitherMerged()}, but deconstruct input tuple and pass it to the $callback function.
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(mixed...): Either, TVO> $callback
+ * @return Either, Seq>
+ *
+ * @see MapTapNMethodReturnTypeProvider
+ */
+ public function traverseEitherMergedN(callable $callback): Either;
+
/**
* Same as {@see SeqTerminalOps::traverseEither()} but use {@see id()} implicitly for $callback.
*
@@ -175,10 +199,21 @@ public function traverseEitherN(callable $callback): Either;
*/
public function sequenceEither(): Either;
+ /**
+ * Same as {@see Seq::sequenceEither()} but merge all left errors into non-empty-list.
+ *
+ * @template E
+ * @template TVO
+ * @psalm-if-this-is Seq, TVO>>
+ *
+ * @return Either, Seq>
+ */
+ public function sequenceEitherMerged(): Either;
+
/**
* Split collection to two parts by predicate function.
* If $predicate returns true then item gonna to right.
- * Otherwise to left.
+ * Otherwise, to left.
*
* ```php
* >>> ArrayList::collect([0, 1, 2, 3, 4, 5])->partition(fn($i) => $i < 3);
diff --git a/src/Fp/Collections/SetTerminalOps.php b/src/Fp/Collections/SetTerminalOps.php
index d58027d4..ceade6e8 100644
--- a/src/Fp/Collections/SetTerminalOps.php
+++ b/src/Fp/Collections/SetTerminalOps.php
@@ -118,7 +118,7 @@ public function traverseOptionN(callable $callback): Option;
public function sequenceOption(): Option;
/**
- * Suppose you have an Set and you want to format each element with a function that returns an Either.
+ * Suppose you have a Set and you want to format each element with a function that returns an Either.
* Using traverseEither you can apply $callback to all elements and directly obtain as a result an Either>
* i.e. an Right> if all the results are Right, or a Left if at least one result is Left.
*
@@ -147,6 +147,30 @@ public function traverseEither(callable $callback): Either;
*/
public function traverseEitherN(callable $callback): Either;
+ /**
+ * Same as {@see SetTerminalOps::traverseEither()}, but collects all errors to non-empty-list.
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(TV): Either, TVO> $callback
+ * @return Either, Set>
+ */
+ public function traverseEitherMerged(callable $callback): Either;
+
+ /**
+ * Same as {@see SetTerminalOps::traverseEitherMerged()}, but deconstruct input tuple and pass it to the $callback function.
+ *
+ * @template E
+ * @template TVO
+ *
+ * @param callable(mixed...): Either, TVO> $callback
+ * @return Either, Set>
+ *
+ * @see MapTapNMethodReturnTypeProvider
+ */
+ public function traverseEitherMergedN(callable $callback): Either;
+
/**
* Same as {@see SetTerminalOps::traverseEither()} but use {@see id()} implicitly for $callback.
*
@@ -166,10 +190,21 @@ public function traverseEitherN(callable $callback): Either;
*/
public function sequenceEither(): Either;
+ /**
+ * Same as {@see Set::sequenceEither()} but merge all left errors into non-empty-list.
+ *
+ * @template E
+ * @template TVO
+ * @psalm-if-this-is Set, TVO>>
+ *
+ * @return Either, Set>
+ */
+ public function sequenceEitherMerged(): Either;
+
/**
* Split collection to two parts by predicate function.
* If $predicate returns true then item gonna to right.
- * Otherwise to left.
+ * Otherwise, to left.
*
* ```php
* >>> HashSet::collect([0, 1, 2, 3, 4, 5])->partition(fn($i) => $i < 3);
@@ -191,7 +226,7 @@ public function partitionN(callable $predicate): Separated;
* Similar to {@see SetTerminalOps::partition()} but uses {@see Either} instead of bool.
* So the output types LO/RO can be different from the input type TV.
* If $callback returns Right then item gonna to right.
- * Otherwise to left.
+ * Otherwise, to left.
*
* ```php
* >>> HashSet::collect([0, 1, 2, 3, 4, 5])
diff --git a/src/Fp/Functional/Assertion.php b/src/Fp/Functional/Assertion.php
index 520ca41c..181601c3 100644
--- a/src/Fp/Functional/Assertion.php
+++ b/src/Fp/Functional/Assertion.php
@@ -5,9 +5,8 @@
namespace Fp\Functional;
/**
- * @template T of string
+ * @template T
*/
interface Assertion
{
-
}
diff --git a/src/Fp/Functional/Either/Either.php b/src/Fp/Functional/Either/Either.php
index 69ec9506..47646a9b 100644
--- a/src/Fp/Functional/Either/Either.php
+++ b/src/Fp/Functional/Either/Either.php
@@ -276,13 +276,11 @@ public static function firstMergedT(Either|Closure $first, Either|Closure ...$ta
* => Left('error!')
* ```
*
- * @todo Replace Either with Either and drop suppress @see https://github.com/vimeo/psalm/issues/6288
- *
* @template TL
* @template TR
* @template TO
*
- * @param callable(): Generator, TR, TO> $computation
+ * @param callable(): Generator, TR, TO> $computation
* @return Either
*/
public static function do(callable $computation): Either {
@@ -291,14 +289,11 @@ public static function do(callable $computation): Either {
while ($generator->valid()) {
$currentStep = $generator->current();
- if ($currentStep->isRight()) {
- /** @psalm-suppress MixedArgument */
- $generator->send($currentStep->get());
- } else {
- /** @var Either $currentStep */
+ if ($currentStep->isLeft()) {
return $currentStep;
}
+ $generator->send($currentStep->get());
}
return Either::right($generator->getReturn());
@@ -442,7 +437,8 @@ public function getOrCall(callable $fallback): mixed
* => true
* ```
*
- * @psalm-assert-if-true Left&\Fp\Functional\Assertion<"must-be-left"> $this
+ * @psalm-assert-if-true Left&\Fp\Functional\Assertion $this
+ * @psalm-assert-if-false Right&\Fp\Functional\Assertion $this
*/
public function isLeft(): bool
{
@@ -460,7 +456,8 @@ public function isLeft(): bool
* => false
* ```
*
- * @psalm-assert-if-true Right&\Fp\Functional\Assertion<"must-be-right"> $this
+ * @psalm-assert-if-true Right&\Fp\Functional\Assertion $this
+ * @psalm-assert-if-false Left&\Fp\Functional\Assertion $this
*/
public function isRight(): bool
{
@@ -728,7 +725,10 @@ public function flatMapN(callable $callback): Either
public function flatTap(callable $callback): Either
{
return $this->flatMap(
- /** @param R $r */
+ /**
+ * @param R $r
+ * @psalm-suppress InvalidArgument
+ */
fn(mixed $r) => $callback($r)->fold(
fn($l) => Either::left($l),
fn() => Either::right($r),
diff --git a/src/Fp/Functional/Option/Option.php b/src/Fp/Functional/Option/Option.php
index f585065b..c9fec1b2 100644
--- a/src/Fp/Functional/Option/Option.php
+++ b/src/Fp/Functional/Option/Option.php
@@ -237,12 +237,10 @@ public static function firstT(Option|Closure $head, Option|Closure ...$tail): Op
* => None
* ```
*
- * @todo Replace Option with Option and drop suppress @see https://github.com/vimeo/psalm/issues/6288
- *
* @template TS
* @template TO
*
- * @param callable(): Generator, TS, TO> $computation
+ * @param callable(): Generator, TS, TO> $computation
* @return Option
*/
public static function do(callable $computation): Option {
@@ -251,14 +249,11 @@ public static function do(callable $computation): Option {
while ($generator->valid()) {
$currentStep = $generator->current();
- if ($currentStep->isSome()) {
- /** @psalm-suppress MixedArgument */
- $generator->send($currentStep->get());
- } else {
- /** @var Option $currentStep */
+ if ($currentStep->isNone()) {
return $currentStep;
}
+ $generator->send($currentStep->get());
}
return Option::some($generator->getReturn());
@@ -398,7 +393,8 @@ public function getOrCall(callable $fallback): mixed
* => false
* ```
*
- * @psalm-assert-if-true Some&\Fp\Functional\Assertion<"must-be-some"> $this
+ * @psalm-assert-if-true Some&\Fp\Functional\Assertion $this
+ * @psalm-assert-if-false None&\Fp\Functional\Assertion $this
*/
public function isSome(): bool
{
@@ -413,7 +409,8 @@ public function isSome(): bool
* => true
* ```
*
- * @psalm-assert-if-true None&\Fp\Functional\Assertion<"must-be-none"> $this
+ * @psalm-assert-if-true None&\Fp\Functional\Assertion $this
+ * @psalm-assert-if-false Some&\Fp\Functional\Assertion $this
*/
public function isNone(): bool
{
@@ -712,7 +709,10 @@ public function flatMapN(callable $callback): Option
public function flatTap(callable $callback): Option
{
return $this->flatMap(
- /** @param A $value */
+ /**
+ * @param A $value
+ * @psalm-suppress InvalidArgument
+ */
fn(mixed $value) => $callback($value)->fold(
fn() => Option::none(),
fn() => Option::some($value),
diff --git a/src/Fp/Functions/Callable/Pipe.php b/src/Fp/Functions/Callable/Pipe.php
new file mode 100644
index 00000000..4824ff7c
--- /dev/null
+++ b/src/Fp/Functions/Callable/Pipe.php
@@ -0,0 +1,33 @@
+>> pipe(
+ * >>> 0,
+ * >>> fn($i) => $i + 11,
+ * >>> fn($i) => $i + 20,
+ * >>> fn($i) => $i + 11,
+ * >>> );
+ * => 42
+ * ```
+ *
+ * @see PipeFunctionStorageProvider
+ * @no-named-arguments
+ */
+function pipe(mixed $a, callable $head, callable ...$tail): mixed
+{
+ foreach ([$head, ...$tail] as $function) {
+ /** @psalm-suppress MixedAssignment */
+ $a = $function($a);
+ }
+
+ return $a;
+}
diff --git a/src/Fp/Functions/Cast/AsGenerator.php b/src/Fp/Functions/Cast/AsGenerator.php
index 978475ee..dcdc01ab 100644
--- a/src/Fp/Functions/Cast/AsGenerator.php
+++ b/src/Fp/Functions/Cast/AsGenerator.php
@@ -8,14 +8,14 @@
/**
* ```php
- * >>> asGenerator(function { yield 1; yield 2; });
+ * >>> asGenerator(function() { yield 1; yield 2; });
* => Generator(1, 2)
* ```
*
* @template TK
* @template TV
*
- * @param callable(): iterable $callback
+ * @param callable(): (iterable | Generator) $callback
* @return Generator
*/
function asGenerator(callable $callback): Generator
@@ -34,4 +34,3 @@ function asGenerator(callable $callback): Generator
return $generator();
}
-
diff --git a/src/Fp/Functions/Cast/AsNonEmptyArray.php b/src/Fp/Functions/Cast/AsNonEmptyArray.php
index 31c61554..6ce67f5b 100644
--- a/src/Fp/Functions/Cast/AsNonEmptyArray.php
+++ b/src/Fp/Functions/Cast/AsNonEmptyArray.php
@@ -6,9 +6,6 @@
use Fp\Functional\Option\Option;
-use function Fp\Collection\head;
-use function Fp\Collection\map;
-
/**
* Try copy and cast collection to non-empty-array
* Returns None if there is no first collection element
diff --git a/src/Fp/Functions/Collection/Drop.php b/src/Fp/Functions/Collection/Drop.php
index 1ec421ee..541bfca5 100644
--- a/src/Fp/Functions/Collection/Drop.php
+++ b/src/Fp/Functions/Collection/Drop.php
@@ -28,11 +28,10 @@
*/
function drop(iterable $collection, int $length): array
{
+ $isList = is_array($collection) && array_is_list($collection);
$gen = DropOperation::of($collection)($length);
- return is_array($collection) && array_is_list($collection)
- ? asList($gen)
- : asArray($gen);
+ return $isList ? asList($gen) : asArray($gen);
}
/**
@@ -73,9 +72,8 @@ function dropRight(iterable $collection, int $length): array
*/
function dropWhile(iterable $collection, callable $predicate): array
{
+ $isList = is_array($collection) && array_is_list($collection);
$gen = DropWhileOperation::of($collection)(dropFirstArg($predicate));
- return is_array($collection) && array_is_list($collection)
- ? asList($gen)
- : asArray($gen);
+ return $isList ? asList($gen) : asArray($gen);
}
diff --git a/src/Fp/Functions/Collection/Filter.php b/src/Fp/Functions/Collection/Filter.php
index b607c234..26f001e8 100644
--- a/src/Fp/Functions/Collection/Filter.php
+++ b/src/Fp/Functions/Collection/Filter.php
@@ -56,10 +56,10 @@ function filter(iterable $collection, callable $predicate): array
*/
function filterKV(iterable $collection, callable $predicate): array
{
+ $isList = is_array($collection) && array_is_list($collection);
$gen = FilterOperation::of($collection)($predicate);
- return is_array($collection) && array_is_list($collection)
- ? asList($gen)
- : asArray($gen);
+
+ return $isList ? asList($gen) : asArray($gen);
}
/**
@@ -81,11 +81,10 @@ function filterKV(iterable $collection, callable $predicate): array
*/
function filterNotNull(iterable $collection): array
{
+ $isList = is_array($collection) && array_is_list($collection);
$gen = FilterNotNullOperation::of($collection)();
- return is_array($collection) && array_is_list($collection)
- ? asList($gen)
- : asArray($gen);
+ return $isList ? asList($gen) : asArray($gen);
}
/**
@@ -130,9 +129,8 @@ function filterMap(iterable $collection, callable $predicate): array
*/
function filterMapKV(iterable $collection, callable $predicate): array
{
+ $isList = is_array($collection) && array_is_list($collection);
$gen = FilterMapOperation::of($collection)($predicate);
- return is_array($collection) && array_is_list($collection)
- ? asList($gen)
- : asArray($gen);
+ return $isList ? asList($gen) : asArray($gen);
}
diff --git a/src/Fp/Functions/Collection/GroupMap.php b/src/Fp/Functions/Collection/GroupMap.php
index 6a7fd6c7..c17f4574 100644
--- a/src/Fp/Functions/Collection/GroupMap.php
+++ b/src/Fp/Functions/Collection/GroupMap.php
@@ -5,8 +5,8 @@
namespace Fp\Collection;
use Fp\Collections\NonEmptyHashMap;
-use Fp\Collections\NonEmptyLinkedList;
use Fp\Operations\GroupMapOperation;
+
use function Fp\Callable\dropFirstArg;
/**
diff --git a/src/Fp/Functions/Collection/Sequence.php b/src/Fp/Functions/Collection/Sequence.php
index 02553f09..82e1392e 100644
--- a/src/Fp/Functions/Collection/Sequence.php
+++ b/src/Fp/Functions/Collection/Sequence.php
@@ -7,7 +7,6 @@
use Closure;
use Fp\Functional\Either\Either;
use Fp\Functional\Option\Option;
-use Fp\Operations\TraverseEitherAccOperation;
use Fp\Operations\TraverseEitherMergedOperation;
use Fp\Operations\TraverseEitherOperation;
use Fp\Operations\TraverseOptionOperation;
@@ -71,6 +70,20 @@ function sequenceEither(iterable $collection): Either
return TraverseEitherOperation::id($collection)->map(asArray(...));
}
+/**
+ * Varargs version of {@see sequenceEither()}.
+ *
+ * @template E
+ * @template TVI
+ *
+ * @param Either | Closure(): Either ...$items
+ * @return Either>
+ */
+function sequenceEitherT(Either|Closure ...$items): Either
+{
+ return TraverseEitherOperation::id($items)->map(asList(...));
+}
+
/**
* Same as {@see traverseEither()} but merge all left errors into non-empty-list.
*
@@ -106,45 +119,3 @@ function sequenceEitherMergedT(Either|Closure ...$items): Either
{
return TraverseEitherMergedOperation::id($items)->map(asList(...));
}
-
-/**
- * Varargs version of {@see sequenceEither()}.
- *
- * @template E
- * @template TVI
- *
- * @param Either | Closure(): Either ...$items
- * @return Either>
- */
-function sequenceEitherT(Either|Closure ...$items): Either
-{
- return TraverseEitherOperation::id($items)->map(asList(...));
-}
-
-/**
- * Same as {@see sequenceEither()} but accumulates all left errors.
- *
- * @template E
- * @template TK of array-key
- * @template TVI
- *
- * @param iterable | Closure(): Either> $collection
- * @return Either, array>
- * @psalm-return (
- * $collection is non-empty-list ? Either, non-empty-list> :
- * $collection is list ? Either, list> :
- * $collection is non-empty-array ? Either, non-empty-array> :
- * Either, array>
- * )
- */
-function sequenceEitherAcc(iterable $collection): Either
-{
- return TraverseEitherAccOperation::id($collection)
- ->mapLeft(function($gen) use ($collection) {
- /** @var non-empty-array */
- return is_array($collection) && array_is_list($collection)
- ? asList($gen)
- : asArray($gen);
- })
- ->map(asArray(...));
-}
diff --git a/src/Fp/Functions/Collection/Traverse.php b/src/Fp/Functions/Collection/Traverse.php
index 811108ee..2bee57ce 100644
--- a/src/Fp/Functions/Collection/Traverse.php
+++ b/src/Fp/Functions/Collection/Traverse.php
@@ -6,14 +6,12 @@
use Fp\Functional\Either\Either;
use Fp\Functional\Option\Option;
-use Fp\Operations\TraverseEitherAccOperation;
use Fp\Operations\TraverseEitherMergedOperation;
use Fp\Operations\TraverseEitherOperation;
use Fp\Operations\TraverseOptionOperation;
use function Fp\Callable\dropFirstArg;
use function Fp\Cast\asArray;
-use function Fp\Cast\asList;
/**
* Suppose you have a list and you want to format each element with a function that returns an Option.
@@ -160,57 +158,3 @@ function traverseEitherMergedKV(iterable $collection, callable $callback): Eithe
{
return TraverseEitherMergedOperation::of($collection)($callback)->map(asArray(...));
}
-
-/**
- * Same as {@see traverseEither()} but accumulates all left errors.
- *
- * @template E
- * @template TK of array-key
- * @template TV
- * @template TVO
- *
- * @param iterable $collection
- * @param callable(TV): Either $callback
- * @return Either>
- * @psalm-return (
- * $collection is non-empty-list ? Either, non-empty-list> :
- * $collection is list ? Either, list> :
- * $collection is non-empty-array ? Either, non-empty-array> :
- * Either, array>
- * )
- */
-function traverseEitherAcc(iterable $collection, callable $callback): Either
-{
- return traverseEitherKVAcc($collection, dropFirstArg($callback));
-}
-
-/**
- * Same as {@see traverseEitherKV()} but accumulates all left errors.
- *
- * @template E
- * @template TK of array-key
- * @template TV
- * @template TVO
- *
- * @param iterable $collection
- * @param callable(TK, TV): Either $callback
- * @return Either>
- *
- * @psalm-return (
- * $collection is non-empty-list ? Either, non-empty-list> :
- * $collection is list ? Either, list> :
- * $collection is non-empty-array ? Either, non-empty-array> :
- * Either, array>
- * )
- */
-function traverseEitherKVAcc(iterable $collection, callable $callback): Either
-{
- return TraverseEitherAccOperation::of($collection)($callback)
- ->mapLeft(function($gen) use ($collection) {
- /** @var non-empty-array */
- return is_array($collection) && array_is_list($collection)
- ? asList($gen)
- : asArray($gen);
- })
- ->map(asArray(...));
-}
diff --git a/src/Fp/Functions/Collection/Unique.php b/src/Fp/Functions/Collection/Unique.php
index f0406ba1..88bbfe5b 100644
--- a/src/Fp/Functions/Collection/Unique.php
+++ b/src/Fp/Functions/Collection/Unique.php
@@ -9,7 +9,6 @@
use function Fp\Cast\asArray;
use function Fp\Cast\asList;
-
/**
* Returns collection unique elements
*
@@ -25,11 +24,9 @@
*/
function unique(iterable $collection): array
{
- $gen = UniqueByOperation::of($collection)(fn(mixed $i): mixed => $i);
-
return is_array($collection) && array_is_list($collection)
- ? asList($gen)
- : asArray($gen);
+ ? asList(UniqueByOperation::of($collection)(fn(mixed $i): mixed => $i))
+ : asArray(UniqueByOperation::of($collection)(fn(mixed $i): mixed => $i));
}
/**
@@ -51,9 +48,7 @@ function unique(iterable $collection): array
*/
function uniqueBy(iterable $collection, callable $callback): array
{
- $gen = UniqueByOperation::of($collection)($callback);
-
return is_array($collection) && array_is_list($collection)
- ? asList($gen)
- : asArray($gen);
+ ? asList(UniqueByOperation::of($collection)($callback))
+ : asArray(UniqueByOperation::of($collection)($callback));
}
diff --git a/src/Fp/Functions/Evidence/ProveArray.php b/src/Fp/Functions/Evidence/ProveArray.php
index 98c3f255..1ff246b9 100644
--- a/src/Fp/Functions/Evidence/ProveArray.php
+++ b/src/Fp/Functions/Evidence/ProveArray.php
@@ -53,8 +53,6 @@
* ? ($vType is null ? Option> : Option>)
* : ($vType is null ? Option> : Option>)
* )
- *
- * @psalm-suppress MixedAssignment, PossiblyNullArrayOffset, InvalidReturnStatement, InvalidReturnType
*/
function proveArray(mixed $value, null|callable $kType = null, null|callable $vType = null): Option
{
diff --git a/src/Fp/Functions/Evidence/ProveString.php b/src/Fp/Functions/Evidence/ProveString.php
index 71fb3b09..169acd25 100644
--- a/src/Fp/Functions/Evidence/ProveString.php
+++ b/src/Fp/Functions/Evidence/ProveString.php
@@ -88,7 +88,10 @@ function proveClassStringOf(mixed $potential, string|array $fqcn, bool $invarian
*/
function classStringOf(string|array $fqcn, bool $invariant = false): Closure
{
- return fn(mixed $potential) => proveClassStringOf($potential, $fqcn, $invariant);
+ return function(mixed $potential) use ($fqcn, $invariant) {
+ /** @var Option> */
+ return proveClassStringOf($potential, $fqcn, $invariant);
+ };
}
/**
diff --git a/src/Fp/Functions/Evidence/ProveUnion.php b/src/Fp/Functions/Evidence/ProveUnion.php
index 9bf1dc63..c892c73c 100644
--- a/src/Fp/Functions/Evidence/ProveUnion.php
+++ b/src/Fp/Functions/Evidence/ProveUnion.php
@@ -70,6 +70,8 @@ function union(array $evidences): Closure
* @param Closure(mixed): Option $evidence
* @param Closure(mixed): Option ...$evidences
* @return Closure(mixed): Option
+ *
+ * @no-named-arguments
*/
function unionT(Closure $evidence, Closure ...$evidences): Closure
{
diff --git a/src/Fp/Functions/Util/JsonDecode.php b/src/Fp/Functions/Util/JsonDecode.php
index 669213d7..4c5d191c 100644
--- a/src/Fp/Functions/Util/JsonDecode.php
+++ b/src/Fp/Functions/Util/JsonDecode.php
@@ -25,6 +25,7 @@
* => Left(JsonException('Syntax error'))
* ```
*
+ * @param int<1, 2147483647> $depth
* @return Either
*/
function jsonDecode(string $json, int $depth = 512, int $flags = 0): Either
@@ -68,6 +69,8 @@ function jsonDecodeArray(string $json): Either
* >>> jsonEncode([1, 2, 3]);
* => '[1, 2, 3]'
* ```
+ *
+ * @param int<1, 2147483647> $depth
*/
function jsonEncode(mixed $value, int $flags = JSON_THROW_ON_ERROR, int $depth = 512): string
{
diff --git a/src/Fp/Functions/Util/ToString.php b/src/Fp/Functions/Util/ToString.php
new file mode 100644
index 00000000..c1f84aec
--- /dev/null
+++ b/src/Fp/Functions/Util/ToString.php
@@ -0,0 +1,12 @@
+gen as $prefixElem) {
- yield $prefixElem;
- }
+ foreach ($this->gen as $prefixElem) {
+ yield $prefixElem;
+ }
- foreach ($suffix as $suffixElem) {
- yield $suffixElem;
- }
- });
+ foreach ($suffix as $suffixElem) {
+ yield $suffixElem;
+ }
}
}
diff --git a/src/Fp/Operations/AppendedOperation.php b/src/Fp/Operations/AppendedOperation.php
index 2d1d13d2..b7adf650 100644
--- a/src/Fp/Operations/AppendedOperation.php
+++ b/src/Fp/Operations/AppendedOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -24,12 +22,10 @@ final class AppendedOperation extends AbstractOperation
*/
public function __invoke(mixed $elem): Generator
{
- return asGenerator(function () use ($elem) {
- foreach ($this->gen as $prefixElem) {
- yield $prefixElem;
- }
+ foreach ($this->gen as $prefixElem) {
+ yield $prefixElem;
+ }
- yield $elem;
- });
+ yield $elem;
}
}
diff --git a/src/Fp/Operations/ChunksOperation.php b/src/Fp/Operations/ChunksOperation.php
index b23da3ff..165f7307 100644
--- a/src/Fp/Operations/ChunksOperation.php
+++ b/src/Fp/Operations/ChunksOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -22,24 +20,22 @@ final class ChunksOperation extends AbstractOperation
*/
public function __invoke(int $size): Generator
{
- return asGenerator(function () use ($size) {
- $chunk = [];
- $i = 0;
-
- foreach ($this->gen as $value) {
- $i++;
+ $chunk = [];
+ $i = 0;
- $chunk[] = $value;
+ foreach ($this->gen as $value) {
+ $i++;
- if (0 === $i % $size) {
- yield $chunk;
- $chunk = [];
- }
- }
+ $chunk[] = $value;
- if (!empty($chunk)) {
+ if (0 === $i % $size) {
yield $chunk;
+ $chunk = [];
}
- });
+ }
+
+ if (!empty($chunk)) {
+ yield $chunk;
+ }
}
}
diff --git a/src/Fp/Operations/DropOperation.php b/src/Fp/Operations/DropOperation.php
index 96e7b0b3..f04c9ae4 100644
--- a/src/Fp/Operations/DropOperation.php
+++ b/src/Fp/Operations/DropOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -21,17 +19,15 @@ final class DropOperation extends AbstractOperation
*/
public function __invoke(int $length): Generator
{
- return asGenerator(function () use ($length) {
- $i = 0;
-
- foreach ($this->gen as $key => $value) {
- if ($i < $length) {
- $i++;
- continue;
- }
+ $i = 0;
- yield $key => $value;
+ foreach ($this->gen as $key => $value) {
+ if ($i < $length) {
+ $i++;
+ continue;
}
- });
+
+ yield $key => $value;
+ }
}
}
diff --git a/src/Fp/Operations/DropWhileOperation.php b/src/Fp/Operations/DropWhileOperation.php
index 8b85be9a..b3ccf4fd 100644
--- a/src/Fp/Operations/DropWhileOperation.php
+++ b/src/Fp/Operations/DropWhileOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -24,14 +22,12 @@ final class DropWhileOperation extends AbstractOperation
*/
public function __invoke(callable $f): Generator
{
- return asGenerator(function () use ($f) {
- $toggle = true;
+ $toggle = true;
- foreach ($this->gen as $key => $value) {
- if (!($toggle = $toggle && $f($key, $value))) {
- yield $key => $value;
- }
+ foreach ($this->gen as $key => $value) {
+ if (!($toggle = $toggle && $f($key, $value))) {
+ yield $key => $value;
}
- });
+ }
}
}
diff --git a/src/Fp/Operations/FilterMapOperation.php b/src/Fp/Operations/FilterMapOperation.php
index 98ce4e16..cc3bd42f 100644
--- a/src/Fp/Operations/FilterMapOperation.php
+++ b/src/Fp/Operations/FilterMapOperation.php
@@ -7,8 +7,6 @@
use Fp\Functional\Option\Option;
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -25,14 +23,12 @@ final class FilterMapOperation extends AbstractOperation
*/
public function __invoke(callable $f): Generator
{
- return asGenerator(function () use ($f) {
- foreach ($this->gen as $key => $value) {
- $res = $f($key, $value);
+ foreach ($this->gen as $key => $value) {
+ $res = $f($key, $value);
- if ($res->isSome()) {
- yield $key => $res->get();
- }
+ if ($res->isSome()) {
+ yield $key => $res->get();
}
- });
+ }
}
}
diff --git a/src/Fp/Operations/FilterNotNullOperation.php b/src/Fp/Operations/FilterNotNullOperation.php
index 09b5718e..4898399f 100644
--- a/src/Fp/Operations/FilterNotNullOperation.php
+++ b/src/Fp/Operations/FilterNotNullOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -21,12 +19,10 @@ final class FilterNotNullOperation extends AbstractOperation
*/
public function __invoke(): Generator
{
- return asGenerator(function () {
- foreach ($this->gen as $key => $value) {
- if (null !== $value) {
- yield $key => $value;
- }
+ foreach ($this->gen as $key => $value) {
+ if (null !== $value) {
+ yield $key => $value;
}
- });
+ }
}
}
diff --git a/src/Fp/Operations/FlatMapOperation.php b/src/Fp/Operations/FlatMapOperation.php
index 02f39488..2a3666df 100644
--- a/src/Fp/Operations/FlatMapOperation.php
+++ b/src/Fp/Operations/FlatMapOperation.php
@@ -7,8 +7,6 @@
use Generator;
use Fp\Collections\Collection;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -26,14 +24,12 @@ final class FlatMapOperation extends AbstractOperation
*/
public function __invoke(callable $f): Generator
{
- return asGenerator(function () use ($f) {
- foreach ($this->gen as $key => $value) {
- $xs = $f($key, $value);
+ foreach ($this->gen as $key => $value) {
+ $xs = $f($key, $value);
- foreach ($xs as $k => $x) {
- yield $k => $x;
- }
+ foreach ($xs as $k => $x) {
+ yield $k => $x;
}
- });
+ }
}
}
diff --git a/src/Fp/Operations/GroupAdjacentByOperation.php b/src/Fp/Operations/GroupAdjacentByOperation.php
index 2504fa81..a1266d36 100644
--- a/src/Fp/Operations/GroupAdjacentByOperation.php
+++ b/src/Fp/Operations/GroupAdjacentByOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -24,31 +22,29 @@ final class GroupAdjacentByOperation extends AbstractOperation
*/
public function __invoke(callable $discriminator): Generator
{
- return asGenerator(function () use ($discriminator) {
- $buffer = [];
- $prevDisc = null;
- $isHead = true;
-
- foreach ($this->gen as $elem) {
- if ($isHead) {
- $isHead = false;
- $prevDisc = $discriminator($elem);
- }
-
- $curDisc = $discriminator($elem);
-
- if ($prevDisc !== $curDisc && !empty($buffer)) {
- yield [$prevDisc, $buffer];
- $buffer = [];
- }
-
- $buffer[] = $elem;
- $prevDisc = $curDisc;
+ $buffer = [];
+ $prevDisc = null;
+ $isHead = true;
+
+ foreach ($this->gen as $elem) {
+ if ($isHead) {
+ $isHead = false;
+ $prevDisc = $discriminator($elem);
}
- if (!empty($buffer)) {
+ $curDisc = $discriminator($elem);
+
+ if ($prevDisc !== $curDisc && !empty($buffer)) {
yield [$prevDisc, $buffer];
+ $buffer = [];
}
- });
+
+ $buffer[] = $elem;
+ $prevDisc = $curDisc;
+ }
+
+ if (!empty($buffer)) {
+ yield [$prevDisc, $buffer];
+ }
}
}
diff --git a/src/Fp/Operations/IntersperseOperation.php b/src/Fp/Operations/IntersperseOperation.php
index c2feea66..c1d2312e 100644
--- a/src/Fp/Operations/IntersperseOperation.php
+++ b/src/Fp/Operations/IntersperseOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -24,18 +22,16 @@ final class IntersperseOperation extends AbstractOperation
*/
public function __invoke(mixed $separator): Generator
{
- return asGenerator(function () use ($separator) {
- $isFirst = true;
-
- foreach ($this->gen as $elem) {
- if ($isFirst) {
- $isFirst = false;
- } else {
- yield $separator;
- }
+ $isFirst = true;
- yield $elem;
+ foreach ($this->gen as $elem) {
+ if ($isFirst) {
+ $isFirst = false;
+ } else {
+ yield $separator;
}
- });
+
+ yield $elem;
+ }
}
}
diff --git a/src/Fp/Operations/KeysOperation.php b/src/Fp/Operations/KeysOperation.php
index ddbf8ce1..431fd6da 100644
--- a/src/Fp/Operations/KeysOperation.php
+++ b/src/Fp/Operations/KeysOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -21,10 +19,8 @@ final class KeysOperation extends AbstractOperation
*/
public function __invoke(): Generator
{
- return asGenerator(function () {
- foreach ($this->gen as $key => $value) {
- yield $key;
- }
- });
+ foreach ($this->gen as $key => $value) {
+ yield $key;
+ }
}
}
diff --git a/src/Fp/Operations/MapOperation.php b/src/Fp/Operations/MapOperation.php
index be1b7c5b..f51e42e7 100644
--- a/src/Fp/Operations/MapOperation.php
+++ b/src/Fp/Operations/MapOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -24,10 +22,8 @@ final class MapOperation extends AbstractOperation
*/
public function __invoke(callable $f): Generator
{
- return asGenerator(function () use ($f) {
- foreach ($this->gen as $key => $value) {
- yield $key => $f($key, $value);
- }
- });
+ foreach ($this->gen as $key => $value) {
+ yield $key => $f($key, $value);
+ }
}
}
diff --git a/src/Fp/Operations/PrependedAllOperation.php b/src/Fp/Operations/PrependedAllOperation.php
index 3807e25e..7549b3ce 100644
--- a/src/Fp/Operations/PrependedAllOperation.php
+++ b/src/Fp/Operations/PrependedAllOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -24,14 +22,12 @@ final class PrependedAllOperation extends AbstractOperation
*/
public function __invoke(iterable $prefix): Generator
{
- return asGenerator(function () use ($prefix) {
- foreach ($prefix as $prefixElem) {
- yield $prefixElem;
- }
+ foreach ($prefix as $prefixElem) {
+ yield $prefixElem;
+ }
- foreach ($this->gen as $suffixElem) {
- yield $suffixElem;
- }
- });
+ foreach ($this->gen as $suffixElem) {
+ yield $suffixElem;
+ }
}
}
diff --git a/src/Fp/Operations/PrependedOperation.php b/src/Fp/Operations/PrependedOperation.php
index 9dcfa7c0..2ae60280 100644
--- a/src/Fp/Operations/PrependedOperation.php
+++ b/src/Fp/Operations/PrependedOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -24,12 +22,10 @@ final class PrependedOperation extends AbstractOperation
*/
public function __invoke(mixed $elem): Generator
{
- return asGenerator(function () use ($elem) {
- yield $elem;
+ yield $elem;
- foreach ($this->gen as $suffixElem) {
- yield $suffixElem;
- }
- });
+ foreach ($this->gen as $suffixElem) {
+ yield $suffixElem;
+ }
}
}
diff --git a/src/Fp/Operations/RepeatNOperation.php b/src/Fp/Operations/RepeatNOperation.php
index de4c5efc..a56efe91 100644
--- a/src/Fp/Operations/RepeatNOperation.php
+++ b/src/Fp/Operations/RepeatNOperation.php
@@ -7,8 +7,6 @@
use Fp\Collections\ArrayList;
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -22,18 +20,16 @@ final class RepeatNOperation extends AbstractOperation
*/
public function __invoke(int $times): Generator
{
- return asGenerator(function () use ($times) {
- $buffer = ArrayList::collect($this->gen);
+ $buffer = ArrayList::collect($this->gen);
+
+ foreach ($buffer as $elem) {
+ yield $elem;
+ }
+ for ($i = 0; $i < $times - 1; $i++) {
foreach ($buffer as $elem) {
yield $elem;
}
-
- for($i = 0; $i < $times - 1; $i++) {
- foreach ($buffer as $elem) {
- yield $elem;
- }
- }
- });
+ }
}
}
diff --git a/src/Fp/Operations/RepeatOperation.php b/src/Fp/Operations/RepeatOperation.php
index c68bc856..61744e68 100644
--- a/src/Fp/Operations/RepeatOperation.php
+++ b/src/Fp/Operations/RepeatOperation.php
@@ -7,8 +7,6 @@
use Fp\Collections\ArrayList;
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -22,18 +20,16 @@ final class RepeatOperation extends AbstractOperation
*/
public function __invoke(): Generator
{
- return asGenerator(function () {
- $buffer = ArrayList::collect($this->gen);
+ $buffer = ArrayList::collect($this->gen);
+
+ foreach ($buffer as $elem) {
+ yield $elem;
+ }
+ while (true) {
foreach ($buffer as $elem) {
yield $elem;
}
-
- while(true) {
- foreach ($buffer as $elem) {
- yield $elem;
- }
- }
- });
+ }
}
}
diff --git a/src/Fp/Operations/TailOperation.php b/src/Fp/Operations/TailOperation.php
index 6334c436..c91b2ab1 100644
--- a/src/Fp/Operations/TailOperation.php
+++ b/src/Fp/Operations/TailOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -21,17 +19,15 @@ final class TailOperation extends AbstractOperation
*/
public function __invoke(): Generator
{
- return asGenerator(function () {
- $isFirst = true;
-
- foreach ($this->gen as $key => $value) {
- if ($isFirst) {
- $isFirst = false;
- continue;
- }
+ $isFirst = true;
- yield $key => $value;
+ foreach ($this->gen as $key => $value) {
+ if ($isFirst) {
+ $isFirst = false;
+ continue;
}
- });
+
+ yield $key => $value;
+ }
}
}
diff --git a/src/Fp/Operations/TakeOperation.php b/src/Fp/Operations/TakeOperation.php
index 18fb1498..d2a4da71 100644
--- a/src/Fp/Operations/TakeOperation.php
+++ b/src/Fp/Operations/TakeOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -21,17 +19,15 @@ final class TakeOperation extends AbstractOperation
*/
public function __invoke(int $length): Generator
{
- return asGenerator(function () use ($length) {
- $i = 0;
-
- foreach ($this->gen as $key => $value) {
- if ($i === $length) {
- break;
- }
+ $i = 0;
- yield $key => $value;
- $i++;
+ foreach ($this->gen as $key => $value) {
+ if ($i === $length) {
+ break;
}
- });
+
+ yield $key => $value;
+ $i++;
+ }
}
}
diff --git a/src/Fp/Operations/TakeWhileOperation.php b/src/Fp/Operations/TakeWhileOperation.php
index 65ba3c70..764de2a3 100644
--- a/src/Fp/Operations/TakeWhileOperation.php
+++ b/src/Fp/Operations/TakeWhileOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -24,14 +22,12 @@ final class TakeWhileOperation extends AbstractOperation
*/
public function __invoke(callable $f): Generator
{
- return asGenerator(function () use ($f) {
- foreach ($this->gen as $key => $value) {
- if (!$f($key, $value)) {
- break;
- }
-
- yield $key => $value;
+ foreach ($this->gen as $key => $value) {
+ if (!$f($key, $value)) {
+ break;
}
- });
+
+ yield $key => $value;
+ }
}
}
diff --git a/src/Fp/Operations/TapOperation.php b/src/Fp/Operations/TapOperation.php
index 69e3f986..bda69cdf 100644
--- a/src/Fp/Operations/TapOperation.php
+++ b/src/Fp/Operations/TapOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -22,11 +20,9 @@ final class TapOperation extends AbstractOperation
*/
public function __invoke(callable $f): Generator
{
- return asGenerator(function () use ($f) {
- foreach ($this->gen as $key => $value) {
- $f($key, $value);
- yield $key => $value;
- }
- });
+ foreach ($this->gen as $key => $value) {
+ $f($key, $value);
+ yield $key => $value;
+ }
}
}
diff --git a/src/Fp/Operations/ToStringOperation.php b/src/Fp/Operations/ToStringOperation.php
index 190e5d15..ac72cbd7 100644
--- a/src/Fp/Operations/ToStringOperation.php
+++ b/src/Fp/Operations/ToStringOperation.php
@@ -6,18 +6,22 @@
use Stringable;
use Throwable;
+
use function Fp\Collection\map;
use function Fp\Collection\mapKV;
+use function Fp\Util\jsonEncode;
final class ToStringOperation
{
public static function of(mixed $value): string
{
if ($value instanceof Throwable) {
- $message = $value->getMessage();
+ $message = self::of($value->getMessage());
$exClass = $value::class;
- return empty($message) ? "{$exClass}()" : "{$exClass}('{$message}')";
+ return $message !== '""'
+ ? "{$exClass}({$message})"
+ : "{$exClass}()";
}
if ($value instanceof Stringable) {
@@ -25,10 +29,8 @@ public static function of(mixed $value): string
}
return match (get_debug_type($value)) {
- 'null' => 'null',
- 'int', 'float' => "{$value}",
- 'bool' => $value ? 'true' : 'false',
- 'string' => "'" . str_replace("'", "\'", $value) . "'",
+ 'null', 'int', 'bool', 'string', => jsonEncode($value),
+ 'float' => $value - ((int) $value) === 0.0 ? "{$value}.00" : "{$value}",
'array' => self::arrayToStr($value),
default => get_debug_type($value),
};
diff --git a/src/Fp/Operations/TraverseEitherAccOperation.php b/src/Fp/Operations/TraverseEitherAccOperation.php
deleted file mode 100644
index 6f9281a1..00000000
--- a/src/Fp/Operations/TraverseEitherAccOperation.php
+++ /dev/null
@@ -1,64 +0,0 @@
-
- */
-final class TraverseEitherAccOperation extends AbstractOperation
-{
- /**
- * @template E
- * @template TVO
- *
- * @param callable(TK, TV): Either $f
- * @return Either, Generator>
- */
- public function __invoke(callable $f): Either
- {
- /** @psalm-var HashTable $rights */
- $rights = new HashTable();
-
- /** @psalm-var HashTable $lefts */
- $lefts = new HashTable();
-
- foreach ($this->gen as $key => $value) {
- $mapped = $f($key, $value);
-
- if ($mapped->isLeft()) {
- $lefts->update($key, $mapped->get());
- } else {
- $rights->update($key, $mapped->get());
- }
- }
-
- return !$lefts->isEmpty()
- ? Either::left($lefts->getKeyValueIterator())
- : Either::right($rights->getKeyValueIterator());
- }
-
- /**
- * @template E
- * @template TKI
- * @template TVI
- *
- * @param iterable | Closure(): Either> $collection
- * @return Either, Generator>
- */
- public static function id(iterable $collection): Either
- {
- return self::of($collection)(
- fn(mixed $_key, Either|Closure $i): Either => $i instanceof Closure ? $i() : $i
- );
- }
-}
diff --git a/src/Fp/Operations/ValuesOperation.php b/src/Fp/Operations/ValuesOperation.php
index 912cc683..692f926e 100644
--- a/src/Fp/Operations/ValuesOperation.php
+++ b/src/Fp/Operations/ValuesOperation.php
@@ -6,8 +6,6 @@
use Generator;
-use function Fp\Cast\asGenerator;
-
/**
* @template TK
* @template TV
@@ -21,10 +19,8 @@ final class ValuesOperation extends AbstractOperation
*/
public function __invoke(): Generator
{
- return asGenerator(function () {
- foreach ($this->gen as $value) {
- yield $value;
- }
- });
+ foreach ($this->gen as $value) {
+ yield $value;
+ }
}
}
diff --git a/src/Fp/Operations/ZipOperation.php b/src/Fp/Operations/ZipOperation.php
index 624ae4ac..f359c93c 100644
--- a/src/Fp/Operations/ZipOperation.php
+++ b/src/Fp/Operations/ZipOperation.php
@@ -24,22 +24,20 @@ final class ZipOperation extends AbstractOperation
*/
public function __invoke(iterable $that): Generator
{
- return asGenerator(function () use ($that) {
- $thisIter = $this->gen;
- $thatIter = asGenerator(fn() => $that);
+ $thisIter = $this->gen;
+ $thatIter = asGenerator(fn() => $that);
- $thisIter->rewind();
- $thatIter->rewind();
+ $thisIter->rewind();
+ $thatIter->rewind();
- while ($thisIter->valid() && $thatIter->valid()) {
- $thisElem = $thisIter->current();
- $thatElem = $thatIter->current();
+ while ($thisIter->valid() && $thatIter->valid()) {
+ $thisElem = $thisIter->current();
+ $thatElem = $thatIter->current();
- yield [$thisElem, $thatElem];
+ yield [$thisElem, $thatElem];
- $thisIter->next();
- $thatIter->next();
- }
- });
+ $thisIter->next();
+ $thatIter->next();
+ }
}
}
diff --git a/src/Fp/Psalm/FunctionalPlugin.php b/src/Fp/Psalm/FunctionalPlugin.php
deleted file mode 100644
index f31f9be5..00000000
--- a/src/Fp/Psalm/FunctionalPlugin.php
+++ /dev/null
@@ -1,69 +0,0 @@
-registerHooksFromClass($hook);
- }
- };
-
- $register(PartialFunctionReturnTypeProvider::class);
- $register(PartitionFunctionReturnTypeProvider::class);
- $register(PluckFunctionReturnTypeProvider::class);
-
- $register(FilterFunctionReturnTypeProvider::class);
- $register(CollectionFilterMethodReturnTypeProvider::class);
- $register(OptionFilterMethodReturnTypeProvider::class);
- $register(EitherFilterOrElseMethodReturnTypeProvider::class);
-
- $register(ProveTrueExpressionAnalyzer::class);
- $register(EitherGetReturnTypeProvider::class);
- $register(OptionGetReturnTypeProvider::class);
-
- $register(SequenceOptionFunctionReturnTypeProvider::class);
- $register(SequenceEitherFunctionReturnTypeProvider::class);
- $register(SequenceEitherAccFunctionReturnTypeProvider::class);
-
- $register(FilterNotNullFunctionReturnTypeProvider::class);
- $register(FoldFunctionReturnTypeProvider::class);
- $register(FoldMethodReturnTypeProvider::class);
- $register(MapTapNMethodReturnTypeProvider::class);
- $register(PluckMethodReturnTypeProvider::class);
- $register(CtorFunctionReturnTypeProvider::class);
- $register(SeparatedToEitherMethodReturnTypeProvider::class);
- }
-}
diff --git a/src/Fp/Psalm/Hook/AfterExpressionAnalysis/ProveTrueExpressionAnalyzer.php b/src/Fp/Psalm/Hook/AfterExpressionAnalysis/ProveTrueExpressionAnalyzer.php
deleted file mode 100644
index 6304f323..00000000
--- a/src/Fp/Psalm/Hook/AfterExpressionAnalysis/ProveTrueExpressionAnalyzer.php
+++ /dev/null
@@ -1,95 +0,0 @@
- Option::some($event->getExpr())
- ->flatMap(of(Yield_::class))
- ->flatMap(self::getProveTrueArgsFromYield(...)),
- fn() => Option::some($event->getStatementsSource())
- ->flatMap(of(StatementsAnalyzer::class))
- ->filter(fn(StatementsAnalyzer $source) => $source->getSource() instanceof ClosureAnalyzer),
- fn() => Option::some($event),
- )->tapN(self::assert(...));
-
- return null;
- }
-
- /**
- * @param Node\Arg[] $prove_true_args
- */
- private static function assert(
- array $prove_true_args,
- StatementsAnalyzer $analyzer,
- AfterExpressionAnalysisEvent $event,
- ): void {
- FunctionCallAnalyzer::analyze(
- $analyzer,
- new VirtualFuncCall(
- new VirtualFullyQualified('assert'),
- $prove_true_args,
- ),
- $event->getContext(),
- );
- }
-
- /**
- * @psalm-return Option
- */
- private static function getProveTrueArgsFromYield(Yield_ $expr): Option
- {
- $visitor = new class extends NodeVisitorAbstract {
- /** @var Node\Arg[] */
- public array $proveTrueArgs = [];
-
- public function leaveNode(Node $node): ?int
- {
- $this->proveTrueArgs = Option::some($node)
- ->flatMap(of(FuncCall::class))
- ->filter(fn(FuncCall $n) => 'Fp\Evidence\proveTrue' === $n->name->getAttribute('resolvedName'))
- ->filter(fn(FuncCall $n) => !$n->isFirstClassCallable())
- ->map(fn(FuncCall $n) => $n->getArgs())
- ->getOrElse([]);
-
- return !empty($this->proveTrueArgs)
- ? NodeTraverser::STOP_TRAVERSAL
- : null;
- }
- };
-
- $traverser = new NodeTraverser();
- $traverser->addVisitor($visitor);
- $traverser->traverse([$expr]);
-
- return proveNonEmptyArray($visitor->proveTrueArgs);
- }
-}
diff --git a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/CtorFunctionReturnTypeProvider.php b/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/CtorFunctionReturnTypeProvider.php
deleted file mode 100644
index 76271e25..00000000
--- a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/CtorFunctionReturnTypeProvider.php
+++ /dev/null
@@ -1,51 +0,0 @@
-getCallArgs($event)
- ->flatMap(fn(ArrayList $args) => $args->head())
- ->pluck('type')
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->flatMap(of(TLiteralClassString::class))
- ->pluck('value')
- ->map(ctor(TNamedObject::class))
- ->map(fn(TNamedObject $class) => [
- new TClosure(
- value: 'Closure',
- params: PsalmApi::$classlikes->getStorage($class)
- ->flatMap(fn(ClassLikeStorage $storage) => at($storage->methods, '__construct'))
- ->map(fn(MethodStorage $method) => $method->params)
- ->getOrElse([]),
- return_type: new Union([$class]),
- )
- ])
- ->map(ctor(Union::class))
- ->get();
- }
-}
diff --git a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/FilterFunctionReturnTypeProvider.php b/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/FilterFunctionReturnTypeProvider.php
deleted file mode 100644
index be1d20e0..00000000
--- a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/FilterFunctionReturnTypeProvider.php
+++ /dev/null
@@ -1,102 +0,0 @@
-getCallArgs($event)
- ->flatMap(fn(ArrayList $args) => sequenceOptionT(
- fn() => Option::some($event->getFunctionId() === 'fp\collection\filterkv'
- ? RefineForEnum::KeyValue
- : RefineForEnum::Value),
- fn() => PredicateExtractor::extract($event),
- fn() => Option::some($event->getContext()),
- fn() => proveOf($event->getStatementsSource(), StatementsAnalyzer::class),
- fn() => $args->firstMap(fn(CallArg $arg) => GetCollectionTypeParams::keyValue($arg->type)),
- ))
- ->mapN(ctor(RefinementContext::class))
- ->map(RefineByPredicate::for(...))
- ->map(fn(CollectionTypeParams $result) => self::getReturnType($event, $result))
- ->get();
- }
-
- private static function getReturnType(FunctionReturnTypeProviderEvent $event, CollectionTypeParams $result): Union
- {
- if (self::isAccessorFunction($event->getFunctionId())) {
- return self::optionType($result);
- }
-
- return first($event->getCallArgs())
- ->flatMap(fn(Arg $preserve_keys) => PsalmApi::$args->getArgType($event, $preserve_keys))
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->map(fn($atomic) => $atomic::class === TList::class || $atomic::class === TNonEmptyList::class
- ? self::listType($result)
- : self::arrayType($result))
- ->getOrCall(fn() => self::listType($result));
- }
-
- private static function isAccessorFunction(string $id): bool
- {
- return $id === strtolower('Fp\Collection\last') || $id === strtolower('Fp\Collection\first');
- }
-
- private static function arrayType(CollectionTypeParams $result): Union
- {
- return new Union([
- new TArray([$result->key_type, $result->val_type]),
- ]);
- }
-
- private static function listType(CollectionTypeParams $result): Union
- {
- return new Union([
- new TList($result->val_type),
- ]);
- }
-
- private static function optionType(CollectionTypeParams $result): Union
- {
- return new Union([
- new TGenericObject(Option::class, [$result->val_type]),
- ]);
- }
-}
diff --git a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/FilterNotNullFunctionReturnTypeProvider.php b/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/FilterNotNullFunctionReturnTypeProvider.php
deleted file mode 100644
index 50abc652..00000000
--- a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/FilterNotNullFunctionReturnTypeProvider.php
+++ /dev/null
@@ -1,49 +0,0 @@
-getFirstCallArgType($event)
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->flatMap(of(TKeyedArray::class))
- ->filter(fn(TKeyedArray $keyed) => !$keyed->is_list)
- ->map(fn(TKeyedArray $keyed) => NonEmptyHashMap::collectNonEmpty($keyed->properties)
- ->map(function(Union $property) {
- if (!$property->isNullable()) {
- return $property;
- }
-
- $possibly_undefined = clone $property;
- $possibly_undefined->removeType('null');
- $possibly_undefined->possibly_undefined = true;
-
- return $possibly_undefined;
- })
- ->toNonEmptyArray())
- ->map(ctor(TKeyedArray::class))
- ->map(fn($keyed) => new Union([$keyed]))
- ->get();
- }
-}
diff --git a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/FoldFunctionReturnTypeProvider.php b/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/FoldFunctionReturnTypeProvider.php
deleted file mode 100644
index 0f58a1dc..00000000
--- a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/FoldFunctionReturnTypeProvider.php
+++ /dev/null
@@ -1,46 +0,0 @@
-flatMap(fn() => sequenceOptionT(
- fn() => PsalmApi::$args->getCallArgs($event)
- ->flatMap(fn(ArrayList $args) => $args->lastElement())
- ->pluck('type')
- ->flatMap(GetCollectionTypeParams::value(...)),
- fn() => PsalmApi::$args->getCallArgs($event)
- ->flatMap(fn(ArrayList $args) => $args->firstElement())
- ->pluck('type')
- ->map(PsalmApi::$types->asNonLiteralType(...)),
- ))
- ->mapN(fn(Union $A, Union $TInit) => [
- new TGenericObject(FoldOperation::class, [$A, $TInit]),
- ])
- ->map(ctor(Union::class))
- ->get();
- }
-}
diff --git a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/PartialFunctionReturnTypeProvider.php b/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/PartialFunctionReturnTypeProvider.php
deleted file mode 100644
index f9d0354a..00000000
--- a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/PartialFunctionReturnTypeProvider.php
+++ /dev/null
@@ -1,141 +0,0 @@
-getCallArgs())
- ->flatMap(fn(Arg $head_arg) => PsalmApi::$args->getArgType($event, $head_arg))
- ->flatMap(fn(Union $head_arg_type) => head(array_merge(
- $head_arg_type->getClosureTypes(),
- $head_arg_type->getCallableTypes(),
- filterNotNull(map(
- collection: asList($head_arg_type->getAtomicTypes()),
- callback: fn(Atomic $atomic) => CallableTypeComparator::getCallableFromAtomic(
- codebase: $event->getStatementsSource()->getCodebase(),
- input_type_part: $atomic
- )
- ))
- )))
- ->map(function (TClosure|TCallable $closure_type) use ($event) {
- $is_partial_right = str_ends_with($event->getFunctionId(), 'right');
- $closure_type_copy = clone $closure_type;
- $closure_params = $closure_type_copy->params ?? [];
- $tail_args = tail($event->getCallArgs());
-
- self::assertValidArgs(
- event: $event,
- callable: $closure_type_copy,
- args: $tail_args,
- is_partial_right: $is_partial_right
- );
-
- $args_tail_size = count($tail_args);
-
- if (0 === $args_tail_size) {
- return new Union([$closure_type_copy]);
- }
-
- $free_params = $is_partial_right
- ? array_slice($closure_params, 0, -$args_tail_size)
- : array_slice($closure_params, $args_tail_size);
-
- $closure_type_copy->params = $free_params;
-
- return new Union([$closure_type_copy]);
- })
- ->get();
- }
-
- /**
- * @psalm-param array $args
- */
- private static function assertValidArgs(
- FunctionReturnTypeProviderEvent $event,
- TClosure|TCallable $callable,
- array $args,
- bool $is_partial_right
- ): void
- {
- $source = $event->getStatementsSource();
- $codebase = $source->getCodebase();
-
- $args_list = array_values($args);
- $params_list = $is_partial_right
- ? array_reverse($callable->params ?? [])
- : $callable->params ?? [];
-
- for ($i = 0; $i < count($args); $i++) {
- $arg = $args_list[$i] ?? null;
- $param = $params_list[$i] ?? null;
-
- if (!isset($arg, $param)) {
- continue;
- }
-
- $param_type = $param->type ?? Type::getMixed();
- $arg_type = PsalmApi::$args->getArgType($event, $arg);
-
- if ($arg_type->isNone()) {
- continue;
- }
-
- $is_subtype_of = $codebase->isTypeContainedByType($arg_type->get(), $param_type);
-
- if ($is_subtype_of) {
- continue;
- }
-
- self::issueInvalidArgument(
- function_id: $event->getFunctionId(),
- code_location: $event->getCodeLocation(),
- expected_type: (string) $param_type
- );
- }
- }
-
-
- private static function issueInvalidArgument(string $function_id, CodeLocation $code_location, string $expected_type): void
- {
- $issue = new InvalidArgument(
- message: sprintf('argument should be of type %s', $expected_type),
- code_location: $code_location,
- function_id: $function_id
- );
-
- IssueBuffer::accepts($issue);
- }
-}
diff --git a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/PartitionFunctionReturnTypeProvider.php b/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/PartitionFunctionReturnTypeProvider.php
deleted file mode 100644
index b891bbe7..00000000
--- a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/PartitionFunctionReturnTypeProvider.php
+++ /dev/null
@@ -1,92 +0,0 @@
-getCallArgs();
- $partition_count = count(tail($args));
-
- return head($args)
- ->flatMap(fn(Arg $head_arg) => PsalmApi::$args->getArgType($event, $head_arg))
- ->flatMap(GetCollectionTypeParams::keyValue(...))
- ->flatMap(
- fn(CollectionTypeParams $type_params) => ArrayList::range(1, $partition_count + 1)
- ->traverseOption(fn(int $offset) => sequenceOptionT(
- fn() => Option::some(RefineForEnum::Value),
- fn() => at($args, $offset)->pluck('value')->flatMap(of(FunctionLike::class)),
- fn() => Option::some($event->getContext()),
- fn() => proveOf($event->getStatementsSource(), StatementsAnalyzer::class),
- fn() => Option::some($type_params),
- ))
- ->map(function(ArrayList $args) use ($type_params) {
- $init_types = $args
- ->mapN(ctor(RefinementContext::class))
- ->map(RefineByPredicate::for(...))
- ->map(fn(CollectionTypeParams $params) => $params->val_type);
-
- $last_type = $init_types
- ->fold($type_params->val_type)(
- function(Union $last, Union $current) {
- $cloned = clone $last;
- $cloned->removeType($current->getId());
-
- return $cloned;
- },
- );
-
- return $init_types
- ->appended($last_type)
- ->map(fn(Union $type) => [new TList($type)])
- ->map(ctor(Union::class))
- ->toList();
- })
- )
- ->flatMap(fn(array $partitions) => asNonEmptyArray($partitions))
- ->map(function(array $non_empty_partitions) {
- $tuple = new TKeyedArray($non_empty_partitions);
- $tuple->is_list = true;
- $tuple->sealed = true;
-
- return new Union([$tuple]);
- })
- ->get();
- }
-}
diff --git a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/PluckFunctionReturnTypeProvider.php b/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/PluckFunctionReturnTypeProvider.php
deleted file mode 100644
index 7e133b63..00000000
--- a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/PluckFunctionReturnTypeProvider.php
+++ /dev/null
@@ -1,85 +0,0 @@
-getCallArgs($event)
- ->flatMap(fn(ArrayList $args) => sequenceOptionT(
- fn() => $args->lastElement()
- ->pluck('type')
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->flatMap(of(TLiteralString::class)),
- fn() => $args->firstElement()
- ->pluck('type')
- ->flatMap(GetCollectionTypeParams::value(...))
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->flatMap(fn($atomic) => proveOf($atomic, [TNamedObject::class, TKeyedArray::class])),
- fn() => Option::some($event->getStatementsSource()),
- fn() => Option::some($event->getCodeLocation()),
- ))
- ->mapN(ctor(PluckResolveContext::class))
- ->flatMap(PluckPropertyTypeResolver::resolve(...))
- ->map(fn(Union $result) => [
- match (true) {
- self::itWas(TNonEmptyList::class, $event) => new TNonEmptyList($result),
- self::itWas(TList::class, $event) => new TList($result),
- self::itWas(TNonEmptyArray::class, $event) => new TNonEmptyArray([self::getArrayKey($event), $result]),
- default => new TArray([self::getArrayKey($event), $result]),
- },
- ])
- ->map(ctor(Union::class))
- ->get();
- }
-
- private static function getArrayKey(FunctionReturnTypeProviderEvent $event): Union
- {
- return PsalmApi::$args->getCallArgs($event)
- ->flatMap(fn(ArrayList $args) => $args->head())
- ->pluck('type')
- ->flatMap(GetCollectionTypeParams::key(...))
- ->getOrCall(fn() => Type::getArrayKey());
- }
-
- private static function itWas(string $class, FunctionReturnTypeProviderEvent $event): bool
- {
- return PsalmApi::$args->getCallArgs($event)
- ->flatMap(fn(ArrayList $args) => $args->head())
- ->pluck('type')
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->map(fn(Type\Atomic $atomic) => $atomic instanceof $class)
- ->getOrElse(false);
- }
-}
diff --git a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/SequenceEitherAccFunctionReturnTypeProvider.php b/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/SequenceEitherAccFunctionReturnTypeProvider.php
deleted file mode 100644
index 84fd6ba3..00000000
--- a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/SequenceEitherAccFunctionReturnTypeProvider.php
+++ /dev/null
@@ -1,70 +0,0 @@
-getFirstCallArgType($event)
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->flatMap(of(TKeyedArray::class))
- ->flatMap(fn(TKeyedArray $either_shape) => sequenceOptionT(
- self::mapEither($either_shape, GetEitherTypeParam::GET_LEFT),
- self::mapEither($either_shape, GetEitherTypeParam::GET_RIGHT),
- ))
- ->map(fn(array $type_params) => [
- new TGenericObject(Either::class, $type_params),
- ])
- ->map(ctor(Union::class))
- ->get();
- }
-
- /**
- * @param GetEitherTypeParam::GET_* $idx
- * @return Option
- */
- private static function mapEither(TKeyedArray $either_shape, int $idx): Option
- {
- return NonEmptyHashMap::collectNonEmpty($either_shape->properties)
- ->traverseOption(fn($property_type) => GetEitherTypeParam::from($property_type, $idx))
- ->map(fn(NonEmptyHashMap $properties) => $properties->map(
- fn(Union $property_type) => $idx === GetEitherTypeParam::GET_LEFT
- ? PsalmApi::$types->asPossiblyUndefined($property_type)
- : $property_type
- ))
- ->map(function(NonEmptyHashMap $properties) use ($idx) {
- $is_list = $properties->keys()->every(is_int(...));
-
- $keyed = new TKeyedArray($properties->toNonEmptyArray());
- $keyed->is_list = $is_list;
- $keyed->sealed = $is_list;
-
- return new Union([$keyed]);
- });
- }
-}
diff --git a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/SequenceEitherFunctionReturnTypeProvider.php b/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/SequenceEitherFunctionReturnTypeProvider.php
deleted file mode 100644
index f3026c03..00000000
--- a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/SequenceEitherFunctionReturnTypeProvider.php
+++ /dev/null
@@ -1,102 +0,0 @@
-orElse(fn() => self::getInputTypeFromSequenceEitherT($event))
- ->flatMap(fn(TKeyedArray $types) => sequenceOptionT(
- fn() => NonEmptyHashMap::collectNonEmpty($types->properties)
- ->traverseOption(GetEitherTypeParam::left(...))
- ->map(fn(NonEmptyHashMap $props) => $props->values()->toNonEmptyList())
- ->map(fn(array $left_cases) => Type::combineUnionTypeArray($left_cases, PsalmApi::$codebase)),
- fn() => NonEmptyHashMap::collectNonEmpty($types->properties)
- ->traverseOption(GetEitherTypeParam::right(...))
- ->map(function(NonEmptyHashMap $props) {
- $is_list = $props->keys()->every(is_int(...));
-
- $keyed = new TKeyedArray($props->toNonEmptyArray());
- $keyed->is_list = $is_list;
- $keyed->sealed = $is_list;
-
- return new Union([$keyed]);
- }),
- ))
- ->map(fn(array $type_params) => [
- new TGenericObject(Either::class, $type_params),
- ])
- ->map(ctor(Union::class))
- ->get();
- }
-
- /**
- * @return Option
- */
- private static function getInputTypeFromSequenceEither(FunctionReturnTypeProviderEvent $event): Option
- {
- $isSequenceEither = contains($event->getFunctionId(), [
- strtolower('Fp\Collection\sequenceEither'),
- strtolower('Fp\Collection\sequenceEitherMerged'),
- ]);
-
- return proveTrue($isSequenceEither)
- ->flatMap(fn() => PsalmApi::$args->getCallArgs($event))
- ->flatMap(fn($args) => $args->head())
- ->flatMap(fn(CallArg $arg) => PsalmApi::$types->asSingleAtomic($arg->type))
- ->flatMap(of(TKeyedArray::class));
- }
-
- /**
- * @return Option
- */
- private static function getInputTypeFromSequenceEitherT(FunctionReturnTypeProviderEvent $event): Option
- {
- $isSequenceEitherT = contains($event->getFunctionId(), [
- strtolower('Fp\Collection\sequenceEitherT'),
- strtolower('Fp\Collection\sequenceEitherMergedT'),
- ]);
-
- return proveTrue($isSequenceEitherT)
- ->flatMap(fn() => PsalmApi::$args->getCallArgs($event))
- ->flatMap(fn(ArrayList $args) => $args->toNonEmptyArrayList())
- ->map(fn(NonEmptyArrayList $args) => new TKeyedArray(
- $args->map(fn(CallArg $arg) => $arg->type)->toNonEmptyList(),
- ));
- }
-}
diff --git a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/SequenceOptionFunctionReturnTypeProvider.php b/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/SequenceOptionFunctionReturnTypeProvider.php
deleted file mode 100644
index 9ecc5ccc..00000000
--- a/src/Fp/Psalm/Hook/FunctionReturnTypeProvider/SequenceOptionFunctionReturnTypeProvider.php
+++ /dev/null
@@ -1,97 +0,0 @@
-orElse(fn() => self::getInputTypeFromSequenceOptionT($event))
- ->flatMap(fn(TKeyedArray $types) => traverseOption($types->properties, self::getOptionTypeParam(...)))
- ->map(function(array $mapped) {
- $is_list = array_is_list($mapped);
-
- $keyed = new TKeyedArray($mapped);
- $keyed->is_list = $is_list;
- $keyed->sealed = $is_list;
-
- return new Union([
- new TGenericObject(Option::class, [
- new Union([$keyed]),
- ]),
- ]);
- })
- ->get();
- }
-
- /**
- * @return Option
- */
- private static function getInputTypeFromSequenceOption(FunctionReturnTypeProviderEvent $event): Option
- {
- return proveTrue(strtolower('Fp\Collection\sequenceOption') === $event->getFunctionId())
- ->flatMap(fn() => PsalmApi::$args->getCallArgs($event))
- ->flatMap(fn(ArrayList $args) => $args->head())
- ->map(fn(CallArg $arg) => $arg->type)
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->flatMap(of(TKeyedArray::class));
- }
-
- /**
- * @return Option
- */
- private static function getInputTypeFromSequenceOptionT(FunctionReturnTypeProviderEvent $event): Option
- {
- return proveTrue(strtolower('Fp\Collection\sequenceOptionT') === $event->getFunctionId())
- ->flatMap(fn() => PsalmApi::$args->getCallArgs($event))
- ->flatMap(fn(ArrayList $args) => $args->toNonEmptyArrayList())
- ->map(fn(NonEmptyArrayList $args) => new TKeyedArray(
- $args->map(fn(CallArg $arg) => $arg->type)->toNonEmptyList(),
- ));
- }
-
- /**
- * @return Option
- */
- private static function getOptionTypeParam(Union $type): Option
- {
- return PsalmApi::$types->asSingleAtomic($type)
- ->flatMap(fn(Atomic $atomic) => match (true) {
- $atomic instanceof TGenericObject => Option::some($atomic)
- ->filter(fn(TGenericObject $generic) => $generic->value === Option::class)
- ->flatMap(fn(TGenericObject $option) => first($option->type_params)),
- $atomic instanceof TClosure => Option::fromNullable($atomic->return_type)
- ->flatMap(self::getOptionTypeParam(...)),
- default => Option::none(),
- });
- }
-}
diff --git a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/CollectionFilterMethodReturnTypeProvider.php b/src/Fp/Psalm/Hook/MethodReturnTypeProvider/CollectionFilterMethodReturnTypeProvider.php
deleted file mode 100644
index 1e5ba834..00000000
--- a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/CollectionFilterMethodReturnTypeProvider.php
+++ /dev/null
@@ -1,105 +0,0 @@
-getMethodNameLowercase(), self::getMethodNames()))
- ->flatMap(fn() => sequenceOptionT(
- fn() => Option::some($event->getMethodNameLowercase() === strtolower('filterKV')
- ? RefineForEnum::KeyValue
- : RefineForEnum::Value),
- fn() => PredicateExtractor::extract($event),
- fn() => Option::some($event->getContext()),
- fn() => proveOf($event->getSource(), StatementsAnalyzer::class),
- fn() => Option::fromNullable($event->getTemplateTypeParameters())
- ->map(fn($template_params) => 2 === count($template_params)
- ? new CollectionTypeParams($template_params[0], $template_params[1])
- : new CollectionTypeParams(Type::getArrayKey(), $template_params[0])),
- ))
- ->mapN(ctor(RefinementContext::class))
- ->map(RefineByPredicate::for(...))
- ->map(fn(CollectionTypeParams $result) => self::getReturnType($event, $result))
- ->get();
- }
-
- private static function getReturnType(MethodReturnTypeProviderEvent $event, CollectionTypeParams $result): Union
- {
- $class_name = str_replace('NonEmpty', '', $event->getFqClasslikeName());
-
- $template_params = 2 === count($event->getTemplateTypeParameters() ?? [])
- ? [$result->key_type, $result->val_type]
- : [$result->val_type];
-
- return new Union([
- new TGenericObject($class_name, $template_params),
- ]);
- }
-}
diff --git a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/EitherFilterOrElseMethodReturnTypeProvider.php b/src/Fp/Psalm/Hook/MethodReturnTypeProvider/EitherFilterOrElseMethodReturnTypeProvider.php
deleted file mode 100644
index 82f506b5..00000000
--- a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/EitherFilterOrElseMethodReturnTypeProvider.php
+++ /dev/null
@@ -1,75 +0,0 @@
-getMethodNameLowercase())
- ->flatMap(fn() => sequenceOptionT(
- fn() => Option::some(RefineForEnum::Value),
- fn() => PredicateExtractor::extract($event),
- fn() => Option::some($event->getContext()),
- fn() => proveOf($event->getSource(), StatementsAnalyzer::class),
- fn() => second($event->getTemplateTypeParameters() ?? [])
- ->map(fn($value_type) => [Type::getArrayKey(), $value_type])
- ->mapN(ctor(CollectionTypeParams::class)),
- ))
- ->mapN(ctor(RefinementContext::class))
- ->map(RefineByPredicate::for(...))
- ->map(fn(CollectionTypeParams $result) => new TGenericObject(Either::class, [
- self::getOutLeft($event),
- $result->val_type,
- ]))
- ->map(fn(TGenericObject $result) => new Union([$result]))
- ->get();
- }
-
- private static function getOutLeft(MethodReturnTypeProviderEvent $event): Union
- {
- return Type::combineUnionTypes(
- first($event->getTemplateTypeParameters() ?? [])
- ->getOrCall(fn() => Type::getNever()),
- second($event->getCallArgs())
- ->flatMap(fn(Arg $arg) => PsalmApi::$args->getArgType($event, $arg))
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->flatMap(of([TClosure::class, TCallable::class]))
- ->flatMap(fn(TClosure|TCallable $func) => Option::fromNullable($func->return_type))
- ->getOrCall(fn() => Type::getNever()),
- );
- }
-}
diff --git a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/EitherGetReturnTypeProvider.php b/src/Fp/Psalm/Hook/MethodReturnTypeProvider/EitherGetReturnTypeProvider.php
deleted file mode 100644
index eca17587..00000000
--- a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/EitherGetReturnTypeProvider.php
+++ /dev/null
@@ -1,45 +0,0 @@
- match (true) {
- str_starts_with($possibility, '!' . Right::class) => new Union([
- new TGenericObject(Left::class, [$either->type_params[0]]),
- ]),
- str_starts_with($possibility, '!' . Left::class) => new Union([
- new TGenericObject(Right::class, [$either->type_params[1]]),
- ]),
- default => new Union([$either]),
- },
- to_return_type: fn(TGenericObject $generic) => match ($generic->value) {
- Left::class, Right::class => first($generic->type_params),
- default => Option::none(),
- },
- );
- }
-}
diff --git a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/FoldMethodReturnTypeProvider.php b/src/Fp/Psalm/Hook/MethodReturnTypeProvider/FoldMethodReturnTypeProvider.php
deleted file mode 100644
index 683248f3..00000000
--- a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/FoldMethodReturnTypeProvider.php
+++ /dev/null
@@ -1,211 +0,0 @@
-orElse(fn() => self::foldReturnType($event))
- ->get();
- }
-
- /**
- * @return Option
- */
- private static function foldReturnType(MethodReturnTypeProviderEvent $event): Option
- {
- // $list->fold(0)(fn($acc, $cur) => $acc + $cur)
- // ^ ^
- // | |
- // | |
- // TInit TFold (return type of function)
- return proveTrue(FoldOperation::class === $event->getFqClasslikeName())
-
- // Get TInit and TFold
- ->flatMap(fn() => sequenceOptionT(
- fn() => second($event->getTemplateTypeParameters() ?? []),
- fn() => proveOf($event->getStmt(), MethodCall::class)
- ->map(fn(MethodCall $call) => $call->getArgs())
- ->flatMap(fn(array $args) => first($args))
- ->flatMap(fn(Arg $arg) => PsalmApi::$args->getArgType($event, $arg))
- ->flatMap(fn(Union $type) => PsalmApi::$types->asSingleAtomicOf(TClosure::class, $type))
- ->flatMap(fn(TClosure $closure) => Option::fromNullable($closure->return_type))
- ->map(fn(Union $type) => PsalmApi::$types->asNonLiteralType($type))
- ->flatMap(fn(Union $type) => PsalmApi::$types->asSingleAtomic($type))
- ->map(fn(Atomic $atomic) => new Union([$atomic]))
- ))
-
- // TFold must be assignable to TInit
- ->tapN(function(Union $TInit, Union $TFold) use ($event) {
- // $fold = $integers->fold(ArrayList::empty()) === FoldingOperation> (second param is IInit)
- // $fold(fn($list, $num) => $list->appended($num + 1));
- // ^
- // |
- // |
- // Type will be ArrayList (It is TFold)
- //
- // We must check that the TFold is assignable to the TInit.
- // But ArrayList cannot be assigned to ArrayList because the never is supertype of the int.
- // The neverToMixed swaps never type to mixed (Hope this won't be a problem)
- if (PsalmApi::$types->isTypeContainedByType($TFold, self::neverToMixed($TInit))) {
- return;
- }
-
- $source = $event->getSource();
-
- IssueBuffer::accepts(
- e: new InvalidReturnStatement(
- message: "The inferred type '{$TFold->getId()}' does not match the declared return type '{$TInit->getId()}'",
- code_location: $event->getCodeLocation(),
- ),
- suppressed_issues: $source->getSuppressedIssues(),
- );
- })
-
- // Fold return type will be subsume of the TFold and the TInit
- // when TInit = ArrayList
- // and TFold = ArrayList
- // then ArrayList | ArrayList = ArrayList
- ->map(fn($types) => Type::combineUnionTypeArray($types, PsalmApi::$codebase));
- }
-
- /**
- * @return Option
- */
- private static function removeLiteralsFromTInit(MethodReturnTypeProviderEvent $event): Option
- {
- // $fold = $integers->fold(0) === FoldingOperation
- // $fold(fn($sum, $num) => $sum + $num);
- // ^
- // |
- // |
- // Type will be the int
- //
- // In the example above TInit is the int type, but TInit is the 0 (literal type)
- // The int is not assignable to 0.
- // It also extends to other literal types.
- // Next code maps any literal types to non-literal analog.
- // Then $integers->fold(0) will be FoldingOperation
- return proveTrue('fold' === $event->getMethodNameLowercase())
- ->flatMap(fn() => sequenceOptionT(
- fn() => last($event->getTemplateTypeParameters() ?? []),
- fn() => proveOf($event->getStmt(), MethodCall::class)
- ->flatMap(fn(MethodCall $call) => first($call->getArgs()))
- ->flatMap(fn(Arg $arg) => PsalmApi::$args->getArgType($event, $arg))
- ->map(fn(Union $type) => PsalmApi::$types->asNonLiteralType($type))
- ))
- ->mapN(fn(Union $A, Union $TInit) => [
- new TGenericObject(FoldOperation::class, [$A, $TInit]),
- ])
- ->map(ctor(Union::class));
- }
-
- private static function neverToMixed(Union $type): Union
- {
- return new Union(
- map(asList($type->getAtomicTypes()), fn(Atomic $a) => match (true) {
- $a instanceof TKeyedArray => $a->is_list
- ? new TNonEmptyList(
- self::neverToMixed($a->getGenericValueType()),
- )
- : new TNonEmptyArray([
- self::neverToMixed($a->getGenericKeyType()),
- self::neverToMixed($a->getGenericValueType()),
- ]),
- $a instanceof TNonEmptyList => new TNonEmptyList(
- self::neverToMixed($a->type_param),
- ),
- $a instanceof TList => new TList(
- self::neverToMixed($a->type_param),
- ),
- $a instanceof TNonEmptyArray => new TNonEmptyArray([
- self::neverToMixed($a->type_params[0]),
- self::neverToMixed($a->type_params[1]),
- ]),
- $a instanceof TArray => new TArray([
- self::neverToMixed($a->type_params[0]),
- self::neverToMixed($a->type_params[1]),
- ]),
- $a instanceof TGenericObject => new TGenericObject(
- $a->value,
- map($a->type_params, fn(Union $t) => self::neverToMixed($t)),
- ),
- default => $a instanceof TNever || $a instanceof TEmpty ? new TMixed() : $a,
- }),
- );
- }
-}
diff --git a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/MapTapNMethodReturnTypeProvider.php b/src/Fp/Psalm/Hook/MethodReturnTypeProvider/MapTapNMethodReturnTypeProvider.php
deleted file mode 100644
index 801d28be..00000000
--- a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/MapTapNMethodReturnTypeProvider.php
+++ /dev/null
@@ -1,245 +0,0 @@
-flatMap(fn() => proveNonEmptyList($event->getTemplateTypeParameters() ?? []));
-
- // Allows call *N combinators in the origin class without type-check.
- yield proveFalse(in_array($event->getContext()->self, self::getClassLikeNames()));
-
- // Take the most right template:
- // Option -> A
- // Either -> A
- // Map -> A
- $current_args = yield Option::firstT(
- fn() => last($templates)
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->flatMap(of(TKeyedArray::class))
- ->filter(fn(TKeyedArray $keyed) => self::isTuple($keyed) || self::isAssoc($keyed)),
- fn() => self::valueTypeIsNotValidKeyedArrayIssue($event),
- );
-
- $current_args_kind = self::isTuple($current_args)
- ? MapTapNContextEnum::Tuple
- : MapTapNContextEnum::Shape;
-
- // $callback mapN/tapN argument
- $map_callback = yield PsalmApi::$args->getCallArgs($event)
- ->flatMap(fn(ArrayList $args) => $args->firstElement())
- ->pluck('type')
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->flatMap(of([TCallable::class, TClosure::class]));
-
- // Input tuple type inferred by $callback argument
- $func_args = yield Option::some($map_callback)
- ->flatMap(fn(TCallable|TClosure $func) => HashMap::collect($func->params ?? [])
- ->reindexKV(fn(int $idx, FunctionLikeParameter $param) => $current_args_kind === MapTapNContextEnum::Shape ? $param->name : $idx)
- ->map(function(FunctionLikeParameter $param) use ($current_args_kind) {
- $param_type = $param->type ?? Type::getMixed();
-
- return $current_args_kind === MapTapNContextEnum::Shape && $param->is_optional
- ? PsalmApi::$types->asPossiblyUndefined($param_type)
- : $param_type;
- })
- ->toNonEmptyArray())
- ->map(ctor(TKeyedArray::class));
-
- $ctx = new MapTapNContext(
- event: $event,
- func_args: $func_args,
- current_args: $current_args,
- kind: $current_args_kind,
- is_variadic: last($map_callback->params ?? [])
- ->pluck('is_variadic')
- ->getOrElse(false),
- optional_count: ArrayList::collect($map_callback->params ?? [])
- ->filter(fn(FunctionLikeParameter $p) => $p->is_optional)
- ->count(),
- required_count: ArrayList::collect($map_callback->params ?? [])
- ->filter(fn(FunctionLikeParameter $p) => !$p->is_optional)
- ->count(),
- );
-
- Option::firstT(
- fn() => proveFalse($ctx->is_variadic && self::isAssoc($current_args)),
- fn() => self::cannotSafelyCallShapeWithVariadicArg($ctx),
- );
-
- // Assert that $func_args is assignable to $current_args
- Option::firstT(
- fn() => proveTrue(self::isTypeContainedByType($ctx)),
- fn() => self::typesAreNotCompatibleIssue($ctx),
- );
- });
-
- return null;
- }
-
- private static function isSupportedMethod(MethodReturnTypeProviderEvent $event): bool
- {
- $methods = [
- 'mapN',
- 'tapN',
- 'flatMapN',
- 'flatTapN',
- 'reindexN',
- 'filterN',
- 'filterMapN',
- 'everyN',
- 'existsN',
- 'traverseOptionN',
- 'traverseEitherN',
- 'partitionN',
- 'partitionMapN',
- 'firstN',
- 'lastN',
- ];
-
- return exists($methods, fn($method) => $event->getMethodNameLowercase() === strtolower($method));
- }
-
- private static function isTuple(TKeyedArray $keyed): bool
- {
- return array_is_list($keyed->properties);
- }
-
- private static function isAssoc(TKeyedArray $keyed): bool
- {
- return every(keys($keyed->properties), is_string(...));
- }
-
- /**
- * @return Option
- */
- private static function valueTypeIsNotValidKeyedArrayIssue(MethodReturnTypeProviderEvent $event): Option
- {
- $mappable_class = $event->getFqClasslikeName();
- $source = $event->getSource();
-
- $issue = new IfThisIsMismatch(
- message: "Value template of class {$mappable_class} must be tuple (all keys int from 0 to N) or shape (all keys is string)",
- code_location: $event->getCodeLocation(),
- );
-
- IssueBuffer::accepts($issue, $source->getSuppressedIssues());
- return Option::none();
- }
-
- /**
- * @return Option
- */
- private static function cannotSafelyCallShapeWithVariadicArg(MapTapNContext $ctx): Option
- {
- $source = $ctx->event->getSource();
-
- $issue = new IfThisIsMismatch(
- message: 'Shape cannot safely passed to function with variadic parameter.',
- code_location: $ctx->event->getCodeLocation(),
- );
-
- IssueBuffer::accepts($issue, $source->getSuppressedIssues());
- return Option::none();
- }
-
- /**
- * @return Option
- */
- private static function typesAreNotCompatibleIssue(MapTapNContext $ctx): Option
- {
- $mappable_class = $ctx->event->getFqClasslikeName();
- $source = $ctx->event->getSource();
-
- $tuned_func_args = $ctx->kind->tuneForOptionalAndVariadicParams($ctx);
-
- $issue = new IfThisIsMismatch(
- message: implode(', ', [
- "Object must be type of {$mappable_class}<{$tuned_func_args->getId()}>",
- "actual type {$mappable_class}<{$ctx->current_args->getId()}>",
- ]),
- code_location: $ctx->event->getCodeLocation(),
- );
-
- IssueBuffer::accepts($issue, $source->getSuppressedIssues());
- return Option::none();
- }
-
- private static function isTypeContainedByType(MapTapNContext $context): bool
- {
- return PsalmApi::$types->isTypeContainedByType(
- new Union([$context->current_args]),
- new Union([
- $context->kind->tuneForOptionalAndVariadicParams($context),
- ]),
- );
- }
-}
diff --git a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/OptionFilterMethodReturnTypeProvider.php b/src/Fp/Psalm/Hook/MethodReturnTypeProvider/OptionFilterMethodReturnTypeProvider.php
deleted file mode 100644
index 82de7f94..00000000
--- a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/OptionFilterMethodReturnTypeProvider.php
+++ /dev/null
@@ -1,52 +0,0 @@
-getMethodNameLowercase())
- ->flatMap(fn() => sequenceOptionT(
- fn() => Option::some(RefineForEnum::Value),
- fn() => PredicateExtractor::extract($event),
- fn() => Option::some($event->getContext()),
- fn() => proveOf($event->getSource(), StatementsAnalyzer::class),
- fn() => first($event->getTemplateTypeParameters() ?? [])
- ->map(fn($value_type) => [Type::getArrayKey(), $value_type])
- ->mapN(ctor(CollectionTypeParams::class)),
- ))
- ->mapN(ctor(RefinementContext::class))
- ->map(RefineByPredicate::for(...))
- ->map(fn(CollectionTypeParams $result) => new TGenericObject(Option::class, [$result->val_type]))
- ->map(fn(TGenericObject $result) => new Union([$result]))
- ->get();
- }
-}
diff --git a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/OptionGetReturnTypeProvider.php b/src/Fp/Psalm/Hook/MethodReturnTypeProvider/OptionGetReturnTypeProvider.php
deleted file mode 100644
index 2dc72a67..00000000
--- a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/OptionGetReturnTypeProvider.php
+++ /dev/null
@@ -1,52 +0,0 @@
- match (true) {
- str_starts_with($possibility, '!' . Some::class) => new Union([
- new TNamedObject(None::class),
- ]),
- str_starts_with($possibility, '!' . None::class) => new Union([
- new TGenericObject(Some::class, [$option->type_params[0]]),
- ]),
- default => new Union([$option]),
- },
- to_return_type: fn(TGenericObject $generic) => match ($generic->value) {
- Some::class => first($generic->type_params),
- Option::class => first($generic->type_params)->map(function(Union $union) {
- $nullable = clone $union;
- $nullable->addType(new Type\Atomic\TNull());
-
- return $nullable;
- }),
- default => Option::some(Type::getNull())
- },
- );
- }
-}
diff --git a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/PluckMethodReturnTypeProvider.php b/src/Fp/Psalm/Hook/MethodReturnTypeProvider/PluckMethodReturnTypeProvider.php
deleted file mode 100644
index e8b6f3df..00000000
--- a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/PluckMethodReturnTypeProvider.php
+++ /dev/null
@@ -1,59 +0,0 @@
-getMethodNameLowercase())
- ->flatMap(fn() => sequenceOptionT(
- fn() => PsalmApi::$args->getCallArgs($event)
- ->flatMap(fn(ArrayList $args) => $args->lastElement()
- ->pluck('type')
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->flatMap(of(TLiteralString::class))),
- fn() => Option::fromNullable($event->getTemplateTypeParameters())
- ->flatMap(fn(array $templates) => last($templates))
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->flatMap(fn(Atomic $atomic) => proveOf($atomic, [TNamedObject::class, TKeyedArray::class])),
- fn() => Option::some($event->getSource()),
- fn() => Option::some($event->getCodeLocation()),
- ))
- ->mapN(ctor(PluckResolveContext::class))
- ->flatMap(PluckPropertyTypeResolver::resolve(...))
- ->map(fn(Union $result) => [
- new TGenericObject(Option::class, [$result]),
- ])
- ->map(ctor(Union::class))
- ->get();
- }
-}
diff --git a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/SeparatedToEitherMethodReturnTypeProvider.php b/src/Fp/Psalm/Hook/MethodReturnTypeProvider/SeparatedToEitherMethodReturnTypeProvider.php
deleted file mode 100644
index 6f356587..00000000
--- a/src/Fp/Psalm/Hook/MethodReturnTypeProvider/SeparatedToEitherMethodReturnTypeProvider.php
+++ /dev/null
@@ -1,33 +0,0 @@
-getMethodNameLowercase())
- ->filter(fn(string $method) => strtolower('toEither') === $method)
- ->flatMap(fn() => proveNonEmptyList($event->getTemplateTypeParameters() ?? []))
- ->map(fn(array $templates) => new TGenericObject(Either::class, $templates))
- ->map(fn(TGenericObject $object) => new Union([$object]))
- ->get();
- }
-}
diff --git a/src/Fp/Psalm/Util/GetCollectionTypeParams.php b/src/Fp/Psalm/Util/GetCollectionTypeParams.php
deleted file mode 100644
index b2479252..00000000
--- a/src/Fp/Psalm/Util/GetCollectionTypeParams.php
+++ /dev/null
@@ -1,197 +0,0 @@
-
- */
- public static function keyValue(Type\Union $union): Option
- {
- return GetCollectionTypeParams::value($union)
- ->map(fn($val_type) => new CollectionTypeParams(
- key_type: GetCollectionTypeParams::key($union)->getOrCall(fn() => Type::getArrayKey()),
- val_type: $val_type
- ));
- }
-
- /**
- * @return Option
- */
- public static function key(Type\Union $union): Option
- {
- return PsalmApi::$types
- ->asSingleAtomic($union)
- ->flatMap(fn($atomic) => Option::firstT(
- fn() => self::keyFromIterable($atomic),
- fn() => self::keyFromGenerator($atomic),
- fn() => self::keyFromArray($atomic),
- fn() => self::keyFromGenericObject($atomic),
- fn() => self::keyFromKeyedArray($atomic),
- ));
- }
-
- /**
- * @return Option
- */
- private static function keyFromIterable(Type\Atomic $atomic): Option
- {
- return Option::some($atomic)
- ->flatMap(of(Type\Atomic\TIterable::class))
- ->flatMap(fn(Type\Atomic\TIterable $a) => first($a->type_params));
- }
-
- /**
- * @return Option
- */
- private static function keyFromGenerator(Type\Atomic $atomic): Option
- {
- return Option::some($atomic)
- ->flatMap(of(Type\Atomic\TGenericObject::class))
- ->filter(fn(Type\Atomic\TGenericObject $generic) => $generic->value === Generator::class)
- ->flatMap(fn(Type\Atomic\TGenericObject $generic) => first($generic->type_params));
- }
-
- /**
- * @return Option
- */
- private static function keyFromArray(Type\Atomic $atomic): Option
- {
- return Option::some($atomic)
- ->flatMap(of(Type\Atomic\TArray::class))
- ->flatMap(fn(Type\Atomic\TArray $a) => first($a->type_params));
- }
-
- /**
- * @return Option
- */
- private static function keyFromKeyedArray(Type\Atomic $atomic): Option
- {
- return Option::some($atomic)
- ->flatMap(of(Type\Atomic\TKeyedArray::class))
- ->map(fn(Type\Atomic\TKeyedArray $a) => $a->getGenericKeyType());
- }
-
- /**
- * @return Option
- */
- private static function keyFromGenericObject(Type\Atomic $atomic): Option
- {
- return Option::some($atomic)
- ->flatMap(of(Type\Atomic\TGenericObject::class))
- ->flatMap(
- fn(Type\Atomic\TGenericObject $a) => Option::some($a->value)
- ->flatMap(classStringOf([Map::class, NonEmptyMap::class]))
- ->flatMap(fn() => second($a->type_params)),
- );
- }
-
- /**
- * @return Option
- */
- public static function value(Type\Union $union): Option
- {
- return PsalmApi::$types
- ->asSingleAtomic($union)
- ->flatMap(fn($atomic) => Option::firstT(
- fn() => self::valueFromIterable($atomic),
- fn() => self::valueFromGenerator($atomic),
- fn() => self::valueFromArray($atomic),
- fn() => self::valueFromList($atomic),
- fn() => self::valueFromGenericObject($atomic),
- fn() => self::valueFromKeyedArray($atomic),
- ));
- }
-
- /**
- * @return Option
- */
- private static function valueFromIterable(Type\Atomic $atomic): Option
- {
- return Option::some($atomic)
- ->flatMap(of(Type\Atomic\TIterable::class))
- ->flatMap(fn(Type\Atomic\TIterable $a) => second($a->type_params));
- }
-
- /**
- * @return Option
- */
- private static function valueFromGenerator(Type\Atomic $atomic): Option
- {
- return Option::some($atomic)
- ->flatMap(of(Type\Atomic\TGenericObject::class))
- ->filter(fn(Type\Atomic\TGenericObject $generic) => $generic->value === Generator::class)
- ->flatMap(fn(Type\Atomic\TGenericObject $generic) => second($generic->type_params));
- }
-
- /**
- * @return Option
- */
- private static function valueFromArray(Type\Atomic $atomic): Option
- {
- return Option::some($atomic)
- ->flatMap(of(Type\Atomic\TArray::class))
- ->flatMap(fn(Type\Atomic\TArray $a) => second($a->type_params));
- }
-
- /**
- * @return Option
- */
- private static function valueFromList(Type\Atomic $atomic): Option
- {
- return Option::some($atomic)
- ->flatMap(of(Type\Atomic\TList::class))
- ->map(fn(Type\Atomic\TList $a) => $a->type_param);
- }
-
- /**
- * @return Option
- */
- private static function valueFromKeyedArray(Type\Atomic $atomic): Option
- {
- return Option::some($atomic)
- ->flatMap(of(Type\Atomic\TKeyedArray::class))
- ->map(fn(Type\Atomic\TKeyedArray $a) => $a->getGenericValueType());
- }
-
- /**
- * @return Option
- */
- private static function valueFromGenericObject(Type\Atomic $atomic): Option
- {
- return Option::some($atomic)
- ->flatMap(of(Type\Atomic\TGenericObject::class))
- ->flatMap(fn(Type\Atomic\TGenericObject $a) => match (true) {
- is_subclass_of($a->value, Seq::class),
- is_subclass_of($a->value, Set::class),
- is_subclass_of($a->value, NonEmptySet::class),
- is_subclass_of($a->value, NonEmptySeq::class),
- is_subclass_of($a->value, Option::class) => first($a->type_params),
- is_subclass_of($a->value, Map::class),
- is_subclass_of($a->value, NonEmptyMap::class),
- is_subclass_of($a->value, Either::class) => second($a->type_params),
- default => Option::none(),
- });
- }
-}
diff --git a/src/Fp/Psalm/Util/MapTapNContext.php b/src/Fp/Psalm/Util/MapTapNContext.php
deleted file mode 100644
index 76b38bd7..00000000
--- a/src/Fp/Psalm/Util/MapTapNContext.php
+++ /dev/null
@@ -1,21 +0,0 @@
-func_args;
- }
-
- return $this->tuneForOptional(
- func_args: $ctx->is_variadic
- ? $this->tuneForVariadic($ctx->func_args, $ctx->current_args)
- : $ctx->func_args,
- drop_length: $ctx->optional_count > 0
- ? $ctx->required_count + $ctx->optional_count - count($ctx->current_args->properties)
- : 0,
- );
- }
-
- private function tuneForOptional(TKeyedArray $func_args, int $drop_length): TKeyedArray
- {
- $propsOption = proveNonEmptyList(dropRight($func_args->properties, $drop_length));
-
- $cloned = clone $func_args;
- $cloned->properties = $propsOption->getOrElse($cloned->properties);
-
- return $cloned;
- }
-
- private function tuneForVariadic(TKeyedArray $func_args, TKeyedArray $current_args): TKeyedArray
- {
- $func_args_count = count($func_args->properties);
- $current_args_count = count($current_args->properties);
-
- $propsOption = match (true) {
- // There are variadic args: extend type with variadic param type
- $current_args_count > $func_args_count => last($func_args->properties)
- ->map(fn($last) => [
- ...$func_args->properties,
- ...array_fill(0, $current_args_count - $func_args_count, $last),
- ]),
-
- // No variadic args: remove variadic param from type
- $current_args_count < $func_args_count => proveNonEmptyArray(init($func_args->properties)),
-
- // Exactly one variadic arg: leave as is
- default => Option::none(),
- };
-
- $cloned = clone $func_args;
- $cloned->properties = $propsOption->getOrElse($cloned->properties);
-
- return $cloned;
- }
-}
diff --git a/src/Fp/Psalm/Util/Pluck/PluckPropertyTypeResolver.php b/src/Fp/Psalm/Util/Pluck/PluckPropertyTypeResolver.php
deleted file mode 100644
index 294b4597..00000000
--- a/src/Fp/Psalm/Util/Pluck/PluckPropertyTypeResolver.php
+++ /dev/null
@@ -1,95 +0,0 @@
-
- */
- public static function resolve(PluckResolveContext $context): Option
- {
- return self::getTypesForObject($context)
- ->orElse(fn() => self::getTypesForObjectLikeArray($context));
- }
-
- /**
- * @return Option
- * @todo: Templated properties are not supported
- */
- private static function getTypesForObject(PluckResolveContext $context): Option
- {
- return proveOf($context->object, TNamedObject::class)
- ->flatMap(PsalmApi::$classlikes->getStorage(...))
- ->flatMap(fn(ClassLikeStorage $storage) => at($storage->properties, $context->key->value)
- ->orElse(fn() => self::getPropertyFromParentClass($storage, $context->key->value))
- ->map(fn(PropertyStorage $property) => $property->type ?? Type::getMixed())
- ->orElse(fn() => self::undefinedPropertyIssue($storage->name, $context))
- );
- }
-
- /**
- * @return Option
- */
- private static function getPropertyFromParentClass(ClassLikeStorage $storage, string $property): Option
- {
- return at($storage->declaring_property_ids, $property)
- ->flatMap(PsalmApi::$classlikes->getStorage(...))
- ->flatMap(fn(ClassLikeStorage $parent) => at($parent->properties, $property));
- }
-
- /**
- * @return Option
- */
- private static function undefinedPropertyIssue(string $class, PluckResolveContext $context): Option
- {
- $issue = new UndefinedPropertyFetch(
- message: "Property '{$context->key->value}' is undefined",
- code_location: $context->location,
- property_id: "{$class}::\${$context->key->value}",
- );
-
- IssueBuffer::accepts($issue, $context->source->getSuppressedIssues());
- return Option::none();
- }
-
- /**
- * @return Option
- */
- private static function getTypesForObjectLikeArray(PluckResolveContext $context): Option
- {
- return proveOf($context->object, TKeyedArray::class)
- ->flatMap(fn(TKeyedArray $array) => at($array->properties, $context->key->value)
- ->orElse(fn() => self::undefinedArrayKeyIssue($context)));
- }
-
- /**
- * @return Option
- */
- private static function undefinedArrayKeyIssue(PluckResolveContext $context): Option
- {
- $issue = new PossiblyUndefinedArrayOffset(
- message: "Array key '{$context->key->value}' is undefined",
- code_location: $context->location,
- );
-
- IssueBuffer::accepts($issue, $context->source->getSuppressedIssues());
- return Option::none();
- }
-}
diff --git a/src/Fp/Psalm/Util/Pluck/PluckResolveContext.php b/src/Fp/Psalm/Util/Pluck/PluckResolveContext.php
deleted file mode 100644
index c3ff5e7e..00000000
--- a/src/Fp/Psalm/Util/Pluck/PluckResolveContext.php
+++ /dev/null
@@ -1,21 +0,0 @@
-
- */
- public static function left(Union $type): Option
- {
- return self::from($type, self::GET_LEFT);
- }
-
- /**
- * @return Option
- */
- public static function right(Union $type): Option
- {
- return self::from($type, self::GET_RIGHT);
- }
-
- /**
- * @param self::GET_* $idx
- * @return Option
- */
- public static function from(Union $type, int $idx): Option
- {
- return PsalmApi::$types->asSingleAtomic($type)
- ->flatMap(fn(Atomic $atomic) => match (true) {
- $atomic instanceof TGenericObject => Option::some($atomic)
- ->filter(fn(TGenericObject $generic) => $generic->value === Either::class)
- ->flatMap(fn(TGenericObject $option) => at($option->type_params, $idx)),
- $atomic instanceof TClosure => Option::fromNullable($atomic->return_type)
- ->flatMap(fn(Union $t) => self::from($t, $idx)),
- default => Option::none(),
- });
- }
-}
diff --git a/src/Fp/Psalm/Util/TypeFromPsalmAssertionResolver.php b/src/Fp/Psalm/Util/TypeFromPsalmAssertionResolver.php
deleted file mode 100644
index 418115bb..00000000
--- a/src/Fp/Psalm/Util/TypeFromPsalmAssertionResolver.php
+++ /dev/null
@@ -1,87 +0,0 @@
- $to_return_type
- */
- public static function getMethodReturnType(
- MethodReturnTypeProviderEvent $event,
- string $for_class,
- Closure $to_negated,
- Closure $to_return_type,
- string $get_method_name = 'get',
- ): null|Union {
- $stmt = $event->getStmt();
- $ctx = $event->getContext();
-
- return proveTrue($get_method_name === $event->getMethodNameLowercase())
- ->flatMap(fn() => self::getMethodVar($stmt))
- ->tap(fn() => self::negateAssertion($ctx, $for_class, $to_negated))
- ->flatMap(fn($called_variable) => at($ctx->vars_in_scope, $called_variable)
- ->flatMap(fn(Union $union) => PsalmApi::$types->asSingleAtomicOf(TGenericObject::class, $union))
- ->flatMap($to_return_type))
- ->get();
- }
-
- /**
- * @param Closure(TGenericObject, string): Union $to_negated
- * @param class-string $for_class
- */
- private static function negateAssertion(Context $context, string $for_class, Closure $to_negated): void
- {
- foreach ($context->clauses as $clause) {
- if (count($clause->possibilities) > 1) {
- continue;
- }
-
- foreach ($clause->possibilities as $variable => [$possibility]) {
- $reconciled = at($context->vars_in_scope, $variable)
- ->flatMap(fn(Union $from_scope) => PsalmApi::$types->asSingleAtomicOf(TGenericObject::class, $from_scope))
- ->filter(fn(TGenericObject $generic) => $generic->value === $for_class)
- ->map(fn(TGenericObject $generic) => $to_negated($generic, $possibility));
-
- if ($reconciled->isSome()) {
- $context->vars_in_scope[$variable] = $reconciled->get();
- }
- }
- }
- }
-
- /**
- * @return Option
- */
- private static function getMethodVar(Expr $expr): Option
- {
- return Option::do(function() use ($expr) {
- $method_call = yield proveOf($expr, MethodCall::class);
-
- return yield proveOf($method_call->var, MethodCall::class)
- ->flatMap(fn(MethodCall $call) => self::getMethodVar($call))
- ->orElse(fn() => proveOf($method_call->var, Variable::class)
- ->flatMap(fn(Variable $variable) => proveString($variable->name))
- ->map(fn($name) => "\${$name}"));
- });
- }
-}
diff --git a/src/Fp/Psalm/Util/TypeRefinement/CollectionTypeParams.php b/src/Fp/Psalm/Util/TypeRefinement/CollectionTypeParams.php
deleted file mode 100644
index 3115b241..00000000
--- a/src/Fp/Psalm/Util/TypeRefinement/CollectionTypeParams.php
+++ /dev/null
@@ -1,18 +0,0 @@
-
- */
- public static function extract(MethodReturnTypeProviderEvent|FunctionReturnTypeProviderEvent $event): Option
- {
- return Option::firstT(
- fn() => self::getPredicateCallback($event),
- fn() => self::mockNotNullPredicateArg($event),
- fn() => self::mockFirstClassCallable($event),
- );
- }
-
- /**
- * @return Option
- */
- private static function getPredicateCallback(MethodReturnTypeProviderEvent|FunctionReturnTypeProviderEvent $event): Option
- {
- $predicate_arg = $event instanceof MethodReturnTypeProviderEvent
- ? first($event->getCallArgs())
- : second($event->getCallArgs());
-
- return $predicate_arg
- ->map(fn(Arg $arg) => $arg->value)
- ->flatMap(of([Closure::class, ArrowFunction::class]));
- }
-
- /**
- * @psalm-return Option
- */
- private static function mockNotNullPredicateArg(MethodReturnTypeProviderEvent|FunctionReturnTypeProviderEvent $event): Option
- {
- return Option::some($event)
- ->flatMap(of(MethodReturnTypeProviderEvent::class))
- ->filter(fn($e) => $e->getMethodNameLowercase() === strtolower('filterNotNull'))
- ->map(fn() => new VirtualVariable('elem'))
- ->map(fn(VirtualVariable $var) => new VirtualArrowFunction([
- 'expr' => new Isset_([$var]),
- 'params' => [new Param($var)],
- ]));
- }
-
- /**
- * @psalm-return Option
- */
- private static function mockFirstClassCallable(MethodReturnTypeProviderEvent|FunctionReturnTypeProviderEvent $e): Option
- {
- $calling_id = $e instanceof MethodReturnTypeProviderEvent
- ? $e->getMethodNameLowercase()
- : $e->getFunctionId();
-
- $variables = strtolower('filterKV') === $calling_id || strtolower('Fp\Collection\filterKV') === $calling_id
- ? [new VirtualVariable('key'), new VirtualVariable('val')]
- : [new VirtualVariable('val')];
-
- $predicate_arg = $e instanceof MethodReturnTypeProviderEvent
- ? first($e->getCallArgs())
- : second($e->getCallArgs());
-
- return $predicate_arg
- ->map(fn(Arg $arg) => $arg->value)
- ->flatMap(of([FuncCall::class, MethodCall::class, StaticCall::class]))
- ->filter(fn(CallLike $call_like) => $call_like->isFirstClassCallable())
- ->flatMap(fn(CallLike $call_like) => self::createVirtualCall(
- statements_source: $e instanceof MethodReturnTypeProviderEvent ? $e->getSource() : $e->getStatementsSource(),
- self: $e->getContext()->self,
- original_call: $call_like,
- fake_variables: $variables,
- ))
- ->map(fn(CallLike $call_like) => new VirtualArrowFunction([
- 'expr' => $call_like,
- 'params' => map($variables, ctor(Param::class)),
- ]));
- }
-
- /**
- * @param FuncCall|MethodCall|StaticCall $original_call
- * @param non-empty-list $fake_variables
- * @return Option
- */
- private static function createVirtualCall(
- StatementsSource $statements_source,
- null|string $self,
- CallLike $original_call,
- array $fake_variables,
- ): Option {
- $function_id = match (true) {
- $original_call instanceof FuncCall => Option::fromNullable($original_call->name->getAttribute('resolvedName'))
- ->orElse(fn() => Option::some($original_call->name)
- ->flatMap(of(Name::class))
- ->map(fn(Name $name) => (string) $name))
- ->flatMap(proveNonEmptyString(...)),
- $original_call instanceof MethodCall => sequenceOptionT(
- PsalmApi::$types->getType($statements_source, $original_call->var)
- ->flatMap(PsalmApi::$types->asSingleAtomic(...))
- ->flatMap(of(TNamedObject::class))
- ->map(fn(TNamedObject $object) => $object->value),
- Option::some($original_call->name)
- ->flatMap(of(Identifier::class))
- ->map(fn(Identifier $id) => (string) $id),
- )->mapN(self::toMethodId(...)),
- $original_call instanceof StaticCall => sequenceOptionT(
- Option::some($original_call->class)
- ->flatMap(of(Name::class))
- ->map(fn(Name $id) => (string) $id)
- ->map(fn($name) => in_array($name, ['self', 'static', 'parent']) ? $self : $name)
- ->flatMap(proveNonEmptyString(...)),
- Option::some($original_call->name)
- ->flatMap(of(Identifier::class))
- ->map(fn(Identifier $id) => (string) $id),
- )->mapN(self::toMethodId(...)),
- };
-
- $args = map($fake_variables, ctor(VirtualArg::class));
-
- return $function_id->flatMap(
- fn($id) => self::withCustomAssertions($id, $statements_source, match (true) {
- $original_call instanceof FuncCall => new VirtualFuncCall($original_call->name, $args),
- $original_call instanceof MethodCall => new VirtualMethodCall($original_call->var, $original_call->name, $args),
- $original_call instanceof StaticCall => new VirtualStaticCall($original_call->class, $original_call->name, $args),
- })
- );
- }
-
- /**
- * @return non-empty-string
- */
- private static function toMethodId(string $class_name, string $method_name): string
- {
- return "{$class_name}::{$method_name}";
- }
-
- /**
- * @param non-empty-string $function_id
- * @param VirtualStaticCall|VirtualFuncCall|VirtualMethodCall $expr
- * @return Option
- */
- private static function withCustomAssertions(string $function_id, StatementsSource $source, CallLike $expr): Option
- {
- return Option::some($source)
- ->flatMap(of(StatementsAnalyzer::class))
- ->flatMap(fn(StatementsAnalyzer $analyzer) => Option
- ::when(
- cond: PsalmApi::$codebase->functions->functionExists($analyzer, strtolower($function_id)),
- some: fn() => PsalmApi::$codebase->functions->getStorage($analyzer, strtolower($function_id)),
- )
- ->orElse(fn() => Option::when(
- cond: PsalmApi::$codebase->methods->hasStorage(MethodIdentifier::wrap($function_id)),
- some: fn() => PsalmApi::$codebase->methods->getStorage(MethodIdentifier::wrap($function_id)),
- ))
- ->tap(fn(FunctionLikeStorage $storage) => $analyzer->node_data->setIfTrueAssertions($expr, $storage->if_true_assertions))
- ->tap(fn(FunctionLikeStorage $storage) => $analyzer->node_data->setIfFalseAssertions($expr, $storage->if_false_assertions))
- ->map(fn() => $expr));
- }
-}
diff --git a/src/Fp/Psalm/Util/TypeRefinement/RefineByPredicate.php b/src/Fp/Psalm/Util/TypeRefinement/RefineByPredicate.php
deleted file mode 100644
index 7a43fe20..00000000
--- a/src/Fp/Psalm/Util/TypeRefinement/RefineByPredicate.php
+++ /dev/null
@@ -1,174 +0,0 @@
->>
- */
-final class RefineByPredicate
-{
- /**
- * Refine collection type-parameters
- * By predicate expression
- */
- public static function for(RefinementContext $context): CollectionTypeParams
- {
- return new CollectionTypeParams(
- self::forKey($context)->getOrElse($context->type_params->key_type),
- self::forValue($context)->getOrElse($context->type_params->val_type),
- );
- }
-
- /**
- * Try to refine collection key type-parameter
- *
- * @psalm-return Option
- */
- private static function forKey(RefinementContext $context): Option
- {
- return sequenceOptionT(
- fn() => self::getPredicateKeyArgumentName($context),
- fn() => self::getPredicateSingleReturn($context),
- fn() => Option::some($context),
- fn() => Option::some($context->type_params->key_type),
- )->flatMapN(self::refine(...));
- }
-
- /**
- * Try to refine collection value type-parameter
- *
- * @psalm-return Option
- */
- private static function forValue(RefinementContext $context): Option
- {
- return sequenceOptionT(
- fn() => self::getPredicateValueArgumentName($context),
- fn() => self::getPredicateSingleReturn($context),
- fn() => Option::some($context),
- fn() => Option::some($context->type_params->val_type),
- )->flatMapN(self::refine(...));
- }
-
- /**
- * Returns key argument name of $predicate that going to be refined.
- *
- * @psalm-return Option
- */
- private static function getPredicateKeyArgumentName(RefinementContext $context): Option
- {
- return Option::some($context->refine_for)
- ->filter(fn($refine_for) => RefineForEnum::KeyValue === $context->refine_for)
- ->flatMap(fn() => first($context->predicate->getParams()))
- ->flatMap(fn($key_param) => proveOf($key_param->var, Variable::class))
- ->flatMap(fn($variable) => proveString($variable->name))
- ->map(fn($name) => '$' . $name);
- }
-
- /**
- * Returns value argument name of $predicate that going to be refined.
- *
- * @psalm-return Option
- */
- private static function getPredicateValueArgumentName(RefinementContext $context): Option
- {
- return Option::some($context->refine_for)
- ->filter(fn($refine_for) => RefineForEnum::Value === $context->refine_for)
- ->flatMap(fn() => first($context->predicate->getParams()))
- ->orElse(fn() => second($context->predicate->getParams()))
- ->flatMap(fn($value_param) => proveOf($value_param->var, Node\Expr\Variable::class))
- ->flatMap(fn($variable) => proveString($variable->name))
- ->map(fn($name) => '$' . $name);
- }
-
- /**
- * Returns single return expression of $predicate if present.
- * Collection type parameter can be refined only for function with single return.
- *
- * @psalm-return Option
- */
- private static function getPredicateSingleReturn(RefinementContext $context): Option
- {
- return Option::fromNullable($context->predicate->getStmts())
- ->filter(fn($stmts) => 1 === count($stmts))
- ->flatMap(fn($stmts) => firstMap($stmts, of(Return_::class)))
- ->flatMap(fn($return) => Option::fromNullable($return->expr));
- }
-
- /**
- * Collects assertion for $predicate_arg_name from $return_expr.
- *
- * @return Option
- */
- private static function collectAssertions(RefinementContext $context, Expr $return_expr): Option
- {
- $cond_object_id = spl_object_id($return_expr);
-
- $truths = Algebra::getTruthsFromFormula(
- clauses: FormulaGenerator::getFormula(
- conditional_object_id: $cond_object_id,
- creating_object_id: $cond_object_id,
- conditional: $return_expr,
- this_class_name: $context->execution_context->self,
- source: $context->source,
- codebase: PsalmApi::$codebase,
- ),
- creating_conditional_id: $cond_object_id,
- );
-
- return proveNonEmptyArray($truths);
- }
-
- /**
- * Reconciles $collection_type_param with $assertions using internal Psalm api.
- *
- * @psalm-param PsalmAssertions $assertions
- * @psalm-return Option
- */
- private static function refine(
- string $arg_name,
- Expr $return_expr,
- RefinementContext $context,
- Union $type,
- ): Option
- {
- // reconcileKeyedTypes takes it by ref
- $changed_var_ids = [];
-
- return self::collectAssertions($context, $return_expr)
- ->map(fn($assertions) => Reconciler::reconcileKeyedTypes(
- new_types: $assertions,
- active_new_types: $assertions,
- existing_types: [$arg_name => $type],
- changed_var_ids: $changed_var_ids,
- referenced_var_ids: [$arg_name => true],
- statements_analyzer: $context->source,
- template_type_map: $context->source->getTemplateTypeMap() ?: [],
- code_location: new CodeLocation($context->source, $return_expr)
- ))
- ->flatMap(fn($reconciled_types) => at($reconciled_types, $arg_name));
- }
-}
diff --git a/src/Fp/Psalm/Util/TypeRefinement/RefineForEnum.php b/src/Fp/Psalm/Util/TypeRefinement/RefineForEnum.php
deleted file mode 100644
index 473a5cfa..00000000
--- a/src/Fp/Psalm/Util/TypeRefinement/RefineForEnum.php
+++ /dev/null
@@ -1,9 +0,0 @@
-
- * @implements StreamTerminalOps