Skip to content

Commit

Permalink
Make phpdoc types more precise (#93)
Browse files Browse the repository at this point in the history
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 <andrew.moyes@futurenet.com>
  • Loading branch information
athrawes and Andrew Moyes authored Jul 25, 2023
1 parent a8423fa commit d9f88bc
Show file tree
Hide file tree
Showing 9 changed files with 474 additions and 183 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,25 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
ini-values: error_reporting=-1, display_errors=On, log_errors_max_len=0
coverage: none
tools: none
tools: composer

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

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

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ $res = $rewindableMap(func\operator('*', 3), [1, 2, 3]);
$res = iter\callRewindable('iter\\map', func\operator('*', 3), [1, 2, 3]);
```

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

$res = iter\rewindable\map(func\operator('*', 3), [1, 2, 3]);
Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"vimeo/psalm": "^4.18 || ^5.13",
"phpstan/phpstan": "^1.4"
},
"autoload": {
"files": [
Expand Down
18 changes: 18 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0"?>
<psalm
errorLevel="4"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
findUnusedBaselineEntry="true"
findUnusedCode="false"
>
<projectFiles>
<directory name="src" />
<directory name="test" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>
97 changes: 94 additions & 3 deletions src/iter.func.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

namespace iter\func;

/**
* Returns a callable which extracts a given index from an array.
*
* Example:
*
* $array = [ 'foo' => 42 ];
*
* func\index('foo')($array);
* => 42
*
*
* @param array-key $index
*
* @return callable(array):mixed
*/
function index($index) {
return function($array) use ($index) {
return $array[$index];
Expand Down Expand Up @@ -31,9 +46,9 @@ function index($index) {
* $getIndexFooBarBaz($array)
* => 42
*
* @param mixed[] ...$indices Path of indices
* @param array-key ...$indices Path of indices
*
* @return callable
* @return callable(array):mixed
*/
function nested_index(...$indices) {
return function($array) use ($indices) {
Expand All @@ -45,18 +60,73 @@ function nested_index(...$indices) {
};
}

/**
* Returns a callable which returns a given property from an object.
*
* Example:
*
* $object = new \stdClass();
* $object->foo = 42;
*
* func\property('foo')($object);
* => 42
*
* @param string $propertyName
*
* @return callable(object):mixed
*/
function property($propertyName) {
return function($object) use ($propertyName) {
return $object->$propertyName;
};
}

/**
* Returns a callable which calls a method on an object, optionally with some
* provided arguments.
*
* Example:
*
* class Foo {
* public function bar($a, $b) {
* return $a + $b;
* }
* }
*
* $foo = new Foo();
*
* func\method('bar', [1, 2])($foo);
* => 3
*
* @param string $methodName
* @param mixed[] $args
*
* @return callable(object):mixed
*/
function method($methodName, $args = []) {
return function($object) use ($methodName, $args) {
return $object->$methodName(...$args);
};
}

/**
* Returns a callable which applies the specified operator to the argument.
*
* Examples:
*
* $addOne = func\operator('+', 1);
* $addOne(41);
* => 42
*
* $modulo2 = func\operator('%', 2);
* $modulo2(42);
* => 0
*
* @param string $operator
* @param mixed $arg The right-hand argument for the operator
*
* @return callable
*/
function operator($operator, $arg = null) {
$functions = [
'instanceof' => function($a, $b) { return $a instanceof $b; },
Expand Down Expand Up @@ -99,8 +169,29 @@ function operator($operator, $arg = null) {
}
}

/**
* Takes a callable which returns a boolean, and returns another function that
* returns the opposite for all values.
*
* Example:
* $isEven = function($x) {
* return $x % 2 === 0;
* };
*
* $isOdd = func\not($isEven);
*
* $isEven(42);
* => true
*
* $isOdd(42);
* => false
*
* @param callable(...mixed):bool $function
*
* @return callable(...mixed):bool
*/
function not($function) {
return function(...$args) use ($function) {
return !$function(...$args);
};
}
}
Loading

0 comments on commit d9f88bc

Please sign in to comment.