Зависимый тип, кажется, «не работает» при генерации макросом Scala - PullRequest
3 голосов
/ 24 апреля 2019

Извинения за титул handwavey.Я не совсем уверен, как сформулировать вопрос лаконично, поскольку я никогда раньше не сталкивался с чем-то подобным.

Справочная информация:

У меня есть следующая черта, где тип U предназначен для хранения Бесформенной расширяемой записи тип:

trait Flattened[T] {
  type U <: shapeless.HList
  def fields: U
}

Я использую макрос черного ящика (по причинам, выходящим за рамки этого вопроса), чтобы создать новые экземпляры черты:

object flatten {

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

  def apply[T](struct: T): Flattened[T] =
    macro impl[T]

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

    // AST representing a Shapeless extensible record (actual
    // implementation uses values in `struct` to construct this
    // AST, but I've simplified it for this example).
    val fields: Tree = q"""("a" ->> 1) :: ("b" ->> "two") :: ("c" ->> true) :: HNil"""

    // Result type of the above AST
    val tpe: TypeTree = TypeTree(c.typecheck(q"$fields").tpe)

    q"""
    new Flattened[${weakTypeOf[T]}] {
      import shapeless._
      import syntax.singleton._
      import record._

      type U = $tpe
      val fields = $fields
    }
    """
  }
}

Проблема:

Проблема в том, что когда я использую этот макрос для создания нового экземпляра Flattened, тип fields больше не является расширяемой записью:

import shapeless._
import syntax.singleton._
import record._

val s = "some value... it doesn't matter for this example, since it isn't used. I'm just putting something here so the example compiles and runs in a REPL."
val t = flatten(s)

val fields = t.fields
// fields: t.U = 1 :: "two" :: true :: HNil

fields("a")  // compile error!

// The compile error is:
//     cmd5.sc:1: No field String("a") in record ammonite.$sess.cmd4.t.U
//     val res5 = fields("a")
//                      ^
//     Compilation Failed

Примечание:

Как ни странно, если я делаю вручную то, что делает макрос, он работает:

// I can't actually instantiate a new `Flattened[_]` manually, since
// defining the type `U` would be convoluted (not even sure it can be
// done), so an object with the same field is the next best thing.
object Foo {
  import shapeless._
  import syntax.singleton._
  import record._

  val fields = ("a" ->> 1) :: ("b" ->> "two") :: ("c" ->> true) :: HNil
}

val a = Foo.fields("a")
// a: Int = 1

val b = Foo.fields("b")
// b: String = "two"

val c = Foo.fields("c")
// c: Boolean = true

Почему это расхождение и какможно ли заставить макро версию вести себя так же, как и ручная версия?

1 Ответ

3 голосов
/ 24 апреля 2019

С твоим макросом все в порядке, наверное. Это тип подписи:

def apply[T](struct: T): Flattened[T] = macro impl[T]
def impl[T : c.WeakTypeTag](c: Context)(struct: c.Tree): c.Tree

Вы используете макрос черного ящика, и, согласно документации , макросы черного ящика соответствуют своим сигнатурам. То есть, хотя impl создает Flattened[T] { type U = ... }, тот факт, что это макрос черного ящика, означает, что scalac всегда оборачивает его в (_: Flattened[T]), забывая определение U в уточнении. Сделайте это макрокомандой whitebox:

// import scala.reflect.macros.blackbox.context // NO!
import scala.reflect.macros.whitebox.context
def impl[T: c.WeakTypeTag](c: Context)(struct: c.Tree): c.Tree
...