create an ambiguous low priority implicit

2020-07-06 04:23发布

问题:

Consider the default codec as offered in the io package.

implicitly[io.Codec].name  //res0: String = UTF-8

It's a "low priority" implicit so it's easy to override without ambiguity.

implicit val betterCodec: io.Codec = io.Codec("US-ASCII")

implicitly[io.Codec].name  //res1: String = US-ASCII

It's also easy to raise its priority level.

import io.Codec.fallbackSystemCodec
implicit val betterCodec: io.Codec = io.Codec("US-ASCII")

implicitly[io.Codec].name  //won't compile: ambiguous implicit values

But can we go in the opposite direction? Can we create a low level implicit that disables ("ambiguates"?) the default? I've been looking at the priority equation and playing around with low priority implicits but I've yet to create something ambiguous to the default.

回答1:

If I understand correctly you want to check at compile time that there is local implicit io.Codec ("higher-priority") or produce compile error otherwise. This can be done with macros (using compiler internals).

import scala.language.experimental.macros
import scala.reflect.macros.{contexts, whitebox}

object Macros {

  def localImplicitly[A]: A = macro impl[A]

  def impl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
    import c.universe._

    val context = c.asInstanceOf[contexts.Context]
    val global: context.universe.type = context.universe
    val analyzer: global.analyzer.type = global.analyzer
    val callsiteContext = context.callsiteTyper.context

    val tpA = weakTypeOf[A]

    val localImplicit = new analyzer.ImplicitSearch(
      tree = EmptyTree.asInstanceOf[global.Tree],
      pt = tpA.asInstanceOf[global.Type],
      isView = false,
      context0 = callsiteContext.makeImplicit(reportAmbiguousErrors = true),
      pos0 = c.enclosingPosition.asInstanceOf[global.Position]
    ) {
      override def searchImplicit(
                                   implicitInfoss: List[List[analyzer.ImplicitInfo]],
                                   isLocalToCallsite: Boolean
                                 ): analyzer.SearchResult = {
        if (isLocalToCallsite)
          super.searchImplicit(implicitInfoss, isLocalToCallsite)
        else analyzer.SearchFailure
      }
    }.bestImplicit

    if (localImplicit.isSuccess)
      localImplicit.tree.asInstanceOf[c.Tree]
    else c.abort(c.enclosingPosition, s"no local implicit $tpA")
  }
}

localImplicitly[io.Codec].name // doesn't compile
// Error: no local implicit scala.io.Codec

implicit val betterCodec: io.Codec = io.Codec("US-ASCII")
localImplicitly[Codec].name // US-ASCII

import io.Codec.fallbackSystemCodec
localImplicitly[Codec].name // UTF-8

import io.Codec.fallbackSystemCodec
implicit val betterCodec: io.Codec = io.Codec("US-ASCII")
localImplicitly[Codec].name // doesn't compile
//Error: ambiguous implicit values:
// both value betterCodec in object App of type => scala.io.Codec
// and lazy value fallbackSystemCodec in trait LowPriorityCodecImplicits of type => //scala.io.Codec
// match expected type scala.io.Codec

Tested in 2.13.0.

libraryDependencies ++= Seq(
  scalaOrganization.value % "scala-reflect" % scalaVersion.value,
  scalaOrganization.value % "scala-compiler" % scalaVersion.value
)


回答2:

Sort of, yes.

You can do this by creating a 'newtype'. I.e. a type that is simply a proxy to io.Codec, and wraps the instance. This means that you also need to change all your implicit arguments from io.Codec to CodecWrapper, which may not be possible.

trait CodecWraper {
  def orphan: io.Codec
}

object CodecWrapper {
  /* because it's in the companion, this will have the highest implicit resolution priority. */
  implicit def defaultInstance: CodecWrapper = 
    new CodecWrapper {
      def orphan = new io.Codec { /* your default implementation here */ }
    }
  }
}

import io.Codec.fallbackSystemCodec
implicitly[CodecWrapper].orphan // io.Codec we defined above - no ambiguity