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

io.scalaland.chimney.internal.compiletime.datatypes.SealedHierarchiesPlatform.scala Maven / Gradle / Ivy

package io.scalaland.chimney.internal.compiletime.datatypes

import io.scalaland.chimney.internal.compiletime.DefinitionsPlatform

trait SealedHierarchiesPlatform extends SealedHierarchies { this: DefinitionsPlatform =>

  import quotes.*, quotes.reflect.*
  import Type.platformSpecific.*

  protected object SealedHierarchy extends SealedHierarchyModule {

    def isJavaEnum[A: Type]: Boolean =
      TypeRepr.of[A] <:< TypeRepr.typeConstructorOf(classOf[java.lang.Enum[?]])

    def isSealed[A: Type]: Boolean = {
      val flags = TypeRepr.of[A].typeSymbol.flags
      flags.is(Flags.Sealed) // do NOT use flags.is(Flags.Enum) since it will also match enums cases!
    }

    def parse[A: Type]: Option[Enum[A]] =
      // no need for separate java.lang.Enum handling contrary to Scala 2
      if isSealed[A] then Some(symbolsToEnum(extractSealedSubtypes[A]))
      else None

    private def extractSealedSubtypes[A: Type]: List[(String, ?<[A])] = {
      def extractRecursively(sym: Symbol): List[Symbol] =
        if sym.flags.is(Flags.Sealed) then sym.children.flatMap(extractRecursively)
        else if sym.flags.is(Flags.Enum) then List(sym.typeRef.typeSymbol)
        else if sym.flags.is(Flags.Module) then List(sym.typeRef.typeSymbol.moduleClass)
        else List(sym)

      val order = Ordering.Option(Ordering.fromLessThan[Position] { (a, b) =>
        a.startLine < b.startLine || (a.startLine == b.startLine && a.startColumn < b.startColumn)
      })

      // calling .distinct here as `children` returns duplicates for multiply-inherited types
      extractRecursively(TypeRepr.of[A].typeSymbol).distinct
        .sortBy(_.pos.filter(pos => scala.util.Try(pos.start).isSuccess))(order)
        .map(typeSymbol => subtypeName(typeSymbol) -> subtypeTypeOf[A](typeSymbol))
    }

    private def symbolsToEnum[A: Type](subtypes: List[(String, ?<[A])]): Enum[A] =
      Enum(
        subtypes
          .map { case (name, subtypeA: ?<[A]) =>
            subtypeA.mapK[Enum.Element[A, *]] { implicit Subtype: Type[subtypeA.Underlying] => _ =>
              Enum.Element[A, subtypeA.Underlying](name = name, upcast = _.upcastToExprOf[A])
            }
          }
          // with GADT we can have subtypes that shouldn't appear in pattern matching
          .filter(_.Underlying <:< Type[A])
      )

    private def subtypeName(subtype: Symbol): String = {
      val n = subtype.name
      // case objects from Scala 2 has names with $ at the end (like all modules) while Scala 3's name
      // have all these suffixes like "$" or ".type" dropped. We need to align these names to allow comparing
      if n.endsWith("$") then n.substring(0, n.length - 1) else n
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy