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

monocle.macros.Lenses.scala Maven / Gradle / Ivy

package monocle.macros

import scala.reflect.macros.blackbox

class Lenses(prefix: String = "") extends scala.annotation.StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro LensesImpl.lensesAnnotationMacro
}

class PLenses(prefix: String = "") extends scala.annotation.StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro LensesImpl.plensesAnnotationMacro
}

@macrocompat.bundle
private[macros] class LensesImpl(val c: blackbox.Context) {

  def lensesAnnotationMacro(annottees: c.Expr[Any]*): c.Expr[Any] = annotationMacro(annottees, poly = false)

  def plensesAnnotationMacro(annottees: c.Expr[Any]*): c.Expr[Any] = annotationMacro(annottees, poly = true)

  def annotationMacro(annottees: Seq[c.Expr[Any]], poly: Boolean): c.Expr[Any] = {
    import c.universe._

    val LensesTpe = TypeName(if (poly) "PLenses" else "Lenses")
    val prefix = c.macroApplication match {
      case Apply(Select(Apply(Select(New(Ident(LensesTpe)), t), args), _), _) if t == termNames.CONSTRUCTOR => args match {
        case Literal(Constant(s: String)) :: Nil => s
        case _ => ""
      }
      case _ => ""
    }

    def monolenses(tpname: TypeName, params: List[ValDef]): List[Tree] = params.map { param =>
      val lensName = TermName(prefix + param.name.decodedName)
      q"""val $lensName =
        monocle.macros.internal.Macro.mkLens[$tpname, $tpname, ${param.tpt}, ${param.tpt}](${param.name.toString})"""
    }

    def lenses(tpname: TypeName, tparams: List[TypeDef], params: List[ValDef]): List[Tree] = {
      if (tparams.isEmpty) {
        monolenses(tpname, params)
      } else {
        params.map { param =>
          val lensName = TermName(prefix + param.name.decodedName)
          val q"x: $s" = q"x: $tpname[..${tparams.map(_.name)}]"
          val q"x: $a" = q"x: ${param.tpt}"
          q"""def $lensName[..$tparams] =
            monocle.macros.internal.Macro.mkLens[$s, $s, $a, $a](${param.name.toString})"""
        }
      }
    }

    def plenses(tpname: TypeName, tparams: List[TypeDef], params: List[ValDef]): List[Tree] = {
      if (tparams.isEmpty) {
        monolenses(tpname, params)
      } else {
        // number of fields in which each tparam is used
        val tparamsUsages: Map[TypeName, Int] = params.foldLeft(tparams.map { _.name -> 0 }.toMap){ (acc, param) =>
          val typeNames = param.collect{ case Ident(tn@TypeName(_)) => tn }.toSet
          typeNames.foldLeft(acc){ (map, key) => map.get(key).fold(map){ value => map.updated(key, value + 1) }}
        }

        val groupedTpnames: Map[Int, Set[TypeName]] =
          tparamsUsages.toList.groupBy(_._2).map{ case (n, tps) => (n, tps.map(_._1).toSet) }
        val phantomTpnames = groupedTpnames.getOrElse(0, Set.empty)
        val singleFieldTpnames = groupedTpnames.getOrElse(1, Set.empty)

        params.map { param =>
          val lensName = TermName(prefix + param.name.decodedName)
          val tpnames = param.collect{ case Ident(tn@TypeName(_)) => tn }.toSet
          val tpnamesToChange = tpnames.intersect(singleFieldTpnames) ++ phantomTpnames
          val tpnamesMap = tpnamesToChange.foldLeft((tparams.map(_.name).toSet ++ tpnames).map(x => (x, x)).toMap){ (acc, tpname) =>
            acc.updated(tpname, c.freshName(tpname))
          }
          val defParams = tparams ++ tparams.filter(x => tpnamesToChange.contains(x.name)).map{
            case TypeDef(mods, name, tps, rhs) => TypeDef(mods, tpnamesMap(name), tps, rhs)
          }.toSet

          object tptTransformer extends Transformer {
            override def transform(tree: Tree): Tree = tree match {
              case Ident(tn@TypeName(_)) => Ident(tpnamesMap(tn))
              case x => super.transform(x)
            }
          }

          val q"x: $s" = q"x: $tpname[..${tparams.map(_.name)}]"
          val q"x: $t" = q"x: $tpname[..${tparams.map(x => tpnamesMap(x.name))}]"
          val q"x: $a" = q"x: ${param.tpt}"
          val q"x: $b" = q"x: ${tptTransformer.transform(param.tpt)}"

          q"""def $lensName[..$defParams] =
               monocle.macros.internal.Macro.mkLens[$s, $t, $a, $b](${param.name.toString})"""
        }
      }
    }

    val lensDefs = if (poly) plenses _ else lenses _

    val result = annottees map (_.tree) match {
      case (classDef @ q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }")
        :: Nil if mods.hasFlag(Flag.CASE) =>
        val name = tpname.toTermName
        q"""
         $classDef
         object $name {
           ..${lensDefs(tpname, tparams, paramss.head)}
         }
         """
      case (classDef @ q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }")
        :: q"object $objName extends { ..$objEarlyDefs } with ..$objParents { $objSelf => ..$objDefs }"
        :: Nil if mods.hasFlag(Flag.CASE) =>
        q"""
         $classDef
         object $objName extends { ..$objEarlyDefs} with ..$objParents { $objSelf =>
           ..${lensDefs(tpname, tparams, paramss.head)}
           ..$objDefs
         }
         """
      case _ => c.abort(c.enclosingPosition, "Invalid annotation target: must be a case class")
    }

    c.Expr[Any](result)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy