Skip to content

Commit d9f88bc

Browse files
athrawesAndrew Moyes
andauthored
Make phpdoc types more precise (#93)
Improve type annotations by making use of templates, callable signatures and other type system features supported by psalm and phpstan. Additionally, this makes the following changes: * Fix CI to work on PHP 7.1 again. * Add psalm and phpstan to require-dev for validation. * Handle IteratorAggregate returning a non-Iterator in isEmpty(). * In functions that take a `$levels = INF` parameter or similar, changed `INF` to `PHP_INT_MAX` as `INF` is a float type and not int as declared. In these cases, `$levels` can never exceed the `PHP_INT_MAX` anyhow. * Split off RewindableGenerator from RewindableIterator. * Psalm does not have support for recursive types. Functions which use recursive types have had a note added to the docblock to explain this. * I've not added docblocks to the functions in the `\iter\rewindable` namespace, as that would necessarily mean that any changes to the regular function would also need to be reflected in the docblocks for these methods. This does mean that these functions do not benefit from the Psalm type annotations currently. Co-authored-by: Andrew Moyes <[email protected]>
1 parent a8423fa commit d9f88bc

File tree

9 files changed

+474
-183
lines changed

9 files changed

+474
-183
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,25 @@ jobs:
2929

3030
steps:
3131
- name: Checkout code
32-
uses: actions/checkout@v2
32+
uses: actions/checkout@v3
3333

3434
- name: Install PHP
3535
uses: shivammathur/setup-php@v2
3636
with:
3737
php-version: ${{ matrix.php }}
3838
ini-values: error_reporting=-1, display_errors=On, log_errors_max_len=0
3939
coverage: none
40-
tools: none
40+
tools: composer
4141

4242
# Install dependencies and handle caching in one go.
4343
# @link https://github.com/marketplace/actions/install-composer-dependencies
4444
- name: "Install Composer dependencies (PHP < 8.1)"
4545
if: ${{ matrix.php < '8.1' }}
46-
uses: "ramsey/composer-install@v1"
46+
uses: "ramsey/composer-install@v2"
4747

4848
- name: "Install Composer dependencies (PHP 8.1)"
4949
if: ${{ matrix.php >= '8.1' }}
50-
uses: "ramsey/composer-install@v1"
50+
uses: "ramsey/composer-install@v2"
5151
with:
5252
composer-options: --ignore-platform-reqs
5353

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ $res = $rewindableMap(func\operator('*', 3), [1, 2, 3]);
108108
$res = iter\callRewindable('iter\\map', func\operator('*', 3), [1, 2, 3]);
109109
```
110110

111-
The above functions are only useful for your own generators though, for the
112-
`iter` generators rewindable variants are directly provided with an
111+
The above functions are only useful for your own iterators though; for the
112+
`iter` iterators, rewindable variants are directly provided with an
113113
`iter\rewindable` prefix:
114114

115115
$res = iter\rewindable\map(func\operator('*', 3), [1, 2, 3]);

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
"php": ">=7.1"
1818
},
1919
"require-dev": {
20-
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
20+
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
21+
"vimeo/psalm": "^4.18 || ^5.13",
22+
"phpstan/phpstan": "^1.4"
2123
},
2224
"autoload": {
2325
"files": [

psalm.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0"?>
2+
<psalm
3+
errorLevel="4"
4+
resolveFromConfigFile="true"
5+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6+
xmlns="https://getpsalm.org/schema/config"
7+
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
8+
findUnusedBaselineEntry="true"
9+
findUnusedCode="false"
10+
>
11+
<projectFiles>
12+
<directory name="src" />
13+
<directory name="test" />
14+
<ignoreFiles>
15+
<directory name="vendor" />
16+
</ignoreFiles>
17+
</projectFiles>
18+
</psalm>

src/iter.func.php

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
namespace iter\func;
44

5+
/**
6+
* Returns a callable which extracts a given index from an array.
7+
*
8+
* Example:
9+
*
10+
* $array = [ 'foo' => 42 ];
11+
*
12+
* func\index('foo')($array);
13+
* => 42
14+
*
15+
*
16+
* @param array-key $index
17+
*
18+
* @return callable(array):mixed
19+
*/
520
function index($index) {
621
return function($array) use ($index) {
722
return $array[$index];
@@ -31,9 +46,9 @@ function index($index) {
3146
* $getIndexFooBarBaz($array)
3247
* => 42
3348
*
34-
* @param mixed[] ...$indices Path of indices
49+
* @param array-key ...$indices Path of indices
3550
*
36-
* @return callable
51+
* @return callable(array):mixed
3752
*/
3853
function nested_index(...$indices) {
3954
return function($array) use ($indices) {
@@ -45,18 +60,73 @@ function nested_index(...$indices) {
4560
};
4661
}
4762

63+
/**
64+
* Returns a callable which returns a given property from an object.
65+
*
66+
* Example:
67+
*
68+
* $object = new \stdClass();
69+
* $object->foo = 42;
70+
*
71+
* func\property('foo')($object);
72+
* => 42
73+
*
74+
* @param string $propertyName
75+
*
76+
* @return callable(object):mixed
77+
*/
4878
function property($propertyName) {
4979
return function($object) use ($propertyName) {
5080
return $object->$propertyName;
5181
};
5282
}
5383

84+
/**
85+
* Returns a callable which calls a method on an object, optionally with some
86+
* provided arguments.
87+
*
88+
* Example:
89+
*
90+
* class Foo {
91+
* public function bar($a, $b) {
92+
* return $a + $b;
93+
* }
94+
* }
95+
*
96+
* $foo = new Foo();
97+
*
98+
* func\method('bar', [1, 2])($foo);
99+
* => 3
100+
*
101+
* @param string $methodName
102+
* @param mixed[] $args
103+
*
104+
* @return callable(object):mixed
105+
*/
54106
function method($methodName, $args = []) {
55107
return function($object) use ($methodName, $args) {
56108
return $object->$methodName(...$args);
57109
};
58110
}
59111

112+
/**
113+
* Returns a callable which applies the specified operator to the argument.
114+
*
115+
* Examples:
116+
*
117+
* $addOne = func\operator('+', 1);
118+
* $addOne(41);
119+
* => 42
120+
*
121+
* $modulo2 = func\operator('%', 2);
122+
* $modulo2(42);
123+
* => 0
124+
*
125+
* @param string $operator
126+
* @param mixed $arg The right-hand argument for the operator
127+
*
128+
* @return callable
129+
*/
60130
function operator($operator, $arg = null) {
61131
$functions = [
62132
'instanceof' => function($a, $b) { return $a instanceof $b; },
@@ -99,8 +169,29 @@ function operator($operator, $arg = null) {
99169
}
100170
}
101171

172+
/**
173+
* Takes a callable which returns a boolean, and returns another function that
174+
* returns the opposite for all values.
175+
*
176+
* Example:
177+
* $isEven = function($x) {
178+
* return $x % 2 === 0;
179+
* };
180+
*
181+
* $isOdd = func\not($isEven);
182+
*
183+
* $isEven(42);
184+
* => true
185+
*
186+
* $isOdd(42);
187+
* => false
188+
*
189+
* @param callable(...mixed):bool $function
190+
*
191+
* @return callable(...mixed):bool
192+
*/
102193
function not($function) {
103194
return function(...$args) use ($function) {
104195
return !$function(...$args);
105196
};
106-
}
197+
}

0 commit comments

Comments
 (0)