-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add basic support for TASTy annotations. * Introduce experimental `scala.annotations.TastyAnnotation` * Macro annotations can analyze or modify definitions * Macro annotation can add definition around the annotated definition * Added members are not visible while typing * Added members are not visible to other macro annotations * Added definition must have the same owner * Implement macro annotation expansion * Implemented at Inlining phase * Can use macro annotations in staged expressions (expanded when at stage 0) * Can use staged expression to implement macro annotations * Can insert calls to inline methods in macro annotations * Current limitations (to be loosened) * Can only be used on `def`, `val`, `lazy val` and `var` * Can only add `def`, `val`, `lazy val` and `var` definitions Based on: * #15626 * https://infoscience.epfl.ch/record/294615?ln=en
- Loading branch information
1 parent
1f451ec
commit 33d1889
Showing
58 changed files
with
957 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
compiler/src/dotty/tools/dotc/transform/TastyAnnotations.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
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.SymDenotations.NoDenotation | ||
import dotty.tools.dotc.quoted.* | ||
import dotty.tools.dotc.util.SrcPos | ||
import scala.quoted.runtime.impl.{QuotesImpl, SpliceScope} | ||
|
||
import scala.quoted.Quotes | ||
|
||
class TastyAnnotations(thisPhase: DenotTransformer): | ||
import tpd.* | ||
import TastyAnnotations.* | ||
|
||
/** Expands every TASTy annotation that is on this tree. | ||
* Returns a list with transformed definition and any added definitions. | ||
*/ | ||
def transform(tree: MemberDef)(using Context): List[DefTree] = | ||
if !hasMacro(tree.symbol) then | ||
List(tree) | ||
else if tree.symbol.is(Module) then | ||
if tree.symbol.isClass then // error only reported on module class | ||
report.error("TASTy annotations are not supported on object", tree) | ||
List(tree) | ||
else if tree.symbol.isClass then | ||
report.error("TASTy annotations are not supported on class", tree) | ||
List(tree) | ||
else if tree.symbol.isType then | ||
report.error("TASTy annotations are not supported on type", tree) | ||
List(tree) | ||
else | ||
debug.println(i"Expanding TASTy 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 TASTy annotation to `tree` and collect new definitions in order | ||
val transformedTree: DefTree = tree.symbol.annotations.foldLeft(tree) { (tree, annot) => | ||
if isTastyAnnotation(annot) then | ||
debug.println(i"Expanding TASTy annotation: ${annot}") | ||
|
||
// Interpret call to `new myAnnot(..).transform(using <Quotes>)(<tree>)` | ||
val transformedTrees = callMacro(macroInterpreter, tree, annot) | ||
transformedTrees.span(_.symbol != tree.symbol) match | ||
case (prefixed, newTree :: suffixed) => | ||
allTrees ++= prefixed | ||
insertedAfter = suffixed :: insertedAfter | ||
prefixed.foreach(checkAndEnter(_, tree.symbol, annot)) | ||
suffixed.foreach(checkAndEnter(_, tree.symbol, annot)) | ||
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 <Quotes(ctx)>)(<tree>)` */ | ||
private def callMacro(interpreter: Interpreter, tree: MemberDef, annot: Annotation)(using Context): List[MemberDef] = | ||
// TODO: Remove when scala.annaotaion.TastyAnnotation is no longer experimental | ||
import scala.reflect.Selectable.reflectiveSelectable | ||
type TastyAnnotation = { | ||
def transform(using Quotes)(tree: Object/*Erased type of quotes.refelct.Definition*/): List[MemberDef /*quotes.refelct.Definition known to be MemberDef in QuotesImpl*/] | ||
} | ||
|
||
// Interpret TASTy annotation instantiation `new myAnnot(..)` | ||
val annotInstance = interpreter.interpret[TastyAnnotation](annot.tree).get | ||
// TODO: Remove when scala.annaotaion.TastyAnnotation is no longer experimental | ||
assert(annotInstance.getClass.getClassLoader.loadClass("scala.annotation.TastyAnnotation").isInstance(annotInstance)) | ||
|
||
val quotes = QuotesImpl()(using SpliceScope.contextWithNewSpliceScope(tree.symbol.sourcePos)(using MacroExpansion.context(tree)).withOwner(tree.symbol)) | ||
annotInstance.transform(using quotes)(tree.asInstanceOf[quotes.reflect.Definition]) | ||
|
||
/** Check that this tree can be added by the TASTy annotation and enter it if needed */ | ||
private def checkAndEnter(newTree: Tree, annotated: Symbol, annot: Annotation)(using Context) = | ||
val sym = newTree.symbol | ||
if sym.isClass then | ||
report.error("Generating classes is not supported", annot.tree) | ||
else if sym.isType then | ||
report.error("Generating type is not supported", annot.tree) | ||
else if sym.owner != annotated.owner then | ||
report.error(i"TASTy 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 | ||
sym.enteredAfter(thisPhase) | ||
|
||
object TastyAnnotations: | ||
|
||
/** Is this an annotation that implements `scala.annation.TastyAnnotation` */ | ||
def isTastyAnnotation(annot: Annotation)(using Context): Boolean = | ||
val sym = annot.tree.symbol | ||
sym.denot != NoDenotation && sym.owner.derivesFrom(defn.TastyAnnotationClass) | ||
|
||
/** Is this symbol annotated with an annotation that implements `scala.annation.TastyAnnotation` */ | ||
def hasMacro(sym: Symbol)(using Context): Boolean = | ||
sym.getAnnotation(defn.TastyAnnotationClass).isDefined |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.