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

shapeless.compat.LowPriority.scala Maven / Gradle / Ivy

The newest version!
package shapeless.compat

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

import shapeless.{ Lazy => _, _ }

/**
 * Evidence that no implicit `T` instance is available elsewhere.
 *
 * The instance using the `LowPriority[T]` is ignored.
 *
 * Allows to prioritize implicits, for example
 *
 * {{{
 *   trait TC[T] {
 *     def prop: Boolean
 *   }
 *
 *   object TC {
 *     // TC[Int] available by default, with field `prop` true
 *     implicit val intTC: TC[Int] = new TC[Int] { def prop = true }
 *   }
 *
 *   // extra `TC[T]`, with field `prop` false
 *   implicit def extraTC[T](implicit ev: LowPriority[TC[T]]): TC[T] =
 *     new TC[T] { def prop = false }
 *
 *   // Already available instance `intTC` is still found, because `extraTC[Int]` requires a
 *   // `LowPriority[TC[Int]]`, that will refuse to materialize (because `LowPriority` is able to
 *   // know about the already available `intTC`.)
 *   assert(implicitly[TC[Int]].prop == true)
 *
 *   // `extraTC[String]` is found, as no other `TC[String]` can be found elsewhere
 *   assert(implicitly[TC[String]].prop == false)
 * }}}
 *
 * @author Alexandre Archambault
 */
trait LowPriority[T] extends Serializable

object LowPriority extends LazyExtensionCompanion {
  def apply[T](implicit nf: Strict[LowPriority[T]]): LowPriority[T] =
    nf.value


  def id = "low-priority"

  implicit def init[T]: LowPriority[T] = macro initImpl //[LowPriority[T]]

  def instantiate(ctx0: DerivationContext): LazyExtension { type Ctx = ctx0.type } =
    new LowPriorityExtension {
      type Ctx = ctx0.type
      val ctx: ctx0.type = ctx0
    }
}

/**
 * Allows to ignore some implicits in a `LowPriority[T]`.
 *
 * Use like ``LowPriority[Ignoring[Witness.`"ignoredMethod"`.T, T]]``.
 *
 * Typical usage is when a fallback for type class `TC` is defined in its companion, like
 * {{{
 *   object TC {
 *     implicit def anyTC[T]: TC[T] = ...
 *   }
 * }}}
 *
 * With the example of `LowPriority[T]` above,
 * {{{
 *   trait TC[T] {
 *     def prop: Option[Boolean]
 *   }
 *
 *   trait LowPriTC {
 *     // default low priority TC[T] for any T, with field `prop` equal to `None`
 *     implicit def anyTC[T]: TC[T] = new TC[T] { def prop = None }
 *   }
 *
 *   object TC extends LowPriTC {
 *     // TC[Int] available by default, with field `prop` equal to `Some(true)`
 *     implicit val intTC: TC[Int] = new TC[Int] { def prop = Some(true) }
 *   }
 *
 *   // extra `TC[T]`, with field `prop` equal to `Some(false)`
 *   implicit def extraTC[T](implicit ev: LowPriority[Ignoring[Witness.`"anyTC"`.T, TC[T]]]): TC[T] =
 *     new TC[T] { def prop = Some(false) }
 *
 *   // Already available instance `intTC` is still found, because `extraTC[Int]` requires a
 *   // `LowPriority[TC[Int]]`, that will refuse to materialize (because `LowPriority` is able to
 *   // know about the already available `intTC`.)
 *   assert(implicitly[TC[Int]].prop == true)
 *
 *   // `extraTC[String]` is found, as the default `anyTC[String]` is ignored,
 *   assert(implicitly[TC[String]].prop == false)
 * }}}
 */
trait Ignoring[M, T]


trait LowPriorityTypes {
  type C <: whitebox.Context
  val c: C

  import c.universe._


  def lowPriorityTpe: Type = typeOf[LowPriority[_]].typeConstructor

  object LowPriorityTpe {
    def unapply(tpe: Type): Option[Type] =
      tpe.dealias match {
        case TypeRef(_, cpdTpe, List(highTpe))
          if cpdTpe.asType.toType.typeConstructor =:= lowPriorityTpe =>
          Some(highTpe)
        case _ =>
          None
      }
  }

  def ignoringTpe: Type = typeOf[Ignoring[_, _]].typeConstructor

  object IgnoringTpe {
    def unapply(tpe: Type): Option[(Type, Type)] =
      tpe.dealias match {
        case TypeRef(_, cpdTpe, List(mTpe, tTpe))
          if cpdTpe.asType.toType.typeConstructor =:= ignoringTpe =>
          Some(mTpe, tTpe)
        case _ =>
          None
      }
  }

}

trait LowPriorityExtension extends LazyExtension with LowPriorityTypes {
  type C = ctx.c.type
  lazy val c: C = ctx.c

  import ctx._
  import c.universe._

  case class ThisState(
    /**
     * `LowPriority` types whose derivation must fail no matter what.
     *
     * In the initial lookup for a `LowPriority[T]`, a `T` will be looked for elsewhere.
     * During the latter search, `LowPriority[T]` will be in this list, so that further
     * derivations of `LowPriority[T]` will fail, effectively *preventing* the implicit that should
     * be given a lower priority to be found.
     */
    prevent: List[TypeWrapper]
  ) {
    def :+(tpe: Type): ThisState =
      copy(prevent = TypeWrapper(tpe) :: prevent)

    def allowed(tpe: Type): Either[String, Unit] =
      if (prevent.contains(TypeWrapper(tpe)))
        Left(s"Not deriving $tpe")
      else
        Right(())
  }

  def id = LowPriority.id

  def initialState = ThisState(Nil)

  def deriveLowPriority(
    state: State,
    extState: ThisState,
    update: (State, ThisState) => State )(
    wrappedTpe: Type,
    innerTpe: Type,
    ignoring: String
  ): (State, Instance) = {
    val tmpState = update(state, extState :+ wrappedTpe)

    val existingInstOpt = ctx.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")
    else {
      val innerTpe0 =
        if (ignoring.isEmpty)
          innerTpe
        else
          appliedType(ignoringTpe, List(internal.constantType(Constant(ignoring)), innerTpe))

      val low = q"""
        new _root_.shapeless.compat.LowPriority[$innerTpe0] {} : _root_.shapeless.compat.LowPriority[$innerTpe0]
      """
      val lowTpe = appliedType(lowPriorityTpe, List(innerTpe0))

      state.closeInst(wrappedTpe, low, lowTpe)
    }
  }

  private def withIgnored(tpe: Type): Either[String, (Type, String)] =
    tpe match {
      case IgnoringTpe(mTpe, innerTpe0) =>
        mTpe match {
          case ConstantType(Constant(ignored: String)) if ignored.nonEmpty => Right((innerTpe0, ignored))
          case _ => Left(s"Unsupported ignored type: $mTpe")
        }
      case _ => Right((tpe, ""))
    }

  def derive(
    state0: State,
    extState: ThisState,
    update: (State, ThisState) => State )(
    instTpe: Type
  ): Option[Either[String, (State, Instance)]] =
    instTpe match {
      case LowPriorityTpe(innerTpe) =>

        val res =
          for {
            _ <- extState.allowed(instTpe).right
            state <- state0.lookup(instTpe).left
            tpeIgnored <- withIgnored(innerTpe).right
          } yield {
            val (tpe, ignored) = tpeIgnored
            deriveLowPriority(state, extState, update)(instTpe, tpe, ignored)
          }

        Some(res)

      case _ => None
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy