org.scalameta.data.data.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of common_2.11 Show documentation
Show all versions of common_2.11 Show documentation
Bag of private and public helpers used in scala.meta's APIs and implementations
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