Skip to content

Commit

Permalink
Macro-accelerated record Remover, Modifier; added LacksKey, replace.
Browse files Browse the repository at this point in the history
  • Loading branch information
milessabin committed May 9, 2016
1 parent c7dc0f7 commit 7b90b2e
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 29 deletions.
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,9 @@ lazy val mimaSettings = mimaDefaultSettings ++ Seq(
exclude[MissingMethodProblem]("shapeless.CaseClassMacros.shapeless$CaseClassMacros$$$anonfun$15"),
exclude[MissingMethodProblem]("shapeless.CaseClassMacros.shapeless$CaseClassMacros$$$anonfun$16"),
exclude[MissingMethodProblem]("shapeless.CaseClassMacros.shapeless$CaseClassMacros$$$anonfun$17"),
exclude[MissingMethodProblem]("shapeless.UnwrappedInstances.tagUnwrapped")
exclude[MissingMethodProblem]("shapeless.UnwrappedInstances.tagUnwrapped"),
exclude[MissingMethodProblem]("shapeless.CaseClassMacros.findField"),
exclude[MissingMethodProblem]("shapeless.CaseClassMacros.FieldType")
)
}
)
Expand Down
21 changes: 21 additions & 0 deletions core/src/main/scala/shapeless/generic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,22 @@ trait CaseClassMacros extends ReprTypes {
unfold(tpe, List()).reverse
}

object FieldType {
import internal._

def apply(kTpe: Type, vTpe: Type): Type =
refinedType(List(vTpe, typeRef(prefix(keyTagTpe), keyTagTpe.typeSymbol, List(kTpe, vTpe))), NoSymbol)

def unapply(fTpe: Type): Option[(Type, Type)] = {
val KeyTagPre = prefix(keyTagTpe)
val KeyTagSym = keyTagTpe.typeSymbol
fTpe.dealias match {
case RefinedType(List(v0, TypeRef(pre, KeyTagSym, List(k, v1))), _) if pre =:= KeyTagPre && v0 =:= v1 => Some((k, v0))
case _ => None
}
}
}

def unpackFieldType(tpe: Type): (Type, Type) = {
val KeyTagPre = prefix(keyTagTpe)
val KeyTagSym = keyTagTpe.typeSymbol
Expand All @@ -512,6 +528,11 @@ trait CaseClassMacros extends ReprTypes {
}
}

def findField(lTpe: Type, kTpe: Type): Option[(Type, Int)] =
unpackHListTpe(lTpe).zipWithIndex.collectFirst {
case (FieldType(k, v), i) if k =:= kTpe => (v, i)
}

def mkTypTree(tpe: Type): Tree = {
tpe match {
case SingleType(pre @ SingleType(_, _), sym) =>
Expand Down
71 changes: 59 additions & 12 deletions core/src/main/scala/shapeless/hlists.scala
Original file line number Diff line number Diff line change
Expand Up @@ -105,26 +105,73 @@ object HList extends Dynamic {
def selectDynamic(tpeSelector: String): Any = macro LabelledMacros.hlistTypeImpl

@tailrec
def unsafeGet(l: HList, i: Int): Any = {
val c = l.asInstanceOf[::[Any, HList]]
if(i == 0) c.head
else unsafeGet(c.tail, i-1)
def unsafeGet(l: HList, i: Int): Any =
l match {
case hd :: tl if i == 0 => hd
case hd :: tl => unsafeGet(tl, i-1)
}

def unsafeReversePrepend(l: HList, m: HList): HList = {
@tailrec
def loop(l: HList, suffix: HList): HList =
l match {
case HNil => suffix
case hd :: tl => loop(tl, hd :: suffix)
}
loop(l, m)
}

def unsafeUpdate(l: HList, i: Int, e: Any): HList = {
def unsafeReverse(l: HList): HList =
unsafeReversePrepend(l, HNil)

def unsafePrepend(l: HList, m: HList): HList =
unsafeReversePrepend(unsafeReverse(l), m)

def unsafeUpdateAt(l: HList, i: Int, e: Any): HList = {
@tailrec
def loop(l: HList, i: Int, prefix: List[Any]): (List[Any], HList) =
def loop(l: HList, i: Int, revPrefix: HList): HList =
l match {
case HNil => (prefix, e :: HNil)
case hd :: (tl : HList) if i == 0 => (prefix, e :: tl)
case hd :: (tl : HList) => loop(tl, i-1, hd :: prefix)
case hd :: tl if i == 0 => unsafeReversePrepend(revPrefix, e :: tl)
case hd :: tl => loop(tl, i-1, hd :: revPrefix)
}
loop(l, i, HNil)
}

val (prefix, suffix) = loop(l, i, Nil)
prefix.foldLeft(suffix) { (tl, hd) => hd :: tl }
def unsafeUpdateAppend(l: HList, i: Int, e: Any): HList = {
@tailrec
def loop(l: HList, i: Int, revPrefix: HList): HList =
l match {
case HNil => unsafeReversePrepend(revPrefix, e :: HNil)
case hd :: tl if i == 0 => unsafeReversePrepend(revPrefix, e :: tl)
case hd :: tl => loop(tl, i-1, hd :: revPrefix)
}
loop(l, i, HNil)
}
}

@deprecated("use unsafeUpdateAppend instead", "2.3.1")
def unsafeUpdate(l: HList, i: Int, e: Any): HList =
unsafeUpdateAppend(l, i, e)

def unsafeUpdateWith(l: HList, i: Int, f: Any => Any): HList = {
@tailrec
def loop(l: HList, i: Int, revPrefix: HList): HList =
l match {
case hd :: tl if i == 0 => unsafeReversePrepend(revPrefix, f(hd) :: tl)
case hd :: tl => loop(tl, i-1, hd :: revPrefix)
}
loop(l, i, HNil)
}

def unsafeRemove(l: HList, i: Int): (Any, HList) = {
@tailrec
def loop(l: HList, i: Int, revPrefix: HList): (Any, HList) =
l match {
case hd :: tl if i == 0 => (hd, unsafeReversePrepend(revPrefix, tl))
case hd :: tl => loop(tl, i-1, hd :: revPrefix)
}
loop(l, i, HNil)
}
}

/**
* Trait supporting mapping dynamic argument lists of Ints to HList of Nat arguments.
Expand Down
117 changes: 104 additions & 13 deletions core/src/main/scala/shapeless/ops/records.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ package record {
def apply[L <: HList, K](implicit selector: Selector[L, K]): Aux[L, K, selector.Out] = selector

implicit def mkSelector[L <: HList, K, O]: Aux[L, K, O] = macro SelectorMacros.applyImpl[L, K]

}

class UnsafeSelector(i: Int) extends Selector[HList, Any] {
Expand All @@ -64,15 +63,11 @@ package record {
if(!(lTpe <:< hlistTpe))
abort(s"$lTpe is not a record type")

val lTpes = unpackHListTpe(lTpe).zipWithIndex.flatMap { case (fTpe, i) =>
val (k, v) = unpackFieldType(fTpe)
if(k =:= kTpe) Some((v, i)) else None
}
lTpes.headOption match {
case Some((vTpe, i)) =>
findField(lTpe, kTpe) match {
case Some((v, i)) =>
q"""
new _root_.shapeless.ops.record.UnsafeSelector($i).
asInstanceOf[_root_.shapeless.ops.record.Selector.Aux[$lTpe, $kTpe, $vTpe]]
asInstanceOf[_root_.shapeless.ops.record.Selector.Aux[$lTpe, $kTpe, $v]]
"""
case _ =>
abort(s"No field $kTpe in record type $lTpe")
Expand Down Expand Up @@ -127,7 +122,7 @@ package record {

class UnsafeUpdater(i: Int) extends Updater[HList, Any] {
type Out = HList
def apply(l: HList, f: Any): HList = HList.unsafeUpdate(l, i, f)
def apply(l: HList, f: Any): HList = HList.unsafeUpdateAppend(l, i, f)
}

@macrocompat.bundle
Expand Down Expand Up @@ -209,20 +204,56 @@ package record {

type Aux[L <: HList, F, A, B, Out0 <: HList] = Modifier[L, F, A, B] { type Out = Out0 }

implicit def hlistModify1[F, A, B, T <: HList]: Aux[FieldType[F, A] :: T, F, A, B, FieldType[F, B] :: T] =
implicit def mkModifier[L <: HList, K, A, B, O <: HList]: Aux[L, K, A, B, O] = macro ModifierMacros.applyImpl[L, K, A, B]

@deprecated("Retained for binary compatability", "2.3.1")
def hlistModify1[F, A, B, T <: HList]: Aux[FieldType[F, A] :: T, F, A, B, FieldType[F, B] :: T] =
new Modifier[FieldType[F, A] :: T, F, A, B] {
type Out = FieldType[F, B] :: T
def apply(l: FieldType[F, A] :: T, f: A => B): Out = field[F](f(l.head)) :: l.tail
}

implicit def hlistModify[H, T <: HList, F, A, B]
@deprecated("Retained for binary compatability", "2.3.1")
def hlistModify[H, T <: HList, F, A, B]
(implicit mt: Modifier[T, F, A, B]): Aux[H :: T, F, A, B, H :: mt.Out] =
new Modifier[H :: T, F, A, B] {
type Out = H :: mt.Out
def apply(l: H :: T, f: A => B): Out = l.head :: mt(l.tail, f)
}
}

class UnsafeModifier(i: Int) extends Modifier[HList, Any, Any, Any] {
type Out = HList
def apply(l: HList, f: Any => Any): HList = HList.unsafeUpdateWith(l, i, f)
}

@macrocompat.bundle
class ModifierMacros(val c: whitebox.Context) extends CaseClassMacros {
import c.universe._

def applyImpl[L <: HList, K, A, B]
(implicit lTag: WeakTypeTag[L], kTag: WeakTypeTag[K], aTag: WeakTypeTag[A], bTag: WeakTypeTag[B]): Tree = {
val lTpe = lTag.tpe.dealias
val kTpe = kTag.tpe.dealias
if(!(lTpe <:< hlistTpe))
abort(s"$lTpe is not a record type")
val aTpe = aTag.tpe
val bTpe = bTag.tpe

findField(lTpe, kTpe) match {
case Some((v, i)) if v <:< aTpe =>
val (prefix, List(_, suffix @ _*)) = unpackHListTpe(lTpe).splitAt(i)
val outTpe = mkHListTpe(prefix ++ (FieldType(kTpe, bTpe) :: suffix.toList))
q"""
new _root_.shapeless.ops.record.UnsafeModifier($i).
asInstanceOf[_root_.shapeless.ops.record.Modifier.Aux[$lTpe, $kTpe, $aTpe, $bTpe, $outTpe]]
"""
case _ =>
abort(s"No field $kTpe in record type $lTpe")
}
}
}

/**
* Type class supporting record field removal.
*
Expand All @@ -234,7 +265,8 @@ package record {
trait LowPriorityRemover {
type Aux[L <: HList, K, Out0] = Remover[L, K] { type Out = Out0 }

implicit def hlistRemove[H, T <: HList, K, V, OutT <: HList]
@deprecated("Retained for binary compatability", "2.3.1")
def hlistRemove[H, T <: HList, K, V, OutT <: HList]
(implicit rt: Aux[T, K, (V, OutT)]): Aux[H :: T, K, (V, H :: OutT)] =
new Remover[H :: T, K] {
type Out = (V, H :: OutT)
Expand All @@ -248,13 +280,45 @@ package record {
object Remover extends LowPriorityRemover {
def apply[L <: HList, K](implicit remover: Remover[L, K]): Aux[L, K, remover.Out] = remover

implicit def hlistRemove1[K, V, T <: HList]: Aux[FieldType[K, V] :: T, K, (V, T)] =
implicit def mkRemover[L <: HList, K, V, O <: HList]: Aux[L, K, (V, O)] = macro RemoverMacros.applyImpl[L, K]

@deprecated("Retained for binary compatability", "2.3.1")
def hlistRemove1[K, V, T <: HList]: Aux[FieldType[K, V] :: T, K, (V, T)] =
new Remover[FieldType[K, V] :: T, K] {
type Out = (V, T)
def apply(l: FieldType[K, V] :: T): Out = (l.head, l.tail)
}
}

class UnsafeRemover(i: Int) extends Remover[HList, Any] {
type Out = (Any, HList)
def apply(l: HList): (Any, HList) = HList.unsafeRemove(l, i)
}

@macrocompat.bundle
class RemoverMacros(val c: whitebox.Context) extends CaseClassMacros {
import c.universe._

def applyImpl[L <: HList, K](implicit lTag: WeakTypeTag[L], kTag: WeakTypeTag[K]): Tree = {
val lTpe = lTag.tpe.dealias
val kTpe = kTag.tpe.dealias
if(!(lTpe <:< hlistTpe))
abort(s"$lTpe is not a record type")

findField(lTpe, kTpe) match {
case Some((v, i)) =>
val (prefix, List(_, suffix @ _*)) = unpackHListTpe(lTpe).splitAt(i)
val outTpe = mkHListTpe(prefix ++ suffix)
q"""
new _root_.shapeless.ops.record.UnsafeRemover($i).
asInstanceOf[_root_.shapeless.ops.record.Remover.Aux[$lTpe, $kTpe, ($v, $outTpe)]]
"""
case _ =>
abort(s"No field $kTpe in record type $lTpe")
}
}
}

/**
* Type class supporting removal and re-insertion of an element (possibly unlabelled).
*
Expand Down Expand Up @@ -370,6 +434,33 @@ package record {
}
}

trait LacksKey[L <: HList, K]

object LacksKey {
implicit def apply[L <: HList, K]: LacksKey[L, K] = macro LacksKeyMacros.applyImpl[L, K]
}

@macrocompat.bundle
class LacksKeyMacros(val c: whitebox.Context) extends CaseClassMacros {
import c.universe._

def applyImpl[L <: HList, K](implicit lTag: WeakTypeTag[L], kTag: WeakTypeTag[K]): Tree = {
val lTpe = lTag.tpe.dealias
val kTpe = kTag.tpe.dealias
if(!(lTpe <:< hlistTpe))
abort(s"$lTpe is not a record type")

findField(lTpe, kTpe) match {
case None =>
q"""
new _root_.shapeless.ops.record.LacksKey[$lTpe, $kTpe] {}
"""
case _ =>
abort(s"Record type $lTpe contains field $kTpe")
}
}
}

/**
* Type class supporting collecting the keys of a record as an `HList`.
*
Expand Down
11 changes: 9 additions & 2 deletions core/src/main/scala/shapeless/syntax/records.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,17 @@ final class RecordOps[L <: HList](val l : L) extends AnyVal with Serializable {
def fieldAt(k: Witness)(implicit selector : Selector[L, k.T]): FieldType[k.T, selector.Out] = field[k.T](selector(l))

/**
* Updates or adds to this record a field with key type F and value type F#valueType.
* Updates or adds to this record a field with key k. The new field has a value of type V. Only available if this
* record has a field with keyType equal to the singleton type k.T.
*/
def updated[V](k: Witness, v: V)(implicit updater: Updater[L, FieldType[k.T, V]]) : updater.Out = updater(l, field[k.T](v))

/**
* Replaces the value of field k with a value of the same type. Only available if this record has a field with
* keyType equal to the singleton type k.T and valueType equal to V.
*/
def replace[V](k: Witness, v: V)
(implicit ev: Selector.Aux[L, k.T, V], updater: Updater[L, FieldType[k.T, V]]): updater.Out = updater(l, field[k.T](v))

/**
* Updates a field having a value with type A by given function.
Expand All @@ -74,7 +81,7 @@ final class RecordOps[L <: HList](val l : L) extends AnyVal with Serializable {
* Updates or adds to this record a field of type F.
*/
def +[F](f: F)(implicit updater : Updater[L, F]): updater.Out = updater(l, f)

/**
* Remove the field associated with the singleton typed key k, returning the updated record. Only available if this
* record has a field with keyType equal to the singleton type k.T.
Expand Down
Loading

0 comments on commit 7b90b2e

Please sign in to comment.