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

Предположим, мы хотим написать макрос, который определяет анонимный класс с некоторыми членами типа или методами, а затем создает экземпляр этого класса, статически типизированный как структурный тип с этими методами и т. Д. Это возможно с помощью макросистемы в 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} = $1$$1@7da533f6

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

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 = $1$$1@da12492

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

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} = $2$$1@6663f834

scala> res0.test
res1: Int = 42

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


person Travis Brown    schedule 17.01.2013    source источник
comment
Интересно, что если вы напишете тот же код в REPL, а не сгенерируете его в макросе, он будет работать: scala ›{final class anon {def x = 2}; новый анон} res1: AnyRef {def x: Int} = анон $ 1 @ 5295c398. Спасибо за отчет! Я посмотрю на этой неделе.   -  person Eugene Burmako    schedule 17.01.2013
comment
Обратите внимание, что я зарегистрировал проблему здесь.   -  person Travis Brown    schedule 06.06.2013
comment
Нет, не блокировщик, спасибо - дополнительный трюк с анонимным классом работал у меня всякий раз, когда мне это было нужно. Я только что заметил несколько положительных отзывов по этому вопросу, и мне было любопытно узнать о статусе.   -  person Travis Brown    schedule 06.06.2013
comment
Евгений, для потомков, если это уже где-то указано как ошибка, вы можете связать его?   -  person Tomer Gabel    schedule 21.08.2013
comment
@TomerGabel: Это SI-6992 (ссылка на него приведена в комментарии выше).   -  person Travis Brown    schedule 21.08.2013
comment
часть типа члена очень проста - ›wTF? ты супер трещина! конечно в хорошем смысле :)   -  person ZaoTaoBao    schedule 04.09.2013
comment
Для справки: SI-6992 был исправлен в прошлом месяце (спасибо, Евгений!) И только что был закрыт.   -  person Travis Brown    schedule 18.01.2014


Ответы (1)


На этот вопрос дважды отвечает Трэвис здесь. Ссылки на проблему есть в трекере и на обсуждение Евгения (в комментариях и в списке рассылки).

В знаменитом разделе шрифтовика «Скилла и Харибда» наш герой решает, что должно избежать темной анонимности и увидеть свет как член структурного типа.

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

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

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)
  }
}
person som-snytt    schedule 17.11.2013
comment
Я просто отмечу, что на самом деле я предлагаю первый обходной путь в самом этом вопросе (здесь он просто не цитируется). Я счастлив, что этот ответ завершает вопрос - мне кажется, я смутно ждал, пока ошибка будет исправлена. - person Travis Brown; 17.11.2013
comment
@TravisBrown Могу поспорить, у вас есть и другие инструменты в вашем поясе для летучей мыши. Спасибо за внимание: я предположил, что ваш AST - это старый трюк с дополнительными скобками, но теперь я вижу, что ClassDef / Apply не заключены в свой собственный блок, как это происходит с new $anon {}. Еще один вывод: в будущем я не буду использовать anon в макросах с квазицитатами или подобными специальными именами. - person som-snytt; 18.11.2013
comment
q Синтаксис $ {s: String} немного задерживается, особенно если вы используете рай. Так что больше похоже на следующий месяц, чем на следующую неделю. - person Denys Shabalin; 18.11.2013
comment
@ som-snytt @ denys-shabalin, есть ли особые уловки для структурных типов а-ля shapeless.Generic? Несмотря на мои лучшие намерения заставить Aux возвращаемые типы шаблонов, компилятор отказывается видеть структурный тип. - person flavian; 30.03.2017