type accessors for static analysis
This commit is contained in:
parent
b20ef22cfd
commit
61f934ffd7
27
README.md
27
README.md
@ -207,7 +207,7 @@ third to 2. To prevent that, two kinds of callables are called with the value on
|
||||
|
||||
- **PHP's internal functions**, written as first-class callables: `trim(...)`, `round(...)`, `strtoupper(...)`.
|
||||
This also holds when they are wrapped, e.g. `f\partial(str_pad(...), _, 3, '0')`.
|
||||
- **Closures declaring exactly one parameter**, like `fn ($value) => ...`.
|
||||
- **Any callable declaring exactly one parameter**, like `fn ($value) => ...` or a one-parameter method.
|
||||
|
||||
```php
|
||||
[1.25, 2.5, 3.14159] |> c\map(round(...)); // [1.0, 3.0, 3.0], not [1.0, 2.5, 3.14]
|
||||
@ -321,8 +321,10 @@ $byId->get('d4')->destination; // 'Oliwa'
|
||||
### Queries
|
||||
|
||||
`first`, `last`, `find`, `contains`, `any`, `some`, `every`, `count`, `isEmpty`, `min`, `max`, `reduce`, `sum` and
|
||||
`join` turn a collection into a single value. They stop reading as soon as the answer is known, which matters for
|
||||
generators.
|
||||
`join` turn a collection into a single value. `first`, `find`, `any`, `some`, `every`, `contains` and `isEmpty`
|
||||
stop reading as soon as the answer is known, which matters for generators. The others read everything, with two
|
||||
shortcuts: `last` reads arrays and php-ds collections from the end, and `count` asks countable containers for their
|
||||
size.
|
||||
|
||||
```php
|
||||
$departures |> c\every(fn (Departure $departure) => $departure->delay < 600); // true
|
||||
@ -392,8 +394,8 @@ not copied), and results whose type says whether they are a list (`Seq`), a mapp
|
||||
|
||||
## Static analysis
|
||||
|
||||
Every function has `@template` generics, so PHPStan (level 8 in this repository) follows element types through a
|
||||
chain, `|>` included:
|
||||
Every function in `Collections` has `@template` generics, so PHPStan (level 8 in this repository) follows element
|
||||
types through a chain, `|>` included:
|
||||
|
||||
```php
|
||||
/** @param Ds\Seq<Departure> $departures */
|
||||
@ -407,10 +409,17 @@ function delayedLines(Ds\Seq $departures): ?string
|
||||
```
|
||||
|
||||
Eager transforms with a callback keep the container in the type as well (a `Ds\Map` in, a `Ds\Map` out; a
|
||||
generator in, `Ds\Seq|list` out). The limits of what can be inferred:
|
||||
generator in, `Ds\Seq|list` out). A callable PHPStan knows nothing about never causes an error, it only makes
|
||||
the result `mixed`. The limits of what can be inferred:
|
||||
|
||||
- **Element types come from the callback's parameter and return types.** `fn ($departure) => ...` without types
|
||||
gives `mixed`.
|
||||
- **Accessors and predicates are not generic.** `t\property()`, `t\field()`, `t\method()` and `t\getter()` are typed
|
||||
`\Closure(object): mixed`, and the `p\` builders return `Predicate`. Chains using them analyse cleanly, but the
|
||||
elements become `object` (`c\sortBy(t\property('delay'))`) or `mixed` (`c\map(t\property('line'))`,
|
||||
`c\max(t\property('delay'))`). Where the type matters, write the closure:
|
||||
`c\map(fn (Departure $departure): string => $departure->line)`.
|
||||
- **`sum()` doesn't check that the values are numbers** when called without a selector.
|
||||
- **Keys are inferred only when the callback declares a `$key` parameter.** Otherwise the keys of the result are
|
||||
`mixed` (`int|string` for arrays).
|
||||
- **Transforms without a callback** (`values`, `keys`, `unique`, `sort()`, `slice`, `take`, `skip`, `prepend`,
|
||||
@ -424,7 +433,7 @@ generator in, `Ds\Seq|list` out). The limits of what can be inferred:
|
||||
|
||||
```bash
|
||||
composer test # both PHPUnit suites
|
||||
composer phpstan # PHPStan, level 8, on src/ and the type tests in tests/types/
|
||||
composer phpstan # PHPStan level 8: src/ as PHP 8.2 (phpstan.neon), tests/types/ as PHP 8.5 (phpstan.types.neon)
|
||||
```
|
||||
|
||||
- `composer test` runs the main suite and the no-php-ds suite (`phpunit.no-ds.xml`), which hides the polyfill to
|
||||
@ -432,6 +441,6 @@ composer phpstan # PHPStan, level 8, on src/ and the type tests in tests/types/
|
||||
be hidden: it prints a notice and passes. Run it on a PHP without ext-ds to cover those paths.
|
||||
- `tests/PipeSyntaxTest.php` is the only test that uses `|>`. It is loaded only on PHP 8.5 and newer (see
|
||||
`phpunit.xml`), so the rest of the suite still runs on PHP 8.2. Keep `|>` out of other tests.
|
||||
- `tests/types/` holds PHPStan type assertions (`assertType()`). They are analysed, never executed. Add one when
|
||||
you change a signature.
|
||||
- `tests/types/` holds PHPStan type assertions (`assertType()`). They are analysed, never executed, with their own
|
||||
config, because they use `|>`. Add one when you change a signature.
|
||||
- `phpstan-baseline.neon` lists errors in code that predates PHPStan in this project. New code must not add to it.
|
||||
|
||||
@ -41,6 +41,9 @@
|
||||
"phpunit",
|
||||
"phpunit -c phpunit.no-ds.xml"
|
||||
],
|
||||
"phpstan": "phpstan analyse"
|
||||
"phpstan": [
|
||||
"phpstan analyse",
|
||||
"phpstan analyse -c phpstan.types.neon"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,12 +168,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Predicates/objects.php
|
||||
|
||||
-
|
||||
message: '#^Function Kadet\\Functional\\Predicates\\instance\(\) has no return type specified\.$#'
|
||||
identifier: missingType.return
|
||||
count: 1
|
||||
path: src/Predicates/objects.php
|
||||
|
||||
-
|
||||
message: '#^Function Kadet\\Functional\\Predicates\\method\(\) has parameter \$args with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
@ -270,36 +264,18 @@ parameters:
|
||||
count: 1
|
||||
path: src/Reflection/ReflectionPartiallyAppliedFunction.php
|
||||
|
||||
-
|
||||
message: '#^Function Kadet\\Functional\\Transforms\\field\(\) has no return type specified\.$#'
|
||||
identifier: missingType.return
|
||||
count: 1
|
||||
path: src/Transforms/objects.php
|
||||
|
||||
-
|
||||
message: '#^Function Kadet\\Functional\\Transforms\\field\(\) has parameter \$field with no type specified\.$#'
|
||||
identifier: missingType.parameter
|
||||
count: 1
|
||||
path: src/Transforms/objects.php
|
||||
|
||||
-
|
||||
message: '#^Function Kadet\\Functional\\Transforms\\getter\(\) has no return type specified\.$#'
|
||||
identifier: missingType.return
|
||||
count: 1
|
||||
path: src/Transforms/objects.php
|
||||
|
||||
-
|
||||
message: '#^Function Kadet\\Functional\\Transforms\\getter\(\) has parameter \$property with no type specified\.$#'
|
||||
identifier: missingType.parameter
|
||||
count: 1
|
||||
path: src/Transforms/objects.php
|
||||
|
||||
-
|
||||
message: '#^Function Kadet\\Functional\\Transforms\\method\(\) has no return type specified\.$#'
|
||||
identifier: missingType.return
|
||||
count: 1
|
||||
path: src/Transforms/objects.php
|
||||
|
||||
-
|
||||
message: '#^Function Kadet\\Functional\\Transforms\\method\(\) has parameter \$args with no type specified\.$#'
|
||||
identifier: missingType.parameter
|
||||
@ -312,12 +288,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Transforms/objects.php
|
||||
|
||||
-
|
||||
message: '#^Function Kadet\\Functional\\Transforms\\property\(\) has no return type specified\.$#'
|
||||
identifier: missingType.return
|
||||
count: 1
|
||||
path: src/Transforms/objects.php
|
||||
|
||||
-
|
||||
message: '#^Function Kadet\\Functional\\Transforms\\property\(\) has parameter \$property with no type specified\.$#'
|
||||
identifier: missingType.parameter
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
parameters:
|
||||
level: 8
|
||||
# tests/types checks inference through the pipe operator, which needs PHP 8.5 to parse. PHPStan parses with the
|
||||
# lowest configured version, so this can't be a range starting at the library's PHP 8.2 floor; that floor is
|
||||
# covered by running the test suite on PHP 8.2.
|
||||
phpVersion: 80500
|
||||
# The library's floor. The type tests use the 8.5 pipe operator, so they have their own config:
|
||||
# phpstan.types.neon. `composer phpstan` runs both.
|
||||
phpVersion: 80200
|
||||
paths:
|
||||
- src
|
||||
- tests/types
|
||||
ignoreErrors:
|
||||
-
|
||||
# Eager transforms are typed with a conditional closure type that maps the input container to the result
|
||||
|
||||
7
phpstan.types.neon
Normal file
7
phpstan.types.neon
Normal file
@ -0,0 +1,7 @@
|
||||
# Type tests (tests/types): assertType() checks of what PHPStan infers for library users, including through the pipe
|
||||
# operator, which needs PHP 8.5 to parse. The library itself is analysed against PHP 8.2 by phpstan.neon.
|
||||
parameters:
|
||||
level: 8
|
||||
phpVersion: 80500
|
||||
paths:
|
||||
- tests/types
|
||||
@ -30,9 +30,9 @@ use function Kadet\Functional\Collections\Internal\unpairs;
|
||||
/**
|
||||
* Transforms each value with `$fn($value, $key)`, keeping the keys.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TResult
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
* @template TResult = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): TResult $fn
|
||||
*
|
||||
@ -52,8 +52,8 @@ function map(callable $fn): \Closure
|
||||
/**
|
||||
* Keeps the values for which `$fn($value, $key)` is truthy, or the truthy values when `$fn` is null.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param (callable(TValue, TKey): mixed)|null $fn
|
||||
*
|
||||
@ -75,8 +75,8 @@ function filter(?callable $fn = null): \Closure
|
||||
/**
|
||||
* Drops the values for which `$fn($value, $key)` is truthy. The inverse of {@see filter()}.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): mixed $fn
|
||||
*
|
||||
@ -98,9 +98,9 @@ function reject(callable $fn): \Closure
|
||||
/**
|
||||
* Maps each value to an iterable with `$fn($value, $key)` and flattens the results one level, with sequential keys.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TResult
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
* @template TResult = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): iterable<mixed, TResult> $fn
|
||||
*
|
||||
@ -170,8 +170,8 @@ function unique(): \Closure
|
||||
* Removes values whose `$selector($value, $key)` result was already seen, keeping the first occurrence.
|
||||
* Selected values are compared strictly, objects by identity.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): mixed $selector
|
||||
*
|
||||
@ -260,7 +260,7 @@ function skip(int $n): \Closure
|
||||
/**
|
||||
* Puts `$values` before the values of the input, with sequential keys.
|
||||
*
|
||||
* @template TValue
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param TValue ...$values
|
||||
*
|
||||
@ -282,7 +282,7 @@ function prepend(mixed ...$values): \Closure
|
||||
/**
|
||||
* Puts `$values` after the values of the input, with sequential keys.
|
||||
*
|
||||
* @template TValue
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param TValue ...$values
|
||||
*
|
||||
@ -296,7 +296,7 @@ function append(mixed ...$values): \Closure
|
||||
/**
|
||||
* Puts the values of `$other` after the values of the input, with sequential keys.
|
||||
*
|
||||
* @template TValue
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param iterable<mixed, TValue> $other
|
||||
*
|
||||
@ -320,8 +320,8 @@ function concat(iterable $other): \Closure
|
||||
*
|
||||
* The return value of `$fn` is ignored.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): mixed $fn
|
||||
*
|
||||
@ -343,8 +343,8 @@ function each(callable $fn): \Closure
|
||||
/**
|
||||
* Alias of {@see each()}.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): mixed $fn
|
||||
*
|
||||
|
||||
@ -23,9 +23,9 @@ use function Kadet\Functional\Collections\Internal\dsAvailable;
|
||||
/**
|
||||
* Indexes the values by `$selector($value, $key)` into a `Ds\Map`. The last value wins on a duplicate key.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TIndex
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
* @template TIndex = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): TIndex $selector
|
||||
*
|
||||
@ -50,9 +50,9 @@ function keyBy(callable $selector): \Closure
|
||||
* Groups the values by `$selector($value, $key)` into a `Ds\Map` of `Ds\Seq`. Groups and their values keep the
|
||||
* order in which they were encountered.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TGroup
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
* @template TGroup = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): TGroup $selector
|
||||
*
|
||||
|
||||
@ -32,8 +32,8 @@ use function Kadet\Functional\compile;
|
||||
*
|
||||
* Returns null when nothing matches; with `$required` it throws instead, so a found `null` stays distinguishable.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param (callable(TValue, TKey): mixed)|null $predicate
|
||||
*
|
||||
@ -54,8 +54,8 @@ function first(?callable $predicate = null, bool $required = false): \Closure
|
||||
* Arrays and php-ds containers are read from the end, other iterables are read to the end. Returns null when
|
||||
* nothing matches; with `$required` it throws instead.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param (callable(TValue, TKey): mixed)|null $predicate
|
||||
*
|
||||
@ -105,8 +105,8 @@ function last(?callable $predicate = null, bool $required = false): \Closure
|
||||
/**
|
||||
* Alias of {@see first()} with a mandatory predicate: returns the first value matching `$predicate($value, $key)`.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): mixed $predicate
|
||||
*
|
||||
@ -142,8 +142,8 @@ function contains(mixed $value): \Closure
|
||||
/**
|
||||
* Whether any value satisfies `$predicate($value, $key)`. False for an empty collection.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): mixed $predicate
|
||||
*
|
||||
@ -167,8 +167,8 @@ function any(callable $predicate): \Closure
|
||||
/**
|
||||
* Alias of {@see any()}.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): mixed $predicate
|
||||
*
|
||||
@ -182,8 +182,8 @@ function some(callable $predicate): \Closure
|
||||
/**
|
||||
* Whether every value satisfies `$predicate($value, $key)`. True for an empty collection.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): mixed $predicate
|
||||
*
|
||||
@ -250,9 +250,9 @@ function isEmpty(): \Closure
|
||||
*
|
||||
* With a selector this returns the selected value, not the element it was selected from.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TSelected
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
* @template TSelected = mixed
|
||||
*
|
||||
* @param (callable(TValue, TKey): TSelected)|null $selector
|
||||
*
|
||||
@ -270,9 +270,9 @@ function min(?callable $selector = null): \Closure
|
||||
*
|
||||
* With a selector this returns the selected value, not the element it was selected from.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TSelected
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
* @template TSelected = mixed
|
||||
*
|
||||
* @param (callable(TValue, TKey): TSelected)|null $selector
|
||||
*
|
||||
@ -288,9 +288,9 @@ function max(?callable $selector = null): \Closure
|
||||
/**
|
||||
* Folds the collection into a single value with `$fn($carry, $value, $key)`, starting from `$initial`.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TCarry
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
* @template TCarry = mixed
|
||||
*
|
||||
* @param callable(TCarry, TValue, TKey): TCarry $fn
|
||||
* @param TCarry $initial
|
||||
@ -315,14 +315,12 @@ function reduce(callable $fn, mixed $initial = null): \Closure
|
||||
/**
|
||||
* Sums the values, or `$selector($value, $key)` when given. An empty collection sums to 0.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param (callable(TValue, TKey): (int|float))|null $selector
|
||||
*
|
||||
* @return ($selector is null
|
||||
* ? \Closure(iterable<mixed, int|float>): (int|float)
|
||||
* : \Closure(iterable<TKey, TValue>): (int|float))
|
||||
* @return \Closure(iterable<TKey, TValue>): (int|float)
|
||||
*/
|
||||
function sum(?callable $selector = null): \Closure
|
||||
{
|
||||
|
||||
@ -34,9 +34,9 @@ use function Kadet\Functional\compile;
|
||||
*
|
||||
* For `Ds\Set` this follows `Ds\Set::map`, so equal results collapse into one.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TResult
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
* @template TResult = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): TResult $fn
|
||||
*
|
||||
@ -60,8 +60,8 @@ function map(callable $fn): \Closure
|
||||
*
|
||||
* Keys are preserved for arrays and `Ds\Map`; `Ds\Seq` is reindexed.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param (callable(TValue, TKey): mixed)|null $fn
|
||||
*
|
||||
@ -83,8 +83,8 @@ function filter(?callable $fn = null): \Closure
|
||||
/**
|
||||
* Drops the values for which `$fn($value, $key)` is truthy. The inverse of {@see filter()}.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): mixed $fn
|
||||
*
|
||||
@ -108,9 +108,9 @@ function reject(callable $fn): \Closure
|
||||
*
|
||||
* Arrays give a list array, `Ds\Set` gives `Ds\Set`, anything else gives `Ds\Seq`.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TResult
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
* @template TResult = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): iterable<mixed, TResult> $fn
|
||||
*
|
||||
@ -169,8 +169,8 @@ function unique(): \Closure
|
||||
* Removes values whose `$selector($value, $key)` result was already seen, keeping the first occurrence.
|
||||
* Selected values are compared strictly, objects by identity.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): mixed $selector
|
||||
*
|
||||
@ -194,7 +194,7 @@ function uniqueBy(callable $selector): \Closure
|
||||
*
|
||||
* The sort is stable and keeps the keys of arrays and `Ds\Map`. The input is never modified.
|
||||
*
|
||||
* @template TValue
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param (callable(TValue, TValue): int)|null $comparator
|
||||
*
|
||||
@ -219,8 +219,8 @@ function sort(?callable $comparator = null): \Closure
|
||||
* The sort is stable (equal values keep their order in both directions) and keeps the keys of arrays and
|
||||
* `Ds\Map`. The selector is called once per value. The input is never modified.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): mixed $selector
|
||||
*
|
||||
@ -291,7 +291,7 @@ function skip(int $n): \Closure
|
||||
/**
|
||||
* Puts `$values` before the values of the input, as a list (keys are dropped).
|
||||
*
|
||||
* @template TValue
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param TValue ...$values
|
||||
*
|
||||
@ -307,7 +307,7 @@ function prepend(mixed ...$values): \Closure
|
||||
/**
|
||||
* Puts `$values` after the values of the input, as a list (keys are dropped).
|
||||
*
|
||||
* @template TValue
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param TValue ...$values
|
||||
*
|
||||
@ -321,7 +321,7 @@ function append(mixed ...$values): \Closure
|
||||
/**
|
||||
* Puts the values of `$other` after the values of the input, as a list (keys are dropped).
|
||||
*
|
||||
* @template TValue
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param iterable<mixed, TValue> $other
|
||||
*
|
||||
@ -339,8 +339,8 @@ function concat(iterable $other): \Closure
|
||||
*
|
||||
* Arrays and `Ds\Map` are merged by key; for `Ds\Seq`, `Ds\Set` and other iterables the values are appended.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param iterable<TKey, TValue> $other
|
||||
*
|
||||
@ -360,8 +360,8 @@ function merge(iterable $other): \Closure
|
||||
* The return value of `$fn` is ignored. Arrays and php-ds collections are returned as they are; any other
|
||||
* iterable is consumed and its values are returned as a `Ds\Seq` (or a list array without php-ds).
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): mixed $fn
|
||||
*
|
||||
@ -393,8 +393,8 @@ function each(callable $fn): \Closure
|
||||
/**
|
||||
* Alias of {@see each()}.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @template TKey = mixed
|
||||
* @template TValue = mixed
|
||||
*
|
||||
* @param callable(TValue, TKey): mixed $fn
|
||||
*
|
||||
|
||||
@ -49,6 +49,9 @@ function field(string $field, $predicate)
|
||||
return predicate(f\pipe(t\field($field), $predicate));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Kadet\Functional\Predicate
|
||||
*/
|
||||
function instance(string $class)
|
||||
{
|
||||
return new ClosurePredicate(function ($value) use ($class) {
|
||||
|
||||
@ -9,11 +9,17 @@
|
||||
|
||||
namespace Kadet\Functional\Transforms;
|
||||
|
||||
/**
|
||||
* @return \Closure(object): mixed
|
||||
*/
|
||||
function getter($property)
|
||||
{
|
||||
return method('get'.ucfirst($property));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure(object): mixed
|
||||
*/
|
||||
function property($property)
|
||||
{
|
||||
return function ($object) use ($property) {
|
||||
@ -25,6 +31,9 @@ function property($property)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure(object): mixed
|
||||
*/
|
||||
function field($field)
|
||||
{
|
||||
return function ($object) use ($field) {
|
||||
@ -32,6 +41,9 @@ function field($field)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure(object): mixed
|
||||
*/
|
||||
function method($method, ...$args)
|
||||
{
|
||||
return function ($object) use ($method, $args) {
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
|
||||
use Kadet\Functional\Collections as c;
|
||||
use Kadet\Functional\Collections\Lazy as lazy;
|
||||
use Kadet\Functional\Predicates as p;
|
||||
use Kadet\Functional\Transforms as t;
|
||||
|
||||
use function PHPStan\Testing\assertType;
|
||||
|
||||
@ -101,6 +103,24 @@ function queries(\Ds\Seq $foos, \Ds\Map $map, array $ints): void
|
||||
assertType('int', c\reduce(fn (int $carry, Foo $foo): int => $carry + $foo->id, 0)($foos));
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessors (`t\`) and predicates (`p\`) are untyped about the object they read, so the element type is `object` or
|
||||
* `mixed`; what matters is that these idioms analyse without errors.
|
||||
*
|
||||
* @param list<Foo> $list
|
||||
*/
|
||||
function accessors(array $list): void
|
||||
{
|
||||
assertType('array<int|string, mixed>', c\map(t\property('name'))($list));
|
||||
assertType('array<int|string, object>', c\sortBy(t\property('id'))($list));
|
||||
assertType('object|null', $list |> c\sortBy(t\property('id'), descending: true) |> c\first());
|
||||
assertType('float|int', c\sum(t\property('id'))($list));
|
||||
assertType('mixed', c\max(t\property('id'))($list));
|
||||
assertType('Ds\\Map<mixed, object>', c\keyBy(t\property('id'))($list));
|
||||
assertType('array<int|string, mixed>', c\filter(p\property('id', p\gt(1)))($list));
|
||||
assertType('mixed', $list |> c\first(p\property('name', p\same('a'))));
|
||||
}
|
||||
|
||||
/**
|
||||
* The pipe operator: PHPStan infers through `|>` like through a call.
|
||||
*
|
||||
|
||||
Loading…
Reference in New Issue
Block a user