diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index a5e3052d91..0a721663a6 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -17,8 +17,8 @@ package net.liftweb package common -import scala.language.implicitConversions import scala.language.existentials +import scala.language.implicitConversions import scala.reflect.Manifest import java.util.{Iterator => JavaIterator, ArrayList => JavaArrayList} @@ -82,7 +82,7 @@ object Box extends BoxTrait with Tryo { * @param failureErrorMessage The string that should be placed in the message for the Failure. * @return A `Full[List[T]]` if no `Failure`s were present. `ParamFailure[List[Box[T]]]` otherwise. **/ - def toSingleBox(failureErrorMessage: String): Box[List[T]] = { + def toSingleBox(failureErrorMessage: String): ParamTryBox[List[T], List[Box[T]]] = { if (theListOfBoxes.exists(_.isInstanceOf[Failure])) { val failureChain = theListOfBoxes.collect { @@ -200,7 +200,7 @@ sealed trait BoxTrait { * @return `Full` with the contents if the `Option` is `Some` and `Empty` * otherwise. */ - implicit def option2Box[T](in: Option[T]): Box[T] = Box(in) + implicit def option2Box[T](in: Option[T]): PresenceBox[T] = Box(in) /** * This implicit transformation allows one to use a `Box` as an `Option`. @@ -220,7 +220,7 @@ sealed trait BoxTrait { * * @return `Full` if `in` is not null and `Empty` otherwise. */ - def legacyNullTest[T](in: T): Box[T] = in match { + def legacyNullTest[T](in: T): PresenceBox[T] = in match { case null => Empty case _ => Full(in) } @@ -228,14 +228,14 @@ sealed trait BoxTrait { /** * Alias for `[[legacyNullTest]]`. */ - def !![T](in: T): Box[T] = legacyNullTest(in) + def !![T](in: T): PresenceBox[T] = legacyNullTest(in) /** * Create a `Full` box containing the specified value if `in` is an instance of * the specified class `clz` and `Empty` otherwise. * - * This is basically a Java-friendly version of `[[asA]]`, which you should - * prefer when using Scala. + * This is basically a Java-friendly version of `[[asA]]`; you should prefer + * `asA` when using Scala. * * For example: * {{{ @@ -246,7 +246,7 @@ sealed trait BoxTrait { * res1: net.liftweb.common.Box[Int] = Full(5) * }}} */ - def isA[A, B](in: A, clz: Class[B]): Box[B] = { + def isA[A, B](in: A, clz: Class[B]): PresenceBox[B] = { (Box !! in).isA(clz) } @@ -273,7 +273,7 @@ sealed trait BoxTrait { * res1: net.liftweb.common.Box[Int] = Full(5) * }}} */ - def asA[B](in: T forSome { type T })(implicit m: Manifest[B]): Box[B] = { + def asA[B](in: T forSome { type T })(implicit m: Manifest[B]): PresenceBox[B] = { (Box !! in).asA[B] } } @@ -291,14 +291,15 @@ final class DoNotCallThisMethod * (containing a single non-null value) or `[[EmptyBox]]`. An `EmptyBox`, * or empty, can be the `[[Empty]]` singleton, `[[Failure]]` or * `[[ParamFailure]]`. `Failure` and `ParamFailure` contain information about - * why the `Box` is empty including exception information, possibly chained - * `Failure`s and a `String` message. + * why the `Box` is empty including possible exception information, possibly + * chained `Failure`s and a `String` message. * * This serves a similar purpose to the `[[scala.Option Option]]` class from - * Scala standard library but adds several features: + * Scala standard library, combined with the `[[scala.Try Try]]` class from the + * same library, and adds several features to boot: * - You can transform it to a `Failure` object if it is `Empty` (with the * `[[?~]]` or `[[failMsg]]` method). - * - You can chain failure messages on `Failure`s (with the `?~!` or + * - You can chain failure messages on existing `Failure`s (with the `?~!` or * `[[compoundFailMsg]]` method). * - You can "run" a function on a `Box`, with a default to return if the box * is `Empty`: @@ -325,6 +326,12 @@ final class DoNotCallThisMethod * ) // doSomething gets a Box[Int] as well * }}} * + * You can also choose the first `Full` box of a set by using `or` (as with the + * `Option` equivalent, `orElse`) and provide an alternative to a `Box`'s value + * using `openOr` (as with the `Option` equivalent, `getOrElse`). Boxes can be + * ``filter``ed, ``map``ed, ``flatMap``ed, and converted to zero-or-one element + * ``Iterable``s or ``List``s as needed. + * * === Exceptions and Empty Box Handling === * * If you grew up on Java, you're used to `Exception`s as part of your program @@ -384,8 +391,29 @@ final class DoNotCallThisMethod * loggedInUser must_== Full(mockUser) * (loggedInUser === mockUser) must beTrue * }}} + * + * Additionally, when interacting with external APIs, you can look at using the + * `lift-util` helper `tryo`, which executes a function and catches exceptions, + * converting thrown exceptions into `Failure` boxes. + * + * === `Box` Subtypes and Clear Typing === + * + * In addition to the standard hierarchy above, `Box` lets you declare your + * type expectations in a little narrower fashion. If you're expecting to only + * deal with `Full[T]` or `Failure`, you can declare that you expect to return or + * want to receive a `TryBox[T]`. The compiler will then enforce that an `Empty` + * cannot be provided. If you're expecting only `Full[T]` or `Empty` instead, + * you can declare that you expect to return or want to receive a `PresenceBox[T]`. + * Here again, the compiler will enforce a `Failure` cannot be provided. Finally, + * if you're expecting specifically a `Full[T]` or a `ParamFailure[E]`, then you + * can declare that you expect to return or want to receive a `ParamTryBox[T, E]`. + * Here, the compiler will disallow both `Empty` and a bare `Failure`. + * + * The methods on `Box` itself generally return the correct subtypes, so that + * you can rely on transformations like `map` and `flatMap` to preserve the + * closest match to the expected types that are being sent through. */ -sealed abstract class Box[+A] extends Product with Serializable{ +sealed abstract class Box[+A] extends Product with Serializable { self => /** * Returns `true` if this `Box` contains no value (i.e., it is `Empty` or @@ -446,7 +474,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ * @note This means that using `map` with a `Failure` will preserve the * `Failure.` */ - def map[B](f: A => B): Box[B] = Empty + def map[B](f: A => B): Box[B] /** * Apply a function returning a `Box` to the value contained in this `Box` if @@ -458,13 +486,16 @@ sealed abstract class Box[+A] extends Product with Serializable{ */ def flatMap[B](f: A => Box[B]): Box[B] = Empty + /** + * Flatten this `Box[Box[T]]` to be a `Box[T]`. The compiler will prevent + * calling this function if this is a `Box[T]` instead of a `Box[Box[T]]`. + */ def flatten[B](implicit ev: A <:< Box[B]): Box[B] = this match { case Full(internal) => ev(internal) case f: Failure => f case Empty => Empty } - /** * If this `Box` contains a value and it satisfies the specified `predicate`, * return the `Box` unchanged. Otherwise, return an `Empty`. @@ -602,7 +633,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ * @return A `Failure` with the message if this `Box` is `Empty`, this box * otherwise. */ - def ?~(msg: => String): Box[A] = this + def ?~(msg: => String): TryBox[A] /** * Transform an `Empty` or `Failure` to a `ParamFailure` with the specified @@ -613,12 +644,12 @@ sealed abstract class Box[+A] extends Product with Serializable{ * `ParamFailure` or a `Full`. If this is a `Failure`, the * `ParamFailure` will preserve the message of the `Failure`. */ - def ~>[T](errorCode: => T): Box[A] = this + def ~>[T](errorCode: => T): ParamTryBox[A, T] /** * Alias for `[[?~]]`. */ - def failMsg(msg: => String): Box[A] = ?~(msg) + def failMsg(msg: => String): TryBox[A] = ?~(msg) /** * Chain the given `msg` as a `Failure` ahead of any failures this `Box` may @@ -634,12 +665,12 @@ sealed abstract class Box[+A] extends Product with Serializable{ * this box to the new `Failure` if this is a `Failure`. The unchanged * box if it is a `Full`. */ - def ?~!(msg: => String): Box[A] = ?~(msg) + def ?~!(msg: => String): TryBox[A] = ?~(msg) /** * Alias for `?~!`. */ - def compoundFailMsg(msg: => String): Box[A] = ?~!(msg) + def compoundFailMsg(msg: => String): TryBox[A] = ?~!(msg) /** * If this `Box` contains a value and it satisfies the specified `predicate`, @@ -651,7 +682,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ * @return A `Failure` with the message if the box is empty or the predicate * is not satisfied by the value contained in this Box. */ - def filterMsg(msg: String)(p: A => Boolean): Box[A] = filter(p) ?~ msg + def filterMsg(msg: String)(p: A => Boolean): TryBox[A] = filter(p) ?~ msg /** * This method calls the specified function with the specified `in` value and @@ -669,12 +700,12 @@ sealed abstract class Box[+A] extends Product with Serializable{ * * @return This box. */ - def pass(f: Box[A] => Unit): Box[A] = {f(this) ; this} + def pass(f: Box[A] => Unit): self.type = {f(this) ; this} /** * Alias for `[[pass]]`. */ - def $(f: Box[A] => Unit): Box[A] = pass(f) + def $(f: Box[A] => Unit): self.type = pass(f) /** * For `Full` and `Empty`, this has the expected behavior. Equality in terms @@ -775,9 +806,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ * If the partial function is defined at the current Box's value, apply the * partial function. */ - final def collect[B](pf: PartialFunction[A, B]): Box[B] = flatMap { value => - Box(value)(pf) - } + def collect[B](pf: PartialFunction[A, B]): Box[B] /** * An alias for `collect`. @@ -785,7 +814,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ * Although this function is different for true collections, because `Box` is * really a collection of 1, the two functions are identical. */ - final def collectFirst[B](pf: PartialFunction[A, B]): Box[B] = { + def collectFirst[B](pf: PartialFunction[A, B]): Box[B] = { collect(pf) } @@ -825,35 +854,140 @@ sealed abstract class Box[+A] extends Product with Serializable{ * `ParamFailure` or `Empty`. Returns `Empty` if this box is `Full`. In other words, it "flips" the * full/empty status of this Box. */ - def flip[B](flipFn: EmptyBox => B): Box[B] = this match { + def flip[B](flipFn: EmptyBox => B): PresenceBox[B] = this match { case e: EmptyBox => Full(flipFn(e)) case _ => Empty } } +/** + * A `Full` or `Empty` box (stands in trivially for `Option`). + */ +sealed trait PresenceBox[+A] extends Box[A] { + override def isA[B](clsOrg: Class[B]): PresenceBox[B] = Empty + override def asA[B](implicit m: Manifest[B]): PresenceBox[B] = Empty + + def or[B >: A](alternative: => Full[B])(implicit a: DummyImplicit): Full[B] + def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit, b: DummyImplicit): PresenceBox[B] + def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): TryBox[B] + def or[B >: A, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): ParamTryBox[B, E] + + override def map[B](f: A => B): PresenceBox[B] = Empty + def flatMap[B](f: A => PresenceBox[B]): PresenceBox[B] + override def collect[B](pf: PartialFunction[A, B]): PresenceBox[B] + + /** + * Variant of `flatten` that will only work on a `PresenceBox`, and returns a + * `PresenceBox`. `flatten` is not straightforward to specialize due to `Full` + * participating in both `TryBox` and `PresenceBox`, so we introduce this and + * `[[flattenTry TryBox.flattenTry]]` as a workaround. + */ + def flattenPresence[B](implicit ev: A <:< PresenceBox[B]): PresenceBox[B] = this match { + case Full(internal) => ev(internal) + case Empty => Empty + } + + override def filter(p: A => Boolean): PresenceBox[A] = this + override def filterNot(f: A => Boolean): PresenceBox[A] = filter(a => !f(a)) + + override def withFilter(p: A => Boolean): WithPresenceFilter = new WithPresenceFilter(p) + + class WithPresenceFilter(p: A => Boolean) extends WithFilter(p) { + override def map[B](f: A => B): PresenceBox[B] = PresenceBox.this.filter(p).map(f) + def flatMap[B](f: A => PresenceBox[B]): PresenceBox[B] = PresenceBox.this.filter(p).flatMap(f) + override def foreach[U](f: A => U): Unit = PresenceBox.this.filter(p).foreach(f) + override def withFilter(q: A => Boolean): WithPresenceFilter = + new WithPresenceFilter(x => p(x) && q(x)) + } +} +/** + * A `Full` or `Failure` box (somewhat similar to `Try`, but can carry an error + * message without needing an exception, and a parameter as in `ParamFailure`). + */ +sealed trait TryBox[+A] extends Box[A] { + def or[B >: A](alternative: => Full[B])(implicit a: DummyImplicit): Full[B] + def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit, b: DummyImplicit): PresenceBox[B] // * + def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): TryBox[B] + def or[B >: A, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): ParamTryBox[B, E] + + override def map[B](f: A => B): TryBox[B] + def flatMap[B](f: A => TryBox[B]): TryBox[B] + + /** + * Variant of `flatten` that will only work on a `TryBox`, and returns a + * `PresenceBox`. `flatten` is not straightforward to specialize due to `Full` + * participating in both `TryBox` and `PresenceBox`, so we introduce this and + * `[[flattenPresence PresenceBox.flattenPresence]]` as a workaround. + */ + def flattenTry[B](implicit ev: A <:< TryBox[B]): TryBox[B] = this match { + case Full(internal) => ev(internal) + case f: Failure => f + } +} +/** + * A `Full` or `ParamFailure` box; `A` is the type in the box, `E` is the type + * of the `ParamFailure`'s param. + */ +sealed trait ParamTryBox[+A, +E] extends Box[A] with TryBox[A] { + def or[B >: A](alternative: => Full[B])(implicit a: DummyImplicit): Full[B] + def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit, b: DummyImplicit): PresenceBox[B] // * + def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): TryBox[B] + def or[B >: A, E2](alternative: => ParamTryBox[B, E2])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): ParamTryBox[B, E2] + + override def map[B](f: A => B): ParamTryBox[B, E] + def flatMap[B, E2 >: E](f: A => ParamTryBox[B, E2]): ParamTryBox[B, E2] + def flatMap[B](f: A => Full[B])(implicit a: DummyImplicit): ParamTryBox[B, E] +} + /** * `Full` is a `[[Box]]` that contains a value. */ -final case class Full[+A](value: A) extends Box[A] { +final case class Full[+A](value: A) extends Box[A] + with PresenceBox[A] + with TryBox[A] + with ParamTryBox[A, Nothing] { def isEmpty: Boolean = false def openOrThrowException(justification: => String): A = value override def openOr[B >: A](default: => B): B = value - override def or[B >: A](alternative: => Box[B]): Box[B] = this + override def or[B >: A](alternative: => Box[B]): Full[A] = this + override def or[B >: A](alternative: => Full[B])(implicit a: DummyImplicit): Full[A] = this + override def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit, b: DummyImplicit): Full[A] = this + override def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): Full[A] = this + override def or[B >: A, T](alternative: => ParamTryBox[B, T])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): Full[A] = this override def exists(func: A => Boolean): Boolean = func(value) override def forall(func: A => Boolean): Boolean = func(value) - override def filter(p: A => Boolean): Box[A] = if (p(value)) this else Empty + override def filter(p: A => Boolean): PresenceBox[A] = if (p(value)) this else Empty override def foreach[U](f: A => U): Unit = f(value) - override def map[B](f: A => B): Box[B] = Full(f(value)) + override def map[B](f: A => B): Full[B] = Full(f(value)) override def flatMap[B](f: A => Box[B]): Box[B] = f(value) + override def flatMap[B](f: A => PresenceBox[B]): PresenceBox[B] = f(value) + override def flatMap[B](f: A => TryBox[B]): TryBox[B] = f(value) + override def flatMap[B, E2](f: A => ParamTryBox[B, E2]): ParamTryBox[B, E2] = f(value) + override def flatMap[B](f: A => Full[B])(implicit a: DummyImplicit): Full[B] = f(value) + + // Redefine these two with the more specific type information we have at this + // point. + override def collect[B](pf: PartialFunction[A, B]): PresenceBox[B] = { + flatMap { value: A => + if (pf.isDefinedAt(value)) { + Full(pf(value)) + } else { + Empty + } + } + } + override def collectFirst[B](pf: PartialFunction[A, B]): PresenceBox[B] = { + collect(pf) + } override def elements: Iterator[A] = Iterator(value) @@ -865,13 +999,16 @@ final case class Full[+A](value: A) extends Box[A] { override def fullXform[T](v: T)(f: T => A => T): T = f(v)(value) + override def ?~(msg: =>String): TryBox[A] = this + override def ?~!(msg: =>String): TryBox[A] = this + override def ~>[T](errorCode: =>T): ParamTryBox[A, T] = this override def toRight[B](left: => B): Either[B, A] = Right(value) override def toLeft[B](right: => B): Either[A, B] = Left(value) - override def isA[B](clsOrg: Class[B]): Box[B] = value match { + override def isA[B](clsOrg: Class[B]): PresenceBox[B] = value match { case value: AnyRef => val cls = Box.primitiveMap.get(clsOrg) match { case Some(c) => c @@ -883,7 +1020,11 @@ final case class Full[+A](value: A) extends Box[A] { case _ => Empty } - override def asA[B](implicit m: Manifest[B]): Box[B] = this.isA(m.runtimeClass).asInstanceOf[Box[B]] + override def asA[B](implicit m: Manifest[B]): PresenceBox[B] = { + this + .isA(m.runtimeClass) // convert + .asInstanceOf[PresenceBox[B]] // restore type + } override def ===[B >: A](to: B): Boolean = value == to @@ -894,7 +1035,18 @@ final case class Full[+A](value: A) extends Box[A] { * Singleton object representing a completely empty `Box` with no value or * failure information. */ -case object Empty extends EmptyBox +case object Empty extends EmptyBox with PresenceBox[Nothing] { + override def filter(p: Nothing => Boolean) = this + + override def or[B >: Nothing](alternative: => Full[B])(implicit a: DummyImplicit): Full[B] = alternative + override def or[B >: Nothing](alternative: => PresenceBox[B])(implicit a: DummyImplicit, b: DummyImplicit): PresenceBox[B] = alternative + override def or[B >: Nothing](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): TryBox[B] = alternative + override def or[B >: Nothing, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): ParamTryBox[B, E] = alternative + + override def flatMap[B](f: Nothing => PresenceBox[B]): PresenceBox[B] = this + + override def collect[B](pf: PartialFunction[Nothing, B]): PresenceBox[B] = Empty +} /** * An `EmptyBox` is a `Box` containing no value. It can sometimes carry @@ -911,8 +1063,6 @@ sealed abstract class EmptyBox extends Box[Nothing] with Serializable { override def or[B >: Nothing](alternative: => Box[B]): Box[B] = alternative - override def filter(p: Nothing => Boolean): Box[Nothing] = this - override def ?~(msg: => String): Failure = Failure(msg, Empty, Empty) override def ?~!(msg: => String): Failure = Failure(msg, Empty, Empty) @@ -934,7 +1084,7 @@ object Failure { * exception and/or a chain of previous `Failure`s that may have caused this * one. */ -sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Failure]) extends EmptyBox { +sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Failure]) extends EmptyBox with TryBox[Nothing] { type A = Nothing override def openOrThrowException(justification: => String) = @@ -943,13 +1093,23 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai override def getCause() = exception openOr null } - override def map[B](f: A => B): Box[B] = this + override def or[B >: Nothing](alternative: => Full[B])(implicit a: DummyImplicit): Full[B] = alternative + override def or[B >: Nothing](alternative: => PresenceBox[B])(implicit a: DummyImplicit, b: DummyImplicit): PresenceBox[B] = alternative + override def or[B >: Nothing](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): TryBox[B] = alternative + override def or[B >: Nothing, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): ParamTryBox[B, E] = alternative + + override def filter(p: Nothing => Boolean): Failure = this + + override def map[B](f: A => B): Failure = this + + def collect[B](pf: PartialFunction[A, B]): Box[B] = this override def flatMap[B](f: A => Box[B]): Box[B] = this + override def flatMap[B](f: A => TryBox[B]): TryBox[B] = this - override def isA[B](cls: Class[B]): Box[B] = this + override def isA[B](cls: Class[B]): Failure = this - override def asA[B](implicit m: Manifest[B]): Box[B] = this + override def asA[B](implicit m: Manifest[B]): Failure = this private def chainList: List[Failure] = chain match { case Full(f) => f :: f.chainList @@ -1070,25 +1230,33 @@ object ParamFailure { */ final class ParamFailure[T](override val msg: String, override val exception: Box[Throwable], - override val chain: Box[Failure], val param: T) extends - Failure(msg, exception, chain) with Serializable{ - override def toString(): String = "ParamFailure("+msg+", "+exception+ - ", "+chain+", "+param+")" - - override def equals(that: Any): Boolean = that match { - case ParamFailure(m, e, c, p) => - m == msg && e == exception && c == chain && p == param - case _ => false - } + override val chain: Box[Failure], val param: T) + extends Failure(msg, exception, chain) + with ParamTryBox[Nothing, T] + with Serializable { + override def toString(): String = "ParamFailure("+msg+", "+exception+ + ", "+chain+", "+param+")" + + override def equals(that: Any): Boolean = that match { + case ParamFailure(m, e, c, p) => + m == msg && e == exception && c == chain && p == param + case _ => false + } - override def hashCode(): Int = super.hashCode() + (param match { - case null => 0 - case x => x.hashCode() - }) + override def hashCode(): Int = super.hashCode() + (param match { + case null => 0 + case x => x.hashCode() + }) - override def ~>[T](errorCode: => T): ParamFailure[T] = - ParamFailure(msg, exception, Full(this), errorCode) - } + override def ~>[T](errorCode: => T): ParamFailure[T] = + ParamFailure(msg, exception, Full(this), errorCode) + + override def map[B](f: A => B): ParamFailure[T] = this + override def flatMap[B, E2 >: T](f: A => ParamTryBox[B, E2]): ParamFailure[T] = this + override def flatMap[B](f: Nothing => Full[B])(implicit a: DummyImplicit): ParamFailure[T] = this + + override def filter(p: Nothing => Boolean): ParamFailure[T] = this +} /** * A trait that a class can mix into itself to indicate that it can convert diff --git a/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala b/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala index 38e80c4a6b..030af3907e 100644 --- a/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala +++ b/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.liftweb +package net.liftweb package common import scala.language.implicitConversions @@ -22,6 +22,45 @@ import scala.language.implicitConversions /** * Via an `[[HLists.HList HList]]` containing a collection of `[[Box]]`, either generates an * `HList` of the things (unboxed) or a `List[Failure]`. + * + * Usage of this is in two parts: first, you import `CombinableBox._`; then, you + * can use the `:&:` operator to build up your `HList`. For example: + * + * {{{ + * scala> import net.liftweb.common._; import CombinableBox._ + * scala> Full("test") :&: Full("second test") + * res0: net.liftweb.common.CombinableBox.Result[String :+: String :+: net.liftweb.common.HLists.HNil] = Right(test :+: second test :+: HNil) + * scala> Full("test") :&: Failure("boom") + * res1: net.liftweb.common.CombinableBox.Result[String :+: Nothing :+: net.liftweb.common.HLists.HNil] = Left(List(Failure(boom,Empty,Empty))) + * scala> Failure("boom") :&: Failure("boom boom") + * res2: net.liftweb.common.CombinableBox.Result[Nothing :+: Nothing :+: net.liftweb.common.HLists.HNil] = Left(List(Failure(boom,Empty,Empty), Failure(boom boom,Empty,Empty))) + * }}} + * + * `CombinableBox` also contains some implicits to help deal with the outcome. + * By default, results are represented by an `Either[List[Failure], ]`. + * Instead, we can convert this back to a `Box` representation: + * + * {{{ + * scala> import net.liftweb.common.HLists._ + * scala> type ExpectedHList = String :+: String :+: HNil + * defined type alias ExpectedHList + * scala> val combinedFulls: Box[ExpectedHList] = Full("test") :&: Full("second test") + * combinedFulls: net.liftweb.common.Box[ExpectedHList] = Full(test :+: second test :+: HNil) + * scala> val combinedFailure: Box[ExpectedHList] = Full("test") :&: (Failure("boom"): Box[String]) + * combinedFailure: net.liftweb.common.Box[ExpectedHList] = Failure(boom,Empty,Empty) + * scala> val combinedFailures: Box[ExpectedHList] = (Failure("boom"): Box[String]) :&: (Failure("boom boom"): Box[String]) + * combinedFailures: net.liftweb.common.Box[ExpectedHList] = ParamFailure(Multiple Failures, Empty, Empty, FailureList(List(Failure(boom,Empty,Empty), Failure(boom boom,Empty,Empty)))) + * }}} + * + * Note that in case multiple failures are involved, we generate a `ParamFailure` + * whose parameter is a `FailureList` object, which contains the failures found + * along the way. + * + * In many cases, it is more straightforward to use `ListOfBoxes` to deal with a + * list of `Box` that needs to be a `Box` of `List` instead. It works in a very + * similar way, but works on regular lists instead of ``HList``s. `CombinableBox` + * is best reserved for when you want to preserve heterogeneous types out of a + * set of boxes. */ object CombinableBox { import HLists._ @@ -53,7 +92,7 @@ object CombinableBox { FailureList(f)) case Right(x) => Full(x) } - + /** * If the `[[Failure]]` is going to be condensed, a `FailureList` is generated * to allow type-safe pattern matches without worrying about erasure. @@ -73,6 +112,6 @@ object CombinableBox { case (Full(success), Right(successes)) => Right(success :+: successes) } } - + } diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index a920aa4a7c..2714ac8207 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -152,6 +152,22 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Full(Empty).flatten must_== Empty } } + "define a 'flattenPresence' method if it contains another PresenceBox" in { + "If the inner box is a Full box, the final result is identical to that box" in { + (Full(Full(1)).flattenPresence: PresenceBox[Int]) must_== Full(1) + } + "If the inner box is an Empty box, the final result is identical to that box" in { + (Full(Empty).flattenPresence: PresenceBox[Int]) must_== Empty + } + } + "define a 'flattenTry' method if it contains another TryBox" in { + "If the inner box is a Full box, the final result is identical to that box" in { + (Full(Full(1)).flattenTry: TryBox[Int]) must_== Full(1) + } + "If the inner box is a Failure, the final result is identical to that box" in { + (Full(Empty).flattenPresence: PresenceBox[Int]) must_== Empty + } + } "define a 'collect' method that takes a PartialFunction to transform its contents" in { "If the partial-function is defined for the contents of this box, returns a full box containing the result of applying that partial function to this Box's contents" in { Full("Albus") collect { case "Albus" => "Dumbledore"} must_== Full("Dumbledore") @@ -514,7 +530,6 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { } } } - } diff --git a/core/common/src/test/scala/net/liftweb/common/BoxTypingSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxTypingSpec.scala new file mode 100644 index 0000000000..9074b10895 --- /dev/null +++ b/core/common/src/test/scala/net/liftweb/common/BoxTypingSpec.scala @@ -0,0 +1,459 @@ +/* + * Copyright 2007-2011 WorldWide Conferencing, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.liftweb +package common + +import org.specs2.mutable.Specification + +/** + * Verifications for complex Box typing like PresenceBox flatMap T=>PresenceBox + * yields PresenceBox, etc. + */ +class BoxTypingSpec extends Specification { + // IMPORTANT + // We use a few implicits to help us do compile-time proofs of the inferred + // compiler types, but if you choose to edit this spec make sure you don't + // lose typing information during the checks! e.g., if we want to verify that + // fullBox.operation(...) is only a TryBox and nothing else, and we put that + // operation result in a List with other operation results, we're no longer + // checking that operation's result, but rather the supertype of all the + // results of the operations! + // + // As a result, you'll see a bit more repetition below than may seem ideal. + + // Implicit helpers to ask Scala to help us prove the compiler inferred the + // right type for method returns. + trait PlainBoxProof { def plainBox_?(): Boolean } + implicit class DisprovePlainBox[T](notSpecificBox: Box[T]) extends PlainBoxProof { + def plainBox_? = true + } + trait FullProof { def full_?(): Boolean } + implicit class ProveFull[T](full: Full[T]) extends FullProof with PlainBoxProof { + def full_? = true + def plainBox_? = false + } + implicit class DisproveFull[T](notFull: Box[T]) extends FullProof { + def full_? = false + } + trait EmptyProof { def empty_?(): Boolean } + implicit class ProveEmpty(empty: Empty.type) extends EmptyProof with PlainBoxProof { + def empty_? = true + def plainBox_? = false + } + implicit class DisproveEmpty[T](notEmpty: Box[T]) extends EmptyProof { + def empty_? = false + } + trait FailureProof { def failure_?(): Boolean } + implicit class ProveFailure(failure: Failure) extends FailureProof with PlainBoxProof { + def failure_? = true + def plainBox_? = false + } + implicit class DisproveFailure[T](notFailure: Box[T]) extends FailureProof { + def failure_? = false + } + trait ParamFailureProof { def paramFailure_?(): Boolean } + implicit class ProveParamFailure[A](paramFailure: ParamFailure[A]) extends ParamFailureProof with PlainBoxProof { + def paramFailure_? = true + def plainBox_? = false + } + implicit class DisproveParamFailure[T](notFailure: Box[T]) extends ParamFailureProof { + def paramFailure_? = false + } + trait TryProof { def try_?(): Boolean } + implicit class ProveTry[T](`try`: TryBox[T]) extends TryProof with PlainBoxProof { + def try_? = true + def plainBox_? = false + } + implicit class DisproveTry[T](notTry: Box[T]) extends TryProof { + def try_? = false + } + trait ParamTryProof { def paramTry_?(): Boolean } + implicit class ProveParamTry[T](`try`: ParamTryBox[T, Double]) extends ParamTryProof with PlainBoxProof { + def paramTry_? = true + def plainBox_? = false + } + implicit class DisproveParamTry[T](notParamTry: Box[T]) extends ParamTryProof { + def paramTry_? = false + } + trait PresenceProof { def presence_?(): Boolean } + implicit class ProvePresence[T](presence: PresenceBox[T]) extends PresenceProof with PlainBoxProof { + def presence_? = true + def plainBox_? = false + } + implicit class DisprovePresence[T](notPresence: Box[T]) extends PresenceProof { + def presence_? = false + } + + "Box when doing complex typing invocations" should { + val fullBox: Full[Int] = Full(5) + val fullParamTry: ParamTryBox[Int, Double] = fullBox + val fullTry: TryBox[Int] = fullBox + val fullPresence: PresenceBox[Int] = fullBox + val fullPlain: Box[Int] = fullBox + + val emptyPresence: PresenceBox[Int] = Empty + val emptyPlain: Box[Int] = Empty + + val failureBox: Failure = Failure("boom") + val failureTry: TryBox[Int] = failureBox + val failurePlain: Box[Int] = failureBox + + val paramFailureBox: ParamFailure[Double] = failureBox ~> 2.0 + val paramFailureParamTry: ParamTryBox[Int, Double] = paramFailureBox + val paramFailureTry: TryBox[Int] = paramFailureBox + val paramFailurePlain: Box[Int] = paramFailureBox + + val fullStringBox: Full[String] = Full("boom") + val fullStringParamTry: ParamTryBox[String, Double] = fullStringBox + val fullStringTry: TryBox[String] = fullStringBox + val fullStringPresence: PresenceBox[String] = fullStringBox + val fullStringPlain: Box[String] = fullStringBox + + val emptyStringPresence: PresenceBox[String] = Empty + val emptyStringPlain: Box[String] = Empty + + val failureStringTry: TryBox[String] = failureBox + val failureStringPlain: Box[String] = failureBox + + val paramFailureStringParamTry: ParamTryBox[String, Double] = paramFailureBox + val paramFailureStringTry: TryBox[String] = paramFailureBox + val paramFailureStringPlain: Box[String] = paramFailureBox + + "flatMap outer Full correctly" in { + fullBox.flatMap(_ => fullStringBox).try_? must beTrue + fullBox.flatMap(_ => fullStringBox).paramTry_? must beTrue + fullBox.flatMap(_ => fullStringBox).presence_? must beTrue + + fullParamTry.flatMap(_ => fullStringBox).try_? must beTrue + fullParamTry.flatMap(_ => fullStringBox).paramTry_? must beTrue + fullParamTry.flatMap(_ => fullStringBox).presence_? must beFalse + + fullTry.flatMap(_ => fullStringBox).try_? must beTrue + fullTry.flatMap(_ => fullStringBox).paramTry_? must beFalse + fullTry.flatMap(_ => fullStringBox).presence_? must beFalse + + fullPresence.flatMap(_ => fullStringBox).try_? must beFalse + fullPresence.flatMap(_ => fullStringBox).paramTry_? must beFalse + fullPresence.flatMap(_ => fullStringBox).presence_? must beTrue + + fullPlain.flatMap(_ => fullStringBox).try_? must beFalse + fullPlain.flatMap(_ => fullStringBox).paramTry_? must beFalse + fullPlain.flatMap(_ => fullStringBox).presence_? must beFalse + + fullBox.flatMap(_ => fullStringBox) must_== fullStringBox + fullParamTry.flatMap(_ => fullStringBox) must_== fullStringBox + fullTry.flatMap(_ => fullStringBox) must_== fullStringBox + fullPresence.flatMap(_ => fullStringBox) must_== fullStringBox + fullPlain.flatMap(_ => fullStringBox) must_== fullStringBox + } + + "flatMap outer Empty correctly" in { + Empty.flatMap(_ => fullStringBox).try_? must beFalse + Empty.flatMap(_ => fullStringBox).paramTry_? must beFalse + Empty.flatMap(_ => fullStringBox).presence_? must beTrue + + emptyPresence.flatMap(_ => fullStringBox).try_? must beFalse + emptyPresence.flatMap(_ => fullStringBox).paramTry_? must beFalse + emptyPresence.flatMap(_ => fullStringBox).presence_? must beTrue + + emptyPlain.flatMap(_ => fullStringBox).try_? must beFalse + emptyPlain.flatMap(_ => fullStringBox).paramTry_? must beFalse + emptyPlain.flatMap(_ => fullStringBox).presence_? must beFalse + + Empty.flatMap(_ => fullStringBox) must_== Empty + emptyPresence.flatMap(_ => fullStringBox) must_== Empty + emptyPlain.flatMap(_ => fullStringBox) must_== Empty + } + + "flatMap outer Failure correctly" in { + failureBox.flatMap(_ => fullStringBox).try_? must beTrue + failureBox.flatMap(_ => fullStringBox).paramTry_? must beFalse + failureBox.flatMap(_ => fullStringBox).presence_? must beFalse + + failureTry.flatMap(_ => fullStringBox).try_? must beTrue + failureTry.flatMap(_ => fullStringBox).paramTry_? must beFalse + failureTry.flatMap(_ => fullStringBox).presence_? must beFalse + + failurePlain.flatMap(_ => fullStringBox).try_? must beFalse + failurePlain.flatMap(_ => fullStringBox).paramTry_? must beFalse + failurePlain.flatMap(_ => fullStringBox).presence_? must beFalse + + failureBox.flatMap(_ => fullStringBox) must_== failureBox + failureTry.flatMap(_ => fullStringBox) must_== failureBox + failurePlain.flatMap(_ => fullStringBox) must_== failureBox + } + + "flatMap outer ParamFailure correctly" in { + paramFailureBox.flatMap(_ => fullStringBox).try_? must beTrue + paramFailureBox.flatMap(_ => fullStringBox).paramTry_? must beTrue + paramFailureBox.flatMap(_ => fullStringBox).presence_? must beFalse + + paramFailureParamTry.flatMap(_ => fullStringBox).try_? must beTrue + paramFailureParamTry.flatMap(_ => fullStringBox).paramTry_? must beTrue + paramFailureParamTry.flatMap(_ => fullStringBox).presence_? must beFalse + + paramFailureTry.flatMap(_ => fullStringBox).try_? must beTrue + paramFailureTry.flatMap(_ => fullStringBox).paramTry_? must beFalse + paramFailureTry.flatMap(_ => fullStringBox).presence_? must beFalse + + paramFailurePlain.flatMap(_ => fullStringBox).try_? must beFalse + paramFailurePlain.flatMap(_ => fullStringBox).paramTry_? must beFalse + paramFailurePlain.flatMap(_ => fullStringBox).presence_? must beFalse + + paramFailureBox.flatMap(_ => fullStringBox) must_== paramFailureBox + paramFailureParamTry.flatMap(_ => fullStringBox) must_== paramFailureBox + paramFailureTry.flatMap(_ => fullStringBox) must_== paramFailureBox + paramFailurePlain.flatMap(_ => fullStringBox) must_== paramFailureBox + } + + "filter Empty correctly" in { + emptyPresence.filter(_ == 5).presence_? must beTrue + emptyPresence.filter(_ == 5).paramTry_? must beFalse + emptyPresence.filter(_ == 5).try_? must beFalse + + emptyPlain.filter(_ == 5).presence_? must beFalse + emptyPlain.filter(_ == 5).paramTry_? must beFalse + emptyPlain.filter(_ == 5).try_? must beFalse + } + + "filter Failure correctly" in { + failureBox.filter(_ == 5).presence_? must beFalse + failureBox.filter(_ == 5).paramTry_? must beFalse + failureBox.filter(_ == 5).try_? must beTrue + } + + "filter ParamFailure correctly" in { + paramFailureBox.filter(_ == 5).presence_? must beFalse + paramFailureBox.filter(_ == 5).paramTry_? must beTrue + paramFailureBox.filter(_ == 5).try_? must beTrue + } + + "when filtering" in { + // For a Full/Empty, filter leads to PresenceBox, otherwise it's just Box. + "filter a known Full to PresenceBox" in { + fullBox.filter(_ == 7).presence_? must beTrue + fullBox.filter(_ == 7).paramTry_? must beFalse + fullBox.filter(_ == 7).try_? must beFalse + } + + "filter a PresenceBox to PresenceBox" in { + fullPresence.filter(_ == 7).presence_? must beTrue + fullPresence.filter(_ == 7).paramTry_? must beFalse + fullPresence.filter(_ == 7).try_? must beFalse + + emptyPresence.filter(_ == 7).presence_? must beTrue + emptyPresence.filter(_ == 7).paramTry_? must beFalse + emptyPresence.filter(_ == 7).try_? must beFalse + } + + // In order for Full to be able to return a PresenceBox for filter, the + // other types (TryBox, etc) need to return Box (since Full extends those + // as well). However, if we know we are looking at a Failure or + // ParamFailure, we can be specific. + "filter known Failures/ParamFailures to themselves" in { + paramFailureBox.filter(_ == 7).presence_? must beFalse + paramFailureBox.filter(_ == 7).paramTry_? must beTrue + paramFailureBox.filter(_ == 7).try_? must beTrue + + failureBox.filter(_ == 7).presence_? must beFalse + failureBox.filter(_ == 7).paramTry_? must beFalse + failureBox.filter(_ == 7).try_? must beTrue + } + + "filter all other types to Box" in { + fullParamTry.filter(_ == 7).plainBox_? must beTrue + fullTry.filter(_ == 7).plainBox_? must beTrue + fullPlain.filter(_ == 7).plainBox_? must beTrue + + emptyPlain.filter(_ == 7).plainBox_? must beTrue + + paramFailurePlain.filter(_ == 7).plainBox_? must beTrue + paramFailureParamTry.filter(_ == 7).plainBox_? + paramFailureTry.filter(_ == 7).plainBox_? + + failurePlain.filter(_ == 7).plainBox_? must beTrue + failureTry.filter(_ == 7).plainBox_? must beTrue + } + + "filter correctly regardless of type" in { + List(fullBox, fullParamTry, fullTry, fullPresence, fullPlain) + .map(_.filter(_ == 7)) + .forall(_ == Empty) must beTrue + + List(fullBox, fullParamTry, fullTry, fullPresence, fullPlain) + .map(_.filter(_ == 5)) + .exists(_ == Empty) must beFalse + } + } + + "when or-ing" should { + "or known Fulls with all other types preserving the Fullness" in { + (fullBox or failureBox).full_? must beTrue + (fullBox or failureTry).full_? must beTrue + (fullBox or paramFailureBox).full_? must beTrue + (fullBox or paramFailureParamTry).full_? must beTrue + (fullBox or emptyPresence).full_? must beTrue + (fullBox or emptyPlain).full_? must beTrue + } + + "or Empties with other types preserving the supertypes" in { + (Empty or fullBox).full_? must beTrue + (Empty or paramFailureParamTry).paramTry_? must beTrue + (Empty or failureBox).failure_? must beFalse + (Empty or failureBox).try_? must beTrue + (Empty or failureTry).try_? must beTrue + (Empty or paramFailureBox).paramFailure_? must beFalse + (Empty or paramFailureBox).paramTry_? must beTrue + } + + "or Failures with other types preserving the supertypes" in { + (failureBox or fullBox).full_? must beTrue + (failureBox or paramFailureParamTry).paramTry_? must beTrue + (failureBox or failureBox).failure_? must beFalse + (failureBox or failureBox).try_? must beTrue + (failureBox or failureTry).try_? must beTrue + (failureBox or paramFailureBox).paramFailure_? must beFalse + (failureBox or paramFailureBox).paramTry_? must beTrue + } + + "or ParamFailures with other types preserving the supertypes" in { + (paramFailureBox or fullBox).full_? must beTrue + (paramFailureBox or paramFailureParamTry).paramTry_? must beTrue + (paramFailureBox or failureBox).failure_? must beFalse + (paramFailureBox or failureBox).try_? must beTrue + (paramFailureBox or failureTry).try_? must beTrue + (paramFailureBox or paramFailureBox).paramFailure_? must beFalse + (paramFailureBox or paramFailureBox).paramTry_? must beTrue + } + + "or plain Box with other types to Box" in { + (emptyPlain or fullBox).plainBox_? must beTrue + (emptyPlain or Empty).plainBox_? must beTrue + (emptyPlain or failureBox).plainBox_? must beTrue + (emptyPlain or paramFailureBox).plainBox_? must beTrue + } + } + + "when isA/asAing" should { + "preserve PresenceBoxiness for Full, Empty, and PresenceBox" in { + fullBox.asA[String].presence_? must beTrue + fullBox.isA(classOf[String]).presence_? must beTrue + fullBox.asA[Int].presence_? must beTrue + fullBox.isA(classOf[Int]).presence_? must beTrue + + Empty.asA[String].presence_? must beTrue + Empty.isA(classOf[String]).presence_? must beTrue + Empty.asA[Int].presence_? must beTrue + Empty.isA(classOf[Int]).presence_? must beTrue + + fullPresence.asA[String].presence_? must beTrue + fullPresence.isA(classOf[String]).presence_? must beTrue + fullPresence.asA[Int].presence_? must beTrue + fullPresence.isA(classOf[Int]).presence_? must beTrue + + emptyPresence.asA[String].presence_? must beTrue + emptyPresence.isA(classOf[String]).presence_? must beTrue + emptyPresence.asA[Int].presence_? must beTrue + emptyPresence.isA(classOf[Int]).presence_? must beTrue + } + + "preserve Failureness for Failure and ParamFailure" in { + failureBox.asA[String].failure_? must beTrue + failureBox.isA(classOf[String]).failure_? must beTrue + paramFailureBox.asA[String].failure_? must beTrue + paramFailureBox.isA(classOf[String]).failure_? must beTrue + + failureBox.asA[String].presence_? must beFalse + failureBox.isA(classOf[String]).presence_? must beFalse + paramFailureBox.asA[String].presence_? must beFalse + paramFailureBox.isA(classOf[String]).presence_? must beFalse + } + + "preserve Boxiness for Box" in { + fullPlain.asA[String].plainBox_? must beTrue + fullPlain.isA(classOf[String]).plainBox_? must beTrue + emptyPlain.asA[String].plainBox_? must beTrue + emptyPlain.isA(classOf[String]).plainBox_? must beTrue + failurePlain.asA[String].plainBox_? must beTrue + failurePlain.isA(classOf[String]).plainBox_? must beTrue + paramFailurePlain.asA[String].plainBox_? must beTrue + paramFailurePlain.isA(classOf[String]).plainBox_? must beTrue + } + } + + "when collecting and collectFirsting" should { + "preserve PresenceBoxiness for Full, Empty, and PresenceBox" in { + fullBox.collect { case _ => "a" }.presence_? must beTrue + Empty.collect { case _ => "a" }.presence_? must beTrue + + fullPresence.collect { case _ => "a" }.presence_? must beTrue + emptyPresence.collect { case _ => "a" }.presence_? must beTrue + } + + "lose Failureness for Failure and ParamFailure" in { + failureBox.collect { case _ => "a" }.failure_? must beFalse + paramFailureBox.collect { case _ => "a" }.failure_? must beFalse + + failureBox.collect { case _ => "a" }.presence_? must beFalse + paramFailureBox.collect { case _ => "a" }.presence_? must beFalse + + failureBox.collect { case _ => "a" }.plainBox_? must beTrue + paramFailureBox.collect { case _ => "a" }.plainBox_? must beTrue + } + + "preserve Boxiness for Box" in { + fullPlain.collect { case _ => "a" }.plainBox_? must beTrue + emptyPlain.collect { case _ => "a" }.plainBox_? must beTrue + failurePlain.collect { case _ => "a" }.plainBox_? must beTrue + paramFailurePlain.collect { case _ => "a" }.plainBox_? must beTrue + } + } + + "when used in for comprehensions" should { + "preserve closest common type for mapping for comprehensions" in { + (for (a <- fullBox ; b <- failureBox ; c <- fullBox) yield "a").try_? must beTrue + (for (a <- fullBox ; b <- failureTry ; c <- fullBox) yield "a").try_? must beTrue + (for (a <- fullBox ; b <- failurePlain ; c <- fullBox) yield "a").plainBox_? must beTrue + + (for (a <- fullBox ; b <- Empty ; c <- fullBox) yield "a").presence_? must beTrue + (for (a <- fullBox ; b <- emptyPresence ; c <- fullBox) yield "a").presence_? must beTrue + (for (a <- fullBox ; b <- emptyPlain ; c <- fullBox) yield "a").plainBox_? must beTrue + + (for (a <- fullBox ; b <- paramFailureBox ; c <- fullBox) yield "a").paramTry_? must beTrue + (for (a <- fullBox ; b <- paramFailureParamTry ; c <- fullBox) yield "a").paramTry_? must beTrue + (for (a <- fullBox ; b <- paramFailureTry ; c <- fullBox) yield "a").paramTry_? must beFalse + (for (a <- fullBox ; b <- paramFailureTry ; c <- fullBox) yield "a").try_? must beTrue + (for (a <- fullBox ; b <- paramFailurePlain ; c <- fullBox) yield "a").plainBox_? must beTrue + } + + "preserve closest common type to PresenceBox for filtering for comprehensions" in { + (for (a <- fullBox ; b <- failureBox ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + (for (a <- fullBox ; b <- failureTry ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + (for (a <- fullBox ; b <- failurePlain ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + + (for (a <- fullBox ; b <- Empty ; c <- fullBox if c == 5) yield "a").presence_? must beTrue + (for (a <- fullBox ; b <- emptyPresence ; c <- fullBox if c == 5) yield "a").presence_? must beTrue + (for (a <- fullBox ; b <- emptyPlain ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + + (for (a <- fullBox ; b <- paramFailureBox ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + (for (a <- fullBox ; b <- paramFailureParamTry ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + (for (a <- fullBox ; b <- paramFailureTry ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + (for (a <- fullBox ; b <- paramFailurePlain ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + } + } + } +}