2

travisbrownに触発されて、マクロを使用して「スマートコンストラクター」を作成しようとしています。

与えられた

package mypkg
sealed trait Hello[A]
case class Ohayo[A,B](a: (A,B)) extends Hello[A]

val smartConstructors = FreeMacros.liftConstructors[Hello]

マクロは、 のすべてのサブクラスを見つけHello、それらのコンストラクターを調べ、いくつかの要素を抽出して、「スマート コンストラクター」用にこのツリーを設定する必要があります。

q"""
  def $methodName[..$typeParams](...$paramLists): $baseType =
    $companionSymbol[..$typeArgs](...$argLists)
 """

私は得たいと思っていました:

val smartConstructors = new {
  def ohayo[A, B](a: (A, B)): Hello[A] = Ohayo[A, B](a)
}

代わりに次を取得します。

error: type mismatch;
 found   : (A(in class Ohayo), B(in class Ohayo))
 required: ((some other)A(in class Ohayo), (some other)B(in class Ohayo))
       val liftedConstructors = FreeMacros.liftConstructors[Hello]

一見すると、ツリーは問題ないように見えます。

scala> q" new { ..$wellTyped }"
res1: u.Tree =
{
  final class $anon extends scala.AnyRef {
    def <init>() = {
      super.<init>();
      ()
    };
    def ohayo[A, B](a: (A, B)): net.arya.constructors.Hello[A] = Ohayo[A, B](a)
  };
  new $anon()
}

しかし、目に見えないほどではないと思います。単純に typeParams をinfo.typeParams.map(p => TypeName(p.name.toString))で更新しようとすると、準引用を行うときに「型パラメーターとして A をスプライスできません」というメッセージが表示されます。

どこが間違っていますか?ご覧いただきありがとうございます。

-アリア

import scala.language.experimental.macros
import scala.reflect.api.Universe
import scala.reflect.macros.whitebox

class FreeMacros(val c: whitebox.Context) {
  import c.universe._
  import FreeMacros._


  def liftedImpl[F[_]](implicit t: c.WeakTypeTag[F[_]]): Tree = {
    val atc = t.tpe

    val childSymbols: Set[ClassSymbol] = subCaseClassSymbols(c.universe)(atc.typeSymbol.asClass)
    val wellTyped = childSymbols.map(ctorsForSymbol(c.universe)(atc)).unzip


    q"new { ..${wellTyped} }"
  }
}


object FreeMacros {
  def liftConstructors[F[_]]: Any = macro FreeMacros.liftedImpl[F]


  def smartName(name: String): String = (
    name.toList match {
      case h :: t => h.toLower :: t
      case Nil => Nil
    }
  ).mkString


  def subCaseClassSymbols(u: Universe)(root: u.ClassSymbol): Set[u.ClassSymbol] = {
    val subclasses = root.knownDirectSubclasses
    val cast = subclasses.map(_.asInstanceOf[u.ClassSymbol])
    val partitioned = mapped.partition(_.isCaseClass)
    partitioned match {
      case (caseClasses, regularClasses) => caseClasses ++ regularClasses.flatMap(r => subCaseClassSymbols(u)(r))
    }
  }


  def ctorsForSymbol(u: Universe)(atc: u.Type)(caseClass: u.ClassSymbol): (u.DefDef, u.DefDef) = {
    import u._
    import internal._

// these didn't help
//    def clearTypeSymbol(s: Symbol): TypeSymbol = internal.newTypeSymbol(NoSymbol, s.name.toTypeName, s.pos, if(s.isImplicit)Flag.IMPLICIT else NoFlags)
//    def clearTypeSymbol2(s: Symbol): TypeSymbol = internal.newTypeSymbol(NoSymbol, s.name.toTypeName, NoPosition, if(s.isImplicit)Flag.IMPLICIT else NoFlags)
//    def clearTypeDef(d: TypeDef): TypeDef = internal.typeDef(clearTypeSymbol(d.symbol))

    val companionSymbol: Symbol = caseClass.companion
    val info: Type = caseClass.info

    val primaryCtor: Symbol = caseClass.primaryConstructor
    val method = primaryCtor.asMethod
    val typeParams = info.typeParams.map(internal.typeDef(_))
//    val typeParams = info.typeParams.map(s => typeDef(newTypeSymbol(NoSymbol, s.name.toTypeName, NoPosition, NoFlags)))
//    val typeParams = info.typeParams.map(s => internal.typeDef(clearTypeSymbol2(s)))
    val typeArgs = info.typeParams.map(_.name)
    val paramLists = method.paramLists.map(_.map(internal.valDef(_)))
    val argLists = method.paramLists.map(_.map(_.asTerm.name))
    val baseType = info.baseType(atc.typeSymbol)
    val List(returnType) = baseType.typeArgs

    val methodName = TermName(smartName(caseClass.name.toString))

    val wellTyped =
      q"""
          def $methodName[..$typeParams](...$paramLists): $baseType =
            $companionSymbol[..$typeArgs](...$argLists)
        """


    wellTyped
  }
}

PS私はこの記事に従ってtoolbox.untypecheck/typecheckを試してきましたが、機能する組み合わせは見つかりませんでした。

4

1 に答える 1