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

org.scalameta.data.data.scala Maven / Gradle / Ivy

Go to download

Bag of private and public helpers used in scala.meta's APIs and implementations

There is a newer version: 4.12.2
Show newest version
package org.scalameta.data

import org.scalameta.internal.MacroHelpers

import scala.annotation.StaticAnnotation
import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context

// Virtually the same as the `case' modifier:
// can be used on both classes and objects with very similar effect.
//
// The main different is customizability - we can stuff any extensions we want into @data.
// Currently it's just two things:
//    1) Support for lazy parameters (implemented via a @byNeed marker annotation),
//       as in e.g.: `@leaf class Nonrecursive(tpe: Type @byNeed) extends Typing`.
//       NOTE: @byNeed isn't defined anywhere - it's just a syntactic marker.
//    2) Support for on-demand member generation via named parameters to @data,
//       as in e.g. `@data(toString = false) class ...`.
//
// Performance of pattern matching may be subpar until SI-9029 is fixed,
// as well as some minor semantic details may differ.
class data extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro DataMacros.data
}

class none extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro DataMacros.none
}

class DataMacros(val c: Context) extends MacroHelpers {
  import c.universe.Flag._
  import c.universe._

  def data(annottees: Tree*): Tree = annottees.transformAnnottees(new ImplTransformer {
    override def transformClass(cdef: ClassDef, mdef: ModuleDef): List[ImplDef] = {
      val q"$mods class $name[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =
        cdef
      val q"$mmods object $mname extends { ..$mearlydefns } with ..$mparents { $mself => ..$mstats }" =
        mdef
      val parents1 = ListBuffer[Tree]() ++ parents
      val stats1 = ListBuffer[Tree]() ++ stats
      val anns1 = ListBuffer[Tree]() ++ mods.annotations
      def mods1 = mods.mapAnnotations(_ => anns1.toList)
      val manns1 = ListBuffer[Tree]() ++ mmods.annotations
      def mmods1 = mmods.mapAnnotations(_ => manns1.toList)
      val mstats1 = ListBuffer[Tree]() ++ mstats

      implicit class XtensionDataModifiers(mods: Modifiers) {
        def mkByneed = Modifiers(
          mods.flags,
          mods.privateWithin,
          mods.annotations :+ q"new $AdtMetadataModule.byNeedField"
        )
      }
      def needs(name: Name, companion: Boolean, duplicate: Boolean) = {
        val q"new $_(...$argss).macroTransform(..$_)" = c.macroApplication
        val ban = argss.flatten.exists {
          case AssignOrNamedArg(Ident(TermName(param)), Literal(Constant(false))) => param ==
              name.toString
          case _ => false
        }
        def present = {
          val where = if (companion) mstats else stats
          where.exists { case mdef: MemberDef => mdef.name == name; case _ => false }
        }
        !ban && (duplicate || !present)
      }

      object VanillaParam {
        def unapply(tree: ValDef): Option[(Modifiers, TermName, Tree, Tree)] = tree match {
          case ByNeedParam(_, _, _, _) => None
          case VarargParam(_, _, _, _) => None
          case _ => Some((tree.mods, tree.name, tree.tpt, tree.rhs))
        }
      }
      object VarargParam {
        def unapply(tree: ValDef): Option[(Modifiers, TermName, Tree, Tree)] = tree.tpt match {
          case Vararg(_) => Some((tree.mods, tree.name, tree.tpt, tree.rhs))
          case _ => None
        }
      }
      object ByNeedParam {
        def unapply(tree: ValDef): Option[(Modifiers, TermName, Tree, Tree)] = tree.tpt match {
          case Annotated(
                Apply(Select(New(Ident(TypeName("byNeed"))), termNames.CONSTRUCTOR), List()),
                tpt
              ) =>
            if (Vararg.unapply(tree.tpt).nonEmpty) c
              .abort(cdef.pos, "vararg parameters cannot be by-need")
            else Some((tree.mods, tree.name, tpt, tree.rhs))
          case _ => None
        }
      }
      def unByNeed(tree: ValDef): ValDef = tree match {
        case ByNeedParam(mods, name, tpt, default) => ValDef(mods, name, tpt, default)
        case _ => tree.duplicate
      }
      def byNameTpt(tpt: Tree): Tree = {
        val DefDef(_, _, _, List(List(ValDef(_, _, byNameTpt, _))), _, _) =
          q"def dummy(dummy: => $tpt) = ???"
        byNameTpt
      }
      object Vararg {
        @tailrec
        def unapply(tpt: Tree): Option[Tree] = tpt match {
          case Annotated(_, arg) => unapply(arg)
          case AppliedTypeTree(Select(Select(Ident(root), scala), repeated), List(arg))
              if root == termNames.ROOTPKG && scala == TermName("scala") &&
                repeated == definitions.RepeatedParamClass.name.decodedName => Some(arg)
          case _ => None
        }
      }
      val isVararg = paramss.flatten.lastOption.flatMap(p => Vararg.unapply(p.tpt)).nonEmpty
      val itparams = tparams.map { case q"$mods type $name[..$tparams] >: $low <: $high" =>
        q"${mods.unVariant} type $name[..$tparams] >: $low <: $high"
      }
      val tparamrefs = tparams.map(tparam => Ident(tparam.name))

      // step 1: validate the shape of the class
      if (mods.hasFlag(SEALED)) c.abort(cdef.pos, "sealed is redundant for @data classes")
      if (mods.hasFlag(FINAL)) c.abort(cdef.pos, "final is redundant for @data classes")
      if (mods.hasFlag(CASE)) c.abort(cdef.pos, "case is redundant for @data classes")
      if (mods.hasFlag(ABSTRACT)) c.abort(cdef.pos, "@data classes cannot be abstract")
      if (paramss.isEmpty) c.abort(cdef.pos, "@data classes must define a non-empty parameter list")

      // step 2: create parameters, generate getters and validation checks
      val paramss1 = paramss.map(_.map {
        case VanillaParam(mods, name, tpt, default) =>
          stats1 += q"$DataTyperMacrosModule.nullCheck(this.$name)"
          stats1 += q"$DataTyperMacrosModule.emptyCheck(this.$name)"
          q"${mods.unPrivate} val $name: $tpt = $default"
        case VarargParam(mods, name, tpt, default) =>
          stats1 += q"$DataTyperMacrosModule.nullCheck(this.$name)"
          stats1 += q"$DataTyperMacrosModule.emptyCheck(this.$name)"
          q"${mods.unPrivate} val $name: $tpt = $default"
        case ByNeedParam(mods, name, tpt, default) =>
          val flagName = TermName(s"${name}Flag")
          val statusName = TermName(s"is${name.toString.capitalize}Loaded")
          val valueName = TermName(s"${name}Value")
          val storageName = TermName(s"${name}Storage")
          val getterName = name
          val paramName = TermName(s"_$name")
          val paramTpt = tq"_root_.scala.Function0[$tpt]"
          stats1 +=
            q"@$AdtMetadataModule.byNeedField private[this] var $flagName: _root_.scala.Boolean = false"
          stats1 += q"def $statusName: _root_.scala.Boolean = this.$flagName"
          stats1 += q"@$AdtMetadataModule.byNeedField private[this] var $storageName: $tpt = _"
          stats1 +=
            q"""
            def $getterName = {
              if (!this.$flagName) {
                val $valueName = this.$paramName()
                $DataTyperMacrosModule.nullCheck($valueName)
                $DataTyperMacrosModule.emptyCheck($valueName)
                this.$paramName = null
                this.$storageName = $valueName
                this.$flagName = true
              }
              this.$storageName
            }
          """
          q"${mods.mkPrivate.mkByneed.mkMutable} val $paramName: $paramTpt = $default"
      })

      // step 3: implement Object
      if (needs(TermName("toString"), companion = false, duplicate = false)) stats1 +=
        q"override def toString: _root_.scala.Predef.String = _root_.scala.runtime.ScalaRunTime._toString(this)"
      if (needs(TermName("hashCode"), companion = false, duplicate = false)) stats1 +=
        q"override def hashCode: _root_.scala.Int = _root_.scala.runtime.ScalaRunTime._hashCode(this)"
      if (needs(TermName("equals"), companion = false, duplicate = false)) {
        stats1 +=
          q"override def canEqual(other: _root_.scala.Any): _root_.scala.Boolean = other.isInstanceOf[$name[..$tparamrefs]]"
        stats1 +=
          q"""
          override def equals(other: _root_.scala.Any): _root_.scala.Boolean = (
            this.canEqual(other) && (other match {
              case other: Product if this.productArity == other.productArity =>
                this.productIterator sameElements other.productIterator
              case _ =>
                false
            })
          )
        """
      }

      // step 4: implement Product
      parents1 += tq"_root_.scala.Product"
      if (needs(TermName("product"), companion = false, duplicate = false)) {
        val params: List[ValDef] = paramss.head
        stats1 += q"override def productPrefix: _root_.scala.Predef.String = ${name.toString}"
        stats1 += q"override def productArity: _root_.scala.Int = ${params.length}"
        val pelClauses = ListBuffer[Tree]()
        pelClauses ++= params.zipWithIndex.map { case (v, i) => cq"$i => this.${v.name}" }
        pelClauses += cq"_ => throw new _root_.scala.IndexOutOfBoundsException(n.toString)"
        stats1 +=
          q"override def productElement(n: _root_.scala.Int): Any = n match { case ..$pelClauses }"
        stats1 +=
          q"override def productIterator: _root_.scala.Iterator[_root_.scala.Any] = _root_.scala.runtime.ScalaRunTime.typedProductIterator(this)"
      }

      // step 5: implement Serializable
      parents1 += tq"_root_.scala.Serializable"

      // step 6: generate copy
      if (needs(TermName("copy"), companion = false, duplicate = false) && !isVararg) {
        val copyParamss = paramss.map(_.map {
          case VanillaParam(mods, name, tpt, default) => q"$mods val $name: $tpt = this.$name"
          case VarargParam(mods, name, tpt, default) => q"$mods val $name: $tpt = this.$name"
          // NOTE: This doesn't compile, producing nonsensical errors
          // about incompatibility between the default value and the type of the parameter
          // e.g. "expected: => T, actual: T"
          // Therefore, I'm making the parameter of copy eager, even though I'd like it to be lazy.
          // case ByNeedParam(mods, name, tpt, default) => q"$mods val $name: ${byNameTpt(tpt)} = this.$name"
          case ByNeedParam(mods, name, tpt, default) => q"$mods val $name: $tpt = this.$name"
        })
        val copyArgss = paramss.map(_.map {
          case VanillaParam(mods, name, tpt, default) => q"$name"
          case VarargParam(mods, name, tpt, default) => q"$name: _*"
          case ByNeedParam(mods, name, tpt, default) => q"(() => $name)"
        })
        stats1 +=
          q"def copy[..$itparams](...$copyParamss): $name[..$tparamrefs] = new $name[..$tparamrefs](...$copyArgss)"
      }

      // step 7: generate Companion.apply
      if (needs(TermName("apply"), companion = true, duplicate = false)) {
        val applyParamss = paramss.map(_.map {
          case VanillaParam(mods, name, tpt, default) => q"$mods val $name: $tpt = $default"
          case VarargParam(mods, name, tpt, default) => q"$mods val $name: $tpt = $default"
          case ByNeedParam(mods, name, tpt, default) =>
            q"$mods val $name: ${byNameTpt(tpt)} = $default"
        })
        val applyArgss = paramss.map(_.map {
          case VanillaParam(mods, name, tpt, default) => q"$name"
          case VarargParam(mods, name, tpt, default) => q"$name: _*"
          case ByNeedParam(mods, name, tpt, default) => q"(() => $name)"
        })
        mstats1 +=
          q"def apply[..$itparams](...$applyParamss): $name[..$tparamrefs] = new $name[..$tparamrefs](...$applyArgss)"
      }

      // step 8: generate Companion.unapply
      val unapplyName = if (isVararg) TermName("unapplySeq") else TermName("unapply")
      if (needs(TermName("unapply"), companion = true, duplicate = false) &&
        needs(TermName("unapplySeq"), companion = true, duplicate = false)) {
        val unapplyParamss = paramss.map(_.map(unByNeed))
        val unapplyParams = unapplyParamss.head
        if (unapplyParams.length > 22) {
          // do nothing
        } else if (unapplyParams.length != 0) {
          val successTargs = unapplyParams.map {
            case VanillaParam(mods, name, tpt, default) => tpt
            case VarargParam(mods, name, Vararg(tpt), default) => tq"_root_.scala.Seq[$tpt]"
            case ByNeedParam(mods, name, tpt, default) => tpt
          }
          val successTpe = tq"(..$successTargs)"
          val successArgs = q"(..${unapplyParams.map(p => q"x.${p.name}")})"
          mstats1 +=
            q"""
            def $unapplyName[..$itparams](x: $name[..$tparamrefs]): Option[$successTpe] = {
              if (x == null) _root_.scala.None
              else _root_.scala.Some($successArgs)
            }
          """
        } else mstats1 += q"def $unapplyName(x: $name): Boolean = true"
      }

      val cdef1 =
        q"${mods1.mkFinal} class $name[..$tparams] $ctorMods(...$paramss1) extends { ..$earlydefns } with ..$parents1 { $self => ..$stats1 }"
      val mdef1 =
        q"$mmods1 object $mname extends { ..$mearlydefns } with ..$mparents { $mself => ..$mstats1 }"
      List(cdef1, mdef1)
    }

    override def transformModule(mdef: ModuleDef): ModuleDef = {
      val q"$mmods object $mname extends { ..$mearlydefns } with ..$mparents { $mself => ..$mstats }" =
        mdef

      // step 1: validate the shape of the module
      if (mmods.hasFlag(FINAL)) c.abort(mdef.pos, "final is redundant for @data objects")
      if (mmods.hasFlag(CASE)) c.abort(mdef.pos, "case is redundant for @data objects")

      q"${mmods.mkCase} object $mname extends { ..$mearlydefns } with ..$mparents { $mself => ..$mstats }"
    }
  })

  def none(annottees: Tree*): Tree = annottees.transformAnnottees(new ImplTransformer {
    override def transformModule(mdef: ModuleDef): ModuleDef = {
      val q"new $_(...$argss).macroTransform(..$_)" = c.macroApplication
      val q"$mmods object $mname extends { ..$mearlydefns } with ..$mparents { $mself => ..$mstats }" =
        mdef
      val manns1 = ListBuffer[Tree]() ++ mmods.annotations
      def mmods1 = mmods.mapAnnotations(_ => manns1.toList)
      val mstats1 = ListBuffer[Tree]() ++ mstats

      manns1 += q"new $DataAnnotation"
      mstats1 += q"override def isEmpty: $BooleanClass = true"

      q"$mmods1 object $mname extends { ..$mearlydefns } with ..$mparents { $mself => ..$mstats1 }"
    }
  })
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy