Skip to content

Commit

Permalink
Ensure LowPriority is fine with the Aux pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
alexarchambault committed Jan 8, 2018
1 parent e17b96e commit ed119b7
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 1 deletion.
9 changes: 8 additions & 1 deletion core/src/main/scala/shapeless/lowpriority.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,18 @@ class LowPriorityMacros(val c: whitebox.Context) extends OpenImplicitMacros with

def strictTpe = typeOf[Strict[_]].typeConstructor

// FIXME Also in LazyMacros
def stripRefinements(tpe: Type): Option[Type] =
tpe match {
case RefinedType(parents, _) => Some(parents.head)
case _ => None
}

def mkLowPriority: Tree =
secondOpenImplicitTpe match {
case Some(tpe) =>
c.inferImplicitValue(
appliedType(strictTpe, appliedType(lowPriorityForTpe, tpe)),
appliedType(strictTpe, appliedType(lowPriorityForTpe, stripRefinements(tpe.dealias).getOrElse(tpe))),
silent = false
)

Expand Down
115 changes: 115 additions & 0 deletions core/src/test/scala/shapeless/lowpriorityderivation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,87 @@ object LowPriorityDerivationTests {
implicit val cc1TC: TC0[CC1] = instance[CC1](_ => "CC1")
}

trait TCR[T] {
type Out
def msg(n: Int): String
}

object TCR {
def apply[T](implicit tc: TCR[T]): Aux[T, tc.Out] = tc

type Aux[T, Out0] = TCR[T] { type Out = Out0 }

def instance[T, Out0](msg0: Int => String): Aux[T, Out0] =
new TCR[T] {
type Out = Out0
def msg(n: Int) = if (n >= 0) msg0(n) else ""
}

// These implicits are similar to the ones in the companion of TC above -
// I found no way to share their definitions (in a common trait like Companion[TC[_]], say)
// without running into implicit collisions with defaultTC.

implicit val int: Aux[Int, Int] = instance(_ => "Int")
implicit val boolean: Aux[Boolean, Boolean] = instance(_ => "Boolean")
implicit def option[T, TOut](implicit t: Aux[T, TOut]): Aux[Option[T], Option[TOut]] =
instance(n => s"Option[${t.msg(n-1)}]")
implicit def tuple2[A, B, AOut, BOut](implicit a: Aux[A, AOut], b: Aux[B, BOut]): Aux[(A, B), AOut :: BOut :: HNil] =
instance(n => s"(${a.msg(n-1)}, ${b.msg(n-1)})")
implicit val cc1: Aux[CC1, Int] = instance(_ => "CC1")
}

object TCRDeriver {

trait MkHListTC[L <: HList] {
type Out <: HList
def tc: TCR.Aux[L, Out]
}

object MkHListTC {
type Aux[L <: HList, Out0 <: HList] = MkHListTC[L] { type Out = Out0 }
implicit def hnil: Aux[HNil, HNil] =
new MkHListTC[HNil] {
type Out = HNil
val tc = TCR.instance[HNil, HNil](_ => "HNil")
}
implicit def hcons[H, T <: HList, HOut, TOut <: HList]
(implicit
head: Strict[TCR.Aux[H, HOut]],
tail: Aux[T, TOut]
): Aux[H :: T, HOut :: TOut] =
new MkHListTC[H :: T] {
type Out = HOut :: TOut
lazy val tc = TCR.instance[H :: T, HOut :: TOut](n => s"${head.value.msg(n-1)} :: ${tail.tc.msg(n-1)}")
}
}

trait MkTC[T] {
type Out
def tc: TCR.Aux[T, Out]
}

object MkTC {
type Aux[T, Out0] = MkTC[T] { type Out = Out0 }
implicit def generic[P, L <: HList, Out0 <: HList]
(implicit
gen: Generic.Aux[P, L],
underlying: Lazy[MkHListTC.Aux[L, Out0]]
): Aux[P, Out0] =
new MkTC[P] {
type Out = Out0
lazy val tc = TCR.instance[P, Out0](n => s"Generic[${underlying.value.tc.msg(n-1)}]")
}
}

implicit def mkTCR[T, Out]
(implicit
ev: LowPriority,
cached: Strict[MkTC.Aux[T, Out]]
): TCR.Aux[T, Out] =
cached.value.tc
}


trait SimpleDeriver[TC[_] <: {def msg(n: Int): String}] {
def instance[T](msg0: Int => String): TC[T]

Expand Down Expand Up @@ -316,6 +397,11 @@ class LowPriorityDerivationTests {
assert(expected == msg)
}

def validateTCR[T](expected: String, n: Int = Int.MaxValue)(implicit tcr: TCR[T]): Unit = {
val msg = tcr.msg(n)
assert(expected == msg, s"Expected: $expected, got: $msg")
}

@Test
def simple {
import SimpleTCDeriver._
Expand Down Expand Up @@ -396,6 +482,35 @@ class LowPriorityDerivationTests {
validateTC[Tree]("Generic[Leaf :+: Generic[Generic[Leaf :+: Generic[Generic[Leaf :+: … :+: …] :: Generic[… :+: …] :: Int :: HNil] :+: CNil] :: Generic[Leaf :+: Generic[Generic[… :+: …] :: Generic[…] :: … :: …] :+: CNil] :: Int :: HNil] :+: CNil]", 12)
}

@Test
def refinement {
import TCRDeriver._

// Orphans
validateTCR[Int]("Int")
validateTCR[CC1]("CC1")
validateTCR[Option[Int]]("Option[Int]")
validateTCR[Option[CC1]]("Option[CC1]")
validateTCR[(Int, CC1)]("(Int, CC1)")
validateTCR[(CC1, Int)]("(CC1, Int)")
validateTCR[(CC1, Boolean)]("(CC1, Boolean)")

val cc1 = TCR[CC1]
val cc1Value: cc1.Out = 2

mkTCR[CC2, Int :: HNil](null, implicitly)

// Derived
validateTCR[CC2]("Generic[Int :: HNil]")

val cc2 = TCR[CC2]
val cc2Value: cc2.Out = 2 :: HNil

// Mix of orphans and derived
validateTCR[(Int, CC1, Boolean)]("Generic[Int :: CC1 :: Boolean :: HNil]")
validateTCR[(Int, CC2)]("(Int, Generic[Int :: HNil])")
}

@Test
def simpleWithIgnoring {
import SimpleTC0Deriver._
Expand Down

0 comments on commit ed119b7

Please sign in to comment.