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
The 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