Skip to content

Commit 67d24de

Browse files
authored
Merge pull request #7 from ghostwriter/feature/optimize-and-cleanup
feature/optimize and cleanup
2 parents 39ce110 + be05389 commit 67d24de

9 files changed

+99
-204
lines changed

psalm-baseline.xml

+1-20
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,2 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<files psalm-version="4.23.0@f1fe6ff483bf325c803df9f510d09a03fd796f88">
3-
<file src="src/None.php">
4-
<PropertyNotSetInConstructor occurrences="1">
5-
<code>None</code>
6-
</PropertyNotSetInConstructor>
7-
</file>
8-
<file src="tests/Unit/NoneTest.php">
9-
<InvalidArgument occurrences="1">
10-
<code>static fn () =&gt; 0</code>
11-
</InvalidArgument>
12-
</file>
13-
<file src="tests/Unit/SomeTest.php">
14-
<MissingClosureReturnType occurrences="1">
15-
<code>static fn ($x) =&gt; $x</code>
16-
</MissingClosureReturnType>
17-
<MixedArgumentTypeCoercion occurrences="1">
18-
<code>static fn ($x) =&gt; $x</code>
19-
</MixedArgumentTypeCoercion>
20-
</file>
21-
</files>
2+
<files psalm-version="v4.25.0@d7cd84c4ebca74ba3419b9601f81d177bcbe2aac"/>

src/AbstractOption.php

+13-67
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,28 @@
44

55
namespace Ghostwriter\Option;
66

7-
use Closure;
87
use Ghostwriter\Option\Contract\NoneInterface;
98
use Ghostwriter\Option\Contract\OptionInterface;
109
use Ghostwriter\Option\Contract\SomeInterface;
1110
use Ghostwriter\Option\Exception\InvalidReturnTypeException;
1211
use Ghostwriter\Option\Exception\NullPointerException;
13-
use ReflectionClass;
14-
use ReflectionFunction;
15-
use ReflectionNamedType;
1612
use Throwable;
1713
use Traversable;
18-
use function array_key_exists;
19-
use function is_array;
20-
use function is_callable;
2114

2215
/**
2316
* @template TValue
17+
* @immutable
2418
* @implements OptionInterface<TValue>
2519
*/
2620
abstract class AbstractOption implements OptionInterface
2721
{
2822
/**
29-
* @var TValue
23+
* @param TValue $value
3024
*/
31-
protected mixed $value;
25+
protected function __construct(protected mixed $value)
26+
{
27+
// Singleton
28+
}
3229

3330
public function and(OptionInterface $option): OptionInterface
3431
{
@@ -74,14 +71,8 @@ public function expect(Throwable $throwable): mixed
7471
public function filter(callable $function): OptionInterface
7572
{
7673
return $this->andThen(
77-
/**
78-
* @param TValue $value
79-
*
80-
* @return OptionInterface
81-
*/
82-
fn (mixed $value) => true === $function($value) ?
83-
$this :
84-
None::create()
74+
/** @param TValue $value */
75+
fn (mixed $value): OptionInterface => $function($value) ? $this : None::create()
8576
);
8677
}
8778

@@ -122,7 +113,7 @@ public function map(callable $function): OptionInterface
122113
return Some::create($function($this->value));
123114
}
124115

125-
public function mapOr(mixed $fallback, callable $function): mixed
116+
public function mapOr(callable $function, mixed $fallback): mixed
126117
{
127118
if ($this instanceof NoneInterface) {
128119
return $fallback;
@@ -131,7 +122,7 @@ public function mapOr(mixed $fallback, callable $function): mixed
131122
return $function($this->value);
132123
}
133124

134-
public function mapOrElse(callable $fallback, callable $function): mixed
125+
public function mapOrElse(callable $function, callable $fallback): mixed
135126
{
136127
if ($this instanceof NoneInterface) {
137128
return $fallback();
@@ -140,42 +131,14 @@ public function mapOrElse(callable $fallback, callable $function): mixed
140131
return $function($this->value);
141132
}
142133

143-
public static function of(mixed $value, mixed $noneValue = null): OptionInterface
134+
public static function of(mixed $value): OptionInterface
144135
{
145-
if ($value instanceof OptionInterface) {
146-
return $value;
147-
}
148-
149-
if ($noneValue === $value) {
150-
return None::create();
151-
}
152-
153136
if (null === $value) {
154137
return None::create();
155138
}
156139

157-
if (is_callable($value)) {
158-
/** @var array{0:class-string|object,1:string}|callable $value */
159-
$returnType = is_array($value) ?
160-
(new ReflectionClass($value[0]))->getMethod($value[1])->getReturnType() :
161-
(new ReflectionFunction(Closure::fromCallable($value)))->getReturnType();
162-
163-
if ($returnType instanceof ReflectionNamedType) {
164-
$returnTypeName = $returnType->getName();
165-
if (array_key_exists(
166-
$returnTypeName,
167-
[
168-
OptionInterface::class=>0,
169-
SomeInterface::class=>0,
170-
NoneInterface::class=>0,
171-
]
172-
)) {
173-
return None::create()->orElse(static function () use ($value): OptionInterface {
174-
/** @var callable():OptionInterface<TValue> $value */
175-
return $value();
176-
});
177-
}
178-
}
140+
if ($value instanceof OptionInterface) {
141+
return $value;
179142
}
180143

181144
return Some::create($value);
@@ -230,21 +193,4 @@ public function unwrapOrElse(callable $function): mixed
230193

231194
return $function();
232195
}
233-
234-
public function xor(OptionInterface $option): OptionInterface
235-
{
236-
if ($this instanceof SomeInterface) {
237-
if ($option instanceof SomeInterface) {
238-
return None::create();
239-
}
240-
241-
return $this;
242-
}
243-
244-
if ($option instanceof SomeInterface) {
245-
return $option;
246-
}
247-
248-
return $this;
249-
}
250196
}

src/Contract/NoneInterface.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
*/
1212
interface NoneInterface extends OptionInterface
1313
{
14-
/**
15-
* @return self<TValue>
16-
*/
14+
/** @return self<TValue> */
1715
public static function create(): self;
1816
}

src/Contract/OptionInterface.php

+12-21
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ public function contains(mixed $value): bool;
4343
public function expect(Throwable $throwable): mixed;
4444

4545
/**
46-
* Returns None if the option is None, otherwise calls predicate with the wrapped value and returns: Some(TValue) if
47-
* $function returns true (where TValue is the wrapped value), and None if $function returns false.
46+
* Returns None if the option is None, otherwise calls $function with the wrapped value
47+
* and returns: Some(TValue) if $function returns true (where TValue is the wrapped value),
48+
* and None if $function returns false.
4849
*
4950
* @param callable(TValue): bool $function
5051
*/
@@ -83,44 +84,39 @@ public function map(callable $function): self;
8384
*
8485
* @template TFallbackValue
8586
*
86-
* @param TFallbackValue $fallback
8787
* @param callable(TValue): TFallbackValue $function
88+
* @param TFallbackValue $fallback
8889
*
8990
* @return TFallbackValue
9091
*/
91-
public function mapOr(mixed $fallback, callable $function): mixed;
92+
public function mapOr(callable $function, mixed $fallback): mixed;
9293

9394
/**
9495
* Applies a function to the contained value (if any), or computes a default (if not).
9596
*
9697
* @template TFallbackValue
9798
*
98-
* @param callable(): mixed $fallback
9999
* @param callable(mixed): mixed $function
100+
* @param callable(): mixed $fallback
100101
*/
101-
public function mapOrElse(callable $fallback, callable $function): mixed;
102+
public function mapOrElse(callable $function, callable $fallback): mixed;
102103

103104
/**
104105
* Creates an option with the given value.
105106
*
106-
* This is intended for consuming existing APIs and allows you to easily convert them to an option.
107-
*
108107
* By default, we treat null as the None case, and everything else as Some.
109108
*
110-
* @template TNoneValue
109+
* @template TNullableValue
111110
*
112-
* @param TValue $value the actual return value
113-
* @param TNoneValue $noneValue the value which should be considered "None"; null by default
114-
*
115-
* @throws ReflectionException
111+
* @param TNullableValue $value the actual value
116112
*/
117-
public static function of(mixed $value, mixed $noneValue = null): self;
113+
public static function of(mixed $value): self;
118114

119115
/**
120116
* Returns the option if it contains a value, otherwise returns $option.
121117
*
122-
* Arguments passed to or are eagerly evaluated; if you are passing the result of a function call, it is recommended
123-
* to use orElse, which is lazily evaluated.
118+
* Arguments passed to or are eagerly evaluated; if you are passing the result of a function call,
119+
* it is recommended to use orElse, which is lazily evaluated.
124120
*/
125121
public function or(self $option): self;
126122

@@ -163,9 +159,4 @@ public function unwrapOr(mixed $fallback): mixed;
163159
* @return TCallableResultValue|TValue
164160
*/
165161
public function unwrapOrElse(callable $function): mixed;
166-
167-
/**
168-
* Returns Some if exactly one of self, $option is Some, otherwise returns None.
169-
*/
170-
public function xor(self $option): self;
171162
}

src/None.php

+1-6
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,8 @@ final class None extends AbstractOption implements NoneInterface
1616
{
1717
private static ?NoneInterface $instance = null;
1818

19-
private function __construct()
20-
{
21-
// Singleton
22-
}
23-
2419
public static function create(): NoneInterface
2520
{
26-
return self::$instance ??= new self();
21+
return self::$instance ??= new self(null);
2722
}
2823
}

src/Some.php

+3-17
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,16 @@
1616
final class Some extends AbstractOption implements SomeInterface
1717
{
1818
/**
19-
* @param TValue $value
20-
*/
21-
private function __construct(mixed $value)
22-
{
23-
// Singleton
24-
$this->value = $value;
25-
}
26-
27-
/**
28-
* @template TNoneValue
19+
* @template TSomeValue
2920
*
30-
* @param TValue $value
31-
* @param null|TNoneValue $noneValue
21+
* @param TSomeValue $value
3222
*/
33-
public static function create(mixed $value, mixed $noneValue = null): SomeInterface
23+
public static function create(mixed $value): SomeInterface
3424
{
3525
if (null === $value) {
3626
throw new NullPointerException();
3727
}
3828

39-
if ($noneValue === $value) {
40-
throw new NullPointerException();
41-
}
42-
4329
return new self($value);
4430
}
4531
}

tests/Unit/AbstractOptionTest.php

+5-12
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
final class AbstractOptionTest extends TestCase
2424
{
2525
/**
26-
* @return Traversable<array-key, array{0:class-string,1:mixed,2:mixed}>
26+
* @return Traversable<array-key, array{0:class-string,1:mixed}>
2727
*/
2828
public function ofDataProvider(): Traversable
2929
{
@@ -39,13 +39,8 @@ public function ofDataProvider(): Traversable
3939
yield 'object' => [SomeInterface::class, new stdClass()];
4040
yield 'array' => [SomeInterface::class, []];
4141
yield 'Some::class' => [SomeInterface::class, Some::create(1337)];
42-
yield 'None' => [NoneInterface::class, [None::class, 'create']];
43-
yield 'null:null' => [NoneInterface::class, null, null];
44-
yield 'null:false' => [NoneInterface::class, null, false];
45-
yield 'true:true' => [NoneInterface::class, true, true];
46-
yield 'false:false' => [NoneInterface::class, false, false];
47-
yield 'null:true' => [NoneInterface::class, null, true];
48-
yield 'string:string' => [NoneInterface::class, 'string', 'string'];
42+
yield 'null' => [NoneInterface::class, null];
43+
yield 'Some::class' => [NoneInterface::class, None::create()];
4944
}
5045

5146
/**
@@ -56,11 +51,9 @@ public function ofDataProvider(): Traversable
5651
* @dataProvider ofDataProvider
5752
*
5853
* @param class-string $expected
59-
*
60-
* @throws ReflectionException
6154
*/
62-
public function testOf(string $expected, mixed $value, mixed $noneValue = null): void
55+
public function testOf(string $expected, mixed $value): void
6356
{
64-
self::assertInstanceOf($expected, AbstractOption::of($value, $noneValue));
57+
self::assertInstanceOf($expected, AbstractOption::of($value));
6558
}
6659
}

0 commit comments

Comments
 (0)