0

I have some problems with scala macros and identifying the realised type of a constructor. Not sure if i am doing something wrong here or what the correct call would be. From the documentation, it looks like typeSignatureIn should return the correct information, e.g. ClassTag[Int], but when I run the macro, I actually get ClassTag[U] which fails to compile as U is the type parameter, rather than the realised type.

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

def macroImpl[T: c.WeakTypeTag](c: Context) = {
  import c.universe._

  val typeToMock = weakTypeOf[T]

  val primaryConstructorOpt = typeToMock.members.collectFirst {
    case method: MethodSymbolApi if method.isPrimaryConstructor => method
  }

  val constructorArgumentsTypes = primaryConstructorOpt.map { 
    constructor =>
    val constructorTypeContext = constructor.typeSignatureIn(typeToMock)
    val constructorArguments = constructor.paramss
    constructorArguments.map { symbols =>
      symbols.map(_.typeSignatureIn(constructorTypeContext))
    }
  }

  println(typeToMock)
  println(constructorArgumentsTypes)

  c.literalUnit
}

def foo[T] = macro macroImpl[T]

class Foo[U: ClassTag]

foo[Foo[Int]]

running it:

scala> foo[Foo[Int]]
Foo[Int]
Some(List(List(), List(scala.reflect.ClassTag[U]))

I need to get the ClassTag[Int] somehow to be able to generate the correct Tree later, any ideas?

Philipp
  • 967
  • 6
  • 16

1 Answers1

0

Try using dealias at each place you resolve a type. Scala keeps references in place, even when it knows to what they refer. dealias gets you a copy (?) of the type that has the references replaced.

You should also choose either blackbox or whitebox macros, rather than just scala.reflect.macros.Context.

This seems to work:

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

def macroImpl[T: c.WeakTypeTag](c: Context) = {
  import c.universe._

  val typeToMock = weakTypeOf[T].dealias

  val primaryConstructorOpt = typeToMock.members.collectFirst {
    case method: MethodSymbolApi if method.isPrimaryConstructor => method
  }

  val constructorArgumentsTypes = primaryConstructorOpt.map { constructor =>
    val constructorTypeContext = constructor.typeSignatureIn(typeToMock).dealias
    val constructorArguments = constructorTypeContext.paramLists
    constructorArguments.map { symbols =>
      symbols.map(_.typeSignatureIn(constructorTypeContext).dealias)
    }
  }

  println(typeToMock)
  println(constructorArgumentsTypes)

  q"()"
}

def foo[T]: Any = macro macroImpl[T]

class Foo[U: ClassTag]

foo[Foo[Int]]

Result

Foo[Int]
Some(List(List(), List(scala.reflect.ClassTag[Int])))
Jeremy
  • 533
  • 3
  • 10
  • many thanks. Still struggling to integrate that into the wider codebase, but it's given me a good lead what to investigate next – Philipp Feb 05 '17 at 15:14