Получение структурного типа с помощью методов анонимного класса из макроса

Предположим, мы хотим написать макрос, который определяет анонимный класс с некоторыми членами или методами типа, а затем создает экземпляр этого класса, который статически вводится как структурный тип с этими методами и т.д. Это возможно с помощью макросистемы в 2.10.0, а часть элемента элемента очень проста:

object MacroExample extends ReflectionUtils {
  import scala.language.experimental.macros
  import scala.reflect.macros.Context

  def foo(name: String): Any = macro foo_impl
  def foo_impl(c: Context)(name: c.Expr[String]) = {
    import c.universe._

    val Literal(Constant(lit: String)) = name.tree
    val anon = newTypeName(c.fresh)

    c.Expr(Block(
      ClassDef(
        Modifiers(Flag.FINAL), anon, Nil, Template(
          Nil, emptyValDef, List(
            constructor(c.universe),
            TypeDef(Modifiers(), newTypeName(lit), Nil, TypeTree(typeOf[Int]))
          )
        )
      ),
      Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
    ))
  }
}

(Где ReflectionUtils является признаком удобства, который предоставляет мой метод constructor.)

Этот макрос позволяет указать имя элемента типа анонимного класса как строковый литерал:

scala> MacroExample.foo("T")
res0: AnyRef{type T = Int} = [email protected]

Обратите внимание, что он правильно набран. Мы можем подтвердить, что все работает как ожидалось:

scala> implicitly[res0.T =:= Int]
res1: =:=[res0.T,Int] = <function1>

Теперь предположим, что мы пытаемся сделать то же самое с помощью метода:

def bar(name: String): Any = macro bar_impl
def bar_impl(c: Context)(name: c.Expr[String]) = {
  import c.universe._

  val Literal(Constant(lit: String)) = name.tree
  val anon = newTypeName(c.fresh)

  c.Expr(Block(
    ClassDef(
      Modifiers(Flag.FINAL), anon, Nil, Template(
        Nil, emptyValDef, List(
          constructor(c.universe),
          DefDef(
            Modifiers(), newTermName(lit), Nil, Nil, TypeTree(),
            c.literal(42).tree
          )
        )
      )
    ),
    Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
  ))
}

Но когда мы пробуем, мы не получаем структурного типа:

scala> MacroExample.bar("test")
res1: AnyRef = [email protected]

Но если мы добавим дополнительный анонимный класс:

def baz(name: String): Any = macro baz_impl
def baz_impl(c: Context)(name: c.Expr[String]) = {
  import c.universe._

  val Literal(Constant(lit: String)) = name.tree
  val anon = newTypeName(c.fresh)
  val wrapper = newTypeName(c.fresh)

  c.Expr(Block(
    ClassDef(
      Modifiers(), anon, Nil, Template(
        Nil, emptyValDef, List(
          constructor(c.universe),
          DefDef(
            Modifiers(), newTermName(lit), Nil, Nil, TypeTree(),
            c.literal(42).tree
          )
        )
      )
    ),
    ClassDef(
      Modifiers(Flag.FINAL), wrapper, Nil,
      Template(Ident(anon) :: Nil, emptyValDef, constructor(c.universe) :: Nil)
    ),
    Apply(Select(New(Ident(wrapper)), nme.CONSTRUCTOR), Nil)
  ))
}

Работает:

scala> MacroExample.baz("test")
res0: AnyRef{def test: Int} = [email protected]

scala> res0.test
res1: Int = 42

Это очень удобно: например, этот, но я не понимаю, почему он работает, и версия члена типа работает, но не bar. Я знаю, что это не может быть определено поведение, но имеет ли это смысл? Есть ли более чистый способ получить структурный тип (с помощью методов на нем) из макроса?

Ответ 1

Этот вопрос ответил в двух экземплярах Travis здесь. В трекере есть ссылки на эту проблему и обсуждение в Eugene (в комментариях и списке рассылки).

В знаменитом разделе "Skylla и Charybdis" контролера типов наш герой решает, что избежать темной анонимности и увидеть свет как член структурного типа.

Есть несколько способов обмануть контролер типа (который не влечет за собой улов Odysseus обнимать овцу). Самое простое - вставить фиктивный оператор, чтобы блок не выглядел как анонимный класс, за которым следует его создание.

Если typer отмечает, что вы являетесь публичным термином, на который не ссылается внешний вид, он сделает вас закрытым.

object Mac {
  import scala.language.experimental.macros
  import scala.reflect.macros.Context

  /* Make an instance of a structural type with the named member. */
  def bar(name: String): Any = macro bar_impl

  def bar_impl(c: Context)(name: c.Expr[String]) = {
    import c.universe._
    val anon = TypeName(c.freshName)
    // next week, val q"${s: String}" = name.tree
    val Literal(Constant(s: String)) = name.tree
    val A    = TermName(s)
    val dmmy = TermName(c.freshName)
    val tree = q"""
      class $anon {
        def $A(i: Int): Int = 2 * i
      }
      val $dmmy = 0
      new $anon
    """
      // other ploys
      //(new $anon).asInstanceOf[{ def $A(i: Int): Int }]
      // reference the member
      //val res = new $anon
      //val $dmmy = res.$A _
      //res
      // the canonical ploy
      //new $anon { }  // braces required
    c.Expr(tree)
  }
}