Skip to content

Commit f3f8b6a

Browse files
committed
more tests, one failing
1 parent fa7053e commit f3f8b6a

File tree

2 files changed

+178
-39
lines changed

2 files changed

+178
-39
lines changed

core/src/main/scala/ox/either.scala

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import scala.util.control.NonFatal
77
import scala.util.NotGiven
88

99
object either:
10+
/** This technique allows to prevent implicit search from finding the givens thanks to ambiguity. In the scope that returns `A` it will be
11+
* impossible to find a given [[Supervised]] and [[Forked]].
12+
*/
1013
private type WithoutScopeMarkers[A] = (Supervised, Supervised) ?=> (Forked, Forked) ?=> A
1114

1215
private inline def availableInScope[A]: Boolean =
@@ -15,18 +18,18 @@ object either:
1518
case _: A => true
1619
}
1720

18-
/** Within an [[either]] block, allows unwrapping [[Either]] and [[Option]] values using [[ok()]]. The result is the right-value of an
21+
/** Within an [[either]] block, allows unwrapping [[Either]] and [[Option]] values using [[#ok()]]. The result is the right-value of an
1922
* `Either`, or the defined-value of the `Option`. In case a failure is encountered (a left-value of an `Either`, or a `None`), the
20-
* computation is short-circuited and the failure becomes the result. Failures can also be reported using [[fail()]].
23+
* computation is short-circuited and the failure becomes the result. Failures can also be reported using [[#fail()]].
2124
*
2225
* Uses the [[boundary]]-break mechanism.
2326
*
24-
* Uses ambiguity-based given removal technique (given't) to enable usage of [[ok()]] combinator in [[either]] blocks nested inside
27+
* Uses ambiguity-based given removal technique (given't) to enable usage of [[#ok()]] combinator in [[either]] blocks nested inside
2528
* [[ox.fork]] blocks.
2629
*
2730
* @param body
28-
* The code block, within which [[Either]]s and [[Option]]s can be unwrapped using [[ok()]]. Failures can be reported using [[fail()]].
29-
* Both [[ok()]] and [[fail()]] are extension methods.
31+
* The code block, within which [[Either]]s and [[Option]]s can be unwrapped using [[#ok()]]. Failures can be reported using
32+
* [[#fail()]]. Both [[#ok()]] and [[#fail()]] are extension methods.
3033
* @tparam E
3134
* The error type.
3235
* @tparam A
@@ -46,7 +49,7 @@ object either:
4649
*/
4750
inline def apply[E, A](inline body: WithoutScopeMarkers[Label[Either[E, A]] ?=> A]): Either[E, A] =
4851
given Forked = ForkedEvidence // just to satisfy the context function
49-
given Supervised = SupervisedEvidence // just to satisfy the context function
52+
given Supervised = SupervisedEvidence
5053
boundary(Right(body))
5154

5255
extension [E, A](inline t: Either[E, A])
@@ -58,54 +61,54 @@ object either:
5861
error(
5962
"This use of .ok() belongs to either block outside of the fork and is therefore illegal. Use either block inside of the forked block."
6063
)
61-
62-
summonFrom {
63-
case given boundary.Label[Either[E, Nothing]] =>
64-
t match
65-
case Left(e) => break(Left(e))
66-
case Right(a) => a
67-
case given boundary.Label[Either[Nothing, Nothing]] =>
68-
error("The enclosing `either` call uses a different error type.\nIf it's explicitly typed, is the error type correct?")
69-
case _ => error("`.ok()` can only be used within an `either` call.\nIs it present?")
70-
}
64+
else
65+
summonFrom {
66+
case given boundary.Label[Either[E, Nothing]] =>
67+
t match
68+
case Left(e) => break(Left(e))
69+
case Right(a) => a
70+
case given boundary.Label[Either[Nothing, Nothing]] =>
71+
error("The enclosing `either` call uses a different error type.\nIf it's explicitly typed, is the error type correct?")
72+
case _ => error("`.ok()` can only be used within an `either` call.\nIs it present?")
73+
}
7174

7275
extension [A](inline t: Option[A])
7376
/** Unwrap the value of the `Option`, short-circuiting the computation to the enclosing [[either]], in case this is a `None`. Can't be
7477
* used in forked blocks without an either block in fork to prevent escaped Breaks that crash forked threads.
7578
*/
7679
transparent inline def ok(): A =
77-
inline if availableInScope[Forked] then
80+
inline if availableInScope[Forked] && !availableInScope[Supervised] then
7881
error(
7982
"This use of .ok() belongs to either block outside of the fork and is therefore illegal. Use either block inside of the forked block."
8083
)
81-
82-
summonFrom {
83-
case given boundary.Label[Either[Unit, Nothing]] =>
84-
t match
85-
case None => break(Left(()))
86-
case Some(a) => a
87-
case given boundary.Label[Either[Nothing, Nothing]] =>
88-
error(
89-
"The enclosing `either` call uses a different error type.\nIf it's explicitly typed, is the error type correct?\nNote that for options, the error type must contain a `Unit`."
90-
)
91-
case _ => error("`.ok()` can only be used within an `either` call.\nIs it present?")
92-
}
84+
else
85+
summonFrom {
86+
case given boundary.Label[Either[Unit, Nothing]] =>
87+
t match
88+
case None => break(Left(()))
89+
case Some(a) => a
90+
case given boundary.Label[Either[Nothing, Nothing]] =>
91+
error(
92+
"The enclosing `either` call uses a different error type.\nIf it's explicitly typed, is the error type correct?\nNote that for options, the error type must contain a `Unit`."
93+
)
94+
case _ => error("`.ok()` can only be used within an `either` call.\nIs it present?")
95+
}
9396

9497
extension [E](e: E)
9598
/** Fail the computation by short-circuiting the enclosing [[either]] block with en error of type `E`. Can't be used in forked blocks
9699
* without an either block in fork to prevent escaped Breaks that crash forked threads.
97100
*/
98101
transparent inline def fail(): Nothing =
99-
inline if availableInScope[Forked] then
102+
inline if availableInScope[Forked] && !availableInScope[Supervised] then
100103
error(
101104
"This use of .ok() belongs to either block outside of the fork and is therefore illegal. Use either block inside of the forked block."
102105
)
103-
104-
summonFrom {
105-
case given boundary.Label[Either[E, Nothing]] => break(Left(e))
106-
case given boundary.Label[Either[Nothing, Nothing]] =>
107-
error("The enclosing `either` call uses a different error type.\nIf it's explicitly typed, is the error type correct?")
108-
}
106+
else
107+
summonFrom {
108+
case given boundary.Label[Either[E, Nothing]] => break(Left(e))
109+
case given boundary.Label[Either[Nothing, Nothing]] =>
110+
error("The enclosing `either` call uses a different error type.\nIf it's explicitly typed, is the error type correct?")
111+
}
109112

110113
/** Catches non-fatal exceptions that occur when evaluating `t` and returns them as the left side of the returned `Either`. */
111114
inline def catching[T](inline t: => T): Either[Throwable, T] =

core/src/test/scala/ox/ForkEitherInteropTest.scala

Lines changed: 139 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ package ox
22

33
import org.scalatest.flatspec.AnyFlatSpec
44
import org.scalatest.matchers.should.Matchers
5-
import scala.compiletime.testing.typeCheckErrors
5+
import scala.compiletime.testing.*
66

77
class ForkEitherInteropTest extends AnyFlatSpec with Matchers {
88

99
"forkUnsupervised" should "prevent usage of either.ok() combinator" in {
1010
assertReceivesForkedScopeError(
1111
"""import ox.*
1212
import ox.either.*
13-
13+
1414
val e1: Either[Throwable, Unit] = Left(Exception("uh oh"))
1515
unsupervised {
1616
either {
@@ -142,7 +142,8 @@ class ForkEitherInteropTest extends AnyFlatSpec with Matchers {
142142
}
143143
}
144144

145-
"either in fork" should "allow usage of either.ok() combinator" in {
145+
// empty line at the beginning of every snippet left to improve error reporting readability, do not remove
146+
"fork/either interop" should "compile correct combinations" in {
146147
"""
147148
import ox.*
148149
import ox.either.*
@@ -158,6 +159,140 @@ class ForkEitherInteropTest extends AnyFlatSpec with Matchers {
158159
}
159160
}
160161
""" should compile
162+
163+
"""
164+
import ox.*
165+
import ox.either.*
166+
167+
supervised {
168+
either {
169+
fork {
170+
1
171+
}
172+
}
173+
}""" should compile
174+
175+
"""
176+
import ox.*
177+
import ox.either.*
178+
179+
either {
180+
supervised {
181+
fork {
182+
Right(1).ok()
183+
}
184+
}
185+
}""" should compile
186+
187+
"""
188+
import ox.*
189+
import ox.either.*
190+
191+
supervised {
192+
fork {
193+
either {
194+
Right(1).ok()
195+
}
196+
}
197+
}""" should compile
198+
199+
"""
200+
import ox.*
201+
import ox.either.*
202+
203+
either {
204+
supervised {
205+
fork {
206+
supervised {
207+
Right(1).ok()
208+
}
209+
}
210+
}
211+
}""" should compile
212+
213+
"""
214+
import ox.*
215+
import ox.either.*
216+
217+
either {
218+
supervised {
219+
fork {
220+
supervised {
221+
fork {
222+
Right(1).ok()
223+
}
224+
}
225+
}
226+
}
227+
}""" should compile
228+
229+
"""
230+
import ox.*
231+
import ox.either.*
232+
233+
either {
234+
supervised {
235+
fork {
236+
fork {
237+
Right(1).ok()
238+
}
239+
}
240+
}
241+
}""" should compile
242+
}
243+
244+
"fork/either interop" should "fail to compile invalid examples" in {
245+
"""
246+
import ox.*
247+
import ox.either.*
248+
249+
supervised {
250+
either {
251+
fork {
252+
Right(1).ok()
253+
}
254+
}
255+
}""" shouldNot compile
256+
257+
"""
258+
import ox.*
259+
import ox.either.*
260+
261+
supervised {
262+
fork {
263+
either {
264+
fork {
265+
Right(1).ok()
266+
}
267+
}
268+
}
269+
}""" shouldNot compile
270+
271+
"""
272+
import ox.*
273+
import ox.either.*
274+
275+
supervised {
276+
either {
277+
fork {
278+
Right(1).ok()
279+
}
280+
}
281+
}""" shouldNot compile
282+
283+
"""
284+
import ox.*
285+
import ox.either.*
286+
287+
supervised { // Supervised ?=>
288+
either { // Label ?=>
289+
fork { // Forked ?=>
290+
supervised { // Supervised ?=>
291+
Right(1).ok() // if forked && !supervised then error but it is forked and then supervised
292+
}
293+
}
294+
}
295+
}""" shouldNot compile
161296
}
162297

163298
"fork/either interop" should "work with either scopes nested in forks" in {
@@ -207,4 +342,5 @@ class ForkEitherInteropTest extends AnyFlatSpec with Matchers {
207342
"This use of .ok() belongs to either block outside of the fork and is therefore illegal. Use either block inside of the forked block."
208343
)
209344
then throw Exception(errs.mkString("\n"))
345+
210346
}

0 commit comments

Comments
 (0)