2013-08-28 69 views
2

我想使用宏來生成代碼實例看起來像這樣的對象:斯卡拉宏:轉換語境#TypeTag到JavaUniverse#TypeTag

import scala.reflect.runtime.universe._ 
case class Example[T: TypeTag] { 
    val tpe = implicitly[TypeTag[T]].tpe 
} 

顯然,這轉化爲類似如下:

import scala.reflect.runtime.universe._ 
case class Example[T](implicit ev: TypeTag[T]) { 
    val tpe = ev.tpe 
} 

那麼如果這個類是在常規代碼Scala編譯器實例化自動提供TypeTag實例。

不過,我想生成代碼實例化此類不同T s,其中混凝土T小號依賴於用戶輸入的幾個實例,像

sealed trait Test 
case class SubTest1 extends Test 
case class SubTest2 extends Test 

val examples = generate[Test] 
// I want this ^^^^^^^^^^^^^^ to expand into this: 
val examples = Seq(Example[SubTest1], Example[SubTest2]) 

know如何得到一個密封的子類特徵,所以我可以在宏代碼中訪問c.WeakTypeTag[SubTest1]c.WeakTypeTag[SubTest2]。但我不知道如何將它們變成TypeTag s預期的方法Example.apply。我想用in()方法,似乎讓宇宙之間轉移TypeTag S的,但它需要的目的地鏡子,我不知道如何在編譯時從宏內部得到運行反射。

這是我迄今(我增加了一些註釋和額外的報表也更清晰)代碼:你正在尋找這個

object ExampleMacro { 
    def collect[T] = macro collect_impl 

    def collect_impl[T: c.WeakTypeTag](c: Context): c.Expr[Seq[Example[_]]] = { 
    import c.universe._ 

    val symbol = weakTypeOf[T].typeSymbol 

    if (symbol.isClass && symbol.asClass.isTrait && symbol.asClass.isSealed) { 
     val children = symbol.asClass.knownDirectSubclasses.toList 

     if (!children.forall(c => c.isClass && c.asClass.isCaseClass)) { 
     c.abort(c.enclosingPosition, "All children of sealed trait must be case classes") 
     } 

     val args: List[c.Tree] = children.map { ch: Symbol => 
      val childTpe = c.WeakTypeTag(ch.typeSignature) // or c.TypeTag(ch.typeSignature) 

      val runtimeChildTpe: c.Expr[scala.reflect.runtime.universe.TypeTag[_]] = ??? // What should go here? 

      Apply(Select(reify(Example).tree, newTermName("apply")), runtimeChildTpe.tree) 
     } 

     Apply(Select(reify(Seq).tree, newTermName("apply")), args) 
    } else { 
     c.abort(c.enclosingPosition, "Can only construct sequence from sealed trait") 
    } 
    } 
} 

回答

2

c.reifyType(treeBuild.mkRuntimeUniverseRef, EmptyTree, childTpe) 

以上代碼將假設import c.universe._

這將創建一個Tree,最終將評估您所需的運行時scala.reflect.runtime.universe.TypeTag[_]

關於第二個想法,我覺得可能不會在所有需要手動生成這棵樹的。從宏返回的樹會經歷更多的類型檢查,這意味着編譯器可能能夠爲您填充隱式的TypeTag。不過,它需要進行測試。嘗試使用這樣的:

TypeApply(Select(reify(Example).tree, newTermName("apply")), TypeTree().setType(childTpe)) 
+0

哇,太好了。我想我應該先嚐試一下,但我認爲編譯器不是那麼聰明。顯然,我錯了)非常感謝。 –

2

您不必擔心提供運行時類型標籤這裏,編譯器會幫您找到它(因爲我看到另一個答案ghik筆記)。關鍵是要在子類的類型標誌使用toType,而不是typeSignature

object ExampleMacro { 
    def collect[T] = macro collect_impl[T] 

    def collect_impl[T: c.WeakTypeTag](c: Context): c.Expr[Seq[Example[_]]] = { 
    import c.universe._ 

    val symbol = weakTypeOf[T].typeSymbol 

    if (symbol.isClass && symbol.asClass.isTrait && symbol.asClass.isSealed) { 
     val children = symbol.asClass.knownDirectSubclasses.toList 

     if (!children.forall(c => c.isClass && c.asClass.isCaseClass)) c.abort(
     c.enclosingPosition, 
     "All children of sealed trait must be case classes" 
    ) 

     val args: List[c.Tree] = children.collect { 
     case child: TypeSymbol => q"Example[${child.toType}]" 
     } 

     c.Expr[Seq[Example[_]]](
     Apply(Select(reify(Seq).tree, newTermName("apply")), args) 
    ) // or just c.Expr[Seq[Example[_]]](q"Seq(..$args)") 
    } else c.abort(
     c.enclosingPosition, 
     "Can only construct sequence from sealed trait" 
    ) 
    } 
} 

我在這裏用quasiquotes爲清楚起見,並且因爲他們是now easily available in 2.10 projects,但如果你不希望他們它會可以直接使用此代碼來使用手動樹結構。

+0

非常感謝。似乎我試圖超越自我。可悲的是,IDEA不支持quasiquotes,但(我想我必須堅持用手工樹結構,它是不是很辛苦畢竟這種情況下,說實話,這兩個答案都是正確的和有益的,所以我接受了另外一個,因爲它是第一。 –