All Downloads are FREE. Search and download functionalities are using the official Maven repository.

caseclass.macros.CC.scala Maven / Gradle / Ivy

The newest version!
package caseclass.macros

import scala.reflect.macros._


/** Case Class (taken from Type)
  *
  * @param name
  * @param fields
  * @param parents
  * @param body
  * @param methods
  */
private[macros] case class CC(
  name: String,
  sym: Universe#TypeSymbol,
  fields: Seq[Map[String, (Universe#Tree, Universe#Tree)]],
  parents: Seq[String],
  body: Seq[Universe#Tree],
  methods: Seq[Universe#Tree] = Seq.empty) {

  def withFieldsAdded(c: whitebox.Context)(_fields: Seq[Map[String, (Universe#Tree, Universe#Tree)]]): CC = {
    copy(fields = fields.zip(_fields) map { case (lList, rList) =>
      rList.foldLeft(lList) {
        case (agg, (n, (t, d))) =>
          agg.get(n) match {
            case Some((tt, dd)) => if (t == tt && d == dd) { c.error(c.enclosingPosition, ""); null.asInstanceOf[Nothing] } else agg
            case None => agg updated (n, t -> d)
          }
      }
    })
  }

  def withFieldsRemoved(c: whitebox.Context)(names: Set[String])(implicit log: Log): CC = {
    copy(fields = fields map { args =>
      val fieldsToRemove = args.keySet intersect names
      if (fieldsToRemove != names) {
        val absentFields = names diff fieldsToRemove
        log.err(s"Can not reduce type $name. [${absentFields mkString ", "}] fields were not found")
      }

      args -- fieldsToRemove
    })
  }

  def withMethodsAdded(c: whitebox.Context)(tree: Universe#Tree): CC = {
    copy(methods = methods :+ tree)
  }

  def withFieldsRenamed(c: whitebox.Context)(mapping: Map[String, String])(implicit log: Log): CC = {
    copy(fields = fields map { args =>
      val names = mapping.keySet
      val fieldsToRemove = args.keySet intersect names
      if (fieldsToRemove != names) {
        val absentFields = names diff fieldsToRemove
        log.err(s"Can not modify type $name. [${absentFields mkString ", "}] fields were not found")
      }

      args map {
        case (k, v) =>
          val kk = mapping.getOrElse(k, k)
          (kk, v)
      }
    })
  }

  def gen(c: whitebox.Context): c.Tree = {
    import c.universe._

    val lClassName  = TypeName(name)
    val lBody       = body.asInstanceOf[Seq[Tree]]
    val lFields     = fields map { _.map { case (n, (t, d)) => ValDef(Modifiers(), TermName(n), t.asInstanceOf[Tree], d.asInstanceOf[Tree]) }.toSeq }
    val lParents    = parents map { _.asInstanceOf[Tree]}
    val lMethods    = methods map { _.asInstanceOf[Tree]}

    q"""case class $lClassName ( ...$lFields ) extends ..$lParents {
         ..$lBody
         ..$lMethods
       }
     """
  }
}

private[macros] object CC {

  /** Creates a case class representation from a Type.
    * Used to load data from classes refered in annotation
    *
    * @param c
    * @param cd
    * @return
    */
  def fromType(c: whitebox.Context)(cd: c.universe.Type): CC = {
    import c.universe._

    val ccFields = cd.decls.collectFirst {
      case m: MethodSymbol if m.isPrimaryConstructor => m
    }.get.paramLists

    val fields: Seq[Map[String, (Universe#Tree, Universe#Tree)]] = ccFields map {
      _.map { s =>
        // TODO: bring default values back
        (s.name.decodedName.toString, q"${s.typeSignature}" -> c.universe.EmptyTree)
      }.toMap
    }

    CC(
      cd.typeSymbol.asClass.name.decodedName.toString,
      cd.typeSymbol.asClass,
      fields,
      Seq.empty,
      Seq.empty)
  }

  /** Creates a case class representation from a Tree
    *
    * @param c
    * @param cd
    * @param log
    * @return
    */
  def fromTree(c: whitebox.Context)(cd: c.universe.ClassDef)(implicit log: Log): CC = {
    import c.universe._

    cd match {
      case q"case class $className(...$fields) extends ..$parents { ..$body }" =>
        val name = className.asInstanceOf[TypeName]

        CC(
          name.decodedName.toString,
          c.internal.newFreeType(name.decodedName.toString),
          fields map {
            case argList: List[_] =>
              argList.map {
                case q"$_ val $n: $t = $d" =>
                  (n.toString, t.asInstanceOf[Universe#Tree] -> d.asInstanceOf[Universe#Tree])
              }.toMap
          },
          parents map { _.toString },
          body map { _.asInstanceOf[Universe#Tree] })
      case _ =>
        log.fail(s"${cd.name} should be a case class")
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy