Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala macro - Infer implicit value using `c.prefix`

c.inferImplicitValue infers implicit values in the call site scope. Is it possible to infer implicits using the c.prefix scope?

This is not valid code, but expresses what I need:

c.prefix.inferImplicitValue

I'm currently using a naive implementation for this purpose[1], but it has some limitations like not inferring implicit values from defs and detecting duplicated/ambiguous implicit values.

[1] https://github.com/getquill/quill/blob/9a28d4e6c901d3fa07e7d5838e2f4c1f3c16732b/quill-core/src/main/scala/io/getquill/util/InferImplicitValueWithFallback.scala#L12

like image 854
Flávio W. Brasil Avatar asked Sep 16 '15 08:09

Flávio W. Brasil


1 Answers

Simply generating a block with an appropriate (local) import followed by a call to implicitly does the trick:

q"""{import ${c.prefix}._; _root_.scala.Predef.implicitly[$T] }

Where T is an instance of Type representing the type of the implicit value to lookup.

To check if the implicit lookup actually succeeded, you can call Context.typeCheck with silent=true and check if the resulting tree is empty or not.

As an illustration, here is an example that implements an infer method returning None if the implicit was not found in the members of the target object, and otherwise wraps the result in a Some.

import scala.reflect.macros.Context
import scala.language.experimental.macros

def inferImplicitInPrefixContext[T:c.WeakTypeTag](c: Context): c.Tree = {
  import c.universe._
  val T = weakTypeOf[T]
  c.typeCheck(
    q"""{
      import ${c.prefix}._
      _root_.scala.Predef.implicitly[$T]
    }""",
    silent = true
  )
}

def infer_impl[T:c.WeakTypeTag](c: Context): c.Expr[Option[T]] = {
  import c.universe._
  c.Expr[Option[T]](
    inferImplicitInPrefixContext[T](c) match {
      case EmptyTree => q"_root_.scala.None"
      case tree => q"_root_.scala.Some($tree)"
    }
  )
}

trait InferOp {
  def infer[T]: Option[T] = macro infer_impl[T]
}

Let's test it:

object Foo extends InferOp {
  implicit val s = "hello"
}

Foo.infer[String] // res0: Some[String] = Some(hello)

Foo.infer[Int] // res1: None.type = None

implicit val lng: Long = 123L

Foo.infer[Long] // res2: Some[Long] = Some(123)
like image 134
Régis Jean-Gilles Avatar answered Sep 22 '22 09:09

Régis Jean-Gilles