Type error when using a type parameter as a type argument for another type being passed to a macro implementation

StackOverflow https://stackoverflow.com/questions/19378620

  •  30-06-2022
  •  | 
  •  

Frage

I'm trying to create a macro that will allow me to capture the text of an expression that is passed to a constructor. I want the text of the expression for debugging purposes. The macro implementation is as follows:

package nimrandsLibrary.react.macroImpl

object Macros {
    def applyImpl[T : context.WeakTypeTag, U : context.WeakTypeTag](context : scala.reflect.macros.Context) (expression : context.Expr[T]) : context.Expr[U] = {
        import context.universe._
        context.Expr[U](context.universe.New(context.universe.weakTypeOf[U], expression.tree, context.universe.Literal(context.universe.Constant(expression.tree.toString()))))
    }
}

The definiation is as follows:

class Signal(expression : => T, expressionText : String) {
    ...
}

object Signal {
    def apply[T](expression : T) = macro nimrandsLibrary.react.macroImpl.Macros.applyImpl[T, Signal[T]]
}

However, wherever I call it, like below, I get an error.

val mySignal = Signal{ 2 }  //type mismatch; found : Int required : T

But, of course, the type for T is Int, so the error doesn't make sense.

It seems that somehow in the macro expansion the compiler is forgetting to replace Signal[T] with Signal[Int]. As an experiment, I tried changing the definition site so that both types have to be provided, like so:

def apply[T, U](expression : T) = macro nimrandsLibrary.react.macroImpl.Macros.applyImpl[T, U]

Then, I call it like so:

Signal[Int, Signal[Int]]{ 2 }

And, that works. But, of course, thats not at all the syntax I'm aiming for. Is this a bug, or am I doing it wrong somehow? Is there a workaround?

War es hilfreich?

Lösung

The problem is that the weak type tag for U in the macro implementation has as its argument what's essentially the symbol T, not Int.

The following will work (note that I've shortened some names for clarity and replaced deprecated methods):

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

object Macros {
  def applyImpl[
    T: c.WeakTypeTag,
    U: c.WeakTypeTag
  ](c: Context)(e: c.Expr[T]): c.Expr[U] = {
    import c.universe._

    c.Expr[U](
      Apply(
        Select(
          New(
            TypeTree(
              appliedType(weakTypeOf[U].typeConstructor, weakTypeOf[T] :: Nil)
            )
          ),
          nme.CONSTRUCTOR
        ),
        List(e.tree, Literal(Constant(e.tree.toString)))
      )
    )
  }
}

class Signal[T](val expression: T, val expressionText: String)

object Signal {
  def apply[T](e: T) = macro Macros.applyImpl[T, Signal[T]]
}

And then:

scala> Signal(1).expressionText
res0: String = 1

As expected.


As Miles Sabin points out on Twitter, it would be nicer to have U be a type constructor, since in the version above the macro implementation makes some assumptions about U that aren't captured in the types. The following is a safer approach in this respect:

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

object Macros {
  def applyImpl[
    T: c.WeakTypeTag,
    U[_]
  ](c: Context)(e: c.Expr[T])(implicit u: c.WeakTypeTag[U[_]]): c.Expr[U[T]] = {
    import c.universe._

    c.Expr[U[T]](
      Apply(
        Select(
          New(
            TypeTree(
              appliedType(u.tpe.typeConstructor, weakTypeOf[T] :: Nil)
            )
          ),
          nme.CONSTRUCTOR
        ),
        List(e.tree, Literal(Constant(e.tree.toString)))
      )
    )
  }
}

class Signal[T](val expression: T, val expressionText: String)

object Signal {
  def apply[T](e: T) = macro Macros.applyImpl[T, Signal]
}

Note especially the change to the second argument of applyImpl in Signal.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top