-
Notifications
You must be signed in to change notification settings - Fork 396
Closed as not planned
Labels
as-designedThe observed behavior is as-designed, it need not be fixed.The observed behavior is as-designed, it need not be fixed.
Description
Has there been any consideration for getting implicit conversions to work with unions?
I've come up with this which seems to work according to my (limited) testing:
import shapeless.<:!<
import scala.annotation.unused
import scala.reflect.ClassTag
import scala.scalajs.js
import scala.scalajs.js.|
import scala.scalajs.js.|.Evidence
trait UnionOps extends HighPriorityUnionOps {
// These were copied from js.|.
// They have to be here since this is explicitly imported and it will be searched before the compiler goes and checks
// the js.| companion object which ends up taking about 4 times as long. I am not entirely sure why having them here
// is actually faster since I would expect it to find the same candidates before ranking and ultimately picking these.
// Perhaps there are some smarts where it does not consider candidates that could not possibly get a higher ranking.
/** Upcast `A` to `B1 | B2`.
*
* This needs evidence that `A <: B1 | B2`.
*/
implicit def from[A, B1, B2](a: A)(implicit @unused ev: Evidence[A, B1 | B2]): B1 | B2 =
a.asInstanceOf[B1 | B2]
/** Upcast `F[A]` to `F[B]`.
*
* This needs evidence that `F[A] <: F[B]`.
*/
implicit def fromTypeConstructor[F[_], A, B](a: F[A])(implicit @unused ev: Evidence[F[A], F[B]]): F[B] =
a.asInstanceOf[F[B]]
}
trait NoEvidence[A, B]
object NoEvidence {
// If Evidence is found then these will be ambiguous otherwise noEvidence will be found.
implicit def noEvidence[A, B]: NoEvidence[A, B] = new NoEvidence[A, B] {}
implicit def hasEvidence[A, B](implicit @unused ev: Evidence[A, B]): NoEvidence[A, B] = new NoEvidence[A, B] {}
}
trait MappableUnion[A] {
// TODO: What to call these?
def mapLeft[A2, B1, B2](a: A | A2)(a1: A => B1, a2: A2 => B2): B1 | B2
def mapRight[A1, B1, B2](a: A1 | A)(a1: A1 => B1, a2: A => B2): B1 | B2
}
object MappableUnion {
implicit def fromClassTag[A: ClassTag](implicit
// No isInstance of allowed on js.Any.
@unused ev1: A <:!< js.Any,
// isInstance of on js.| blows up at runtime too so make sure that is not allowed.
@unused ev2: A <:!< |[_, _]
): MappableUnion[A] = new MappableUnion[A] {
override def mapLeft[A2, B1, B2](a: A | A2)(f1: A => B1, f2: A2 => B2): B1 | B2 = (a: Any) match {
case a1: A => f1(a1)
case a => f2(a.asInstanceOf[A2])
}
override def mapRight[A1, B1, B2](a: A1 | A)(f1: A1 => B1, f2: A => B2): B1 | B2 = (a: Any) match {
case a2: A => f2(a2)
case a => f1(a.asInstanceOf[A1])
}
}
}
trait HighPriorityUnionOps extends LowPriorityUnionOps {
implicit def toLeft[A, B1, B2](a: A)(implicit @unused noEv: NoEvidence[A, B1], conv: A => B1): B1 | B2 =
conv(a)
implicit def toRight[A, B1, B2](a: A)(implicit @unused noEv: NoEvidence[A, B2], conv: A => B2): B1 | B2 =
conv(a)
implicit def convertLeft[A1, A2, B1](a: A1 | A2)(implicit
// This ensures that fromTypeConstructor wins.
@unused noEv: NoEvidence[A1 | A2, B1 | A2],
mapper: MappableUnion[A1],
conv: A1 => B1
): B1 | A2 =
mapper.mapLeft(a)(conv, identity)
implicit def convertRight[A1, A2, B2](a: A1 | A2)(implicit
// This ensures that fromTypeConstructor wins.
@unused noEv: NoEvidence[A1 | A2, A1 | B2],
mapper: MappableUnion[A1],
conv: A2 => B2
): A1 | B2 =
mapper.mapLeft(a)(identity, conv)
}
trait LowPriorityUnionOps extends LowerPriorityUnionOps {
// These are lower priority otherwise they are ambiguous when both A1 and A2 are a MappableUnion.
implicit def convertLeft2[A1, A2, B1](a: A1 | A2)(implicit
// This ensures that fromTypeConstructor wins.
@unused noEv: NoEvidence[A1 | A2, B1 | A2],
mapper: MappableUnion[A2],
conv: A1 => B1
): B1 | A2 =
mapper.mapRight(a)(conv, identity)
implicit def convertRight2[A1, A2, B2](a: A1 | A2)(implicit
// This ensures that fromTypeConstructor wins.
@unused noEv: NoEvidence[A1 | A2, A1 | B2],
mapper: MappableUnion[A2],
conv: A2 => B2
): A1 | B2 =
mapper.mapRight(a)(identity, conv)
}
trait LowerPriorityUnionOps extends LowestPriorityUnionOps {
implicit def convertBoth[A1, A2, B1, B2](a: A1 | A2)(implicit
// This ensures that fromTypeConstructor wins.
@unused noEv: NoEvidence[A1 | A2, B1 | B2],
mapper: MappableUnion[A1],
conv1: A1 => B1,
conv2: A2 => B2
): B1 | B2 =
mapper.mapLeft(a)(conv1, conv2)
}
trait LowestPriorityUnionOps {
// This is lower priority otherwise it is ambiguous when both A1 and A2 are a MappableUnion.
implicit def convertBoth2[A1, A2, B1, B2](a: A1 | A2)(implicit
// This ensures that fromTypeConstructor wins.
@unused noEv: NoEvidence[A1 | A2, B1 | B2],
mapper: MappableUnion[A2],
conv1: A1 => B1,
conv2: A2 => B2
): B1 | B2 =
mapper.mapRight(a)(conv1, conv2)
}
The compile times seem to be ok so far.
Metadata
Metadata
Assignees
Labels
as-designedThe observed behavior is as-designed, it need not be fixed.The observed behavior is as-designed, it need not be fixed.