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

com.stripe.scrooge.shapes.GenericMacros.scala Maven / Gradle / Ivy

The newest version!
package com.stripe.scrooge.shapes

import scala.reflect.macros.whitebox
import shapeless.{Annotations, Default, DefaultSymbolicLabelling, Generic, HNil}

class GenericMacros(val c: whitebox.Context) {
  import c.universe._

  sealed abstract class Named(name: Name) {
    private[this] val nameType: ConstantType = internal.constantType(Constant(decodedString))

    final def decodedString: String = name.decodedName.toString
    final def labelType: CompoundTypeTree =
      tq"_root_.scala.Symbol with _root_.shapeless.tag.Tagged[$nameType]"
  }

  case class StructMember(name: TermName, valueType: Type) extends Named(name)

  object StructMember {
    def of[A: WeakTypeTag]: List[StructMember] = {
      val companionApply = c.weakTypeOf[A].companion.decl(TermName("apply")).asMethod

      companionApply.paramLists.head.map { param =>
        StructMember(param.asTerm.name, param.asTerm.info)
      }.toList
    }
  }

  case class UnionConstructor(name: TypeName, valueType: Type, valueCompanion: TermName)
      extends Named(name)

  object UnionConstructor {
    def of[A: WeakTypeTag]: List[UnionConstructor] =
      c.weakTypeOf[A]
        .companion
        .decls
        .collect {
          case sym if sym.isClass && sym.asClass.isCaseClass => sym.asClass
        }
        .map { sym =>
          UnionConstructor(
            sym.name,
            sym.primaryConstructor.asMethod.paramLists.head.head.asTerm.info.dealias,
            sym.companion.name.toTermName
          )
        }
        .toList
  }

  private[this] def isUnion[A: WeakTypeTag]: Option[Boolean] = {
    val baseNames = c.weakTypeOf[A].baseClasses.tail.map(_.fullName)

    if (baseNames.contains("com.twitter.scrooge.ThriftStruct")) {
      Some(baseNames.contains("com.twitter.scrooge.ThriftUnion"))
    } else None
  }

  def materializeGeneric[A: WeakTypeTag]: c.Expr[Generic[A]] =
    isUnion[A] match {
      case Some(true) => materializeUnionGeneric[A]
      case Some(false) => materializeStructGeneric[A]
      case None =>
        c.abort(
          c.enclosingPosition,
          s"Cannot derive a Generic instance for ${c.weakTypeOf[A]}"
        )
    }

  def materializeDefaultSymbolicLabelling[A: WeakTypeTag]: c.Expr[DefaultSymbolicLabelling[A]] =
    isUnion[A] match {
      case Some(true) => materializeUnionDefaultSymbolicLabelling[A]
      case Some(false) => materializeStructDefaultSymbolicLabelling[A]
      case None =>
        c.abort(
          c.enclosingPosition,
          s"Cannot derive a DefaultSymbolLabelling instance for ${c.weakTypeOf[A]}"
        )
    }

  private[this] def createGeneric(A: Type, R: Tree, toResult: Tree, fromResult: Tree): Tree =
    q"""
      new _root_.shapeless.Generic[$A] {
        type Repr = $R
        def to(a: $A): Repr = $toResult
        def from(r: Repr): $A = $fromResult
      }: _root_.shapeless.Generic.Aux[$A, $R]
    """

  private[this] def createDefaultSymbolicLabelling(A: Type, names: List[Named]): Tree = {
    val L = names.foldRight[Tree](tq"_root_.shapeless.HNil") {
      case (name, acc) => tq"_root_.shapeless.::[${name.labelType}, $acc]"
    }

    val labels = names.foldRight[Tree](q"_root_.shapeless.HNil") {
      case (name, acc) =>
        q"_root_.shapeless.::(_root_.scala.Symbol(${name.decodedString}).asInstanceOf[${name.labelType}], $acc)"
    }

    q"""
      new _root_.shapeless.DefaultSymbolicLabelling[$A] {
        type Out = $L
        def apply(): $L = $labels
      }: _root_.shapeless.DefaultSymbolicLabelling.Aux[$A, $L]
    """
  }

  private[this] def createDefault(A: Type, names: List[Named]): Tree = {
    val L = names.foldRight[Tree](tq"_root_.shapeless.HNil") {
      case (name, acc) => tq"_root_.shapeless.::[_root_.scala.None.type, $acc]"
    }

    val value = names.foldRight[Tree](q"_root_.shapeless.HNil") {
      case (name, acc) =>
        q"_root_.shapeless.::(_root_.scala.None, $acc)"
    }

    q"_root_.shapeless.Default.mkDefault[$A, $L]($value): _root_.shapeless.Default.Aux[$A, $L]"
  }

  private[this] def createAnnotations(A: Type, T: Type): Tree =
    q"""
      _root_.shapeless.Annotations.mkAnnotations[$A, $T, _root_.shapeless.HNil](
        _root_.shapeless.HNil: _root_.shapeless.HNil
      ): _root_.shapeless.Annotations.Aux[$A, $T, _root_.shapeless.HNil]
    """

  def materializeStructGeneric[A: WeakTypeTag]: c.Expr[Generic[A]] = {
    val members = StructMember.of[A]

    val A = c.weakTypeOf[A]
    val R = members.foldRight[Tree](tq"_root_.shapeless.HNil") {
      case (member, acc) => tq"_root_.shapeless.::[${member.valueType}, $acc]"
    }

    val toResult = members.foldRight[Tree](q"_root_.shapeless.HNil") {
      case (member, acc) => q"_root_.shapeless.::(a.${member.name}, $acc)"
    }

    val companionApply = A.companion.decl(TermName("apply")).asMethod
    val elementNames = (1 to members.size).map(i => TermName(s"e$i"))

    val fromResult = elementNames.foldRight[Tree](q"$companionApply(...${List(elementNames)})") {
      case (h, acc) => q"{ r match { case _root_.shapeless.::($h, r) => $acc } }"
    }

    val tree = createGeneric(A, R, toResult, fromResult)
    c.Expr[Generic[A]](tree)
  }

  def materializeUnionGeneric[A: WeakTypeTag]: c.Expr[Generic[A]] = {
    val constructors: List[UnionConstructor] = UnionConstructor.of[A]

    val A = c.weakTypeOf[A]
    val C = A.typeSymbol.companion
    val R = constructors.foldRight[Tree](tq"_root_.shapeless.CNil") {
      case (constructor, acc) => tq"_root_.shapeless.:+:[${constructor.valueType}, $acc]"
    }

    val toResultCases = constructors.map { constructor =>
      val binding = TermName(c.freshName())
      cq"""
        $C.${constructor.valueCompanion}($binding) => _root_.shapeless.Coproduct[$R](
          $binding.asInstanceOf[${constructor.valueType}]
        )
      """
    }

    val toResult = q"a match { case ..$toResultCases }"

    val fromResult = constructors.foldRight(
      q"r match { case l => l.impossible }"
    ) {
      case (constructor, acc) => q"""
        r match {
          case _root_.shapeless.Inl(l) => $C.${constructor.valueCompanion}(l)
          case _root_.shapeless.Inr(r) => $acc
        }
      """
    }

    val tree = createGeneric(A, R, toResult, fromResult)
    c.Expr[Generic[A]](tree)
  }

  def materializeStructDefaultSymbolicLabelling[A: WeakTypeTag]
    : c.Expr[DefaultSymbolicLabelling[A]] = {
    val members = StructMember.of[A]

    val tree = createDefaultSymbolicLabelling(c.weakTypeOf[A], members)
    c.Expr[DefaultSymbolicLabelling[A]](tree)
  }

  def materializeUnionDefaultSymbolicLabelling[A: WeakTypeTag]
    : c.Expr[DefaultSymbolicLabelling[A]] = {
    val constructors = UnionConstructor.of[A]

    val tree = createDefaultSymbolicLabelling(c.weakTypeOf[A], constructors)
    c.Expr[DefaultSymbolicLabelling[A]](tree)
  }

  def materializeDefault[A: WeakTypeTag]: c.Expr[Default[A]] =
    isUnion[A] match {
      case Some(false) =>
        val members = StructMember.of[A]

        val tree = createDefault(c.weakTypeOf[A], members)
        c.Expr[Default[A]](tree)
      case _ =>
        c.abort(
          c.enclosingPosition,
          s"Cannot derive a Default instance for ${c.weakTypeOf[A]}"
        )
    }

  def materializeAnnotations[A: WeakTypeTag, T: WeakTypeTag]: c.Expr[Annotations.Aux[A, T, HNil]] =
    isUnion[T] match {
      case Some(false) =>
        val tree = createAnnotations(c.weakTypeOf[A], c.weakTypeOf[T])
        c.Expr[Annotations.Aux[A, T, HNil]](tree)
      case _ =>
        c.abort(
          c.enclosingPosition,
          s"Cannot derive an Annotations instance for ${c.weakTypeOf[A]}"
        )
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy