
com.avsystem.commons.macros.misc.MiscMacros.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-macros_2.13 Show documentation
Show all versions of commons-macros_2.13 Show documentation
AVSystem commons library for Scala
package com.avsystem.commons
package macros.misc
import com.avsystem.commons.macros.AbstractMacroCommons
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.reflect.macros.{blackbox, whitebox}
import scala.util.control.NoStackTrace
final class MiscMacros(ctx: blackbox.Context) extends AbstractMacroCommons(ctx) {
import c.universe._
lazy val MaterializeWithAT: Type = getType(tq"$CommonsPkg.meta.MacroInstances.materializeWith")
def infer[T: WeakTypeTag]: Tree =
instrument(inferTpe(weakTypeOf[T], "", NoPosition, withMacrosDisabled = false))
def clueInfer[T: WeakTypeTag](clue: Tree): Tree =
instrument(inferTpe(weakTypeOf[T], clueStr(clue), clue.pos, withMacrosDisabled = false))
def inferNonMacro[T: WeakTypeTag](clue: Tree): Tree =
instrument(inferTpe(weakTypeOf[T], clueStr(clue), clue.pos, withMacrosDisabled = true))
private def clueStr(clue: Tree): String = clue match {
case StringLiteral(str) => str
case _ => abort(s"clue must be a String literal, $clue is not")
}
private def inferTpe(tpe: Type, clue: String, pos: Position, withMacrosDisabled: Boolean): Tree =
inferImplicitValue(tpe, withMacrosDisabled = withMacrosDisabled, expandMacros = true) match {
case EmptyTree => abortAt(clue + implicitNotFoundMsg(tpe), pos)
case t => t
}
def sourceInfo: Tree = {
def enclosingSymName(sym: Symbol) =
sym.filter(_.isTerm).map(_.asTerm.getter).orElse(sym).name.decodedName.toString
val pos = c.enclosingPosition
q"""
$MiscPkg.SourceInfo(
${pos.source.path},
${pos.source.file.name},
${pos.point},
${pos.line},
${pos.column},
${pos.source.lineToString(pos.line - 1)},
$ListObj(..${ownerChain.takeWhile(_ != rootMirror.RootClass).map(enclosingSymName)})
)
"""
}
def crossImpl(forJvm: Tree, forJs: Tree): Tree =
if (isScalaJs) forJs else forJvm
def enumValName: Tree = {
def omitAnonClass(owner: Symbol): Symbol =
if (owner.isConstructor && owner.owner.name.toString.contains("$anon"))
owner.owner.owner
else owner
val owner = omitAnonClass(c.internal.enclosingOwner)
val valid = owner.isTerm && owner.owner == c.prefix.tree.symbol && {
val term = owner.asTerm
term.isVal && term.isFinal && !term.isLazy && term.getter.isPublic &&
term.typeSignature <:< getType(tq"${c.prefix}.Value")
}
if (!valid) {
abort("ValueEnum must be assigned to a public, final, non-lazy val in its companion object " +
"with explicit `Value` type annotation, e.g. `final val MyEnumValue: Value = new MyEnumClass")
}
q"new ${c.prefix}.ValName(${owner.asTerm.getter.name.decodedName.toString})"
}
def optionalizeFirstArg(expr: Tree): Tree = expr match {
case Apply(MaybeTypeApply(Select(prefix, name: TermName), targs), head :: tail) =>
q"if($head ne null) $expr else ${c.untypecheck(prefix)}.$name[..$targs](..$tail)"
case _ =>
c.abort(expr.pos, "function application expected")
}
def compilationError(error: Tree): Tree = error match {
case StringLiteral(errorStr) =>
abortAt(errorStr, error.pos)
case t =>
c.abort(t.pos, "Expected string literal here")
}
case class NonConcreteTypeException(tpe: Type) extends RuntimeException with NoStackTrace
def javaClassName[T: WeakTypeTag]: Tree = instrument {
val tpe = weakTypeOf[T].dealias
if (tpe.typeSymbol.isClass && tpe.typeSymbol != definitions.ArrayClass)
q"new $MiscPkg.JavaClassName(${javaClassName(tpe.erasure.typeSymbol)})"
else
abort(s"$tpe does not represent a regular class")
}
private def javaClassName(sym: Symbol): String = {
val nameSuffix = if (sym.isModuleClass && !sym.isPackageClass) "$" else ""
val selfName = sym.name.encodedName.toString + nameSuffix
val owner = sym.owner
val prefix =
if (owner == rootMirror.RootClass) ""
else if (owner.isPackageClass) javaClassName(owner) + "."
else if (owner.isModuleClass) javaClassName(owner)
else javaClassName(owner) + "$"
prefix + selfName
}
def typeString[T: WeakTypeTag]: Tree = instrument {
val tpe = weakTypeOf[T]
try typeStringParts(tpe) match {
case List(Select(pre, TermName("value"))) => pre
case trees => q"new $MiscPkg.TypeString[$tpe](${mkStringConcat(trees)})"
} catch {
case NonConcreteTypeException(stpe) =>
abort(s"Could not materialize TypeString for $tpe because instance for $stpe is lacking")
}
}
private val allowedSymbols: mutable.Set[Symbol] = new mutable.HashSet
def withAllowed[T](tparams: List[Symbol])(code: => T): T = {
allowedSymbols ++= tparams
try code finally {
allowedSymbols --= tparams
}
}
private def maybeParens(repr: List[Tree], parens: Boolean): List[Tree] =
if (parens) lit("(") :: repr ::: lit(")") :: Nil
else repr
def typeStringParts(tpe: Type, parens: Boolean = false): List[Tree] = {
val resultTpe = getType(tq"$MiscPkg.TypeString[$tpe]")
inferImplicitValue(resultTpe, withMacrosDisabled = true) match {
case EmptyTree => mkTypeString(tpe, parens)
case tree => maybeParens(List(q"$tree.value"), parens)
}
}
def mkStringConcat(trees: List[Tree]): Tree = trees match {
case Nil => StringLiteral("")
case single :: Nil => single
case StringLiteral(str1) :: StringLiteral(str2) :: tail =>
mkStringConcat(StringLiteral(str1 + str2) :: tail)
case head :: tail =>
q"$head+${mkStringConcat(tail)}"
}
def join(trees: List[List[Tree]], sep: String): List[Tree] = trees match {
case Nil => Nil
case List(single) => single
case head :: tail => head ::: lit(sep) :: join(tail, sep)
}
def lit(str: String): Tree =
StringLiteral(str)
private def isOpChar(ch: Char): Boolean =
ch != '`' && !ch.isLetterOrDigit
private def isOpSafe(ch: Char): Boolean =
ch == '.' || ch.isWhitespace
def mkNameString(name: Name, prefix: String = "", suffix: String = ""): String = {
val nameRepr = showCode(Ident(name))
val afterPrefix = if (prefix.nonEmpty && !isOpSafe(prefix.last) && isOpChar(nameRepr.head)) " " else ""
val beforeSuffix = if (suffix.nonEmpty && !isOpSafe(suffix.head) && isOpChar(nameRepr.last)) " " else ""
s"$prefix$afterPrefix$nameRepr$beforeSuffix$suffix"
}
def isRefTo(quantified: Symbol, arg: Type): Boolean = arg match {
case TypeRef(NoPrefix, `quantified`, Nil) => true
case _ => false
}
def areIndependent(quantified: List[Symbol]): Boolean =
quantified.forall(first => quantified.forall(second => !first.typeSignature.contains(second)))
def mkMemberDefString(s: Symbol, wildcard: Boolean = false): List[Tree] = s match {
case ExistentialSingleton(_, name, sig) =>
lit(s"val ${mkNameString(name, suffix = ": ")}") :: mkTypeString(sig)
case ts: TypeSymbol =>
val variance = if (ts.isCovariant) "+" else if (ts.isContravariant) "-" else ""
val beforeName = if (ts.isParameter) variance else "type "
val finalPrefix = if (ts.isParameter) "" else " = "
val baseDecl = if (wildcard) "_" else mkNameString(ts.name, prefix = beforeName)
lit(baseDecl) :: mkSignatureString(ts.typeSignature, finalPrefix)
case ts: TermSymbol =>
val sig = ts.typeSignature
val paramless = sig.typeParams.isEmpty && sig.paramLists.isEmpty
val beforeName =
if (ts.isParameter) ""
else if (ts.isGetter && ts.setter != NoSymbol) "var "
else if (ts.isGetter) "val "
else "def "
val baseDecl = mkNameString(ts.name, prefix = beforeName, suffix = if (paramless) ": " else "")
lit(baseDecl) :: mkSignatureString(sig, if (paramless) "" else ": ")
}
def mkSignatureString(sig: Type, finalPrefix: String): List[Tree] = sig match {
case TypeBounds(lo, hi) =>
val loRepr =
if (lo =:= typeOf[Nothing]) Nil
else lit(" >: ") :: typeStringParts(lo)
val hiRepr =
if (hi =:= typeOf[Any]) Nil
else lit(" <: ") :: typeStringParts(hi)
loRepr ++ hiRepr
case PolyType(tparams, resultType) => withAllowed(tparams) {
lit("[") :: join(tparams.map(mkMemberDefString(_)), ", ") ::: lit("]") ::
mkSignatureString(resultType, finalPrefix)
}
case MethodType(params, resultType) =>
val pre = if (params.headOption.exists(_.isImplicit)) "(implicit " else "("
lit(pre) :: join(params.map(mkMemberDefString(_)), ", ") ::: lit(")") ::
withAllowed(params)(mkSignatureString(resultType, finalPrefix))
case NullaryMethodType(resultType) =>
mkSignatureString(resultType, finalPrefix)
case _ =>
lit(finalPrefix) :: mkTypeString(sig)
}
private val autoImported: Set[Symbol] = Set(
definitions.ScalaPackage,
definitions.JavaLangPackage,
definitions.PredefModule,
rootMirror.RootPackage
)
def isStaticPrefix(pre: Type): Boolean = pre match {
case SingleType(ppre, _) => isStaticPrefix(ppre)
case ThisType(sym) => sym.isPublic && sym.isStatic && sym.isModuleClass
case TypeRef(ppre, sym, Nil) => sym.isPublic && sym.isStatic && sym.isModuleClass && isStaticPrefix(ppre)
case _ => false
}
def isAllowedWithoutPrefix(s: Symbol): Boolean =
(s.isType && s.asType.isAliasType) || allowedSymbols.contains(s)
def mkTypeString(tpe: Type, parens: Boolean = false): List[Tree] = tpe match {
case _ if tpe =:= typeOf[AnyRef] => List(lit("AnyRef"))
case TypeRef(NoPrefix, ExistentialSingleton(_, name, _), Nil) =>
List(lit(mkNameString(name)))
case TypeRef(_, sym, List(arg)) if sym == definitions.ByNameParamClass =>
val res = lit("=> ") :: typeStringParts(arg)
maybeParens(res, parens)
case TypeRef(_, sym, List(arg)) if sym == definitions.RepeatedParamClass =>
val argRepr = typeStringParts(arg)
val starSafe = argRepr.last match {
case StringLiteral(s) => s.charAt(s.length - 1) match {
case ']' | ')' | '}' | '`' => true
case ch => ch.isLetterOrDigit
}
case _ => false
}
argRepr ::: lit(if (starSafe) "*" else " *") :: Nil
case TypeRef(_, sym, args) if definitions.FunctionClass.seq.contains(sym) =>
val fargs = args.init
val fres = args.last
val fargsRes = fargs match {
case Nil => List(lit("()"))
case List(single) => typeStringParts(single, parens = true)
case _ => maybeParens(join(fargs.map(typeStringParts(_)), ", "), parens = true)
}
val res = fargsRes ::: lit(" => ") :: typeStringParts(fres)
maybeParens(res, parens)
case TypeRef(_, sym, args) if definitions.TupleClass.seq.contains(sym) =>
lit("(") :: join(args.map(typeStringParts(_)), ", ") ::: lit(")") :: Nil
case TypeRef(pre, sym, args) if pre != NoPrefix || isAllowedWithoutPrefix(sym) =>
val dealiased = tpe.dealias
if (dealiased.typeSymbol != sym && !isStaticPrefix(pre))
mkTypeString(dealiased, parens = false)
else {
val argsReprs =
if (args.isEmpty) Nil
else lit("[") +: join(args.map(typeStringParts(_)), ", ") :+ lit("]")
mkTypePath(pre, sym.name) ::: argsReprs
}
case SingleType(pre, sym) if pre != NoPrefix || isAllowedWithoutPrefix(sym) =>
mkTypePath(pre, sym.name) :+ lit(".type")
case ThisType(sym) if sym.isStatic && sym.isModuleClass =>
List(lit(mkStaticPrefix(sym) + "type"))
case ExistentialType(quantified, TypeRef(pre, sym, args))
if quantified.corresponds(args)(isRefTo) && quantified.forall(s => !pre.contains(s)) && areIndependent(quantified) =>
withAllowed(quantified) {
val wildcards = lit("[") +: join(quantified.map(mkMemberDefString(_, wildcard = true)), ", ") :+ lit("]")
mkTypePath(pre, sym.name) ::: wildcards
}
case ExistentialType(quantified, underlying) =>
withAllowed(quantified) {
val typeDefs = join(quantified.map(mkMemberDefString(_)), "; ")
maybeParens(typeStringParts(underlying) ::: lit(" forSome {") :: typeDefs ::: lit("}") :: Nil, parens)
}
case RefinedType(bases, scope) =>
val basesRepr = bases match {
case List(anyref) if anyref =:= typeOf[AnyRef] => Nil
case _ => join(bases.map(typeStringParts(_)), " with ")
}
val filteredScope = scope.iterator.filter(s => !s.isTerm || !s.asTerm.isSetter).toList
val scopeRepr =
if (filteredScope.isEmpty) Nil
else {
val memberDefs = withAllowed(List(tpe.typeSymbol))(join(filteredScope.map(mkMemberDefString(_)), "; "))
lit("{") :: memberDefs ::: lit("}") :: Nil
}
val space = if (basesRepr.nonEmpty && scopeRepr.nonEmpty) " " else ""
maybeParens(basesRepr ::: lit(space) :: scopeRepr, parens)
case AnnotatedType(_, underlying) =>
mkTypeString(underlying, parens = false)
case _ =>
throw NonConcreteTypeException(tpe)
}
def mkStaticPrefix(sym: Symbol): String =
if (sym == rootMirror.RootClass) ""
else mkStaticPrefix(sym.owner) + mkNameString(sym.name) + "."
def mkTypePath(pre: Type, name: Name): List[Tree] = pre match {
case t if autoImported.contains(t.termSymbol) =>
List(lit(mkNameString(name)))
case NoPrefix =>
List(lit(mkNameString(name)))
case SingleType(pkg, sym) if sym.name == termNames.PACKAGE && pkg.typeSymbol.isPackageClass =>
mkTypePath(pkg, name)
case SingleType(ppre, sym) if ppre != NoPrefix || isAllowedWithoutPrefix(sym) =>
mkTypePath(ppre, sym.name) ::: lit("." + mkNameString(name)) :: Nil
case ThisType(sym) if sym.isStatic && sym.isModuleClass =>
List(lit(mkStaticPrefix(sym) + mkNameString(name)))
case ThisType(sym) if allowedSymbols.contains(sym) =>
List(lit(mkNameString(name)))
case TypeRef(NoPrefix, ExistentialSingleton(_, valName, _), Nil) =>
List(lit(mkNameString(valName) + "." + mkNameString(name)))
case _ =>
val singletonPrefix = pre.typeSymbol.isModuleClass || pre.termSymbol != NoSymbol
val selection = if (singletonPrefix) "." else "#"
mkTypeString(pre, parens = !singletonPrefix) :+ lit(mkNameString(name, prefix = selection))
}
def lazyMetadata(metadata: Tree): Tree =
q"${c.prefix}($metadata)"
def mkValueOf[T: WeakTypeTag]: Tree = instrument {
val tpe = weakTypeOf[T].dealias
singleValueFor(tpe) match {
case Some(sv) => q"new $MiscPkg.ValueOf[$tpe]($sv)"
case None => abort(s"$tpe is not a singleton type")
}
}
def macroInstances: Tree = {
val resultTpe = c.macroApplication.tpe
val applySig = resultTpe.member(TermName("apply")).typeSignatureIn(resultTpe)
val implicitsTpe = applySig.paramLists.head.head.typeSignature
val instancesTpe = applySig.finalResultType
val instTs = instancesTpe.typeSymbol
if (!(instTs.isClass && instTs.isAbstract)) {
abort(s"Expected trait or abstract class type, got $instancesTpe")
}
val instancesMethods = instancesTpe.members.iterator
.filter(m => m.isAbstract && m.isMethod && !m.asTerm.isSetter).map(_.asMethod).toList.reverse
val CompanionParamName = c.freshName(TermName("companion"))
def impl(singleMethod: Option[Symbol]): Tree = {
val impls = instancesMethods.map { m =>
val sig = m.typeSignatureIn(instancesTpe)
val resultTpe = sig.finalResultType.dealias
val materializer =
if (singleMethod.exists(_ != m))
q"$PredefObj.???"
else findAnnotation(m, MaterializeWithAT) match {
case Some(annot) =>
val errorPos = annot.errorPos.getOrElse(c.enclosingPosition)
annot.tree match {
case Apply(_, List(prefix, macroNameTree)) =>
val macroName = macroNameTree match {
case StringLiteral(name) => name
case t if t.symbol.isSynthetic && t.symbol.name.decodedName == TermName("$default$2") =>
"materialize"
case _ => abortAt("expected string literal as second argument of @materializeWith", errorPos)
}
q"$prefix.${TermName(macroName)}"
case _ =>
abortAt("bad @materializeWith annotation", errorPos)
}
case None =>
val resultCompanion = typedCompanionOf(resultTpe)
.getOrElse(abort(s"$resultTpe has no companion object with `materialize` macro"))
q"$resultCompanion.materialize"
}
val instTpeTree = treeForType(sig.finalResultType)
if (!m.isGetter) {
val tparamDefs = sig.typeParams.map(typeSymbolToTypeDef(_, forMethod = true))
val paramDefs = sig.paramLists.map(_.map(paramSymbolToValDef))
val argss = sig.paramLists match {
case List(Nil) => Nil
case paramss => paramss.filterNot(_.exists(_.isImplicit)).map(_.map(s => q"${s.name.toTermName}"))
}
q"def ${m.name}[..$tparamDefs](...$paramDefs): $instTpeTree = $materializer(...$argss)"
}
else if (m.isVar || m.setter != NoSymbol)
q"var ${m.name}: $instTpeTree = $materializer"
else
q"val ${m.name}: $instTpeTree = $materializer"
}
val implicitsName = c.freshName(TermName("implicits"))
def implicitImports(tpe: Type, expr: Tree): List[Tree] = {
val dtpe = tpe.dealias
if (dtpe =:= typeOf[Unit]) Nil
else if (definitions.TupleClass.seq.contains(dtpe.typeSymbol))
dtpe.typeArgs.zipWithIndex.flatMap {
case (ctpe, idx) => implicitImports(ctpe, q"$expr.${TermName(s"_${idx + 1}")}")
}
else List(q"import $expr._")
}
q"""
new $resultTpe {
def apply($implicitsName: $implicitsTpe, $CompanionParamName: Any): $instancesTpe = {
..${implicitImports(implicitsTpe, Ident(implicitsName))}
new $instancesTpe { ..$impls; () }
}
}
"""
}
//If full implementation doesn't typecheck, find the first problematic typeclass and limit
//compilation errors to that one in order to not overwhelm the user but rather report errors gradually
val fullImpl = impl(None)
debug(show(fullImpl))
val result = c.typecheck(fullImpl, silent = true) match {
case EmptyTree =>
instancesMethods.iterator.map(m => impl(Some(m)))
.find(t => c.typecheck(t, silent = true) == EmptyTree)
.getOrElse(fullImpl)
case t => t
}
enclosingConstructorCompanion match {
case NoSymbol => result
case companionSym =>
// Replace references to companion object being constructed with casted reference to
// `companion` parameter. All this horrible wiring is to workaround stupid overzealous Scala validation of
// self-reference being passed to super constructor parameter (https://github.com/scala/bug/issues/7666)
// We're going to replace some parts of already typechecked tree. This means we must insert already
// typechecked replacements.
val replacementDecl = result.find {
case ValDef(mods, CompanionParamName, _, EmptyTree) => mods.hasFlag(Flag.PARAM)
case _ => false
}
val replacementSym = replacementDecl.fold(NoSymbol)(_.symbol)
// must construct tree which is already fully typechecked
def replacementTree(orig: Tree): Tree = {
val replacementIdent = internal.setType(
internal.setSymbol(Ident(CompanionParamName), replacementSym),
internal.singleType(NoPrefix, replacementSym)
)
val asInstanceOfMethod = definitions.AnyTpe.member(TermName("asInstanceOf"))
val asInstanceOfSelect = internal.setType(
internal.setSymbol(Select(replacementIdent, asInstanceOfMethod), asInstanceOfMethod),
asInstanceOfMethod.info
)
val typeAppliedCast = internal.setType(
internal.setSymbol(TypeApply(asInstanceOfSelect, List(TypeTree(orig.tpe))), asInstanceOfMethod),
orig.tpe
)
typeAppliedCast
}
object replacer extends Transformer {
override def transform(tree: Tree): Tree = tree match {
case This(_) if tree.symbol == companionSym.asModule.moduleClass => replacementTree(tree)
case _ if tree.symbol == companionSym => replacementTree(tree)
case _ => super.transform(tree)
}
}
replacer.transform(result)
}
}
def posPoint: Tree =
q"${c.enclosingPosition.point}"
def applyUnapplyOrFail(tpe: Type): ApplyUnapply =
applyUnapplyFor(tpe).getOrElse(abort(
s"$tpe is not a case class or case-class like type: no matching apply/unapply pair found"))
def applyBody(rawValuesName: TermName, tpe: Type, au: ApplyUnapply): Tree = {
val args = au.params.zipWithIndex.map { case (param, idx) =>
val res = q"$rawValuesName($idx).asInstanceOf[${actualParamType(param.typeSignature)}]"
if (isRepeated(param)) q"$res: _*" else res
}
au.mkApply(args)
}
def unapplyBody(valueName: TermName, tpe: Type, au: ApplyUnapply): Tree = {
if (au.standardCaseClass) q"$ScalaPkg.Array(..${au.params.map(param => q"$valueName.$param")})"
else {
val companion = typedCompanionOf(tpe).getOrElse(EmptyTree)
val unapplyRes = q"$companion.${au.unapply}[..${tpe.typeArgs}]($valueName)"
au.params match {
case Nil => q"$ScalaPkg.Seq.empty[$ScalaPkg.Any]"
case List(_) => q"$ScalaPkg.Seq($unapplyRes.get)"
case _ =>
val resName = c.freshName(TermName("res"))
val elems = au.params.indices.map(i => q"$resName.${TermName(s"_${i + 1}")}")
q"""
val $resName = $unapplyRes.get
$CollectionPkg.immutable.ArraySeq.unsafeWrapArray($ArrayObj[$ScalaPkg.Any](..$elems))
"""
}
}
}
def applier[T: WeakTypeTag]: Tree = instrument {
val tpe = weakTypeOf[T].dealias
val rawValuesName = c.freshName(TermName("rawValues"))
q"""
new $MiscPkg.Applier[$tpe] {
def apply($rawValuesName: $ScalaPkg.Seq[$ScalaPkg.Any]): $tpe =
${applyBody(rawValuesName, tpe, applyUnapplyOrFail(tpe))}
}
"""
}
def unapplier[T: WeakTypeTag]: Tree = instrument {
val tpe = weakTypeOf[T].dealias
val valueName = c.freshName(TermName("value"))
val au = applyUnapplyOrFail(tpe)
if (au.standardCaseClass && tpe <:< ProductTpe)
q"new $MiscPkg.ProductUnapplier[$tpe]"
else
q"""
new $MiscPkg.Unapplier[$tpe] {
def unapply($valueName: $tpe): $ScalaPkg.Seq[$ScalaPkg.Any] =
${unapplyBody(valueName, tpe, au)}
}
"""
}
def applierUnapplier[T: WeakTypeTag]: Tree = instrument {
val tpe = weakTypeOf[T].dealias
val rawValuesName = c.freshName(TermName("rawValues"))
val valueName = c.freshName(TermName("value"))
val au = applyUnapplyOrFail(tpe)
if (au.standardCaseClass && tpe <:< ProductTpe)
q"""
new $MiscPkg.ProductApplierUnapplier[$tpe] {
def apply($rawValuesName: $ScalaPkg.Seq[$ScalaPkg.Any]): $tpe =
${applyBody(rawValuesName, tpe, au)}
}
"""
else
q"""
new $MiscPkg.ApplierUnapplier[$tpe] {
def apply($rawValuesName: $ScalaPkg.Seq[$ScalaPkg.Any]): $tpe =
${applyBody(rawValuesName, tpe, au)}
def unapply($valueName: $tpe): $ScalaPkg.Seq[$ScalaPkg.Any] =
${unapplyBody(valueName, tpe, au)}
}
"""
}
def assertLocal(sym: Symbol): Symbol = {
if (sym.pos.source != c.enclosingPosition.source) {
abort(s"Macro inspection of $sym can only be done in the same source file where it is defined")
}
sym
}
def safeAnnotTree(annot: Annot): Tree = {
if (containsInaccessibleThises(annot.tree)) {
abortAt(s"Reified annotation ${annot.tree} contains inaccessible this-references", annot.tree.pos)
}
c.untypecheck(annot.tree)
}
def classSymbol(sym: Symbol): ClassSymbol = {
if (sym.isClass) sym.asClass
else abort(s"$sym is not a class")
}
def annotationOf[A: WeakTypeTag, T: WeakTypeTag]: Tree = instrument {
val atpe = weakTypeOf[A]
val tpe = weakTypeOf[T]
val sym = assertLocal(classSymbol(tpe.dealias.typeSymbol))
val annot = findAnnotation(sym, atpe)
.getOrElse(abort(s"No annotation of type $atpe found on $sym"))
q"$MiscPkg.AnnotationOf(${safeAnnotTree(annot)})"
}
def optAnnotationOf[A: WeakTypeTag, T: WeakTypeTag]: Tree = instrument {
val atpe = weakTypeOf[A]
val tpe = weakTypeOf[T]
val sym = assertLocal(classSymbol(tpe.dealias.typeSymbol))
val annotTree = findAnnotation(sym, atpe)
.fold[Tree](q"$CommonsPkg.Opt.Empty")(a => q"$CommonsPkg.Opt(${safeAnnotTree(a)})")
q"$MiscPkg.OptAnnotationOf($annotTree)"
}
def annotationsOf[A: WeakTypeTag, T: WeakTypeTag]: Tree = instrument {
val atpe = weakTypeOf[A]
val tpe = weakTypeOf[T]
val sym = assertLocal(classSymbol(tpe.dealias.typeSymbol))
val annots = allAnnotations(sym, atpe).map(safeAnnotTree)
q"$MiscPkg.AnnotationsOf($ListObj(..$annots))"
}
def hasAnnotation[A: WeakTypeTag, T: WeakTypeTag]: Tree = instrument {
val atpe = weakTypeOf[A]
val tpe = weakTypeOf[T]
val sym = assertLocal(classSymbol(tpe.dealias.typeSymbol))
if (findAnnotation(sym, atpe).nonEmpty)
q"$MiscPkg.HasAnnotation.create[$atpe, $tpe]"
else
abort(s"No annotation of type $atpe found on $sym")
}
def classBeingConstructed: Symbol = {
val ownerConstr = c.internal.enclosingOwner
if (!ownerConstr.isConstructor) {
abort(s"${c.macroApplication.symbol} can only be used as super constructor argument")
}
classSymbol(ownerConstr.owner)
}
def selfAnnotation[A: WeakTypeTag]: Tree = instrument {
val atpe = weakTypeOf[A]
val sym = classBeingConstructed
val annot = findAnnotation(sym, atpe)
.getOrElse(abort(s"No annotation of type $atpe found on $sym"))
q"$MiscPkg.SelfAnnotation(${safeAnnotTree(annot)})"
}
def selfOptAnnotation[A: WeakTypeTag]: Tree = instrument {
val atpe = weakTypeOf[A]
val sym = classBeingConstructed
val annotTree = findAnnotation(sym, atpe)
.fold[Tree](q"$CommonsPkg.Opt.Empty")(a => q"$CommonsPkg.Opt(${safeAnnotTree(a)})")
q"$MiscPkg.SelfOptAnnotation($annotTree)"
}
def selfAnnotations[A: WeakTypeTag]: Tree = instrument {
val atpe = weakTypeOf[A]
val sym = classBeingConstructed
val annots = allAnnotations(sym, atpe).map(safeAnnotTree)
q"$MiscPkg.SelfAnnotations($ListObj(..$annots))"
}
def selfInstance[C: WeakTypeTag]: Tree = instrument {
val TypeRef(pre, constrSym, _) = weakTypeOf[C].typeConstructor
val instance = internal.typeRef(pre, constrSym, List(classBeingConstructed.asType.toType))
q"$MiscPkg.SelfInstance($ImplicitsObj.infer[$instance])"
}
def aggregatedAnnots: Tree = {
val aggregatedMethod = c.internal.enclosingOwner
if (!aggregatedMethod.overrides.contains(AggregatedMethodSym)) {
abort("reifyAggregated macro must only be used to implement AnnotationAggregate.aggregated method")
}
if (aggregatedMethod.asMethod.isGetter || !aggregatedMethod.isFinal) {
abort("AnnotationAggregate.aggregated method implemented with reifyAggregated macro must be a final def")
}
val annotTrees = rawAnnotations(aggregatedMethod)
.filter(_.tree.tpe <:< StaticAnnotationTpe).map(a => c.untypecheck(a.tree))
if (annotTrees.isEmpty) {
warning("no aggregated annotations found on enclosing method")
}
q"$ListObj(..$annotTrees)"
}
def simpleClassName[T: WeakTypeTag]: Tree = instrument {
val sym = classSymbol(weakTypeOf[T].dealias.typeSymbol)
q"$MiscPkg.SimpleClassName(${sym.name.decodedName.toString})"
}
}
final class WhiteMiscMacros(ctx: whitebox.Context) extends AbstractMacroCommons(ctx) {
import c.universe._
lazy val WhenAbsentAT: Type = staticType(tq"$CommonsPkg.serialization.whenAbsent[_]")
def whenAbsentValue: Tree = {
val param = c.internal.enclosingOwner match {
case DefaultValueMethod(p) => p
case p => p
}
findAnnotation(param, WhenAbsentAT).map(_.tree).map {
case Apply(_, List(MaybeTyped(arg, _))) => arg
case t => abort(s"unexpected tree for @whenAbsent annotation: $t")
} getOrElse {
abort(s"no @whenAbsent annotation found on $param of ${param.owner}")
}
}
def inferValue: Tree = {
val param = c.internal.enclosingOwner match {
case DefaultValueMethod(p) => p
case p => p
}
if (param.owner.owner.asType.toType <:< AnnotationTpe && findAnnotation(param, InferAT).nonEmpty)
q"""throw new $ScalaPkg.NotImplementedError("infer.value")"""
else
abort(s"infer.value can be only used as default value of @infer annotation parameters")
}
def autoAnnotationMetadata: Tree = {
val param = c.internal.enclosingOwner match {
case DefaultValueMethod(p) => p
case p => p
}
if (param.owner.owner.asType.toType <:< AnnotationTpe)
q"""throw new $ScalaPkg.NotImplementedError("RpcMetadata.auto")"""
else
abort(s"RpcMetadata.auto can be only used as default value of annotation parameters")
}
def normalizeGadtSubtype(tpref: Tree, value: Tree): Tree = {
def print(msg: String): Unit =
if (c.enclosingPosition.line == 57) {
echo(msg)
}
val StringLiteral(tprefStr) = tpref
val quantified = new ListBuffer[Symbol]
print(show(value.tpe))
val unrefined = value.tpe match {
case RefinedType(List(_, second), _) => second
case t => t
}
print(show(unrefined))
val withFullyDetermined = unrefined.map { t =>
if (t.typeSymbol.name.toString.startsWith(tprefStr)) {
t.typeSymbol.typeSignature match {
case TypeBounds(lo, hi) if lo =:= hi =>
lo
case ts =>
print(t.typeSymbol.name.toString + show(ts))
quantified += t.typeSymbol
t
}
} else t
}
print(show(withFullyDetermined))
val withoutMatchedTypes = internal.existentialAbstraction(quantified.result(), withFullyDetermined)
print(show(withoutMatchedTypes))
val innerQuantified = new mutable.HashSet[Symbol]
val outerQuantified = new ListBuffer[Symbol]
withoutMatchedTypes.foreach {
case ExistentialType(iq, _) => innerQuantified ++= iq
case t if t.typeSymbol.isType =>
if (t.typeSymbol.asType.isExistential && !innerQuantified.contains(t.typeSymbol)) {
outerQuantified += t.typeSymbol
}
case _ =>
}
val normTpe = internal.existentialAbstraction(outerQuantified.result(), withoutMatchedTypes)
print(show(normTpe))
q"$value: ${treeForType(normTpe)}"
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy