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

magnolia1.interface.scala Maven / Gradle / Ivy

The newest version!
package magnolia1

import magnolia1.CaseClass.getDefaultEvaluatorFromDefaultVal

import scala.annotation.tailrec
import scala.reflect.*

case class TypeInfo(
    owner: String,
    short: String,
    typeParams: Iterable[TypeInfo]
):
  def full: String = s"$owner.$short"

object CaseClass:
  trait Param[Typeclass[_], Type](
      val label: String,
      val index: Int,
      val repeated: Boolean,
      val annotations: IArray[Any],
      val typeAnnotations: IArray[Any]
  ) extends Serializable:

    type PType

    /** Gives the constructed typeclass for the parameter's type. Eg for a `case class Foo(bar: String, baz: Int)`, where this [[Param]]
      * denotes 'baz', the `typeclass` field returns an instance of `Typeclass[Int]`.
      */
    def typeclass: Typeclass[PType]

    /** Get the value of this param out of the supplied instance of the case class.
      *
      * @param value
      *   an instance of the case class
      * @return
      *   the value of this parameter in the case class
      */
    def deref(param: Type): PType

    /** Recommended compilation with `-Yretain-trees` on.
      * @return
      *   default argument value, if any
      */
    def default: Option[PType]

    /** provides a function to evaluate the default value for this parameter, as defined in the case class constructor */
    def evaluateDefault: Option[() => PType] = None
    def inheritedAnnotations: IArray[Any] = IArray.empty[Any]
    override def toString: String = s"Param($label)"

  object Param:
    def apply[F[_], T, P](
        name: String,
        idx: Int,
        repeated: Boolean,
        cbn: CallByNeed[F[P]],
        defaultVal: CallByNeed[Option[P]],
        annotations: IArray[Any],
        inheritedAnns: IArray[Any],
        typeAnnotations: IArray[Any]
    ): Param[F, T] =
      new CaseClass.Param[F, T](
        name,
        idx,
        repeated,
        annotations,
        typeAnnotations
      ):
        type PType = P
        def default: Option[PType] = defaultVal.value
        override def evaluateDefault: Option[() => PType] = CaseClass.getDefaultEvaluatorFromDefaultVal(defaultVal)
        def typeclass = cbn.value
        override def inheritedAnnotations = inheritedAnns
        def deref(value: T): P =
          value.asInstanceOf[Product].productElement(idx).asInstanceOf[P]

    // for backward compatibility with v1.0.0
    def apply[F[_], T, P](
        name: String,
        idx: Int,
        repeated: Boolean,
        cbn: CallByNeed[F[P]],
        defaultVal: CallByNeed[Option[P]],
        annotations: IArray[Any],
        typeAnnotations: IArray[Any]
    ): Param[F, T] =
      new CaseClass.Param[F, T](
        name,
        idx,
        repeated,
        annotations,
        typeAnnotations
      ):
        type PType = P
        def default: Option[PType] = defaultVal.value
        override def evaluateDefault: Option[() => PType] = getDefaultEvaluatorFromDefaultVal(defaultVal)
        def typeclass = cbn.value
        def deref(value: T): P =
          value.asInstanceOf[Product].productElement(idx).asInstanceOf[P]
  end Param

  private def getDefaultEvaluatorFromDefaultVal[P](defaultVal: CallByNeed[Option[P]]): Option[() => P] =
    defaultVal.valueEvaluator.flatMap { evaluator =>
      evaluator().fold[Option[() => P]](None) { _ =>
        Some(() => evaluator().get)
      }
    }
end CaseClass

/** In the terminology of Algebraic Data Types (ADTs), case classes are known as 'product types'.
  *
  * @param parameters
  *   an array giving information about the parameters of the case class. Each [[Param]] element has a very useful
  *   [[CaseClass.Param.typeclass]] field giving the constructed typeclass for the parameter's type. Eg for a `case class Foo(bar: String,
  *   baz: Int)`, you can obtain `Typeclass[String]`, `Typeclass[Int]`.
  */
abstract class CaseClass[Typeclass[_], Type](
    val typeInfo: TypeInfo,
    val isObject: Boolean,
    val isValueClass: Boolean,
    val parameters: IArray[CaseClass.Param[Typeclass, Type]],
    val annotations: IArray[Any],
    val inheritedAnnotations: IArray[Any] = IArray.empty[Any],
    val typeAnnotations: IArray[Any]
) extends Serializable:

  // for backward compatibility with v1.0.0
  def this(
      typeInfo: TypeInfo,
      isObject: Boolean,
      isValueClass: Boolean,
      parameters: IArray[CaseClass.Param[Typeclass, Type]],
      annotations: IArray[Any],
      typeAnnotations: IArray[Any]
  ) = this(
    typeInfo,
    isObject,
    isValueClass,
    parameters,
    annotations,
    IArray.empty[Any],
    typeAnnotations
  )

  def params: IArray[CaseClass.Param[Typeclass, Type]] = parameters

  type Param = CaseClass.Param[Typeclass, Type]

  override def toString: String =
    s"CaseClass(${typeInfo.full}, ${parameters.mkString(",")})"
  def construct[PType](makeParam: Param => PType)(using ClassTag[PType]): Type
  def constructMonadic[Monad[_]: Monadic, PType: ClassTag](
      make: Param => Monad[PType]
  ): Monad[Type]
  def constructEither[Err, PType: ClassTag](
      makeParam: Param => Either[Err, PType]
  ): Either[List[Err], Type]
  def rawConstruct(fieldValues: Seq[Any]): Type

  def param[P](
      name: String,
      idx: Int,
      repeated: Boolean,
      cbn: CallByNeed[Typeclass[P]],
      defaultVal: CallByNeed[Option[P]],
      annotations: IArray[Any],
      inheritedAnns: IArray[Any],
      typeAnnotations: IArray[Any]
  ): Param =
    new CaseClass.Param[Typeclass, Type](
      name,
      idx,
      repeated,
      annotations,
      typeAnnotations
    ):
      type PType = P
      def default: Option[PType] = defaultVal.value
      override def evaluateDefault: Option[() => PType] = getDefaultEvaluatorFromDefaultVal(defaultVal)
      def typeclass = cbn.value
      override def inheritedAnnotations = inheritedAnns
      def deref(value: Type): P =
        value.asInstanceOf[Product].productElement(idx).asInstanceOf[P]

  // for backward compatibility with v1.0.0
  def param[P](
      name: String,
      idx: Int,
      repeated: Boolean,
      cbn: CallByNeed[Typeclass[P]],
      defaultVal: CallByNeed[Option[P]],
      annotations: IArray[Any],
      typeAnnotations: IArray[Any]
  ): Param = param(
    name,
    idx,
    repeated,
    cbn,
    defaultVal,
    annotations,
    IArray.empty[Any],
    typeAnnotations
  )

end CaseClass

/** Represents a Sealed-Trait or a Scala 3 Enum.
  *
  * In the terminology of Algebraic Data Types (ADTs), sealed-traits/enums are termed 'sum types'.
  */
case class SealedTrait[Typeclass[_], Type](
    typeInfo: TypeInfo,
    subtypes: IArray[SealedTrait.Subtype[Typeclass, Type, _]],
    annotations: IArray[Any],
    typeAnnotations: IArray[Any],
    isEnum: Boolean,
    inheritedAnnotations: IArray[Any]
) extends Serializable:

  // for backward compatibility with v1.0.0
  def this(
      typeInfo: TypeInfo,
      subtypes: IArray[SealedTrait.Subtype[Typeclass, Type, _]],
      annotations: IArray[Any],
      typeAnnotations: IArray[Any],
      isEnum: Boolean
  ) = this(
    typeInfo,
    subtypes,
    annotations,
    typeAnnotations,
    isEnum,
    IArray.empty[Any]
  )

  // for backward compatibility with v1.0.0
  def copy(
      typeInfo: TypeInfo,
      subtypes: IArray[SealedTrait.Subtype[Typeclass, Type, _]],
      annotations: IArray[Any],
      typeAnnotations: IArray[Any],
      isEnum: Boolean
  ): SealedTrait[Typeclass, Type] = this.copy(
    typeInfo,
    subtypes,
    annotations,
    typeAnnotations,
    isEnum,
    this.inheritedAnnotations
  )

  type Subtype[S] = SealedTrait.SubtypeValue[Typeclass, Type, S]

  override def toString: String =
    s"SealedTrait($typeInfo, IArray[${subtypes.mkString(",")}])"

  /** Provides a way to recieve the type info for the explicit subtype that 'value' is an instance of. So if 'Type' is a Sealed Trait or
    * Scala 3 Enum like 'Suit', the 'handle' function will be supplied with the type info for the specific subtype of 'value', eg
    * 'Diamonds'.
    *
    * @param value
    *   must be instance of a subtype of typeInfo
    * @param handle
    *   function that will be passed the Subtype of 'value'
    * @tparam Return
    *   whatever type the 'handle' function wants to return
    * @return
    *   whatever the 'handle' function returned!
    */
  def choose[Return](value: Type)(handle: Subtype[_] => Return): Return =
    @tailrec def rec(ix: Int): Return =
      if ix < subtypes.length then
        val sub = subtypes(ix)
        if sub.cast.isDefinedAt(value) then handle(SealedTrait.SubtypeValue(sub, value))
        else rec(ix + 1)
      else
        throw new IllegalArgumentException(
          s"The given value `$value` is not a sub type of `$typeInfo`"
        )

    rec(0)

end SealedTrait

object SealedTrait:

  // for backward compatibility with v1.0.0
  def apply[Typeclass[_], Type](
      typeInfo: TypeInfo,
      subtypes: IArray[SealedTrait.Subtype[Typeclass, Type, _]],
      annotations: IArray[Any],
      typeAnnotations: IArray[Any],
      isEnum: Boolean
  ) = new SealedTrait[Typeclass, Type](
    typeInfo,
    subtypes,
    annotations,
    typeAnnotations,
    isEnum,
    IArray.empty[Any]
  )

  /** @tparam Type
    *   the type of the Sealed Trait or Scala 3 Enum, eg 'Suit'
    * @tparam SType
    *   the type of the subtype, eg 'Diamonds' or 'Clubs'
    */
  class Subtype[Typeclass[_], Type, SType](
      val typeInfo: TypeInfo,
      val annotations: IArray[Any],
      val inheritedAnnotations: IArray[Any],
      val typeAnnotations: IArray[Any],
      val isObject: Boolean,
      val index: Int,
      callByNeed: CallByNeed[Typeclass[SType]],
      isType: Type => Boolean,
      asType: Type => SType & Type
  ) extends PartialFunction[Type, SType & Type],
        Serializable:

    // for backward compatibility with v1.0.0
    def this(
        typeInfo: TypeInfo,
        annotations: IArray[Any],
        typeAnnotations: IArray[Any],
        isObject: Boolean,
        index: Int,
        callByNeed: CallByNeed[Typeclass[SType]],
        isType: Type => Boolean,
        asType: Type => SType & Type
    ) = this(
      typeInfo,
      annotations,
      IArray.empty[Any],
      typeAnnotations,
      isObject,
      index,
      callByNeed,
      isType,
      asType
    )

    /** @return
      *   the already-constructed typeclass instance for this subtype
      */
    def typeclass: Typeclass[SType & Type] =
      callByNeed.value.asInstanceOf[Typeclass[SType & Type]]
    def cast: PartialFunction[Type, SType & Type] = this
    def isDefinedAt(t: Type): Boolean = isType(t)
    def apply(t: Type): SType & Type = asType(t)
    override def toString: String = s"Subtype(${typeInfo.full})"

  class SubtypeValue[Typeclass[_], Type, S](
      val subtype: Subtype[Typeclass, Type, S],
      v: Type
  ):
    export subtype.{typeclass, typeAnnotations, annotations, inheritedAnnotations, cast, typeInfo}
    def value: S & Type = cast(v)

end SealedTrait

object CallByNeed:
  /** Initializes a class that allows for suspending evaluation of a value until it is needed. Evaluation of a value via `.value` can only
    * happen once.
    */
  def createLazy[A](a: () => A): CallByNeed[A] = new CallByNeed(a, () => false)

  /** Initializes a class that allows for suspending evaluation of a value until it is needed. Evaluation of a value via `.value` can only
    * happen once.
    *
    * If by-name parameter causes serialization issue, use [[createLazy]].
    */
  def apply[A](a: => A): CallByNeed[A] = new CallByNeed(() => a, () => false)

  /** Initializes a class that allows for suspending evaluation of a value until it is needed. Evaluation of a value via `.value` can only
    * happen once. Evaluation of a value via `.valueEvaluator.map(evaluator => evaluator())` will happen every time the evaluator is called
    */
  def createValueEvaluator[A](a: () => A): CallByNeed[A] = new CallByNeed(a, () => true)

  /** Initializes a class that allows for suspending evaluation of a value until it is needed. Evaluation of a value via `.value` can only
    * happen once. Evaluation of a value via `.valueEvaluator.map(evaluator => evaluator())` will happen every time the evaluator is called
    *
    * If by-name parameter causes serialization issue, use [[withValueEvaluator]].
    */
  def withValueEvaluator[A](a: => A): CallByNeed[A] = new CallByNeed(() => a, () => true)
end CallByNeed

// Both params are later nullified to reduce overhead and increase performance.
// The supportDynamicValueEvaluation is passed as a function so that it can be nullified. Otherwise, there is no need for the function value.
final class CallByNeed[+A] private (private[this] var eval: () => A, private var supportDynamicValueEvaluation: () => Boolean)
    extends Serializable {

  // This second constructor is necessary to support backwards compatibility for v1.3.6 and earlier
  def this(eval: () => A) = this(eval, () => false)

  val valueEvaluator: Option[() => A] = {
    val finalRes = if (supportDynamicValueEvaluation()) {
      val res = Some(eval)
      eval = null
      res
    } else {
      None
    }
    supportDynamicValueEvaluation = null
    finalRes
  }

  lazy val value: A = {
    if (eval == null) {
      valueEvaluator.get.apply()
    } else {
      val result = eval()
      eval = null
      result
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy