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

enumeratum.EnumMacros.scala Maven / Gradle / Ivy

There is a newer version: 1.6.5
Show newest version
package enumeratum

import scala.quoted.{Expr, Quotes, Type}

object EnumMacros:
  /** Finds any [A] in the current scope and returns an expression for a list of them. */
  def findValuesImpl[A](using tpe: Type[A], q: Quotes): Expr[IndexedSeq[A]] = {
    import q.reflect.*

    // println(s"${child.show} :: ${child.typeSymbol.methodMember("findValues")}")
    val definingTpeSym = Symbol.spliceOwner.maybeOwner.maybeOwner

    if (!definingTpeSym.flags.is(Flags.Module)) {
      report.errorAndAbort(
        // Root must be a module to have same behaviour
        s"The enum (i.e. the class containing the case objects and the call to `findValues`) must be an object: ${definingTpeSym.fullName}"
      )
    }

    val repr       = validateType[A]
    val subclasses = enclosedSubClasses[A](q)(repr)

    buildSeqExpr[A](q)(subclasses)
  }

  /** Given an A, provides its companion. */
  def materializeEnumImpl[A, M: Type](using tpe: Type[A], q: Quotes): Expr[M] = {
    import q.reflect.*

    val repr = TypeRepr.of[A](using tpe)

    if (!repr.typeSymbol.companionModule.exists) {
      report.errorAndAbort(
        s"""
           |
           |  Could not find the companion object for type ${repr.typeSymbol}.
           |
           |  If you're sure the companion object exists, you might be able to fix this error by annotating the
           |  value you're trying to find the companion object for with a parent type (e.g. Light.Red: Light).
           |
           |  This error usually happens when trying to find the companion object of a hard-coded enum member, and
           |  is caused by Scala inferring the type to be the member's singleton type (e.g. Light.Red.type instead of
           |  Light).
           |
           |  To illustrate, given an enum:
           |
           |  sealed abstract class Light extends EnumEntry
           |  case object Light extends Enum[Light] {
           |    val values = findValues
           |    case object Red   extends Light
           |    case object Blue  extends Light
           |    case object Green extends Light
           |  }
           |
           |  and a method:
           |
           |  def indexOf[A <: EnumEntry: Enum](entry: A): Int = implicitly[Enum[A]].indexOf(entry)
           |
           |  Instead of calling like so: indexOf(Light.Red)
           |                Call like so: indexOf(Light.Red: Light)
         """.stripMargin
      )
    } else {
      Ref(repr.typeSymbol.companionModule).asExprOf[M]
    }
  }

  /** Makes sure that we can work with the given type as an enum:
    *
    * Aborts if the type is not sealed.
    *
    * @tparam T
    *   the `Enum` type
    */
  private[enumeratum] def validateType[T](using q: Quotes, tpe: Type[T]): q.reflect.TypeRepr = {
    import q.reflect.*

    val repr = TypeRepr.of[T](using tpe)

    if (!repr.classSymbol.exists(_.flags is Flags.Sealed)) {
      report.errorAndAbort(
        "You can only use findValues on sealed traits or classes"
      )
    }

    repr
  }

  /** Returns a sequence of symbols for objects that implement the given type
    *
    * @tparam T
    *   the `Enum` type
    * @param tpr
    *   the representation of type `T` (also specified by `tpe`)
    */
  private[enumeratum] def enclosedSubClasses[T](q: Quotes)(
      tpr: q.reflect.TypeRepr
  )(using tpe: Type[T]): List[q.reflect.TypeRepr] = {
    import q.reflect.*

    given quotes: q.type = q

    @annotation.tailrec
    def isObject(sym: Symbol, ok: Boolean = false): Boolean = {
      if (!sym.flags.is(Flags.Module)) {
        false
      } else {
        val owner = sym.maybeOwner

        if (!owner.exists || owner == defn.RootClass || owner.isTerm) {
          if (
            sym.flags.is(Flags.Module) && !sym.flags.is(Flags.Package) &&
            !sym.fullName.startsWith(tpr.typeSymbol.companionModule.fullName)
          ) {
            // See EnumSpec#'should return -1 for elements that do not exist'

            report.warning(
              s"The entry '${sym.fullName}' must be defined in the enum companion"
            )

            false
          } else {
            ok
          }
        } else {
          isObject(sym.maybeOwner, true)
        }
      }
    }

    type IsEntry[E <: T] = E

    @annotation.tailrec
    def subclasses(
        children: List[Tree],
        out: List[TypeRepr]
    ): List[TypeRepr] = {
      val childTpr: Option[TypeRepr] = children.headOption.collect {
        case tpd: Typed =>
          tpd.tpt.tpe

        case vd: ValDef =>
          vd.tpt.tpe

        case cd: ClassDef =>
          cd.symbol.typeRef match {
            case TypeRef(prefix, _) =>
              prefix.select(cd.symbol)

          }
      }

      childTpr match {
        case Some(child) => {
          child.asType match {
            case ct @ '[IsEntry[t]] => {
              val tpeSym = child.typeSymbol

              if (!isObject(tpeSym)) {
                subclasses(tpeSym.children.map(_.tree) ::: children.tail, out)
              } else {
                subclasses(children.tail, child :: out)
              }
            }

            case _ =>
              subclasses(children.tail, out)
          }
        }

        case _ =>
          out.reverse
      }
    }

    tpr.classSymbol
      .flatMap { cls =>
        val types = subclasses(cls.children.map(_.tree), Nil)

        if (types.isEmpty) None else Some(types)
      }
      .getOrElse(List.empty[TypeRepr])
  }

  /** Builds and returns an expression for an `IndexedSeq`, containing singletons for the specified
    * subclasses.
    */
  private[enumeratum] def buildSeqExpr[T](q: Quotes)(
      subclasses: Seq[q.reflect.TypeRepr]
  )(using tpe: Type[T]): Expr[IndexedSeq[T]] = {
    given quotes: q.type = q
    import q.reflect.*

    if (subclasses.isEmpty) {
      '{ IndexedSeq.empty[T] }
    } else {
      val valueExprs = subclasses.map { sub =>
        Ref(sub.typeSymbol.companionModule).asExprOf[T]
      }

      val values = Expr.ofSeq(valueExprs)

      '{
        IndexedSeq[T](${ values }: _*)
      }
    }
  }

end EnumMacros




© 2015 - 2024 Weber Informatics LLC | Privacy Policy