shapeless.lazy.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2013-16 Miles Sabin
*
* 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 shapeless
import scala.language.experimental.macros
import scala.annotation.implicitNotFound
import scala.collection.immutable.ListMap
import scala.reflect.macros.whitebox
/**
* Wraps a lazily computed value. Also circumvents cycles during implicit search, or wrong implicit divergences
* as illustrated below, and holds the corresponding implicit value lazily.
*
* The following implicit search sometimes fails to compile, because of a wrongly reported implicit divergence,
* {{{
* case class ListCC(list: List[CC])
* case class CC(i: Int, s: String)
*
* trait TC[T]
*
* object TC {
* implicit def intTC: TC[Int] = ???
* implicit def stringTC: TC[String] = ???
* implicit def listTC[T](implicit underlying: TC[T]): TC[List[T]] = ???
*
* implicit def genericTC[F, G](implicit
* gen: Generic.Aux[F, G],
* underlying: TC[G]
* ): TC[F] = ???
*
* implicit def hnilTC: TC[HNil] = ???
*
* implicit def hconsTC[H, T <: HList](implicit
* headTC: TC[H],
* tailTC: TC[T]
* ): TC[H :: T] = ???
* }
*
* implicitly[TC[ListCC]] // fails with: diverging implicit expansion for type TC[ListCC]
* }}}
*
* This wrongly reported implicit divergence can be circumvented by wrapping some of the implicit values in
* `Lazy`,
* {{{
* case class ListCC(list: List[CC])
* case class CC(i: Int, s: String)
*
* trait TC[T]
*
* object TC {
* implicit def intTC: TC[Int] = ???
* implicit def stringTC: TC[String] = ???
* implicit def listTC[T](implicit underlying: TC[T]): TC[List[T]] = ???
*
* implicit def genericTC[F, G](implicit
* gen: Generic.Aux[F, G],
* underlying: Lazy[TC[G]] // wrapped in Lazy
* ): TC[F] = ???
*
* implicit def hnilTC: TC[HNil] = ???
*
* implicit def hconsTC[H, T <: HList](implicit
* headTC: Lazy[TC[H]], // wrapped in Lazy
* tailTC: TC[T]
* ): TC[H :: T] = ???
* }
*
* implicitly[TC[ListCC]]
* }}}
*
* When looking for an implicit `Lazy[TC[T]]`, the `Lazy.mkLazy` macro will itself trigger the implicit search
* for a `TC[T]`. If this search itself triggers searches for types wrapped in `Lazy`, these will be done
* only once, their result put in a `lazy val`, and a reference to this `lazy val` will be returned as the corresponding
* value. It will then wrap all the resulting values together, and return a reference to the first one.
*
* E.g. with the above example definitions, when looking up for an implicit `TC[ListCC]`, the returned tree roughly looks
* like
* {{{
* TC.genericTC(
* Generic[ListCC], // actually, the tree returned by Generic.materialize, not written here for the sake of brevity
* Lazy {
* lazy val impl1: TC[List[CC] :: HNil] = TC.hconsTC(
* Lazy(impl2),
* TC.hnilTC
* )
* lazy val impl2: TC[List[CC]] = TC.listTC(TC.genericTC(
* Generic[CC], // actually, the tree returned by Generic.materialize
* Lazy(impl1) // cycles to the initial TC[List[CC] :: HNil]
* ))
*
* impl1
* }
* )
* }}}
*
*/
@implicitNotFound("could not find Lazy implicit value of type ${T}")
trait Lazy[+T] extends Serializable {
val value: T
def map[U](f: T => U): Lazy[U] = Lazy { f(value) }
def flatMap[U](f: T => Lazy[U]): Lazy[U] = Lazy { f(value).value }
}
object Lazy {
implicit def apply[T](t: => T): Lazy[T] =
new Lazy[T] {
lazy val value = t
}
def unapply[T](lt: Lazy[T]): Option[T] = Some(lt.value)
@implicitNotFound("could not find Lazy implicit values for all of the types enumerated in ${T}")
class Values[T <: HList](val values: T) extends Serializable
object Values {
implicit val hnilValues: Values[HNil] = new Values(HNil)
implicit def hconsValues[H, T <: HList](implicit lh: Lazy[H], t: Values[T]): Values[H :: T] =
new Values(lh.value :: t.values)
}
def values[T <: HList](implicit lv: Lazy[Values[T]]): T = lv.value.values
implicit def mkLazy[I]: Lazy[I] = macro LazyMacrosRef.mkLazyImpl[I]
}
object lazily {
def apply[T](implicit lv: Lazy[T]): T = lv.value
}
/**
* Wraps an eagerly computed value. Prevents wrongly reported implicit divergence, like `Lazy` does, but,
* unlike it, does not circumvent implicit cycles.
*
* Creation of `Lazy` instances usually triggers the creation of an anonymous class, to compute the wrapped
* value (e.g. with the by-name argument of `Lazy.apply`). `Strict` avoids that, which can lead to less
* overhead during compilation.
*/
trait Strict[+T] extends Serializable {
val value: T
def map[U](f: T => U): Strict[U] = Strict { f(value) }
def flatMap[U](f: T => Strict[U]): Strict[U] = Strict { f(value).value }
}
object Strict {
implicit def apply[T](t: T): Strict[T] =
new Strict[T] {
val value = t
}
def unapply[T](lt: Strict[T]): Option[T] = Some(lt.value)
implicit def mkStrict[I]: Strict[I] = macro LazyMacrosRef.mkStrictImpl[I]
}
@macrocompat.bundle
trait OpenImplicitMacros {
val c: whitebox.Context
import c.universe._
def openImplicitTpe: Option[Type] =
c.openImplicits.headOption.map(_.pt)
def openImplicitTpeParam: Option[Type] =
openImplicitTpe.map {
case TypeRef(_, _, List(tpe)) =>
tpe.map(_.dealias)
case other =>
c.abort(c.enclosingPosition, s"Bad materialization: $other")
}
def secondOpenImplicitTpe: Option[Type] =
c.openImplicits match {
case (List(_, second, _ @ _*)) =>
Some(second.pt)
case _ => None
}
}
@macrocompat.bundle
class LazyMacros(val c: whitebox.Context) extends CaseClassMacros with OpenImplicitMacros with LowPriorityTypes {
import c.universe._
import c.internal._
import decorators._
def mkLazyImpl[I](implicit iTag: WeakTypeTag[I]): Tree =
mkImpl[I](
(tree, actualType) => q"_root_.shapeless.Lazy.apply[$actualType]($tree)",
q"null.asInstanceOf[_root_.shapeless.Lazy[_root_.scala.Nothing]]"
)
def mkStrictImpl[I](implicit iTag: WeakTypeTag[I]): Tree =
mkImpl[I](
(tree, actualType) => q"_root_.shapeless.Strict.apply[$actualType]($tree)",
q"null.asInstanceOf[_root_.shapeless.Strict[_root_.scala.Nothing]]"
)
def mkImpl[I](mkInst: (Tree, Type) => Tree, nullInst: => Tree)(implicit iTag: WeakTypeTag[I]): Tree = {
openImplicitTpeParam match {
case Some(tpe) => LazyMacros.deriveInstance(this)(tpe, mkInst)
case None =>
val tpe = iTag.tpe.dealias
if (tpe.typeSymbol.isParameter)
nullInst
else
LazyMacros.deriveInstance(this)(tpe, mkInst)
}
}
def setAnnotation(msg: String): Unit = {
val tree0 =
c.typecheck(
q"""
new _root_.scala.annotation.implicitNotFound("dummy")
""",
silent = false
)
class SubstMessage extends Transformer {
val global = c.universe.asInstanceOf[scala.tools.nsc.Global]
import global.nme
override def transform(tree: Tree): Tree = {
super.transform {
tree match {
case Literal(Constant("dummy")) => Literal(Constant(msg))
case t => t
}
}
}
}
val tree = new SubstMessage().transform(tree0)
symbolOf[Lazy[Any]].setAnnotations(Annotation(tree))
}
def resetAnnotation: Unit =
setAnnotation("could not find Lazy implicit value of type ${T}")
trait LazyDefinitions {
case class Instance(
instTpe: Type,
name: TermName,
symbol: Symbol,
inst: Option[Tree],
actualTpe: Type,
dependsOn: List[Type]
) {
def ident = Ident(symbol)
}
object Instance {
def apply(instTpe: Type) = {
val nme = TermName(c.freshName("inst"))
val sym = c.internal.setInfo(c.internal.newTermSymbol(NoSymbol, nme), instTpe)
new Instance(instTpe, nme, sym, None, instTpe, Nil)
}
}
class TypeWrapper(val tpe: Type) {
override def equals(other: Any): Boolean =
other match {
case TypeWrapper(tpe0) => tpe =:= tpe0
case _ => false
}
override def toString = tpe.toString
}
object TypeWrapper {
def apply(tpe: Type) = new TypeWrapper(tpe)
def unapply(tw: TypeWrapper): Option[Type] = Some(tw.tpe)
}
}
class DerivationContext extends LazyDefinitions {
object State {
val empty = State("", ListMap.empty, Nil, Nil)
private var current = Option.empty[State]
def resolveInstance(state: State)(tpe: Type): Option[(State, Tree)] = {
val former = State.current
State.current = Some(state)
val (state0, tree) =
try {
val tree = c.inferImplicitValue(tpe, silent = true)
if(tree.isEmpty) {
tpe.typeSymbol.annotations.
find(_.tree.tpe =:= typeOf[_root_.scala.annotation.implicitNotFound]).foreach { infAnn =>
val global = c.universe.asInstanceOf[scala.tools.nsc.Global]
val analyzer: global.analyzer.type = global.analyzer
val gTpe = tpe.asInstanceOf[global.Type]
val errorMsg = gTpe.typeSymbolDirect match {
case analyzer.ImplicitNotFoundMsg(msg) =>
msg.format(TermName("evidence").asInstanceOf[global.TermName], gTpe)
case _ =>
s"Implicit value of type $tpe not found"
}
setAnnotation(errorMsg)
}
}
(State.current.get, tree)
} finally {
State.current = former
}
if (tree == EmptyTree) None
else Some((state0, tree))
}
def deriveInstance(instTpe0: Type, root: Boolean, mkInst: (Tree, Type) => Tree): Tree = {
if (root) {
assert(current.isEmpty)
val open = c.openImplicits
val name = if (open.length > 1) open(1).sym.name.toTermName.toString else "lazy"
current = Some(empty.copy(name = "anon$"+name))
}
derive(current.get)(instTpe0) match {
case Right((state, inst)) =>
val (tree, actualType) = if (root) mkInstances(state)(instTpe0) else (inst.ident, inst.actualTpe)
current = if (root) None else Some(state)
if (root) {
val valNme = TermName(c.freshName("inst"))
q"""
val $valNme: $actualType = $tree
${mkInst(q"$valNme", actualType)}
"""
} else
mkInst(tree, actualType)
case Left(err) =>
abort(err)
}
}
}
case class State(
name: String,
dict: ListMap[TypeWrapper, Instance],
open: List[Instance],
/** Types whose derivation must fail no matter what */
prevent: List[TypeWrapper]
) {
def addDependency(tpe: Type): State = {
import scala.::
val open0 = open match {
case Nil => Nil
case h :: t => h.copy(dependsOn = if (h.instTpe =:= tpe || h.dependsOn.exists(_ =:= tpe)) h.dependsOn else tpe :: h.dependsOn) :: t
}
copy(open = open0)
}
private def update(inst: Instance): State =
copy(dict = dict.updated(TypeWrapper(inst.instTpe), inst))
def openInst(tpe: Type): (State, Instance) = {
val inst = Instance(tpe)
val state0 = addDependency(tpe)
(state0.copy(open = inst :: state0.open).update(inst), inst)
}
def closeInst(tpe: Type, tree: Tree, actualTpe: Type): (State, Instance) = {
assert(open.nonEmpty)
assert(open.head.instTpe =:= tpe)
val instance = open.head
val sym = c.internal.setInfo(instance.symbol, actualTpe)
val instance0 = instance.copy(inst = Some(tree), actualTpe = actualTpe, symbol = sym)
(copy(open = open.tail).update(instance0), instance0)
}
def lookup(instTpe: Type): Either[State, (State, Instance)] =
dict.get(TypeWrapper(instTpe)) match {
case Some(i) => Right((addDependency(instTpe), i))
case None => Left(openInst(instTpe)._1)
}
def dependsOn(tpe: Type): List[Instance] = {
import scala.::
def helper(tpes: List[List[Type]], acc: List[Instance]): List[Instance] =
tpes match {
case Nil => acc
case Nil :: t =>
helper(t, acc)
case (h :: t0) :: t =>
if (acc.exists(_.instTpe =:= h))
helper(t0 :: t, acc)
else {
val inst = dict(TypeWrapper(h))
helper(inst.dependsOn :: t0 :: t, inst :: acc)
}
}
helper(List(List(tpe)), Nil)
}
}
def stripRefinements(tpe: Type): Option[Type] =
tpe match {
case RefinedType(parents, decls) => Some(parents.head)
case _ => None
}
def resolve(state: State)(inst: Instance): Option[(State, Instance)] =
resolve0(state)(inst.instTpe)
.filter{case (_, tree, _) => !tree.equalsStructure(inst.ident) }
.map {case (state0, extInst, actualTpe) =>
state0.closeInst(inst.instTpe, extInst, actualTpe)
}
def resolve0(state: State)(tpe: Type): Option[(State, Tree, Type)] = {
val extInstOpt =
State.resolveInstance(state)(tpe)
.orElse(
stripRefinements(tpe).flatMap(State.resolveInstance(state))
)
extInstOpt.map {case (state0, extInst) =>
(state0, extInst, extInst.tpe.finalResultType)
}
}
def deriveLowPriority(
state0: State,
instTpe: Type
): Option[Either[String, (State, Instance)]] = {
def helper(
state: State,
wrappedTpe: Type,
innerTpe: Type,
ignoring: String
): (State, Instance) = {
val tmpState = state.copy(prevent = state.prevent :+ TypeWrapper(wrappedTpe))
val existingInstOpt = derive(tmpState)(innerTpe).right.toOption.flatMap {
case (state2, inst) =>
if (inst.inst.isEmpty)
resolve0(state2)(innerTpe).map { case (_, tree, _) => tree }
else
Some(inst.inst.get)
}
val existingInstAvailable = existingInstOpt.exists { actualTree =>
def ignored = actualTree match {
case TypeApply(method, other) => method.toString().endsWith(ignoring)
case _ => false
}
ignoring.isEmpty || !ignored
}
if (existingInstAvailable)
c.abort(c.enclosingPosition, s"$innerTpe available elsewhere")
val lowTpe =
if (ignoring.isEmpty)
appliedType(lowPriorityForTpe, List(innerTpe))
else
appliedType(lowPriorityForIgnoringTpe, List(internal.constantType(Constant(ignoring)), innerTpe))
val low = q"null: $lowTpe"
state.closeInst(wrappedTpe, low, lowTpe)
}
if (state0.prevent.contains(TypeWrapper(instTpe)))
Some(Left(s"Not deriving $instTpe"))
else
instTpe match {
case LowPriorityFor(ignored, tpe) =>
val res = state0.lookup(instTpe) match {
case Left(state) => helper(state, instTpe, tpe, ignored)
case Right(res) => res
}
Some(Right(res))
case _ => None
}
}
def derive(state: State)(tpe: Type): Either[String, (State, Instance)] = {
deriveLowPriority(state, tpe).getOrElse {
state.lookup(tpe).left.flatMap { state0 =>
val inst = state0.dict(TypeWrapper(tpe))
resolve(state0)(inst)
.toRight(s"Unable to derive $tpe")
}
}
}
// Workaround for https://issues.scala-lang.org/browse/SI-5465
class StripUnApplyNodes extends Transformer {
val global = c.universe.asInstanceOf[scala.tools.nsc.Global]
import global.nme
override def transform(tree: Tree): Tree = {
super.transform {
tree match {
case UnApply(Apply(Select(qual, nme.unapply | nme.unapplySeq), List(Ident(nme.SELECTOR_DUMMY))), args) =>
Apply(transform(qual), transformTrees(args))
case UnApply(Apply(TypeApply(Select(qual, nme.unapply | nme.unapplySeq), _), List(Ident(nme.SELECTOR_DUMMY))), args) =>
Apply(transform(qual), transformTrees(args))
case t => t
}
}
}
}
def mkInstances(state: State)(primaryTpe: Type): (Tree, Type) = {
val instances = state.dict.values.toList
val (from, to) = instances.map { d => (d.symbol, NoSymbol) }.unzip
def clean(inst: Tree) = {
val cleanInst = c.untypecheck(c.internal.substituteSymbols(inst, from, to))
new StripUnApplyNodes().transform(cleanInst)
}
if (instances.length == 1) {
val instance = instances.head
import instance._
inst match {
case Some(inst) =>
val cleanInst = clean(inst)
(q"$cleanInst.asInstanceOf[$actualTpe]", actualTpe)
case None =>
abort(s"Uninitialized $instTpe lazy implicit")
}
} else {
val instTrees =
instances.map { instance =>
import instance._
inst match {
case Some(inst) =>
val cleanInst = clean(inst)
q"""lazy val $name: $actualTpe = $cleanInst.asInstanceOf[$actualTpe]"""
case None =>
abort(s"Uninitialized $instTpe lazy implicit")
}
}
val primaryInstance = state.lookup(primaryTpe).right.get._2
val primaryNme = primaryInstance.name
val clsName = TypeName(c.freshName(state.name))
val tree =
q"""
final class $clsName extends _root_.scala.Serializable {
..$instTrees
}
(new $clsName).$primaryNme
"""
val actualType = primaryInstance.actualTpe
(tree, actualType)
}
}
}
}
object LazyMacros extends LazyMacrosCompat
© 2015 - 2024 Weber Informatics LLC | Privacy Policy