-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Enable returning classes from MacroAnnotations (part 3) #16534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
0dcf795
5fcb869
079a786
a3ce771
ac239fd
89c550b
8c30d37
429103e
9f4042a
1acd745
ecba50d
41a46fc
6c6dc77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,6 +63,12 @@ class Inlining extends MacroTransform with IdentityDenotTransformer { | |
} | ||
|
||
private class InliningTreeMap extends TreeMapWithImplicits { | ||
|
||
/** List of top level classes added by macro annotation in a package object. | ||
* These are added the PackageDef that owns this particular package object. | ||
*/ | ||
private val topClasses = new collection.mutable.ListBuffer[Tree] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately I think this isn't sufficient because package objects can be nested: package foo {
val x = 1
package bar {
val y = 2
}
} Instead, maybe the MemberDef case of transform should return a Thicket with the top-level classes, and we should add an extra case to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This use case was considered and works. I added tests for it in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that after after post typer the tree is
This implies that nested classes are processed first and the buffer never overlaps and is emptied just after transforming the nested package. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is subtle, so this precondition should be documented in the code (and ideally checked somewhere, in case it breaks) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found cases where this precondition does not hold. I updated the implementation to handle such cases. |
||
|
||
override def transform(tree: Tree)(using Context): Tree = { | ||
tree match | ||
case tree: MemberDef => | ||
|
@@ -74,7 +80,15 @@ class Inlining extends MacroTransform with IdentityDenotTransformer { | |
&& MacroAnnotations.hasMacroAnnotation(tree.symbol) | ||
then | ||
val trees = new MacroAnnotations(thisPhase).expandAnnotations(tree) | ||
flatTree(trees.map(super.transform)) | ||
val trees1 = trees.map(super.transform) | ||
|
||
// Find classes added to the top level from a package object | ||
val (topClasses0, trees2) = | ||
if ctx.owner.isPackageObject then trees1.partition(_.symbol.owner == ctx.owner.owner) | ||
else (Nil, trees1) | ||
topClasses ++= topClasses0 | ||
|
||
flatTree(trees2) | ||
else super.transform(tree) | ||
case _: Typed | _: Block => | ||
super.transform(tree) | ||
|
@@ -86,6 +100,14 @@ class Inlining extends MacroTransform with IdentityDenotTransformer { | |
super.transform(tree)(using StagingContext.quoteContext) | ||
case _: GenericApply if tree.symbol.isExprSplice => | ||
super.transform(tree)(using StagingContext.spliceContext) | ||
case _: PackageDef => | ||
super.transform(tree) match | ||
case tree1: PackageDef if !topClasses.isEmpty => | ||
topClasses ++= tree1.stats | ||
val newStats = topClasses.result() | ||
topClasses.clear() | ||
cpy.PackageDef(tree1)(tree1.pid, newStats) | ||
case tree1 => tree1 | ||
case _ => | ||
super.transform(tree) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import scala.annotation.{experimental, MacroAnnotation} | ||
import scala.quoted._ | ||
import scala.collection.mutable | ||
|
||
@experimental | ||
// Assumes annotation is on top level def or val | ||
class addTopLevelMethodOutsidePackageObject extends MacroAnnotation: | ||
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = | ||
import quotes.reflect._ | ||
val methType = MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Int]) | ||
val methSym = Symbol.newMethod(Symbol.spliceOwner.owner, Symbol.freshName("toLevelMethod"), methType, Flags.EmptyFlags, Symbol.noSymbol) | ||
val methDef = ValDef(methSym, Some(Literal(IntConstant(1)))) | ||
List(methDef, tree) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
@addTopLevelMethodOutsidePackageObject // error | ||
def foo = 1 | ||
|
||
@addTopLevelMethodOutsidePackageObject // error | ||
val bar = 1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import scala.annotation.{experimental, MacroAnnotation} | ||
import scala.quoted._ | ||
import scala.collection.mutable | ||
|
||
@experimental | ||
// Assumes annotation is on top level def or val | ||
class addTopLevelValOutsidePackageObject extends MacroAnnotation: | ||
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = | ||
import quotes.reflect._ | ||
val valSym = Symbol.newVal(Symbol.spliceOwner.owner, Symbol.freshName("toLevelVal"), TypeRepr.of[Int], Flags.EmptyFlags, Symbol.noSymbol) | ||
val valDef = ValDef(valSym, Some(Literal(IntConstant(1)))) | ||
List(valDef, tree) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
@addTopLevelValOutsidePackageObject // error | ||
def foo = 1 | ||
|
||
@addTopLevelValOutsidePackageObject // error | ||
val bar = 1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
macro generated main | ||
executed in: Bar$macro$1 | ||
macro generated main | ||
executed in: Bar$macro$2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import scala.annotation.{experimental, MacroAnnotation} | ||
import scala.quoted._ | ||
import scala.collection.mutable | ||
|
||
@experimental | ||
class addClass extends MacroAnnotation: | ||
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = | ||
import quotes.reflect._ | ||
tree match | ||
case DefDef(name, List(TermParamClause(Nil)), tpt, Some(rhs)) => | ||
val parents = List(TypeTree.of[Object]) | ||
def decls(cls: Symbol): List[Symbol] = | ||
List(Symbol.newMethod(cls, "run", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]), Flags.EmptyFlags, Symbol.noSymbol)) | ||
|
||
val newClassName = Symbol.freshName("Bar") | ||
val cls = Symbol.newClass(Symbol.spliceOwner.owner, newClassName, parents = parents.map(_.tpe), decls, selfType = None) | ||
val runSym = cls.declaredMethod("run").head | ||
|
||
val runDef = DefDef(runSym, _ => Some(rhs)) | ||
val clsDef = ClassDef(cls, parents, body = List(runDef)) | ||
|
||
val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil) | ||
|
||
val newDef = DefDef.copy(tree)(name, List(TermParamClause(Nil)), tpt, Some(Apply(Select(newCls, runSym), Nil))) | ||
List(clsDef, newDef) | ||
case _ => | ||
report.error("Annotation only supports `def` with one argument") | ||
List(tree) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
@addClass def foo(): Unit = | ||
println("macro generated main") | ||
println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) | ||
//> class Baz$macro$1 extends Object { | ||
//> def run() = | ||
//> println("macro generated main") | ||
//> println("executed in: " + getClass.getName) | ||
//> } | ||
//> def foo(): Unit = | ||
//> new Baz$macro$1.run | ||
|
||
@addClass def bar(): Unit = | ||
println("macro generated main") | ||
println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) | ||
//> class Baz$macro$2 extends Object { | ||
//> def run() = | ||
//> println("macro generated main") | ||
//> println("executed in: " + getClass.getName) | ||
//> } | ||
//> def foo(): Unit = | ||
//> new Baz$macro$2.run | ||
|
||
@main def Test(): Unit = | ||
foo() | ||
bar() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
macro generated main | ||
executed in: Test_2$package$Baz$1 | ||
macro generated main | ||
executed in: Test_2$package$Baz$2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import scala.annotation.{experimental, MacroAnnotation} | ||
import scala.quoted._ | ||
import scala.collection.mutable | ||
|
||
@experimental | ||
class addClass extends MacroAnnotation: | ||
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = | ||
import quotes.reflect._ | ||
tree match | ||
case DefDef(name, List(TermParamClause(Nil)), tpt, Some(rhs)) => | ||
val parents = List(TypeTree.of[Object]) | ||
def decls(cls: Symbol): List[Symbol] = | ||
List(Symbol.newMethod(cls, "run", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]), Flags.EmptyFlags, Symbol.noSymbol)) | ||
|
||
val cls = Symbol.newClass(Symbol.spliceOwner, "Baz", parents = parents.map(_.tpe), decls, selfType = None) | ||
val runSym = cls.declaredMethod("run").head | ||
|
||
val runDef = DefDef(runSym, _ => Some(rhs)) | ||
val clsDef = ClassDef(cls, parents, body = List(runDef)) | ||
|
||
val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil) | ||
|
||
val newDef = DefDef.copy(tree)(name, List(TermParamClause(Nil)), tpt, Some(Apply(Select(newCls, runSym), Nil))) | ||
List(clsDef, newDef) | ||
case _ => | ||
report.error("Annotation only supports `def` with one argument") | ||
List(tree) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
@main def Test(): Unit = | ||
@addClass def foo(): Unit = | ||
println("macro generated main") | ||
println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) | ||
//> class Baz extends Object { | ||
//> def run() = | ||
//> println("macro generated main") | ||
//> println("executed in: " + getClass.getName) | ||
//> } | ||
//> def foo(): Unit = | ||
//> new Baz().run | ||
|
||
@addClass def bar(): Unit = | ||
println("macro generated main") | ||
println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) | ||
//> class Baz extends Object { | ||
//> def run() = | ||
//> println("macro generated main") | ||
//> println("executed in: " + getClass.getName) | ||
//> } | ||
//> def Baz(): Unit = | ||
//> new Baz().run | ||
|
||
foo() | ||
bar() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
macro generated main | ||
executed in: Test_2$package$Baz$2 | ||
macro generated main | ||
executed in: Test_2$package$Baz$4 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import scala.annotation.{experimental, MacroAnnotation} | ||
import scala.quoted._ | ||
import scala.collection.mutable | ||
|
||
@experimental | ||
class addClass extends MacroAnnotation: | ||
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = | ||
import quotes.reflect._ | ||
tree match | ||
case DefDef(name, List(TermParamClause(Nil)), tpt, Some(rhs)) => | ||
val parents = List(TypeTree.of[Object]) | ||
def decls(cls: Symbol): List[Symbol] = | ||
List(Symbol.newMethod(cls, "run", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]), Flags.Static, Symbol.noSymbol)) | ||
|
||
// FIXME: missing flags: Final | Module | ||
// FIXME: how to set the self type? | ||
val cls = Symbol.newClass(Symbol.spliceOwner, "Baz", parents = parents.map(_.tpe), decls, selfType = None) | ||
val mod = Symbol.newVal(Symbol.spliceOwner, "Baz", cls.typeRef, Flags.Module | Flags.Lazy | Flags.Final, Symbol.noSymbol) | ||
val runSym = cls.declaredMethod("run").head | ||
|
||
val runDef = DefDef(runSym, _ => Some(rhs)) | ||
|
||
val clsDef = ClassDef(cls, parents, body = List(runDef)) | ||
|
||
val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil) | ||
val modVal = ValDef(mod, Some(newCls)) | ||
|
||
val newDef = DefDef.copy(tree)(name, List(TermParamClause(Nil)), tpt, Some(Apply(Select(Ref(mod), runSym), Nil))) | ||
List(modVal, clsDef, newDef) | ||
case _ => | ||
report.error("Annotation only supports `def` with one argument") | ||
List(tree) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
@main def Test(): Unit = | ||
@addClass def foo(): Unit = | ||
println("macro generated main") | ||
println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) | ||
//> object Baz { | ||
//> def run() = | ||
//> println("macro generated main") | ||
//> println("executed in: " + getClass.getName) | ||
//> } | ||
//> def foo(): Unit = | ||
//> Baz.run | ||
|
||
@addClass def bar(): Unit = | ||
println("macro generated main") | ||
println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) | ||
//> object Baz { | ||
//> def run() = | ||
//> println("macro generated main") | ||
//> println("executed in: " + getClass.getName) | ||
//> } | ||
//> def Baz(): Unit = | ||
//> Baz.run | ||
|
||
foo() | ||
bar() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
macro generated main | ||
executed in: Foo$Bar$macro$1 | ||
macro generated main | ||
executed in: Foo$Bar$macro$2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import scala.annotation.{experimental, MacroAnnotation} | ||
import scala.quoted._ | ||
import scala.collection.mutable | ||
|
||
@experimental | ||
class addClass extends MacroAnnotation: | ||
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = | ||
import quotes.reflect._ | ||
tree match | ||
case DefDef(name, List(TermParamClause(Nil)), tpt, Some(rhs)) => | ||
val parents = List(TypeTree.of[Object]) | ||
def decls(cls: Symbol): List[Symbol] = | ||
List(Symbol.newMethod(cls, "run", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]), Flags.EmptyFlags, Symbol.noSymbol)) | ||
|
||
val newClassName = Symbol.freshName("Bar") | ||
val cls = Symbol.newClass(Symbol.spliceOwner, newClassName, parents = parents.map(_.tpe), decls, selfType = None) | ||
val runSym = cls.declaredMethod("run").head | ||
|
||
val runDef = DefDef(runSym, _ => Some(rhs)) | ||
val clsDef = ClassDef(cls, parents, body = List(runDef)) | ||
|
||
val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil) | ||
|
||
val newDef = DefDef.copy(tree)(name, List(TermParamClause(Nil)), tpt, Some(Apply(Select(newCls, runSym), Nil))) | ||
List(clsDef, newDef) | ||
case _ => | ||
report.error("Annotation only supports `def` with one argument") | ||
List(tree) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
class Foo(): | ||
@addClass def foo(): Unit = | ||
println("macro generated main") | ||
println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) | ||
//> class Baz$macro$1 extends Object { | ||
//> def run() = | ||
//> println("macro generated main") | ||
//> println("executed in: " + getClass.getName) | ||
//> } | ||
//> def foo(): Unit = | ||
//> new Baz$macro$1.run | ||
|
||
@addClass def bar(): Unit = | ||
println("macro generated main") | ||
println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) | ||
//> class Baz$macro$2 extends Object { | ||
//> def run() = | ||
//> println("macro generated main") | ||
//> println("executed in: " + getClass.getName) | ||
//> } | ||
//> def foo(): Unit = | ||
//> new Baz$macro$2.run | ||
|
||
@main def Test(): Unit = | ||
new Foo().foo() | ||
new Foo().bar() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
macro generated main |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import scala.annotation.{experimental, MacroAnnotation} | ||
import scala.quoted._ | ||
import scala.collection.mutable | ||
|
||
@experimental | ||
class mainMacro extends MacroAnnotation: | ||
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = | ||
import quotes.reflect._ | ||
tree match | ||
case DefDef(name, List(TermParamClause(Nil)), _, _) => | ||
val parents = List(TypeTree.of[Object]) | ||
def decls(cls: Symbol): List[Symbol] = | ||
List(Symbol.newMethod(cls, "main", MethodType(List("args"))(_ => List(TypeRepr.of[Array[String]]), _ => TypeRepr.of[Unit]), Flags.Static, Symbol.noSymbol)) | ||
|
||
val cls = Symbol.newClass(Symbol.spliceOwner.owner, name, parents = parents.map(_.tpe), decls, selfType = None) | ||
val mainSym = cls.declaredMethod("main").head | ||
|
||
val mainDef = DefDef(mainSym, _ => Some(Apply(Ref(tree.symbol), Nil))) | ||
val clsDef = ClassDef(cls, parents, body = List(mainDef)) | ||
|
||
List(clsDef, tree) | ||
case _ => | ||
report.error("Annotation only supports `def` without arguments") | ||
List(tree) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
@mainMacro def Test(): Unit = println("macro generated main") |
Uh oh!
There was an error while loading. Please reload this page.