All Downloads are FREE. Search and download functionalities are using the official Maven repository.

shapeless.cached.scala Maven / Gradle / Ivy

The newest version!
package shapeless

import scala.language.experimental.macros
import scala.reflect.macros.whitebox

/**
 * Wraps a cached implicit `T`.
 *
 * Looking for an implicit `Cached[T]` first triggers a look for an implicit `T`, caches the resulting
 * tree, and returns it immediately and in subsequent look ups for an implicit `Cached[T]`. Thus,
 * subsequent look ups do not trigger looking for an implicit `T`, only returning the instance kept in
 * cache.
 *
 * Beware that if the contexts in which two subsequent look ups are different, so that looking for a
 * `T` in each of them doesn't return the same result, this change would be ignored by caching. Looking
 * for a `Cached[T]` in the first context would put the implicit `T` of this context in cache, and then
 * looking for a `Cached[T]` in the second context would return the former instance from the first
 * context. E.g.
 *
 * {{{
 *   trait TC[T] {
 *     def msg: String
 *   }
 *
 *   object First {
 *     implicit val tc: TC[Int] = new TC[Int] {
 *       val msg = "first"
 *     }
 *
 *     def print() = println(implicitly[TC[Int]].msg)
 *     def printCached() = println(cached[TC[Int]].msg)
 *   }
 *
 *   object Second {
 *     implicit val tc: TC[Int] = new TC[Int] {
 *       val msg = "second"
 *     }
 *
 *     def print() = println(implicitly[TC[Int]].msg)
 *     def printCached() = println(cached[TC[Int]].msg)
 *   }
 *
 *   First.print()
 *   Second.print()
 *   First.printCached()
 *   Second.printCached()
 * }}}
 *
 * would print "first" then "second" (non cached `TC[Int]` instances), then "first" twice (first instance, returned
 * the second time too through the cache).
 *
 * @author Alexandre Archambault
 */
case class Cached[+T](value: T) extends AnyVal

object Cached {
  implicit def materialize[I]: Cached[I] = macro CachedMacros.materializeCached[I]

  def implicitly[T](implicit cached: Cached[T]): T = cached.value
}

object CachedMacros {
  var deriving = false
  var cache = List.empty[(Any, Any)]
}

@macrocompat.bundle
class CachedMacros(override val c: whitebox.Context) extends LazyMacros(c) with OpenImplicitMacros {
  import c.universe._

  def deepCopyTree(t: Tree): Tree = {
    val treeDuplicator = new Transformer {
      // by default Transformers don’t copy trees which haven’t been modified,
      // so we need to use use strictTreeCopier
      override val treeCopy =
        c.asInstanceOf[reflect.macros.runtime.Context].global.newStrictTreeCopier.asInstanceOf[TreeCopier]
    }

    treeDuplicator.transform(t)
  }

  def materializeCached[T: WeakTypeTag]: Tree = {
    // Getting the actual type parameter T, using the same trick as Lazy/Strict
    val tpe = openImplicitTpeParam.getOrElse(weakTypeOf[T])

    val concurrentLazy = !CachedMacros.deriving && LazyMacros.dcRef(this).nonEmpty

    // Ensuring we are not caching parts of trees derived during a Lazy/Strict lookup
    // (but caching the full tree of a Lazy/Strict is fine), as these can reference values
    // (other entries of the Lazy/Strict derivation) that should not be accessible if
    // re-using the tree in other contexts, after caching.
    if (concurrentLazy)
      c.warning(c.enclosingPosition,
        s"Cached[$tpe] called from a Lazy/Strict, you might want to consider caching " +
        "an implicit earlier, so that the whole Lazy/Strict itself gets cached. Caching " +
        "is disabled here."
      )

    if (CachedMacros.deriving || concurrentLazy) {
      // Caching only the first (root) Cached, not subsequent ones as here
      val tree0 = c.inferImplicitValue(tpe)
      if (tree0 == EmptyTree)
        c.abort(c.enclosingPosition, s"Implicit $tpe not found")
      q"_root_.shapeless.Cached($tree0)"
    } else {
      CachedMacros.deriving = true

      try {
        val treeOpt = CachedMacros.cache.asInstanceOf[List[(Type, Tree)]].collectFirst {
          case (eTpe, eTree) if eTpe =:= tpe => eTree
        }

        deepCopyTree(treeOpt.getOrElse {
          // Cached instances are derived like Lazy or Strict instances.
          // Trying to derive them in a standalone way raised
          // https://github.com/fommil/spray-json-shapeless/issues/14.
          val tree0 = mkImpl[T](
            (tree, actualType) => q"_root_.shapeless.Cached[$actualType]($tree)",
            q"null.asInstanceOf[_root_.shapeless.Cached[_root_.scala.Nothing]]"
          )
          val tree = c.untypecheck(tree0)

          CachedMacros.cache = (tpe -> tree) :: CachedMacros.cache
          tree
        })
      } finally {
        CachedMacros.deriving = false
      }
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy