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

kyo.SafeClassTag.scala Maven / Gradle / Ivy

package kyo

import kyo.Maybe
import kyo.internal.SafeClassTagMacro
import scala.annotation.tailrec
import scala.quoted.*

/** An alternative to ClassTag that supports union and intersection types.
  *
  * SafeClassTag provides runtime type information and type checking capabilities for both simple and complex types, including unions and
  * intersections. Unlike ClassTag, SafeClassTag can represent and check against union and intersection types, making it safer for complex
  * type scenarios. It also properly handles special types like AnyVal and Nothing instead of falling back to java.lang.Object like
  * ClassTag.
  *
  * Limitations:
  *   - Does not support generic types (types with type parameters)
  *   - Cannot represent Null type
  *
  * @tparam A
  *   The type for which this SafeClassTag is defined
  */
opaque type SafeClassTag[A] >: SafeClassTag.Element = Class[?] | SafeClassTag.Element

object SafeClassTag:
    given [A, B]: CanEqual[SafeClassTag[A], SafeClassTag[B]] = CanEqual.derived

    sealed trait Element

    case class Union(elements: Set[SafeClassTag[Any]])        extends Element
    case class Intersection(elements: Set[SafeClassTag[Any]]) extends Element
    case class LiteralTag(value: Any)                         extends Element

    sealed trait Primitive extends Element
    case object IntTag     extends Primitive
    case object LongTag    extends Primitive
    case object DoubleTag  extends Primitive
    case object FloatTag   extends Primitive
    case object ByteTag    extends Primitive
    case object ShortTag   extends Primitive
    case object CharTag    extends Primitive
    case object BooleanTag extends Primitive
    case object UnitTag    extends Element
    case object AnyValTag  extends Element
    case object NothingTag extends Element

    inline given apply[A]: SafeClassTag[A] = ${ SafeClassTagMacro.derive[A] }

    extension [A](self: SafeClassTag[A])

        /** Checks if the given value is accepted by this SafeClassTag
          *
          * @param value
          *   The value to check
          * @return
          *   true if the value is accepted, false otherwise
          */
        def accepts(value: Any): Boolean =
            !isNull(value) && {
                given CanEqual[Any, Any] = CanEqual.derived
                self match
                    case Union(elements)        => elements.exists(_.accepts(value))
                    case Intersection(elements) => elements.forall(_.accepts(value))
                    case LiteralTag(literal)    => value == literal
                    case NothingTag             => false
                    case UnitTag                => value.isInstanceOf[Unit]
                    case AnyValTag =>
                        value match
                            case (_: Int | _: Long | _: Double | _: Float | _: Byte | _: Short | _: Char | _: Boolean) =>
                                true
                            case _ =>
                                val cls = value.getClass
                                classOf[AnyVal].isAssignableFrom(cls) && (cls ne classOf[AnyVal])
                    case _: Primitive =>
                        value match
                            case _: Int     => self eq IntTag
                            case _: Long    => self eq LongTag
                            case _: Double  => self eq DoubleTag
                            case _: Float   => self eq FloatTag
                            case _: Byte    => self eq ByteTag
                            case _: Short   => self eq ShortTag
                            case _: Char    => self eq CharTag
                            case _: Boolean => self eq BooleanTag
                            case _          => false
                    case self: Class[?] => self.isInstance(value)
                end match
            }
        end accepts

        /** Attempts to extract a value of type A from the given value
          *
          * @param value
          *   The value to extract from
          * @return
          *   A Maybe containing the extracted value if successful, or empty if not
          */
        def unapply(value: Any): Maybe.Ops[A] =
            if accepts(value) then Maybe(value.asInstanceOf[A]) else Maybe.empty

        /** Combines this SafeClassTag with another to form an intersection type
          *
          * @param that
          *   The SafeClassTag to intersect with
          * @return
          *   A new SafeClassTag representing the intersection of this and that
          */
        infix def &[B](that: SafeClassTag[B]): SafeClassTag[A & B] =
            self match
                case Intersection(e1) => that match
                        case Intersection(e2) => Intersection(e1 ++ e2)
                        case _                => Intersection(e1 + that)
                case _ => that match
                        case Intersection(e2) => Intersection(e2 + self)
                        case _                => Intersection(Set(self, that))

        /** Combines this SafeClassTag with another to form a union type
          *
          * @param that
          *   The SafeClassTag to union with
          * @return
          *   A new SafeClassTag representing the union of this and that
          */
        infix def |[B](that: SafeClassTag[B]): SafeClassTag[A | B] =
            self match
                case Union(e1) => that match
                        case Union(e2) => Union(e1 ++ e2)
                        case _         => Union(e1 + that)
                case _ => that match
                        case Union(e2) => Union(e2 + self)
                        case _         => Union(Set(self, that))

        /** Checks if this SafeClassTag is a subtype of another SafeClassTag
          *
          * @param that
          *   The SafeClassTag to check against
          * @return
          *   true if this is a subtype of that, false otherwise
          */
        infix def <:<[B](that: SafeClassTag[B]): Boolean =
            given CanEqual[Any, Any] = CanEqual.derived
            self match
                case NothingTag             => true
                case Union(elements)        => elements.forall(_ <:< that)
                case Intersection(elements) => elements.exists(_ <:< that)
                case LiteralTag(value)      => that.accepts(value)
                case _ =>
                    that match
                        case NothingTag             => false
                        case Union(elements)        => elements.exists(self <:< _)
                        case Intersection(elements) => elements.forall(self <:< _)
                        case LiteralTag(value)      => false
                        case AnyValTag =>
                            self match
                                case AnyValTag | IntTag | LongTag | DoubleTag | FloatTag | ByteTag | ShortTag | CharTag | BooleanTag | UnitTag =>
                                    true
                                case _ => false
                        case UnitTag      => self eq UnitTag
                        case _: Primitive => self eq that
                        case cls: Class[?] =>
                            self match
                                case self: Class[?] => cls.isAssignableFrom(self)
                                case _              => false
            end match
        end <:<

        /** Returns a string representation of this SafeClassTag
          *
          * @return
          *   A string describing the structure of this SafeClassTag
          */
        def show: String =
            def showInner(tag: SafeClassTag[Any]): String =
                given CanEqual[Any, Any] = CanEqual.derived
                tag match
                    case Union(elements)        => s"(${elements.map(showInner).mkString(" | ")})"
                    case Intersection(elements) => s"(${elements.map(showInner).mkString(" & ")})"
                    case LiteralTag(value)      => s"$value"
                    case NothingTag             => "Nothing"
                    case UnitTag                => "Unit"
                    case AnyValTag              => "AnyVal"
                    case IntTag                 => "Int"
                    case LongTag                => "Long"
                    case DoubleTag              => "Double"
                    case FloatTag               => "Float"
                    case ByteTag                => "Byte"
                    case ShortTag               => "Short"
                    case CharTag                => "Char"
                    case BooleanTag             => "Boolean"
                    case cls: Class[?]          => cls.getSimpleName
                end match
            end showInner

            s"SafeClassTag[${showInner(self)}]"
        end show
    end extension

end SafeClassTag




© 2015 - 2024 Weber Informatics LLC | Privacy Policy