diff --git a/composer.lock b/composer.lock index e278e3fb..1072ea60 100644 --- a/composer.lock +++ b/composer.lock @@ -339,16 +339,16 @@ }, { "name": "composer/pcre", - "version": "3.0.2", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "4482b6409ca6bfc2af043a5711cd21ac3e7a8dfb" + "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/4482b6409ca6bfc2af043a5711cd21ac3e7a8dfb", - "reference": "4482b6409ca6bfc2af043a5711cd21ac3e7a8dfb", + "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", + "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", "shasum": "" }, "require": { @@ -390,7 +390,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.0.2" + "source": "https://github.com/composer/pcre/tree/3.1.0" }, "funding": [ { @@ -406,7 +406,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T20:24:16+00:00" + "time": "2022-11-17T09:50:14+00:00" }, { "name": "composer/semver", @@ -1817,16 +1817,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.1", + "version": "v4.15.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" + "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", + "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", "shasum": "" }, "require": { @@ -1867,9 +1867,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2" }, - "time": "2022-09-04T07:30:47+00:00" + "time": "2022-11-12T15:38:23+00:00" }, { "name": "ocramius/package-versions", @@ -2671,16 +2671,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.18", + "version": "9.2.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a" + "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/12fddc491826940cf9b7e88ad9664cf51f0f6d0a", - "reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c77b56b63e3d2031bd8997fcec43c1925ae46559", + "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559", "shasum": "" }, "require": { @@ -2736,7 +2736,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.18" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.19" }, "funding": [ { @@ -2744,7 +2744,7 @@ "type": "github" } ], - "time": "2022-10-27T13:35:33+00:00" + "time": "2022-11-18T07:47:47+00:00" }, { "name": "phpunit/php-file-iterator", @@ -3606,16 +3606,16 @@ }, { "name": "sanmai/pipeline", - "version": "v6.1", + "version": "v6.2", "source": { "type": "git", "url": "https://github.com/sanmai/pipeline.git", - "reference": "3a88f2617237e18d5cd2aa38ca3d4b22770306c2" + "reference": "1c14946cb089776fb024807f1e4f5a985db0ce3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sanmai/pipeline/zipball/3a88f2617237e18d5cd2aa38ca3d4b22770306c2", - "reference": "3a88f2617237e18d5cd2aa38ca3d4b22770306c2", + "url": "https://api.github.com/repos/sanmai/pipeline/zipball/1c14946cb089776fb024807f1e4f5a985db0ce3c", + "reference": "1c14946cb089776fb024807f1e4f5a985db0ce3c", "shasum": "" }, "require": { @@ -3659,7 +3659,7 @@ "description": "General-purpose collections pipeline", "support": { "issues": "https://github.com/sanmai/pipeline/issues", - "source": "https://github.com/sanmai/pipeline/tree/v6.1" + "source": "https://github.com/sanmai/pipeline/tree/v6.2" }, "funding": [ { @@ -3667,7 +3667,7 @@ "type": "github" } ], - "time": "2022-01-30T08:15:59+00:00" + "time": "2022-11-09T05:47:57+00:00" }, { "name": "sebastian/cli-parser", @@ -5351,16 +5351,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", "shasum": "" }, "require": { @@ -5375,7 +5375,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5413,7 +5413,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" }, "funding": [ { @@ -5429,20 +5429,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "433d05519ce6990bf3530fba6957499d327395c2" + "reference": "511a08c03c1960e08a883f4cffcacd219b758354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", - "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354", "shasum": "" }, "require": { @@ -5454,7 +5454,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5494,7 +5494,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" }, "funding": [ { @@ -5510,20 +5510,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", "shasum": "" }, "require": { @@ -5535,7 +5535,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5578,7 +5578,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" }, "funding": [ { @@ -5594,20 +5594,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", "shasum": "" }, "require": { @@ -5622,7 +5622,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5661,7 +5661,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" }, "funding": [ { @@ -5677,20 +5677,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", "shasum": "" }, "require": { @@ -5699,7 +5699,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5744,7 +5744,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" }, "funding": [ { @@ -5760,20 +5760,20 @@ "type": "tidelift" } ], - "time": "2022-05-10T07:21:04+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" + "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", - "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", + "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", "shasum": "" }, "require": { @@ -5782,7 +5782,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5823,7 +5823,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" }, "funding": [ { @@ -5839,7 +5839,7 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/process", @@ -6399,16 +6399,16 @@ }, { "name": "vimeo/psalm", - "version": "4.29.0", + "version": "4.30.0", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "7ec5ffbd5f68ae03782d7fd33fff0c45a69f95b3" + "reference": "d0bc6e25d89f649e4f36a534f330f8bb4643dd69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/7ec5ffbd5f68ae03782d7fd33fff0c45a69f95b3", - "reference": "7ec5ffbd5f68ae03782d7fd33fff0c45a69f95b3", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/d0bc6e25d89f649e4f36a534f330f8bb4643dd69", + "reference": "d0bc6e25d89f649e4f36a534f330f8bb4643dd69", "shasum": "" }, "require": { @@ -6501,9 +6501,9 @@ ], "support": { "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/4.29.0" + "source": "https://github.com/vimeo/psalm/tree/4.30.0" }, - "time": "2022-10-11T17:09:17+00:00" + "time": "2022-11-06T20:37:08+00:00" }, { "name": "webmozart/assert", diff --git a/src/Psl/Internal/Loader.php b/src/Psl/Internal/Loader.php index bd68d5fd..67633509 100644 --- a/src/Psl/Internal/Loader.php +++ b/src/Psl/Internal/Loader.php @@ -478,6 +478,8 @@ final class Loader 'Psl\\OS\\family' => 'Psl/OS/family.php', 'Psl\\OS\\is_windows' => 'Psl/OS/is_windows.php', 'Psl\\OS\\is_darwin' => 'Psl/OS/is_darwin.php', + 'Psl\\Option\\some' => 'Psl/Option/some.php', + 'Psl\\Option\\none' => 'Psl/Option/none.php', ]; public const INTERFACES = [ @@ -562,6 +564,7 @@ final class Loader 'Psl\\Vec\\Exception\\ExceptionInterface' => 'Psl/Vec/Exception/ExceptionInterface.php', 'Psl\\Dict\\Exception\\ExceptionInterface' => 'Psl/Dict/Exception/ExceptionInterface.php', 'Psl\\PseudoRandom\\Exception\\ExceptionInterface' => 'Psl/PseudoRandom/Exception/ExceptionInterface.php', + 'Psl\\Option\\Exception\\ExceptionInterface' => 'Psl/Option/Exception/ExceptionInterface.php', ]; public const TRAITS = [ @@ -738,6 +741,8 @@ final class Loader 'Psl\\Iter\\Exception\\InvalidArgumentException' => 'Psl/Iter/Exception/InvalidArgumentException.php', 'Psl\\PseudoRandom\\Exception\\InvalidArgumentException' => 'Psl/PseudoRandom/Exception/InvalidArgumentException.php', 'Psl\\Async\\Exception\\InvalidArgumentException' => 'Psl/Async/Exception/InvalidArgumentException.php', + 'Psl\\Option\\Exception\\NoneException' => 'Psl/Option/Exception/NoneException.php', + 'Psl\\Option\\Option' => 'Psl/Option/Option.php', ]; public const ENUMS = [ diff --git a/src/Psl/Option/Exception/ExceptionInterface.php b/src/Psl/Option/Exception/ExceptionInterface.php new file mode 100644 index 00000000..9247b8aa --- /dev/null +++ b/src/Psl/Option/Exception/ExceptionInterface.php @@ -0,0 +1,11 @@ + + */ + public static function some(mixed $value): Option + { + return new self([$value]); + } + + /** + * Create an option with none value. + * + * @template Tn + * + * @return Option + */ + public static function none(): Option + { + return new self(null); + } + + /** + * Returns true if the option is a some value. + */ + public function isSome(): bool + { + return $this->option !== null; + } + + /** + * Returns true if the option is a some and the value inside of it matches a predicate. + * + * @param (Closure(T): bool) $predicate + */ + public function isSomeAnd(Closure $predicate): bool + { + return $this->option !== null && $predicate($this->option[0]); + } + + /** + * Returns true if the option is a none. + */ + public function isNone(): bool + { + return $this->option === null; + } + + /** + * Returns the contained Some value, consuming the self value. + * + * because this function may throw, its use is generally discouraged. + * Instead, prefer to use `Option::unwrapOr()`, `Option::unwrapOrElse()`. + * + * @throws Exception\NoneException If the option is none. + * + * @return T + */ + public function unwrap(): mixed + { + if ($this->option !== null) { + return $this->option[0]; + } + + throw new Exception\NoneException('Attempting to unwrap a none option.'); + } + + /** + * Returns the contained some value or the provided default. + * + * @note: Arguments passed to `Option::unwrapOr()` are eagerly evaluated; + * if you are passing the result of a function call, it is recommended to use `Option::unwrapOrElse()`, which is lazily evaluated. + * + * @param T $default + * + * @return T + */ + public function unwrapOr(mixed $default): mixed + { + if ($this->option !== null) { + return $this->option[0]; + } + + return $default; + } + + /** + * Returns the contained some value or computes it from a closure. + * + * @param (Closure(): T) $default + * + * @return T + */ + public function unwrapOrElse(Closure $default): mixed + { + if ($this->option !== null) { + return $this->option[0]; + } + + return $default(); + } + + /** + * Return none if either `$this`, or `$other` options are none, otherwise returns `$other`. + * + * @template Tu + * + * @param Option $other + * + * @return Option + */ + public function and(Option $other): Option + { + if ($this->option !== null && $other->option !== null) { + return $other; + } + + return none(); + } + + /** + * Returns the option if it contains a value, otherwise returns $option. + * + * @note: Arguments passed to `Option::or()` are eagerly evaluated; + * if you are passing the result of a function call, it is recommended to use `Option::orElse()`, which is lazily evaluated. + * + * @param Option $option + * + * @return Option + */ + public function or(Option $option): Option + { + if ($this->option !== null) { + return $this; + } + + return $option; + } + + /** + * Returns none if the option is none, otherwise calls `$predicate` with the wrapped value and returns: + * - Option::some() if `$predicate` returns true (where t is the wrapped value), and + * - Option::none() if `$predicate` returns false. + * + * @param (Closure(T): bool) $predicate + * + * @return Option + */ + public function filter(Closure $predicate): Option + { + if ($this->option !== null) { + return $predicate($this->option[0]) ? $this : none(); + } + + return $this; + } + + /** + * Returns true if the option is a `Option::some()` value containing the given value. + * + * @psalm-assert-if-true T $value + */ + public function contains(mixed $value): bool + { + if ($this->option !== null) { + return $this->option[0] === $value; + } + + return false; + } + + /** + * Maps an `Option` to `Option` by applying a function to a contained value. + * + * @template Tu + * + * @param (Closure(T): Tu) $closure + * + * @return Option + */ + public function map(Closure $closure): Option + { + if ($this->option !== null) { + return some($closure($this->option[0])); + } + + /** @var Option */ + return $this; + } + + /** + * Applies a function to the contained value (if some), + * or returns `Option::some()` with the provided `$default` value (if none). + * + * @note: arguments passed to `Option::mapOr()` are eagerly evaluated; + * if you are passing the result of a function call, it is recommended to use `Option::mapOrElse()`, which is lazily evaluated. + * + * @template Tu + * + * @param (Closure(T): Tu) $closure + * @param Tu $default + * + * @return Option + */ + public function mapOr(Closure $closure, mixed $default): Option + { + if ($this->option !== null) { + return some($closure($this->option[0])); + } + + return some($default); + } + + /** + * Applies a function to the contained value (if some), + * or computes a default function result (if none). + * + * @template Tu + * + * @param (Closure(T): Tu) $closure + * @param (Closure(): Tu) $else + * + * @return Option + */ + public function mapOrElse(Closure $closure, Closure $default): Option + { + if ($this->option !== null) { + return some($closure($this->option[0])); + } + + return some($default()); + } +} diff --git a/src/Psl/Option/none.php b/src/Psl/Option/none.php new file mode 100644 index 00000000..53f4ed84 --- /dev/null +++ b/src/Psl/Option/none.php @@ -0,0 +1,17 @@ + + */ +function none(): Option +{ + return Option::none(); +} diff --git a/src/Psl/Option/some.php b/src/Psl/Option/some.php new file mode 100644 index 00000000..96610e9a --- /dev/null +++ b/src/Psl/Option/some.php @@ -0,0 +1,19 @@ + + */ +function some(mixed $value): Option +{ + return Option::some($value); +} diff --git a/tests/unit/Option/NoneTest.php b/tests/unit/Option/NoneTest.php new file mode 100644 index 00000000..41b55dfe --- /dev/null +++ b/tests/unit/Option/NoneTest.php @@ -0,0 +1,104 @@ +isNone()); + static::assertFalse($option->isSome()); + } + + public function testIsSomeAnd(): void + { + $option = Option\none(); + + static::assertFalse($option->isSomeAnd(static fn($i) => $i < 10)); + static::assertFalse($option->isSomeAnd(static fn($i) => $i > 10)); + } + + public function testUnwrap(): void + { + $option = Option\none(); + + $this->expectException(NoneException::class); + $this->expectErrorMessage('Attempting to unwrap a none option.'); + + $option->unwrap(); + } + + public function testUnwrapOr(): void + { + $option = Option\none(); + + static::assertSame(4, $option->unwrapOr(4)); + } + + public function testUnwrapOrElse(): void + { + $option = Option\none(); + + static::assertSame(4, $option->unwrapOrElse(static fn() => 4)); + } + + public function testAnd(): void + { + static::assertFalse(Option\none()->and(Option\none())->isSome()); + static::assertFalse(Option\none()->and(Option\some(4))->isSome()); + static::assertTrue(Option\none()->and(Option\none())->isNone()); + static::assertTrue(Option\none()->and(Option\some(4))->isNone()); + } + + public function testOr(): void + { + static::assertFalse(Option\none()->or(Option\none())->isSome()); + static::assertTrue(Option\none()->or(Option\some(4))->isSome()); + static::assertTrue(Option\none()->or(Option\none())->isNone()); + static::assertFalse(Option\none()->or(Option\some(4))->isNone()); + } + + public function testFilter(): void + { + $option = Option\none(); + + static::assertTrue($option->filter(static fn($_) => true)->isNone()); + static::assertTrue($option->filter(static fn($_) => false)->isNone()); + } + + public function testContains(): void + { + $option = Option\none(); + + static::assertFalse($option->contains(4)); + } + + public function testMap(): void + { + $option = Option\none(); + + static::assertNull($option->map(static fn($i) => $i + 1)->unwrapOr(null)); + } + + public function testMapOr(): void + { + $option = Option\none(); + + static::assertSame(4, $option->mapOr(static fn($i) => $i + 1, 4)->unwrap()); + } + + public function testMapOrElse(): void + { + $option = Option\none(); + + static::assertSame(4, $option->mapOrElse(static fn($i) => $i + 1, static fn() => 4)->unwrap()); + } +} diff --git a/tests/unit/Option/SomeTest.php b/tests/unit/Option/SomeTest.php new file mode 100644 index 00000000..8d032e5f --- /dev/null +++ b/tests/unit/Option/SomeTest.php @@ -0,0 +1,101 @@ +isNone()); + static::assertTrue($option->isSome()); + } + + public function testIsSomeAnd(): void + { + $option = Option\some(4); + + static::assertTrue($option->isSomeAnd(static fn($i) => $i < 10)); + static::assertFalse($option->isSomeAnd(static fn($i) => $i > 10)); + } + + public function testUnwrap(): void + { + $option = Option\some(4); + + static::assertSame(4, $option->unwrap()); + } + + public function testUnwrapOr(): void + { + $option = Option\some(2); + + static::assertSame(2, $option->unwrapOr(4)); + } + + public function testUnwrapOrElse(): void + { + $option = Option\some(2); + + static::assertSame(2, $option->unwrapOrElse(static fn() => 4)); + } + + public function testAnd(): void + { + static::assertFalse(Option\some(2)->and(Option\none())->isSome()); + static::assertTrue(Option\some(2)->and(Option\some(4))->isSome()); + static::assertTrue(Option\some(2)->and(Option\none())->isNone()); + static::assertFalse(Option\some(2)->and(Option\some(4))->isNone()); + } + + public function testOr(): void + { + static::assertTrue(Option\some(2)->or(Option\none())->isSome()); + static::assertTrue(Option\some(2)->or(Option\some(4))->isSome()); + static::assertFalse(Option\some(2)->or(Option\none())->isNone()); + static::assertFalse(Option\some(2)->or(Option\some(4))->isNone()); + } + + public function testFilter(): void + { + $option = Option\some(2); + + static::assertTrue($option->filter(static fn($_) => true)->isSome()); + static::assertTrue($option->filter(static fn($_) => false)->isNone()); + } + + public function testContains(): void + { + $option = Option\some(2); + + static::assertFalse($option->contains(4)); + static::assertTrue($option->contains(2)); + } + + public function testMap(): void + { + $option = Option\some(2); + + static::assertSame(3, $option->map(static fn($i) => $i + 1)->unwrapOr(0)); + } + + public function testMapOr(): void + { + $option = Option\some(2); + + static::assertSame(3, $option->mapOr(static fn($i) => $i + 1, 4)->unwrap()); + } + + public function testMapOrElse(): void + { + $option = Option\some(2); + + static::assertSame(3, $option->mapOrElse(static fn($i) => $i + 1, static fn() => 4)->unwrap()); + } +}