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

japgolly.microlibs.adt_macros.AdtMacros.scala Maven / Gradle / Ivy

The newest version!
package japgolly.microlibs.adt_macros

import japgolly.microlibs.adt_macros.AdtMacros.{AdtIso, AdtIsoSet}
import japgolly.microlibs.compiletime.MacroUtils
import japgolly.microlibs.nonempty.{NonEmptySet, NonEmptyVector}
import scala.reflect.macros.blackbox

object AdtMacros {

  type AdtIso[Adt, T] = (Adt => T, T => Adt, NonEmptyVector[Adt], NonEmptyVector[T])
  def adtIso[Adt, T](f: Adt => T): AdtIso[Adt, T] = macro AdtMacros.quietAdtIso[Adt, T]
  def _adtIso[Adt, T](f: Adt => T): AdtIso[Adt, T] = macro AdtMacros.debugAdtIso[Adt, T]

  type AdtIsoSet[Adt, T] = (Adt => T, T => Adt, NonEmptySet[Adt], NonEmptySet[T])
  def adtIsoSet[Adt, T](f: Adt => T): AdtIsoSet[Adt, T] = macro AdtMacros.quietAdtIsoSet[Adt, T]
  def _adtIsoSet[Adt, T](f: Adt => T): AdtIsoSet[Adt, T] = macro AdtMacros.debugAdtIsoSet[Adt, T]

  def adtValues[T]: NonEmptyVector[T] = macro AdtMacros.quietAdtValues[T]
  def _adtValues[T]: NonEmptyVector[T] = macro AdtMacros.debugAdtValues[T]

  /** Because sometimes order matters. */
  def adtValuesManually[T](vs: T*): NonEmptyVector[T] = macro AdtMacros.quietAdtValuesManual[T]
  def _adtValuesManually[T](vs: T*): NonEmptyVector[T] = macro AdtMacros.debugAdtValuesManual[T]

  def valuesForAdt[T, V](f: T => V): NonEmptyVector[V] = macro AdtMacros.quietValuesForAdt[T, V]
  def _valuesForAdt[T, V](f: T => V): NonEmptyVector[V] = macro AdtMacros.debugValuesForAdt[T, V]

  def valuesForAdtF[T, V](f: T => V): (NonEmptyVector[V], T => V) = macro AdtMacros.quietValuesForAdtF[T, V]
  def _valuesForAdtF[T, V](f: T => V): (NonEmptyVector[V], T => V) = macro AdtMacros.debugValuesForAdtF[T, V]
}

class AdtMacros(val c: blackbox.Context) extends MacroUtils with JapgollyAccess {
  import c.universe._

  def quietAdtIso[Adt: c.WeakTypeTag, T: c.WeakTypeTag](f: c.Expr[Adt => T]): c.Expr[AdtIso[Adt, T]] = implAdtIso(false)(f)
  def debugAdtIso[Adt: c.WeakTypeTag, T: c.WeakTypeTag](f: c.Expr[Adt => T]): c.Expr[AdtIso[Adt, T]] = implAdtIso(true)(f)
  def implAdtIso[Adt: c.WeakTypeTag, T: c.WeakTypeTag](debug: Boolean)(f: c.Expr[Adt => T]): c.Expr[AdtIso[Adt, T]] = {
    val Adt       = weakTypeOf[Adt]
    val T         = weakTypeOf[T]
    val fromFn    = readMacroArg_tToTree(f)
    val adtTypes  = findConcreteTypesNE(Adt, LeavesOnly)
    var toCases   = Vector.empty[CaseDef]
    var toValues  = Set.empty[Any]
    var adtValues = Vector.empty[Tree]

    for (adtClass <- adtTypes) {
      val adt = determineAdtType(Adt, adtClass)

      ensureConcrete(adt)
      if (primaryConstructorParams(adt).nonEmpty)
        fail(s"$adt requires constructor params.")

      val matchingCases = fromFn.filter(adt <:< _._1.fold(_.tpe, identity))
      if (matchingCases.size != 1)
        fail(s"Found ${matchingCases.size} cases for $adt.")

      val fromCase = matchingCases.head
      val toValue = fromCase._2 match {
        case Literal(Constant(v)) => v
        case x => fail(s"Expected a constant literal, got: ${showRaw(x)} ")
      }
      if (toValues contains toValue)
        fail(s"Non-unique value encountered: $toValue")
      toValues += toValue

      val adtObj = toSelectFQN(adtClass)
      adtValues :+= adtObj
      toCases :+= cq"${fromCase._2} => $adtObj"
    }

    val impl = q"""
      val from: $Adt => $T = $f
      val to: $T => $Adt = {case ..$toCases}
      val adts = $SelectNonEmptyVector.varargs[$Adt](..$adtValues)
      val tos = $SelectNonEmptyVector.varargs[$T](..${fromFn.map(_._2)})
      assert(adts.length == tos.length)
      (from,to,adts,tos)
    """

    if (debug) println("\n" + showCode(impl) + "\n")
    c.Expr[AdtIso[Adt, T]](impl)
  }

  def quietAdtIsoSet[Adt: c.WeakTypeTag, T: c.WeakTypeTag](f: c.Expr[Adt => T]): c.Expr[AdtIsoSet[Adt, T]] = implAdtIsoSet(false)(f)
  def debugAdtIsoSet[Adt: c.WeakTypeTag, T: c.WeakTypeTag](f: c.Expr[Adt => T]): c.Expr[AdtIsoSet[Adt, T]] = implAdtIsoSet(true)(f)
  def implAdtIsoSet[Adt: c.WeakTypeTag, T: c.WeakTypeTag](debug: Boolean)(f: c.Expr[Adt => T]): c.Expr[AdtIsoSet[Adt, T]] = {
    val Adt    = weakTypeOf[Adt]
    val T      = weakTypeOf[T]
    val adtIso = implAdtIso[Adt, T](false)(f)
    val impl = q"""
      val (from,to,adtVec,toVec) = $adtIso
      val adtSet = adtVec.toNES[$Adt]
      val toSet = toVec.toNES[$T]
      assert(adtSet.forall(a => to(from(a)) == a))
      (from,to,adtSet,toSet)
    """
    if (debug) println("\n" + showCode(impl) + "\n")
    c.Expr[AdtIsoSet[Adt, T]](impl)
  }

  def quietAdtValuesManual[T: c.WeakTypeTag](vs: c.Expr[T]*): c.Expr[NonEmptyVector[T]] = implAdtValuesManual(false)(vs)
  def debugAdtValuesManual[T: c.WeakTypeTag](vs: c.Expr[T]*): c.Expr[NonEmptyVector[T]] = implAdtValuesManual(true )(vs)
  def implAdtValuesManual[T: c.WeakTypeTag](debug: Boolean)(vs: Seq[c.Expr[T]]): c.Expr[NonEmptyVector[T]] = {
    val T = weakTypeOf[T]

    val all = findConcreteTypesNE(T, LeavesOnly)
    if (all.isEmpty)
      fail(s"At least one concrete subtype of $T required.")
    for (a <- all)
      if (a.typeParams.nonEmpty)
        fail(s"Polymorphic case not supported: ${a.toType}")

    var unseen = all.map(_.toType)
    var seen = Set.empty[String]

    def attempt(tree: Tree): Unit = {
      val t = tree.tpe.dealias
      if (!(t <:< T))
        fail(s"$t is not a subclass of $T")
      val expr = tree.toString()
      if (seen.contains(expr))
        fail(s"Duplicate value: $expr")
      unseen = unseen.filterNot(_ <:< t)
      seen += expr
    }

    for (v <- vs)
      v.tree match {
        case t if t.isTerm => attempt(t)
//        case a@Apply(_, _) => attempt(a)
//        case s@Select(_, _) => attempt(s)
        case x => fail("Don't know how to interpret " + showRaw(x))
      }

    if (unseen.nonEmpty)
      fail("Not all value accounted for: " + unseen.map("\n  - " + _).mkString)

    val addValues = vs.map(v => q"b += $v")

    val impl =
      q"""
         val b = _root_.scala.collection.immutable.Vector.newBuilder[$T]
         ..$addValues
         $SelectNonEmptyVector.force(b.result())
       """

    if (debug) println("\n" + showCode(impl) + "\n")
    c.Expr[NonEmptyVector[T]](impl)
  }

