2015-03-30 3 views
1

Я использую следующий Scala макрос (сильно вдохновленный код из this SO question), чтобы получить список всех объектов, содержащихся в данном пакете, наследующих определенную черту:символ класса Instantiate с помощью макроса

object Macros { 
    def allObjects[T <: AnyRef](packageName: String): List[Any] = macro allObjectsImpl[T] 

    def allObjectsImpl[T <: AnyRef: c.WeakTypeTag](c: Context)(packageName: c.Expr[String]) = { 
    import c.universe._ 

    val baseTraitSymbol = c.weakTypeOf[T].typeSymbol 
    val pkg = packageName.tree match { 
     case Literal(Constant(name: String)) => c.mirror.staticPackage(name) 
    } 

    val types = pkg.typeSignature.members.collect { 
     case moduleSymbol: ModuleSymbol if moduleSymbol.moduleClass.asClass.baseClasses contains baseTraitSymbol => Ident(moduleSymbol) 
    }.toList 

    val listApply = Select(reify(List).tree, TermName("apply")) 
    c.Expr[List[T]](Apply(listApply, types)) 
    } 
} 

Что хорошо работает.

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

АСТ при создании экземпляра объекта выглядит следующим образом:

scala> import scala.reflect.runtime.{universe => u} 
import scala.reflect.runtime.{universe=>u} 

scala> u showRaw (u reify {new Object}) 
res42: String = Expr(Apply(Select(New(Ident(java.lang.Object)), termNames.CONSTRUCTOR), List())) 

Так я думал, меняя мой код, это будет работать:

object Macros { 
    def allInstances[T <: AnyRef](packageName: String): List[Any] = macro allInstancesImpl[T] 

    def allInstancesImpl[T <: AnyRef: c.WeakTypeTag](c: Context)(packageName: c.Expr[String]) = { 
    import c.universe._ 

    val baseTraitSymbol = c.weakTypeOf[T].typeSymbol 
    val pkg = packageName.tree match { 
     case Literal(Constant(name: String)) => c.mirror.staticPackage(name) 
    } 

    def isConcreteChildClass(child: ClassSymbol, base: Symbol) = { 
     !child.isAbstract && (child.baseClasses contains base) 
    } 

    val types = pkg.typeSignature.members.collect { 
     case classSymbol: ClassSymbol if isConcreteChildClass(classSymbol, baseTraitSymbol) => { 
     Apply(Select(New(Ident(classSymbol.primaryConstructor)), termNames.CONSTRUCTOR), List()) 
     } 
    }.toList 

    val listApply = Select(reify(List).tree, TermName("apply")) 
    c.Expr[List[T]](Apply(listApply, types)) 
    } 
} 

Однако, когда я пытаюсь использовать обновленный код макроса в тестовом пакете, я получаю следующую ошибку:

scala> Macros.allInstances[AnyRef]("test") 
<console>:9: error: class type required but()test.TestClass found 
       Macros.allInstances[AnyRef]("test") 

Из того, что я нахожу eing, похоже, что макрос фактически возвращает сам конструктор вместо того, чтобы возвращать экземпляр, который должен быть построен конструктором, но я не могу понять, что мне не хватает.

ответ

2

Проблема заключается в этой строке (переформатирован для ясности):

Apply(
    Select(New(Ident(classSymbol.primaryConstructor)), termNames.CONSTRUCTOR), 
    List() 
) 

Вы существенно выбрать конструктор дважды. Вы можете просто бросить primaryConstructor:

Apply(
    Select(New(Ident(classSymbol)), termNames.CONSTRUCTOR), 
    List() 
) 

Использование ApplyConstructor также будет работать:

ApplyConstructor(Ident(classSymbol), Nil) 

Или вы можете просто пойти с quasiquotes:

q"new ${Ident(classSymbol)}()" 

Решение quasiquote является наиболее перспективное ,

Смежные вопросы