Skip to content

Commit b18f147

Browse files
committed
avoid capturing $this for a deferred either
1 parent d74ec8b commit b18f147

File tree

1 file changed

+56
-8
lines changed

1 file changed

+56
-8
lines changed

src/Either/Defer.php

+56-8
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,23 @@ public function __construct($deferred)
3232

3333
public function map(callable $map): self
3434
{
35-
return new self(fn() => $this->unwrap()->map($map));
35+
$captured = $this->capture();
36+
37+
return new self(static fn() => self::detonate($captured)->map($map));
3638
}
3739

3840
public function flatMap(callable $map): Either
3941
{
40-
return Either::defer(fn() => $this->unwrap()->flatMap($map));
42+
$captured = $this->capture();
43+
44+
return Either::defer(static fn() => self::detonate($captured)->flatMap($map));
4145
}
4246

4347
public function leftMap(callable $map): self
4448
{
45-
return new self(fn() => $this->unwrap()->leftMap($map));
49+
$captured = $this->capture();
50+
51+
return new self(static fn() => self::detonate($captured)->leftMap($map));
4652
}
4753

4854
public function match(callable $right, callable $left)
@@ -52,17 +58,23 @@ public function match(callable $right, callable $left)
5258

5359
public function otherwise(callable $otherwise): Either
5460
{
55-
return Either::defer(fn() => $this->unwrap()->otherwise($otherwise));
61+
$captured = $this->capture();
62+
63+
return Either::defer(static fn() => self::detonate($captured)->otherwise($otherwise));
5664
}
5765

5866
public function filter(callable $predicate, callable $otherwise): Implementation
5967
{
60-
return new self(fn() => $this->unwrap()->filter($predicate, $otherwise));
68+
$captured = $this->capture();
69+
70+
return new self(static fn() => self::detonate($captured)->filter($predicate, $otherwise));
6171
}
6272

6373
public function maybe(): Maybe
6474
{
65-
return Maybe::defer(fn() => $this->unwrap()->maybe());
75+
$captured = $this->capture();
76+
77+
return Maybe::defer(static fn() => self::detonate($captured)->maybe());
6678
}
6779

6880
/**
@@ -75,12 +87,16 @@ public function memoize(): Either
7587

7688
public function flip(): self
7789
{
78-
return new self(fn() => $this->unwrap()->flip());
90+
$captured = $this->capture();
91+
92+
return new self(static fn() => self::detonate($captured)->flip());
7993
}
8094

8195
public function eitherWay(callable $right, callable $left): Either
8296
{
83-
return Either::defer(fn() => $this->unwrap()->eitherWay($right, $left));
97+
$captured = $this->capture();
98+
99+
return Either::defer(static fn() => self::detonate($captured)->eitherWay($right, $left));
84100
}
85101

86102
/**
@@ -94,4 +110,36 @@ private function unwrap(): Either
94110
*/
95111
return $this->value ??= ($this->deferred)();
96112
}
113+
114+
/**
115+
* @return array{\WeakReference<self<L1, R1>>, callable(): Either<L1, R1>}
116+
*/
117+
private function capture(): array
118+
{
119+
/** @psalm-suppress ImpureMethodCall */
120+
return [
121+
\WeakReference::create($this),
122+
$this->deferred,
123+
];
124+
}
125+
126+
/**
127+
* @template A
128+
* @template B
129+
*
130+
* @param array{\WeakReference<self<A, B>>, callable(): Either<A, B>} $captured
131+
*
132+
* @return Either<A, B>
133+
*/
134+
private static function detonate(array $captured): Either
135+
{
136+
[$ref, $deferred] = $captured;
137+
$self = $ref->get();
138+
139+
if (\is_null($self)) {
140+
return $deferred();
141+
}
142+
143+
return $self->unwrap();
144+
}
97145
}

0 commit comments

Comments
 (0)