  def quietAdtValues[T: c.WeakTypeTag]: c.Expr[NonEmptyVector[T]] = implAdtValues(false)
  def debugAdtValues[T: c.WeakTypeTag]: c.Expr[NonEmptyVector[T]] = implAdtValues(true)
  def implAdtValues[T: c.WeakTypeTag](debug: Boolean): c.Expr[NonEmptyVector[T]] = {
    val T     = weakTypeOf[T]
    val types = findConcreteTypesNE(T, LeavesOnly)
    if (types.isEmpty)
      fail(s"At least one concrete subtype of $T required.")

    val values = types.iterator.filterNot(_.isAbstract).map { cs =>
      if (cs.isModuleClass)
        toSelectFQN(cs)
      else
        fail(s"Case object expected. Found: ${cs.fullName}")
    }.toList

    val impl = q"$SelectNonEmptyVector.varargs[$T](..$values)"

    if (debug) println("\n" + showCode(impl) + "\n")
    c.Expr[NonEmptyVector[T]](impl)
  }

  def quietValuesForAdt[T: c.WeakTypeTag, V: c.WeakTypeTag](f: c.Expr[T => V]): c.Expr[NonEmptyVector[V]] = implValuesForAdt(false)(f)
  def debugValuesForAdt[T: c.WeakTypeTag, V: c.WeakTypeTag](f: c.Expr[T => V]): c.Expr[NonEmptyVector[V]] = implValuesForAdt(true)(f)
  def implValuesForAdt[T: c.WeakTypeTag, V: c.WeakTypeTag](debug: Boolean)(f: c.Expr[T => V]): c.Expr[NonEmptyVector[V]] = {
    val T       = weakTypeOf[T]
    val V       = weakTypeOf[V]
    val valueFn = readMacroArg_tToTree(f)
    val values  = valueFn.map(_._2)

    val types = findConcreteAdtTypesNE(T, LeavesOnly)
    if (types.isEmpty)
      fail(s"At least one concrete subtype of $T required.")

    var unseen = types.toSet
    for ((_case, ind) <- valueFn.iterator.map(_._1).zipWithIndex) {
      val _type = _case.fold(_.tpe, identity)
      val matches = unseen.filter(_ <:< _type)
      if (matches.isEmpty)
        fail(s"Case ${ind + 1} (${_type}) doesn't match any remaining cases (${showUnorderedTypes(unseen)}).")
      else
        unseen --= matches
    }
    if (unseen.nonEmpty)
      fail(s"The following types are unaccounted for: ${showUnorderedTypes(unseen)}")

    val impl = q"$SelectNonEmptyVector.varargs[$V](..$values)"

    if (debug) println("\n" + showCode(impl) + "\n")
    c.Expr[NonEmptyVector[V]](impl)
  }

  def quietValuesForAdtF[T: c.WeakTypeTag, V: c.WeakTypeTag](f: c.Expr[T => V]): c.Expr[(NonEmptyVector[V], T => V)] = implValuesForAdtF(false)(f)
  def debugValuesForAdtF[T: c.WeakTypeTag, V: c.WeakTypeTag](f: c.Expr[T => V]): c.Expr[(NonEmptyVector[V], T => V)] = implValuesForAdtF(true)(f)
  def implValuesForAdtF[T: c.WeakTypeTag, V: c.WeakTypeTag](debug: Boolean)(f: c.Expr[T => V]): c.Expr[(NonEmptyVector[V], T => V)] = {
    val nev = implValuesForAdt[T, V](false)(f)
    val impl = q"($nev, $f)"
    if (debug) println("\n" + showCode(impl) + "\n")
    c.Expr[(NonEmptyVector[V], T => V)](impl)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy