dotty.tools.dotc.transform.MacroAnnotations.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala3-compiler_3 Show documentation
Show all versions of scala3-compiler_3 Show documentation
scala3-compiler-bootstrapped
package dotty.tools.dotc
package transform
import scala.language.unsafeNulls
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.Trees.*
import dotty.tools.dotc.config.Printers.{macroAnnot => debug}
import dotty.tools.dotc.core.Annotations.*
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.core.Decorators.*
import dotty.tools.dotc.core.DenotTransformers.DenotTransformer
import dotty.tools.dotc.core.Flags.*
import dotty.tools.dotc.core.MacroClassLoader
import dotty.tools.dotc.core.Symbols.*
import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.quoted.*
import dotty.tools.dotc.util.SrcPos
import scala.quoted.runtime.impl.{QuotesImpl, SpliceScope}
import scala.quoted.Quotes
import scala.util.control.NonFatal
import java.lang.reflect.InvocationTargetException
class MacroAnnotations:
import tpd.*
import MacroAnnotations.*
/** Expands every macro annotation that is on this tree.
* Returns a list with transformed definition and any added definitions.
*/
def expandAnnotations(tree: MemberDef)(using Context): List[DefTree] =
if !hasMacroAnnotation(tree.symbol) then
List(tree)
else if tree.symbol.is(Module) && !tree.symbol.isClass then
// only class is transformed
List(tree)
else if tree.symbol.isType && !tree.symbol.isClass then
report.error("macro annotations are not supported on type", tree)
List(tree)
else
debug.println(i"Expanding macro annotations of:\n$tree")
val macroInterpreter = new Interpreter(tree.srcPos, MacroClassLoader.fromContext)
val allTrees = List.newBuilder[DefTree]
var insertedAfter: List[List[DefTree]] = Nil
// Apply all macro annotation to `tree` and collect new definitions in order
val transformedTree: DefTree = tree.symbol.annotations.foldLeft(tree) { (tree, annot) =>
if isMacroAnnotation(annot) then
debug.println(i"Expanding macro annotation: ${annot}")
// Interpret call to `new myAnnot(..).transform(using )()`
val transformedTrees =
try callMacro(macroInterpreter, tree, annot)
catch
// TODO: Replace this case when scala.annaotaion.MacroAnnotation is no longer experimental and reflectiveSelectable is not used
// Replace this case with the nested cases.
case ex0: InvocationTargetException =>
ex0.getCause match
case ex: scala.quoted.runtime.StopMacroExpansion =>
if !ctx.reporter.hasErrors then
report.error("Macro expansion was aborted by the macro without any errors reported. Macros should issue errors to end-users when aborting a macro expansion with StopMacroExpansion.", annot.tree)
List(tree)
case Interpreter.MissingClassDefinedInCurrentRun(sym) =>
Interpreter.suspendOnMissing(sym, annot.tree)
case NonFatal(ex) =>
val stack0 = ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.MacroAnnotations")
val stack = stack0.take(1 + stack0.lastIndexWhere(_.getMethodName == "transform"))
val msg =
em"""Failed to evaluate macro.
| Caused by ${ex.getClass}: ${if (ex.getMessage == null) "" else ex.getMessage}
| ${stack.mkString("\n ")}
|"""
report.error(msg, annot.tree)
List(tree)
case _ =>
throw ex0
transformedTrees.span(_.symbol != tree.symbol) match
case (prefixed, newTree :: suffixed) =>
allTrees ++= prefixed
insertedAfter = suffixed :: insertedAfter
prefixed.foreach(checkMacroDef(_, tree, annot))
suffixed.foreach(checkMacroDef(_, tree, annot))
transform.TreeChecker.checkMacroGeneratedTree(tree, newTree)
newTree
case (Nil, Nil) =>
report.error(i"Unexpected `Nil` returned by `(${annot.tree}).transform(..)` during macro expansion", annot.tree.srcPos)
tree
case (_, Nil) =>
report.error(i"Transformed tree for ${tree} was not return by `(${annot.tree}).transform(..)` during macro expansion", annot.tree.srcPos)
tree
else
tree
}
allTrees += transformedTree
insertedAfter.foreach(allTrees.++=)
val result = allTrees.result()
debug.println(result.map(_.show).mkString("expanded to:\n", "\n", ""))
result
/** Interpret the code `new annot(..).transform(using )()` */
private def callMacro(interpreter: Interpreter, tree: MemberDef, annot: Annotation)(using Context): List[MemberDef] =
// TODO: Remove when scala.annaotaion.MacroAnnotation is no longer experimental
import scala.reflect.Selectable.reflectiveSelectable
type MacroAnnotation = {
def transform(using Quotes)(tree: Object/*Erased type of quotes.refelct.Definition*/): List[MemberDef /*quotes.refelct.Definition known to be MemberDef in QuotesImpl*/]
}
// Interpret macro annotation instantiation `new myAnnot(..)`
val annotInstance = interpreter.interpret[MacroAnnotation](annot.tree).get
// TODO: Remove when scala.annaotaion.MacroAnnotation is no longer experimental
assert(annotInstance.getClass.getClassLoader.loadClass("scala.annotation.MacroAnnotation").isInstance(annotInstance))
val quotes = QuotesImpl()(using SpliceScope.contextWithNewSpliceScope(tree.symbol.sourcePos)(using MacroExpansion.context(tree)).withOwner(tree.symbol.owner))
annotInstance.transform(using quotes)(tree.asInstanceOf[quotes.reflect.Definition])
/** Check that this tree can be added by the macro annotation */
private def checkMacroDef(newTree: DefTree, annotatedTree: Tree, annot: Annotation)(using Context) =
transform.TreeChecker.checkMacroGeneratedTree(annotatedTree, newTree)
val sym = newTree.symbol
val annotated = annotatedTree.symbol
if sym.isType && !sym.isClass then
report.error(i"macro annotation cannot return a `type`. $annot tried to add $sym", annot.tree)
else if sym.owner != annotated.owner && !(annotated.owner.isPackageObject && (sym.isClass || sym.is(Module)) && sym.owner == annotated.owner.owner) then
report.error(i"macro annotation $annot added $sym with an inconsistent owner. Expected it to be owned by ${annotated.owner} but was owned by ${sym.owner}.", annot.tree)
else if annotated.isClass && annotated.owner.is(Package) /*&& !sym.isClass*/ then
report.error(i"macro annotation can not add top-level ${sym.showKind}. $annot tried to add $sym.", annot.tree)
object MacroAnnotations:
/** Is this an annotation that implements `scala.annation.MacroAnnotation` */
def isMacroAnnotation(annot: Annotation)(using Context): Boolean =
annot.tree.symbol.maybeOwner.derivesFrom(defn.MacroAnnotationClass)
/** Is this symbol annotated with an annotation that implements `scala.annation.MacroAnnotation` */
def hasMacroAnnotation(sym: Symbol)(using Context): Boolean =
sym.getAnnotation(defn.MacroAnnotationClass).isDefined
© 2015 - 2025 Weber Informatics LLC | Privacy Policy