Статический возвращаемый тип макросов Scala

Итак, у меня есть этот макрос:

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

class Foo
class Bar extends Foo { def launchMissiles = "launching" }

object FooExample {
  def foo: Foo = macro foo_impl
  def foo_impl(c: Context): c.Expr[Foo] =
    c.Expr[Foo](c.universe.reify(new Bar).tree)
}

Я сказал три раза, что хочу foo вернуть foo, и все же я могу сделать следующее (в 2.10.0-RC3):

scala> FooExample.foo
res0: Bar = [email protected]

scala> res0.launchMissiles
res1: String = launching

То же самое происходит, если я удаляю параметры типа на c.Expr. Если я действительно хочу удостовериться, что тот, кто называет foo, не может видеть, что они получают Bar, мне нужно добавить типу типа в самом дереве.

Это на самом деле довольно хорошо - это означает, например, что я могу указать макрос в какой-либо схеме и создать анонимный подкласс некоторого класса Vocabulary с помощью методов-членов, представляющих термины в словаре, и они будут доступны на возвращаемом объекте.

Я хотел бы точно понять, что я делаю, поэтому у меня есть несколько вопросов. Во-первых, каков тип возврата для метода foo на самом деле? Доступно ли это для (необязательной) документации? Он явно ограничивает тип возврата (например, я не могу изменить его на Int в этом случае), и если я полностью его удалю, то получаю следующее:

scala> FooExample.foo
<console>:8: error: type mismatch;
 found   : Bar
 required: Nothing
              FooExample.foo
                         ^

Но я могу изменить его на Any и все равно получить статически типизированный Bar при вызове foo.

Во-вторых, такое поведение указано где-то? Это похоже на довольно элементарный набор проблем, но я не смог найти четкое объяснение или обсуждение.

Ответ 1

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

Также временами поведение является непоследовательным, например. когда макрос попадает в середину вывода типа, его статическая подпись будет использоваться (т.е. Foo в вашем примере), а не тип фактического расширения. Это потому, что макрораспределение намеренно задерживается до тех пор, пока не будет сделан вывод типа (так что реализации макросов получат вид предполагаемых типов, а не тип vars). Это компромисс, а не самый лучший, поэтому мы планируем скоро его пересмотреть: https://issues.scala-lang.org/browse/SI-6755.

Другая проблема в этом отделе связана с неявными макросами. Когда тип возврата неявного макроса является общим и должен быть выведен из запрашиваемого типа неявного значения, происходят плохие вещи. Это делает невозможным использование макросов для генерации тегов типов: https://issues.scala-lang.org/browse/SI-5923.