diff --git a/.gitignore b/.gitignore
index 650c8a0b..92ba894c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ vendor/
composer.lock
phpcs.xml
phpunit.xml
+/lsp/
diff --git a/.travis.yml b/.travis.yml
index 4d44dfd4..c62bf6e2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,3 +18,4 @@ install:
script:
- composer tests
- composer coding-style
+ - composer types
diff --git a/composer.json b/composer.json
index fd0706b7..1da7bb82 100644
--- a/composer.json
+++ b/composer.json
@@ -15,7 +15,8 @@
}
],
"require": {
- "php": "~7"
+ "php": "~7",
+ "vimeo/psalm": "^3.7"
},
"require-dev": {
"phpunit/phpunit": "~7",
@@ -124,6 +125,7 @@
},
"scripts": {
"tests": "vendor/bin/phpunit",
+ "types": "vendor/bin/psalm",
"coding-style": "vendor/bin/phpcs && vendor/bin/php-cs-fixer fix --dry-run --diff --config=.php_cs.dist",
"clear": "rm -rf vendor/"
}
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 00000000..42f355b2
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Functional/Average.php b/src/Functional/Average.php
index b3029f34..d0e4adbf 100644
--- a/src/Functional/Average.php
+++ b/src/Functional/Average.php
@@ -17,7 +17,7 @@
* Returns the average of all numeric values in the array or null if no numeric value was found
*
* @param Traversable|array $collection
- * @return null|float|int
+ * @return numeric|null
*/
function average($collection)
{
@@ -28,7 +28,7 @@ function average($collection)
foreach ($collection as $element) {
if (\is_numeric($element)) {
- $sum += $element;
+ $sum = ($sum === null) ? $element : $sum + $element;
++$divisor;
}
}
diff --git a/src/Functional/Concat.php b/src/Functional/Concat.php
index 64fe8d57..c607e08f 100644
--- a/src/Functional/Concat.php
+++ b/src/Functional/Concat.php
@@ -13,7 +13,7 @@
/**
* Concatenates zero or more strings
*
- * @param string[] ...$strings
+ * @param array $strings
* @return string
*/
function concat(string ...$strings)
diff --git a/src/Functional/Curry.php b/src/Functional/Curry.php
index 97bb835e..e473a5a8 100644
--- a/src/Functional/Curry.php
+++ b/src/Functional/Curry.php
@@ -24,8 +24,10 @@
*/
function curry(callable $function, $required = true)
{
+ /** @psalm-suppress ArgumentTypeCoercion */
if (\method_exists('Closure', 'fromCallable')) {
// Closure::fromCallable was introduced in PHP 7.1
+ /** @psalm-suppress InvalidArgument */
$reflection = new ReflectionFunction(Closure::fromCallable($function));
} else {
if (\is_string($function) && \strpos($function, '::', 1) !== false) {
@@ -35,6 +37,7 @@ function curry(callable $function, $required = true)
} elseif (\is_object($function) && \method_exists($function, '__invoke')) {
$reflection = new ReflectionMethod($function, '__invoke');
} else {
+ /** @psalm-suppress InvalidArgument */
$reflection = new ReflectionFunction($function);
}
}
diff --git a/src/Functional/Difference.php b/src/Functional/Difference.php
index 0c906f17..469ae0c1 100644
--- a/src/Functional/Difference.php
+++ b/src/Functional/Difference.php
@@ -17,8 +17,8 @@
* Takes a collection and returns the difference of all elements
*
* @param Traversable|array $collection
- * @param integer|float $initial
- * @return integer|float
+ * @param numeric $initial
+ * @return numeric
*/
function difference($collection, $initial = 0)
{
diff --git a/src/Functional/Exceptions/InvalidArgumentException.php b/src/Functional/Exceptions/InvalidArgumentException.php
index 5a64a3ac..7d9a9301 100644
--- a/src/Functional/Exceptions/InvalidArgumentException.php
+++ b/src/Functional/Exceptions/InvalidArgumentException.php
@@ -22,7 +22,7 @@ public static function assertCallback($callback, $callee, $parameterPosition)
{
if (!\is_callable($callback)) {
if (!\is_array($callback) && !\is_string($callback)) {
- throw new static(
+ throw new self(
\sprintf(
'%s() expected parameter %d to be a valid callback, no array, string, closure or functor given',
$callee,
@@ -51,7 +51,7 @@ public static function assertCallback($callback, $callee, $parameterPosition)
break;
}
- throw new static(
+ throw new self(
\sprintf(
"%s() expects parameter %d to be a valid callback, %s '%s' not found or invalid %s name",
$callee,
@@ -77,7 +77,7 @@ public static function assertArrayAccess($collection, $callee, $parameterPositio
public static function assertMethodName($methodName, $callee, $parameterPosition)
{
if (!\is_string($methodName)) {
- throw new static(
+ throw new self(
\sprintf(
'%s() expects parameter %d to be string, %s given',
$callee,
@@ -102,7 +102,7 @@ public static function assertPropertyName($propertyName, $callee, $parameterPosi
!\is_float($propertyName) &&
!\is_null($propertyName)
) {
- throw new static(
+ throw new self(
\sprintf(
'%s() expects parameter %d to be a valid property name or array index, %s given',
$callee,
@@ -119,7 +119,7 @@ public static function assertPositiveInteger($value, $callee, $parameterPosition
$type = self::getType($value);
$type = $type === 'integer' ? 'negative integer' : $type;
- throw new static(
+ throw new self(
\sprintf(
'%s() expects parameter %d to be positive integer, %s given',
$callee,
@@ -133,7 +133,7 @@ public static function assertPositiveInteger($value, $callee, $parameterPosition
/**
* @param mixed $key
* @param string $callee
- * @throws static
+ * @throws InvalidArgumentException
*/
public static function assertValidArrayKey($key, $callee)
{
@@ -142,7 +142,7 @@ public static function assertValidArrayKey($key, $callee)
$keyType = \gettype($key);
if (!\in_array($keyType, $keyTypes, true)) {
- throw new static(
+ throw new self(
\sprintf(
'%s(): callback returned invalid array key of type "%s". Expected %4$s or %3$s',
$callee,
@@ -157,7 +157,7 @@ public static function assertValidArrayKey($key, $callee)
public static function assertArrayKeyExists($collection, $key, $callee)
{
if (!isset($collection[$key])) {
- throw new static(
+ throw new self(
\sprintf(
'%s(): unknown key "%s"',
$callee,
@@ -176,7 +176,7 @@ public static function assertArrayKeyExists($collection, $key, $callee)
public static function assertBoolean($value, $callee, $parameterPosition)
{
if (!\is_bool($value)) {
- throw new static(
+ throw new self(
\sprintf(
'%s() expects parameter %d to be boolean, %s given',
$callee,
@@ -196,7 +196,7 @@ public static function assertBoolean($value, $callee, $parameterPosition)
public static function assertInteger($value, $callee, $parameterPosition)
{
if (!\is_int($value)) {
- throw new static(
+ throw new self(
\sprintf(
'%s() expects parameter %d to be integer, %s given',
$callee,
@@ -217,7 +217,7 @@ public static function assertInteger($value, $callee, $parameterPosition)
public static function assertIntegerGreaterThanOrEqual($value, $limit, $callee, $parameterPosition)
{
if (!\is_int($value) || $value < $limit) {
- throw new static(
+ throw new self(
\sprintf(
'%s() expects parameter %d to be an integer greater than or equal to %d',
$callee,
@@ -238,7 +238,7 @@ public static function assertIntegerGreaterThanOrEqual($value, $limit, $callee,
public static function assertIntegerLessThanOrEqual($value, $limit, $callee, $parameterPosition)
{
if (!\is_int($value) || $value > $limit) {
- throw new static(
+ throw new self(
\sprintf(
'%s() expects parameter %d to be an integer less than or equal to %d',
$callee,
@@ -252,7 +252,7 @@ public static function assertIntegerLessThanOrEqual($value, $limit, $callee, $pa
public static function assertResolvablePlaceholder(array $args, $position)
{
if (\count($args) === 0) {
- throw new static(
+ throw new self(
\sprintf('Cannot resolve parameter placeholder at position %d. Parameter stack is empty.', $position)
);
}
@@ -268,7 +268,7 @@ public static function assertResolvablePlaceholder(array $args, $position)
private static function assertCollectionAlike($collection, $className, $callee, $parameterPosition)
{
if (!\is_array($collection) && !$collection instanceof $className) {
- throw new static(
+ throw new self(
\sprintf(
'%s() expects parameter %d to be array or instance of %s, %s given',
$callee,
diff --git a/src/Functional/Match.php b/src/Functional/Match.php
index 83d3b2a3..3370954a 100644
--- a/src/Functional/Match.php
+++ b/src/Functional/Match.php
@@ -21,7 +21,7 @@
*
* @param array $conditions the conditions to check against
*
- * @return callable|null the function that calls the callable of the first truthy condition
+ * @return callable the function that calls the callable of the first truthy condition
*/
function match(array $conditions)
{
diff --git a/src/Functional/Maximum.php b/src/Functional/Maximum.php
index f2c4596f..15097581 100644
--- a/src/Functional/Maximum.php
+++ b/src/Functional/Maximum.php
@@ -17,7 +17,7 @@
* Returns the maximum value of a collection
*
* @param Traversable|array $collection
- * @return integer|float
+ * @return numeric|null
*/
function maximum($collection)
{
diff --git a/src/Functional/Minimum.php b/src/Functional/Minimum.php
index d0d67fb8..3898a348 100644
--- a/src/Functional/Minimum.php
+++ b/src/Functional/Minimum.php
@@ -17,7 +17,7 @@
* Returns the minimum value of a collection
*
* @param Traversable|array $collection
- * @return integer|float
+ * @return numeric|null
*/
function minimum($collection)
{
diff --git a/src/Functional/Poll.php b/src/Functional/Poll.php
index ca48fcb3..667d4a17 100644
--- a/src/Functional/Poll.php
+++ b/src/Functional/Poll.php
@@ -33,6 +33,7 @@ function poll(callable $callback, $timeout, Traversable $delaySequence = null)
$delays = new AppendIterator();
if ($delaySequence) {
+ /** @psalm-suppress ArgumentTypeCoercion */
$delays->append(new InfiniteIterator($delaySequence));
}
$delays->append(new InfiniteIterator(new ArrayIterator([0])));
diff --git a/src/Functional/Product.php b/src/Functional/Product.php
index ccd3a87e..697f204d 100644
--- a/src/Functional/Product.php
+++ b/src/Functional/Product.php
@@ -17,8 +17,8 @@
* Takes a collection and returns the product of all elements
*
* @param Traversable|array $collection
- * @param integer|float $initial
- * @return integer|float
+ * @param numeric $initial
+ * @return numeric
*/
function product($collection, $initial = 1)
{
diff --git a/src/Functional/Retry.php b/src/Functional/Retry.php
index 02a74bf6..6382ee4c 100644
--- a/src/Functional/Retry.php
+++ b/src/Functional/Retry.php
@@ -34,6 +34,7 @@ function retry(callable $callback, $retries, Traversable $delaySequence = null)
if ($delaySequence) {
$delays = new AppendIterator();
+ /** @psalm-suppress ArgumentTypeCoercion */
$delays->append(new InfiniteIterator($delaySequence));
$delays->append(new InfiniteIterator(new ArrayIterator([0])));
$delays = new LimitIterator($delays, $retries);
diff --git a/src/Functional/Sum.php b/src/Functional/Sum.php
index f79f5de5..642daceb 100644
--- a/src/Functional/Sum.php
+++ b/src/Functional/Sum.php
@@ -17,8 +17,8 @@
* Takes a collection and returns the sum of the elements
*
* @param Traversable|array $collection
- * @param integer|float $initial
- * @return integer|float
+ * @param numeric $initial
+ * @return numeric
*/
function sum($collection, $initial = 0)
{
diff --git a/src/Functional/ZipAll.php b/src/Functional/ZipAll.php
index 2c7658b8..80002e0f 100644
--- a/src/Functional/ZipAll.php
+++ b/src/Functional/ZipAll.php
@@ -27,6 +27,7 @@ function zip_all(...$args)
/** @var callable|null $callback */
$callback = null;
if (\is_callable(\end($args))) {
+ /** @var callable $callback */
$callback = \array_pop($args);
}
diff --git a/tests/Functional/MatchTest.php b/tests/Functional/MatchTest.php
index 323fc1bb..e52fb187 100644
--- a/tests/Functional/MatchTest.php
+++ b/tests/Functional/MatchTest.php
@@ -18,14 +18,19 @@ class MatchTest extends AbstractTestCase
{
public function testMatch()
{
- $test = match([
- [equal('foo'), const_function('is foo')],
- [equal('bar'), const_function('is bar')],
- [equal('baz'), const_function('is baz')],
- [const_function(true), function ($x) {
- return 'default is ' . $x;
- }],
- ]);
+ $test = match(
+ [
+ [equal('foo'), const_function('is foo')],
+ [equal('bar'), const_function('is bar')],
+ [equal('baz'), const_function('is baz')],
+ [
+ const_function(true),
+ function ($x) {
+ return 'default is ' . $x;
+ },
+ ],
+ ]
+ );
$this->assertEquals('is foo', $test('foo'));
$this->assertEquals('is bar', $test('bar'));
@@ -35,10 +40,12 @@ public function testMatch()
public function testNothingMatch()
{
- $test = match([
- [equal('foo'), const_function('is foo')],
- [equal('bar'), const_function('is bar')],
- ]);
+ $test = match(
+ [
+ [equal('foo'), const_function('is foo')],
+ [equal('bar'), const_function('is bar')],
+ ]
+ );
$this->assertNull($test('baz'));
}
@@ -50,36 +57,46 @@ public function testMatchConditionIsArray()
$callable = function () {
};
- $test = match([
- [$callable, $callable],
- '',
- ]);
+ $test = match(
+ [
+ [$callable, $callable],
+ '',
+ ]
+ );
}
public function testMatchConditionLength()
{
- $this->expectArgumentError('Functional\match() expects size of condition at key 1 to be greater than or equals to 2, 1 given');
+ $this->expectArgumentError(
+ 'Functional\match() expects size of condition at key 1 to be greater than or equals to 2, 1 given'
+ );
$callable = function () {
};
- $test = match([
- [$callable, $callable],
- [''],
- ]);
+ $test = match(
+ [
+ [$callable, $callable],
+ [''],
+ ]
+ );
}
public function testMatchConditionCallables()
{
$this->expectException(\Functional\Exceptions\InvalidArgumentException::class);
- $this->expectExceptionMessage('Functional\match() expects first two items of condition at key 1 to be callables');
+ $this->expectExceptionMessage(
+ 'Functional\match() expects first two items of condition at key 1 to be callables'
+ );
$callable = function () {
};
- $test = match([
- [$callable, $callable],
- [$callable, ''],
- ]);
+ $test = match(
+ [
+ [$callable, $callable],
+ [$callable, ''],
+ ]
+ );
}
}