From 0dcf795e07e0577304e118e296d12a6b7b282333 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 25 Nov 2022 10:34:00 +0100 Subject: [PATCH 01/13] Enable returning classes from MacroAnnotations (part 3) Enable the addition of classes from a `MacroAnnotation`: * Can add new `class` definitions next to the annotated definition Special cases: * An annotated top-level `def`, `val`, `var`, `lazy val` can return a `class` definition that is owned by the package or package object. Related PRs: * Follows #16454 --- .../dotty/tools/dotc/transform/Inlining.scala | 24 +++++++++++++- .../dotc/transform/MacroAnnotations.scala | 8 ++--- library/src/scala/quoted/Quotes.scala | 3 +- .../Macro_1.scala | 13 ++++++++ .../Test_2.scala | 5 +++ .../Macro_1.scala | 12 +++++++ .../Test_2.scala | 5 +++ tests/run-macros/annot-add-global-class.check | 4 +++ .../annot-add-global-class/Macro_1.scala | 28 ++++++++++++++++ .../annot-add-global-class/Test_2.scala | 25 +++++++++++++++ tests/run-macros/annot-add-local-class.check | 4 +++ .../annot-add-local-class/Macro_1.scala | 27 ++++++++++++++++ .../annot-add-local-class/Test_2.scala | 25 +++++++++++++++ tests/run-macros/annot-add-local-object.check | 4 +++ .../annot-add-local-object/Macro_1.scala | 32 +++++++++++++++++++ .../annot-add-local-object/Test_2.scala | 25 +++++++++++++++ tests/run-macros/annot-add-nested-class.check | 4 +++ .../annot-add-nested-class/Macro_1.scala | 28 ++++++++++++++++ .../annot-add-nested-class/Test_2.scala | 26 +++++++++++++++ tests/run-macros/annot-macro-main.check | 1 + .../run-macros/annot-macro-main/Macro_1.scala | 24 ++++++++++++++ .../run-macros/annot-macro-main/Test_2.scala | 1 + 22 files changed, 321 insertions(+), 7 deletions(-) create mode 100644 tests/neg-macros/annot-mod-top-method-add-top-method/Macro_1.scala create mode 100644 tests/neg-macros/annot-mod-top-method-add-top-method/Test_2.scala create mode 100644 tests/neg-macros/annot-mod-top-method-add-top-val/Macro_1.scala create mode 100644 tests/neg-macros/annot-mod-top-method-add-top-val/Test_2.scala create mode 100644 tests/run-macros/annot-add-global-class.check create mode 100644 tests/run-macros/annot-add-global-class/Macro_1.scala create mode 100644 tests/run-macros/annot-add-global-class/Test_2.scala create mode 100644 tests/run-macros/annot-add-local-class.check create mode 100644 tests/run-macros/annot-add-local-class/Macro_1.scala create mode 100644 tests/run-macros/annot-add-local-class/Test_2.scala create mode 100644 tests/run-macros/annot-add-local-object.check create mode 100644 tests/run-macros/annot-add-local-object/Macro_1.scala create mode 100644 tests/run-macros/annot-add-local-object/Test_2.scala create mode 100644 tests/run-macros/annot-add-nested-class.check create mode 100644 tests/run-macros/annot-add-nested-class/Macro_1.scala create mode 100644 tests/run-macros/annot-add-nested-class/Test_2.scala create mode 100644 tests/run-macros/annot-macro-main.check create mode 100644 tests/run-macros/annot-macro-main/Macro_1.scala create mode 100644 tests/run-macros/annot-macro-main/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index a6f41bccfcfb..647f15549995 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -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] + 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) } diff --git a/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala b/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala index 712c44bcb66b..3edec6346841 100644 --- a/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala +++ b/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala @@ -119,13 +119,11 @@ class MacroAnnotations(thisPhase: DenotTransformer): annotInstance.transform(using quotes)(tree.asInstanceOf[quotes.reflect.Definition]) /** Check that this tree can be added by the macro annotation and enter it if needed */ - private def checkAndEnter(newTree: Tree, annotated: Symbol, annot: Annotation)(using Context) = + private def checkAndEnter(newTree: DefTree, annotated: Symbol, annot: Annotation)(using Context) = val sym = newTree.symbol - if sym.isClass then - report.error(i"macro annotation returning a `class` is not yet supported. $annot tried to add $sym", annot.tree) - else if sym.isType then + 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 then + 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) diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index c342a2b1b444..281988d7384a 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3638,6 +3638,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be * direct or indirect children of the reflection context's owner. */ + // TODO: add flags and privateWithin @experimental def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol /** Generates a new method symbol with the given parent, name and type. @@ -4217,7 +4218,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => // FLAGS // /////////////// - /** FlagSet of a Symbol */ + /** Flags of a Symbol */ type Flags /** Module object of `type Flags` */ diff --git a/tests/neg-macros/annot-mod-top-method-add-top-method/Macro_1.scala b/tests/neg-macros/annot-mod-top-method-add-top-method/Macro_1.scala new file mode 100644 index 000000000000..e2a257ca716b --- /dev/null +++ b/tests/neg-macros/annot-mod-top-method-add-top-method/Macro_1.scala @@ -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) diff --git a/tests/neg-macros/annot-mod-top-method-add-top-method/Test_2.scala b/tests/neg-macros/annot-mod-top-method-add-top-method/Test_2.scala new file mode 100644 index 000000000000..151b722a0dda --- /dev/null +++ b/tests/neg-macros/annot-mod-top-method-add-top-method/Test_2.scala @@ -0,0 +1,5 @@ +@addTopLevelMethodOutsidePackageObject // error +def foo = 1 + +@addTopLevelMethodOutsidePackageObject // error +val bar = 1 diff --git a/tests/neg-macros/annot-mod-top-method-add-top-val/Macro_1.scala b/tests/neg-macros/annot-mod-top-method-add-top-val/Macro_1.scala new file mode 100644 index 000000000000..c6c4c32afcb8 --- /dev/null +++ b/tests/neg-macros/annot-mod-top-method-add-top-val/Macro_1.scala @@ -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) diff --git a/tests/neg-macros/annot-mod-top-method-add-top-val/Test_2.scala b/tests/neg-macros/annot-mod-top-method-add-top-val/Test_2.scala new file mode 100644 index 000000000000..076a636267ab --- /dev/null +++ b/tests/neg-macros/annot-mod-top-method-add-top-val/Test_2.scala @@ -0,0 +1,5 @@ +@addTopLevelValOutsidePackageObject // error +def foo = 1 + +@addTopLevelValOutsidePackageObject // error +val bar = 1 diff --git a/tests/run-macros/annot-add-global-class.check b/tests/run-macros/annot-add-global-class.check new file mode 100644 index 000000000000..775d4e6992d5 --- /dev/null +++ b/tests/run-macros/annot-add-global-class.check @@ -0,0 +1,4 @@ +macro generated main +executed in: Bar$macro$1 +macro generated main +executed in: Bar$macro$2 diff --git a/tests/run-macros/annot-add-global-class/Macro_1.scala b/tests/run-macros/annot-add-global-class/Macro_1.scala new file mode 100644 index 000000000000..ba5ab0e88613 --- /dev/null +++ b/tests/run-macros/annot-add-global-class/Macro_1.scala @@ -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) diff --git a/tests/run-macros/annot-add-global-class/Test_2.scala b/tests/run-macros/annot-add-global-class/Test_2.scala new file mode 100644 index 000000000000..c056db841de4 --- /dev/null +++ b/tests/run-macros/annot-add-global-class/Test_2.scala @@ -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() diff --git a/tests/run-macros/annot-add-local-class.check b/tests/run-macros/annot-add-local-class.check new file mode 100644 index 000000000000..f61f009cb720 --- /dev/null +++ b/tests/run-macros/annot-add-local-class.check @@ -0,0 +1,4 @@ +macro generated main +executed in: Test_2$package$Baz$1 +macro generated main +executed in: Test_2$package$Baz$2 diff --git a/tests/run-macros/annot-add-local-class/Macro_1.scala b/tests/run-macros/annot-add-local-class/Macro_1.scala new file mode 100644 index 000000000000..8cbf30dfc923 --- /dev/null +++ b/tests/run-macros/annot-add-local-class/Macro_1.scala @@ -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) diff --git a/tests/run-macros/annot-add-local-class/Test_2.scala b/tests/run-macros/annot-add-local-class/Test_2.scala new file mode 100644 index 000000000000..e52ca30f8d9f --- /dev/null +++ b/tests/run-macros/annot-add-local-class/Test_2.scala @@ -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() diff --git a/tests/run-macros/annot-add-local-object.check b/tests/run-macros/annot-add-local-object.check new file mode 100644 index 000000000000..324bc42bfb9a --- /dev/null +++ b/tests/run-macros/annot-add-local-object.check @@ -0,0 +1,4 @@ +macro generated main +executed in: Test_2$package$Baz$2 +macro generated main +executed in: Test_2$package$Baz$4 diff --git a/tests/run-macros/annot-add-local-object/Macro_1.scala b/tests/run-macros/annot-add-local-object/Macro_1.scala new file mode 100644 index 000000000000..4ca1b3cd5d71 --- /dev/null +++ b/tests/run-macros/annot-add-local-object/Macro_1.scala @@ -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) diff --git a/tests/run-macros/annot-add-local-object/Test_2.scala b/tests/run-macros/annot-add-local-object/Test_2.scala new file mode 100644 index 000000000000..a7f6d74a02e2 --- /dev/null +++ b/tests/run-macros/annot-add-local-object/Test_2.scala @@ -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() diff --git a/tests/run-macros/annot-add-nested-class.check b/tests/run-macros/annot-add-nested-class.check new file mode 100644 index 000000000000..8d64901e1844 --- /dev/null +++ b/tests/run-macros/annot-add-nested-class.check @@ -0,0 +1,4 @@ +macro generated main +executed in: Foo$Bar$macro$1 +macro generated main +executed in: Foo$Bar$macro$2 diff --git a/tests/run-macros/annot-add-nested-class/Macro_1.scala b/tests/run-macros/annot-add-nested-class/Macro_1.scala new file mode 100644 index 000000000000..2edbe5e48cd3 --- /dev/null +++ b/tests/run-macros/annot-add-nested-class/Macro_1.scala @@ -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) diff --git a/tests/run-macros/annot-add-nested-class/Test_2.scala b/tests/run-macros/annot-add-nested-class/Test_2.scala new file mode 100644 index 000000000000..f363cf58c3a4 --- /dev/null +++ b/tests/run-macros/annot-add-nested-class/Test_2.scala @@ -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() diff --git a/tests/run-macros/annot-macro-main.check b/tests/run-macros/annot-macro-main.check new file mode 100644 index 000000000000..969dea2e0757 --- /dev/null +++ b/tests/run-macros/annot-macro-main.check @@ -0,0 +1 @@ +macro generated main diff --git a/tests/run-macros/annot-macro-main/Macro_1.scala b/tests/run-macros/annot-macro-main/Macro_1.scala new file mode 100644 index 000000000000..0de1a69f81a5 --- /dev/null +++ b/tests/run-macros/annot-macro-main/Macro_1.scala @@ -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) diff --git a/tests/run-macros/annot-macro-main/Test_2.scala b/tests/run-macros/annot-macro-main/Test_2.scala new file mode 100644 index 000000000000..cdf7f6fd78da --- /dev/null +++ b/tests/run-macros/annot-macro-main/Test_2.scala @@ -0,0 +1 @@ +@mainMacro def Test(): Unit = println("macro generated main") From 5fcb869748a3f52e47d4f6c5d4ca17a7ea766f77 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 16 Dec 2022 12:00:07 +0100 Subject: [PATCH 02/13] Add reflect `Symbol.newModule` --- .../dotc/transform/MacroAnnotations.scala | 2 +- .../quoted/runtime/impl/QuotesImpl.scala | 26 ++++++-- library/src/scala/quoted/Quotes.scala | 60 +++++++++++++++++++ .../stdlibExperimentalDefinitions.scala | 1 + .../run-macros/annot-add-global-object.check | 4 ++ .../annot-add-global-object/Macro_1.scala | 31 ++++++++++ .../annot-add-global-object/Test_2.scala | 28 +++++++++ tests/run-macros/annot-add-local-object.check | 4 +- .../annot-add-local-object/Macro_1.scala | 9 ++- .../run-macros/annot-add-nested-object.check | 4 ++ .../annot-add-nested-object/Macro_1.scala | 31 ++++++++++ .../annot-add-nested-object/Test_2.scala | 26 ++++++++ 12 files changed, 213 insertions(+), 13 deletions(-) create mode 100644 tests/run-macros/annot-add-global-object.check create mode 100644 tests/run-macros/annot-add-global-object/Macro_1.scala create mode 100644 tests/run-macros/annot-add-global-object/Test_2.scala create mode 100644 tests/run-macros/annot-add-nested-object.check create mode 100644 tests/run-macros/annot-add-nested-object/Macro_1.scala create mode 100644 tests/run-macros/annot-add-nested-object/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala b/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala index 3edec6346841..f2c8b2d60547 100644 --- a/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala +++ b/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala @@ -127,7 +127,7 @@ class MacroAnnotations(thisPhase: DenotTransformer): 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) - else + else if !sym.is(Module) then // To avoid entering it twice sym.enteredAfter(thisPhase) object MacroAnnotations: diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 7a3b33c4a9f4..a604fd704dc9 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -8,15 +8,16 @@ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.untpd import dotty.tools.dotc.core.Annotations import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.core.Types +import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Flags._ import dotty.tools.dotc.core.NameKinds +import dotty.tools.dotc.core.NameOps._ import dotty.tools.dotc.core.StdNames._ -import dotty.tools.dotc.quoted.reflect._ -import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Types import dotty.tools.dotc.NoCompilationUnit - -import dotty.tools.dotc.quoted.{MacroExpansion, PickledQuotes} +import dotty.tools.dotc.quoted.MacroExpansion +import dotty.tools.dotc.quoted.PickledQuotes +import dotty.tools.dotc.quoted.reflect._ import scala.quoted.runtime.{QuoteUnpickler, QuoteMatching} import scala.quoted.runtime.impl.printers._ @@ -2481,6 +2482,21 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler for sym <- decls(cls) do cls.enter(sym) cls + def newModule(owner: Symbol, name: String, modFlags: Flags, clsFlags: Flags, parents: List[TypeRepr], decls: Symbol => List[Symbol], privateWithin: Symbol): Symbol = + assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class") + val mod = dotc.core.Symbols.newCompleteModuleSymbol( + owner, + name.toTermName, + modFlags | Flags.Final | Flags.Lazy | Flags.Module, + clsFlags | Flags.Final | Flags.Module, + parents.asInstanceOf, // FIXME + dotc.core.Scopes.newScope, + privateWithin) + val cls = mod.moduleClass.asClass + cls.enter(dotc.core.Symbols.newConstructor(cls, dotc.core.Flags.Synthetic, Nil, Nil)) + for sym <- decls(cls) do cls.enter(sym) + mod + def newMethod(owner: Symbol, name: String, tpe: TypeRepr): Symbol = newMethod(owner, name, tpe, Flags.EmptyFlags, noSymbol) def newMethod(owner: Symbol, name: String, tpe: TypeRepr, flags: Flags, privateWithin: Symbol): Symbol = diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 281988d7384a..ac346527674a 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3641,6 +3641,66 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => // TODO: add flags and privateWithin @experimental def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol + /** Generates a new module symbol with an associated module class symbol. + * This returns the module symbol. The module class can be accessed calling `moduleClass` on this symbol. + * + * Example usage: + * ```scala + * //{ + * given Quotes = ??? + * import quotes.reflect._ + * //} + * val moduleName: String = Symbol.freshName("MyModule") + * 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 mod = Symbol.newModule(Symbol.spliceOwner, moduleName, Flags.EmptyFlags, Flags.EmptyFlags, parents.map(_.tpe), decls, Symbol.noSymbol) + * val cls = mod.moduleClass + * val runSym = cls.declaredMethod("run").head + * + * val runDef = DefDef(runSym, _ => Some('{ println("run") }.asTerm)) + * 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 modDef = List(modVal, clsDef) + * + * val callRun = Apply(Select(Ref(mod), runSym), Nil) + * + * Block(modDef, callRun) + * ``` + * constructs the equivalent to + * ```scala + * //{ + * given Quotes = ??? + * import quotes.reflect._ + * //} + * '{ + * object MyModule$macro$1 extends Object: + * def run(): Unit = println("run") + * MyModule$macro$1.run() + * } + * ``` + * + * @param parent The owner of the class + * @param name The name of the class + * @param modFlags extra flags to with which the module symbol should be constructed + * @param clsFlags extra flags to with which the module class symbol should be constructed + * @param parents The parent classes of the class. The first parent must not be a trait. + * @param decls The member declarations of the module provided the symbol of this class + * @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. + * + * This symbol starts without an accompanying definition. + * It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing + * this symbol to the ClassDef and ValDef constructor. + * + * @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be + * direct or indirect children of the reflection context's owner. + * + * @syntax markdown + */ + @experimental def newModule(owner: Symbol, name: String, modFlags: Flags, clsFlags: Flags, parents: List[TypeRepr], decls: Symbol => List[Symbol], privateWithin: Symbol): Symbol + /** Generates a new method symbol with the given parent, name and type. * * To define a member method of a class, use the `newMethod` within the `decls` function of `newClass`. diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index 422d88efc095..3084a4d5a326 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -74,6 +74,7 @@ val experimentalDefinitionInLibrary = Set( // Need experimental annotation macros to check that design works. "scala.quoted.Quotes.reflectModule.ClassDefModule.apply", "scala.quoted.Quotes.reflectModule.SymbolModule.newClass", + "scala.quoted.Quotes.reflectModule.SymbolModule.newModule", "scala.quoted.Quotes.reflectModule.SymbolModule.freshName", "scala.quoted.Quotes.reflectModule.SymbolMethods.info", diff --git a/tests/run-macros/annot-add-global-object.check b/tests/run-macros/annot-add-global-object.check new file mode 100644 index 000000000000..055fee03d5f5 --- /dev/null +++ b/tests/run-macros/annot-add-global-object.check @@ -0,0 +1,4 @@ +macro generated main +executed in: Test_2$package$Bar$macro$1$ +macro generated main +executed in: Test_2$package$Bar$macro$2$ diff --git a/tests/run-macros/annot-add-global-object/Macro_1.scala b/tests/run-macros/annot-add-global-object/Macro_1.scala new file mode 100644 index 000000000000..e66414274e95 --- /dev/null +++ b/tests/run-macros/annot-add-global-object/Macro_1.scala @@ -0,0 +1,31 @@ +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 mod = Symbol.newModule(Symbol.spliceOwner, Symbol.freshName("Bar"), Flags.EmptyFlags, Flags.EmptyFlags, parents.map(_.tpe), decls, Symbol.noSymbol) + val cls = mod.moduleClass + + 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) diff --git a/tests/run-macros/annot-add-global-object/Test_2.scala b/tests/run-macros/annot-add-global-object/Test_2.scala new file mode 100644 index 000000000000..2007c751bbec --- /dev/null +++ b/tests/run-macros/annot-add-global-object/Test_2.scala @@ -0,0 +1,28 @@ +@addClass def foo(): Unit = + println("macro generated main") + println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) +//> object Baz$macro$1 { +//> def run() = +//> println("macro generated main") +//> println("executed in: " + getClass.getName) +//> } +//> def foo(): Unit = +//> Baz$macro$1.run + +@addClass def bar(): Unit = + println("macro generated main") + println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) +//> object Baz$macro$2 { +//> def run() = +//> println("macro generated main") +//> println("executed in: " + getClass.getName) +//> } +//> def foo(): Unit = +//> Baz$macro$2.run + +object Bar: + def run() = () + +@main def Test(): Unit = + foo() + bar() diff --git a/tests/run-macros/annot-add-local-object.check b/tests/run-macros/annot-add-local-object.check index 324bc42bfb9a..7a615bf05c24 100644 --- a/tests/run-macros/annot-add-local-object.check +++ b/tests/run-macros/annot-add-local-object.check @@ -1,4 +1,4 @@ macro generated main -executed in: Test_2$package$Baz$2 +executed in: Test_2$package$Baz$2$ macro generated main -executed in: Test_2$package$Baz$4 +executed in: Test_2$package$Baz$4$ diff --git a/tests/run-macros/annot-add-local-object/Macro_1.scala b/tests/run-macros/annot-add-local-object/Macro_1.scala index 4ca1b3cd5d71..e2cd05881bca 100644 --- a/tests/run-macros/annot-add-local-object/Macro_1.scala +++ b/tests/run-macros/annot-add-local-object/Macro_1.scala @@ -10,12 +10,11 @@ class addClass extends MacroAnnotation: 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)) + List(Symbol.newMethod(cls, "run", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]), Flags.EmptyFlags, Symbol.noSymbol)) + + val mod = Symbol.newModule(Symbol.spliceOwner, "Baz", Flags.EmptyFlags, Flags.EmptyFlags, parents.map(_.tpe), decls, Symbol.noSymbol) + val cls = mod.moduleClass - // 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)) diff --git a/tests/run-macros/annot-add-nested-object.check b/tests/run-macros/annot-add-nested-object.check new file mode 100644 index 000000000000..022c0d358fa5 --- /dev/null +++ b/tests/run-macros/annot-add-nested-object.check @@ -0,0 +1,4 @@ +macro generated main +executed in: Foo$Bar$macro$1$ +macro generated main +executed in: Foo$Bar$macro$2$ diff --git a/tests/run-macros/annot-add-nested-object/Macro_1.scala b/tests/run-macros/annot-add-nested-object/Macro_1.scala new file mode 100644 index 000000000000..e66414274e95 --- /dev/null +++ b/tests/run-macros/annot-add-nested-object/Macro_1.scala @@ -0,0 +1,31 @@ +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 mod = Symbol.newModule(Symbol.spliceOwner, Symbol.freshName("Bar"), Flags.EmptyFlags, Flags.EmptyFlags, parents.map(_.tpe), decls, Symbol.noSymbol) + val cls = mod.moduleClass + + 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) diff --git a/tests/run-macros/annot-add-nested-object/Test_2.scala b/tests/run-macros/annot-add-nested-object/Test_2.scala new file mode 100644 index 000000000000..2bf54d73d0e6 --- /dev/null +++ b/tests/run-macros/annot-add-nested-object/Test_2.scala @@ -0,0 +1,26 @@ +class Foo(): + @addClass def foo(): Unit = + println("macro generated main") + println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) + //> object Baz$macro$1 { + //> def run() = + //> println("macro generated main") + //> println("executed in: " + getClass.getName) + //> } + //> def foo(): Unit = + //> Baz$macro$1.run + + @addClass def bar(): Unit = + println("macro generated main") + println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) + //> object Baz$macro$2 { + //> def run() = + //> println("macro generated main") + //> println("executed in: " + getClass.getName) + //> } + //> def foo(): Unit = + //> Baz$macro$2.run + +@main def Test(): Unit = + new Foo().foo() + new Foo().bar() From 079a7866d0f2e819adcd1762ed95bcefa07196a9 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 20 Dec 2022 09:32:09 +0100 Subject: [PATCH 03/13] Update MacroAnnotation documentation --- .../scala/annotation/MacroAnnotation.scala | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/library/src/scala/annotation/MacroAnnotation.scala b/library/src/scala/annotation/MacroAnnotation.scala index 3124edbf1765..5c39ef45f417 100644 --- a/library/src/scala/annotation/MacroAnnotation.scala +++ b/library/src/scala/annotation/MacroAnnotation.scala @@ -4,25 +4,41 @@ package annotation import scala.quoted._ -/** Base trait for macro annotation that will transform a definition */ +/** Base trait for macro annotation implementation. + * Macro annotations can transform definitions and add new definitions. + * + * See: `MacroAnnotation.transform` + * + * @syntax markdown + */ @experimental trait MacroAnnotation extends StaticAnnotation: - /** Transform the `tree` definition and add other definitions + /** Transform the `tree` definition and add new definitions * * This method takes as argument the annotated definition. * It returns a non-empty list containing the modified version of the annotated definition. * The new tree for the definition must use the original symbol. * New definitions can be added to the list before or after the transformed definitions, this order - * will be retained. + * will be retained. New definitions will not be visible from outside the macro expansion. * - * All definitions in the result must have the same owner. The owner can be recovered from `tree.symbol.owner`. + * #### Restrictions + * - All definitions in the result must have the same owner. The owner can be recovered from `Symbol.spliceOwner`. + * - Special case: an annotated top-level `def`, `val`, `var`, `lazy val` can return a `class`/`object` +definition that is owned by the package or package object. + * - Can not return a `type`. + * - Annotated top-level `class`/`object` can not return top-level `def`, `val`, `var`, `lazy val`. + * - Can not see new definition in user written code. * - * The result cannot add new `class`, `object` or `type` definition. This limitation will be relaxed in the future. + * #### Good practices + * - Make your new definitions private if you can. + * - New definitions added as class members should use a fresh name (`Symbol.freshName`) to avoid collisions. + * - New top-level definitions should use a fresh name (`Symbol.freshName`) that includes the name of the annotated + * member as a prefix to avoid collisions of definitions added in other files. * - * IMPORTANT: When developing and testing a macro annotation, you must enable `-Xcheck-macros` and `-Ycheck:all`. + * **IMPORTANT**: When developing and testing a macro annotation, you must enable `-Xcheck-macros` and `-Ycheck:all`. * - * Example 1: + * #### Example 1 * This example shows how to modify a `def` and add a `val` next to it using a macro annotation. * ```scala * import scala.quoted.* @@ -54,7 +70,10 @@ trait MacroAnnotation extends StaticAnnotation: * List(tree) * ``` * with this macro annotation a user can write - * ```scala sc:nocompile + * ```scala + * //{ + * class memoize extends scala.annotation.StaticAnnotation + * //} * @memoize * def fib(n: Int): Int = * println(s"compute fib of $n") @@ -74,7 +93,7 @@ trait MacroAnnotation extends StaticAnnotation: * ) * ``` * - * Example 2: + * #### Example 2 * This example shows how to modify a `class` using a macro annotation. * It shows how to override inherited members and add new ones. * ```scala @@ -164,7 +183,10 @@ trait MacroAnnotation extends StaticAnnotation: * } * ``` * with this macro annotation a user can write - * ```scala sc:nocompile + * ```scala + * //{ + * class equals extends scala.annotation.StaticAnnotation + * //} * @equals class User(val name: String, val id: Int) * ``` * and the macro will modify the class definition to generate the following code @@ -184,5 +206,7 @@ trait MacroAnnotation extends StaticAnnotation: * * @param Quotes Implicit instance of Quotes used for tree reflection * @param tree Tree that will be transformed + * + * @syntax markdown */ def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] From a3ce7712b0c1d9afd4ca44ecdbddada517084a15 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 4 Jan 2023 16:03:49 +0000 Subject: [PATCH 04/13] Fix typos --- compiler/src/dotty/tools/dotc/transform/Inlining.scala | 2 +- library/src/scala/quoted/Quotes.scala | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index 647f15549995..b7632852d5a6 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -65,7 +65,7 @@ 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. + * These are added to the PackageDef that owns this particular package object. */ private val topClasses = new collection.mutable.ListBuffer[Tree] diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index ac346527674a..8d406b31e434 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3641,7 +3641,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => // TODO: add flags and privateWithin @experimental def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol - /** Generates a new module symbol with an associated module class symbol. + /** Generates a new module symbol with an associated module class symbol, * This returns the module symbol. The module class can be accessed calling `moduleClass` on this symbol. * * Example usage: @@ -3684,10 +3684,10 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * * @param parent The owner of the class * @param name The name of the class - * @param modFlags extra flags to with which the module symbol should be constructed - * @param clsFlags extra flags to with which the module class symbol should be constructed + * @param modFlags extra flags with which the module symbol should be constructed + * @param clsFlags extra flags with which the module class symbol should be constructed * @param parents The parent classes of the class. The first parent must not be a trait. - * @param decls The member declarations of the module provided the symbol of this class + * @param decls A function that takes the symbol of the module class as input and return the symbols of its declared members * @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. * * This symbol starts without an accompanying definition. From ac239fd6b4702581a7558ddf555366d740c5c1ff Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 4 Jan 2023 16:04:58 +0000 Subject: [PATCH 05/13] Use ModuleValCreationFlags and ModuleClassCreationFlags --- compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index a604fd704dc9..41cef850ca0b 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2487,8 +2487,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler val mod = dotc.core.Symbols.newCompleteModuleSymbol( owner, name.toTermName, - modFlags | Flags.Final | Flags.Lazy | Flags.Module, - clsFlags | Flags.Final | Flags.Module, + modFlags | dotc.core.Flags.ModuleValCreationFlags, + clsFlags | dotc.core.Flags.ModuleClassCreationFlags, parents.asInstanceOf, // FIXME dotc.core.Scopes.newScope, privateWithin) From 89c550b3c00384df7935c25b0f33fd5d2796e295 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 5 Jan 2023 09:06:03 +0000 Subject: [PATCH 06/13] Add test for top level classes added from defs in nested packages --- tests/run-macros/annot-add-global-class.check | 16 +++++++--- .../annot-add-global-class/Macro_1.scala | 2 ++ .../annot-add-global-class/Test_2.scala | 31 +++++++++++++++++-- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/tests/run-macros/annot-add-global-class.check b/tests/run-macros/annot-add-global-class.check index 775d4e6992d5..7ae53396a951 100644 --- a/tests/run-macros/annot-add-global-class.check +++ b/tests/run-macros/annot-add-global-class.check @@ -1,4 +1,12 @@ -macro generated main -executed in: Bar$macro$1 -macro generated main -executed in: Bar$macro$2 +macro generated class +executed in: Bar$macro$5 +macro generated class +executed in: Bar$macro$6 +macro generated class +executed in: a.Bar$macro$3 +macro generated class +executed in: a.b.Bar$macro$1 +macro generated class +executed in: a.Bar$macro$4 +macro generated class +executed in: a.c.Bar$macro$2 diff --git a/tests/run-macros/annot-add-global-class/Macro_1.scala b/tests/run-macros/annot-add-global-class/Macro_1.scala index ba5ab0e88613..128b66bd6eb1 100644 --- a/tests/run-macros/annot-add-global-class/Macro_1.scala +++ b/tests/run-macros/annot-add-global-class/Macro_1.scala @@ -1,3 +1,5 @@ +package mymacro + import scala.annotation.{experimental, MacroAnnotation} import scala.quoted._ import scala.collection.mutable diff --git a/tests/run-macros/annot-add-global-class/Test_2.scala b/tests/run-macros/annot-add-global-class/Test_2.scala index c056db841de4..11386bcc0bc5 100644 --- a/tests/run-macros/annot-add-global-class/Test_2.scala +++ b/tests/run-macros/annot-add-global-class/Test_2.scala @@ -1,5 +1,7 @@ +import mymacro.addClass + @addClass def foo(): Unit = - println("macro generated main") + println("macro generated class") println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) //> class Baz$macro$1 extends Object { //> def run() = @@ -10,16 +12,39 @@ //> new Baz$macro$1.run @addClass def bar(): Unit = - println("macro generated main") + println("macro generated class") 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 = +//> def bar(): Unit = //> new Baz$macro$2.run +package a: + @addClass def foo(): Unit = + println("macro generated class") + println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) + + package b: + @addClass def foo(): Unit = + println("macro generated class") + println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) + + @addClass def bar(): Unit = + println("macro generated class") + println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) + + package c: + @addClass def foo(): Unit = + println("macro generated class") + println("executed in: " + (new Throwable().getStackTrace().head.getClassName)) + @main def Test(): Unit = foo() bar() + a.foo() + a.b.foo() + a.bar() + a.c.foo() From 8c30d37543b5866b47b877ec2ad3d2a464ef2770 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 5 Jan 2023 09:27:12 +0000 Subject: [PATCH 07/13] Add generated classes at the end of the package --- compiler/src/dotty/tools/dotc/transform/Inlining.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index b7632852d5a6..fe9fff6a95e6 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -103,8 +103,7 @@ class Inlining extends MacroTransform with IdentityDenotTransformer { case _: PackageDef => super.transform(tree) match case tree1: PackageDef if !topClasses.isEmpty => - topClasses ++= tree1.stats - val newStats = topClasses.result() + val newStats = tree1.stats ::: topClasses.result() topClasses.clear() cpy.PackageDef(tree1)(tree1.pid, newStats) case tree1 => tree1 From 429103e72afa03bd8c12955cccd8aeeac210f681 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 5 Jan 2023 09:35:41 +0000 Subject: [PATCH 08/13] Remove IdentityDenotTransformer from MacroTransform --- .../src/dotty/tools/dotc/transform/Inlining.scala | 5 ++--- .../tools/dotc/transform/MacroAnnotations.scala | 12 +++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index fe9fff6a95e6..87924c7932eb 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -16,8 +16,7 @@ import dotty.tools.dotc.core.DenotTransformers.IdentityDenotTransformer /** Inlines all calls to inline methods that are not in an inline method or a quote */ -class Inlining extends MacroTransform with IdentityDenotTransformer { - thisPhase => +class Inlining extends MacroTransform { import tpd._ @@ -79,7 +78,7 @@ class Inlining extends MacroTransform with IdentityDenotTransformer { && StagingContext.level == 0 && MacroAnnotations.hasMacroAnnotation(tree.symbol) then - val trees = new MacroAnnotations(thisPhase).expandAnnotations(tree) + val trees = (new MacroAnnotations).expandAnnotations(tree) val trees1 = trees.map(super.transform) // Find classes added to the top level from a package object diff --git a/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala b/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala index f2c8b2d60547..701a6772743e 100644 --- a/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala +++ b/compiler/src/dotty/tools/dotc/transform/MacroAnnotations.scala @@ -23,7 +23,7 @@ import scala.util.control.NonFatal import java.lang.reflect.InvocationTargetException -class MacroAnnotations(thisPhase: DenotTransformer): +class MacroAnnotations: import tpd.* import MacroAnnotations.* @@ -82,8 +82,8 @@ class MacroAnnotations(thisPhase: DenotTransformer): case (prefixed, newTree :: suffixed) => allTrees ++= prefixed insertedAfter = suffixed :: insertedAfter - prefixed.foreach(checkAndEnter(_, tree.symbol, annot)) - suffixed.foreach(checkAndEnter(_, tree.symbol, annot)) + prefixed.foreach(checkMacroDef(_, tree.symbol, annot)) + suffixed.foreach(checkMacroDef(_, tree.symbol, annot)) newTree case (Nil, Nil) => report.error(i"Unexpected `Nil` returned by `(${annot.tree}).transform(..)` during macro expansion", annot.tree.srcPos) @@ -118,8 +118,8 @@ class MacroAnnotations(thisPhase: DenotTransformer): 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 and enter it if needed */ - private def checkAndEnter(newTree: DefTree, annotated: Symbol, annot: Annotation)(using Context) = + /** Check that this tree can be added by the macro annotation */ + private def checkMacroDef(newTree: DefTree, annotated: Symbol, annot: Annotation)(using Context) = val sym = newTree.symbol if sym.isType && !sym.isClass then report.error(i"macro annotation cannot return a `type`. $annot tried to add $sym", annot.tree) @@ -127,8 +127,6 @@ class MacroAnnotations(thisPhase: DenotTransformer): 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) - else if !sym.is(Module) then // To avoid entering it twice - sym.enteredAfter(thisPhase) object MacroAnnotations: From 9f4042aedf8381b848f1c3d6605e8645954936c5 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 5 Jan 2023 09:44:34 +0000 Subject: [PATCH 09/13] Implement newNormalizedModuleSymbol Similar to newNormalizedClassSymbol but for modules. --- .../src/dotty/tools/dotc/core/Symbols.scala | 26 +++++++++++++++++++ .../quoted/runtime/impl/QuotesImpl.scala | 4 +-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 775062c26b0c..11513d8698c4 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -630,6 +630,32 @@ object Symbols { owner.thisType, modcls, parents, decls, TermRef(owner.thisType, module)), privateWithin, coord, assocFile) + /** Same as `newCompleteModuleSymbol` except that `parents` can be a list of arbitrary + * types which get normalized into type refs and parameter bindings. + */ + def newNormalizedModuleSymbol( + owner: Symbol, + name: TermName, + modFlags: FlagSet, + clsFlags: FlagSet, + parentTypes: List[Type], + decls: Scope, + privateWithin: Symbol = NoSymbol, + coord: Coord = NoCoord, + assocFile: AbstractFile | Null = null)(using Context): TermSymbol = { + def completer(module: Symbol) = new LazyType { + def complete(denot: SymDenotation)(using Context): Unit = { + val cls = denot.asClass.classSymbol + val decls = newScope + denot.info = ClassInfo(owner.thisType, cls, parentTypes.map(_.dealias), decls, TermRef(owner.thisType, module)) + } + } + newModuleSymbol( + owner, name, modFlags, clsFlags, + (module, modcls) => completer(module), + privateWithin, coord, assocFile) + } + /** Create a package symbol with associated package class * from its non-info fields and a lazy type for loading the package's members. */ diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 41cef850ca0b..60f259be85b5 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2484,12 +2484,12 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def newModule(owner: Symbol, name: String, modFlags: Flags, clsFlags: Flags, parents: List[TypeRepr], decls: Symbol => List[Symbol], privateWithin: Symbol): Symbol = assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class") - val mod = dotc.core.Symbols.newCompleteModuleSymbol( + val mod = dotc.core.Symbols.newNormalizedModuleSymbol( owner, name.toTermName, modFlags | dotc.core.Flags.ModuleValCreationFlags, clsFlags | dotc.core.Flags.ModuleClassCreationFlags, - parents.asInstanceOf, // FIXME + parents, dotc.core.Scopes.newScope, privateWithin) val cls = mod.moduleClass.asClass From 1acd74506084d90c459971d9169ce4c73c0f568e Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 5 Jan 2023 10:28:26 +0000 Subject: [PATCH 10/13] Add reflect ClassDef.module --- .../quoted/runtime/impl/QuotesImpl.scala | 8 +++++ library/src/scala/quoted/Quotes.scala | 31 ++++++++++++++++--- .../stdlibExperimentalDefinitions.scala | 1 + .../annot-add-global-object/Macro_1.scala | 7 ++--- .../annot-add-local-object/Macro_1.scala | 5 +-- .../annot-add-nested-object/Macro_1.scala | 5 +-- 6 files changed, 39 insertions(+), 18 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 60f259be85b5..5dda6a1dbede 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -243,6 +243,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def unapply(cdef: ClassDef): (String, DefDef, List[Tree /* Term | TypeTree */], Option[ValDef], List[Statement]) = val rhs = cdef.rhs.asInstanceOf[tpd.Template] (cdef.name.toString, cdef.constructor, cdef.parents, cdef.self, rhs.body) + + def module(module: Symbol, parents: List[Tree /* Term | TypeTree */], body: List[Statement]): (ValDef, ClassDef) = { + val cls = module.moduleClass + val clsDef = ClassDef(cls, parents, body) + val newCls = Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil) + val modVal = ValDef(module, Some(newCls)) + (modVal, clsDef) + } end ClassDef given ClassDefMethods: ClassDefMethods with diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 8d406b31e434..183532c84797 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -467,9 +467,33 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * otherwise the can be `Term` containing the `New` applied to the parameters of the extended class. * @param body List of members of the class. The members must align with the members of `cls`. */ + // TODO add selfOpt: Option[ValDef]? @experimental def apply(cls: Symbol, parents: List[Tree /* Term | TypeTree */], body: List[Statement]): ClassDef def copy(original: Tree)(name: String, constr: DefDef, parents: List[Tree /* Term | TypeTree */], selfOpt: Option[ValDef], body: List[Statement]): ClassDef def unapply(cdef: ClassDef): (String, DefDef, List[Tree /* Term | TypeTree */], Option[ValDef], List[Statement]) + + + /** Create the ValDef and ClassDef of a module. + * + * Equivalent to + * ``` + * def module(module: Symbol, parents: List[Tree], body: List[Statement]): (ValDef, ClassDef) = + * val modCls = module.moduleClass + * val modClassDef = ClassDef(modCls, parents, body) + * val modValDef = ValDef(module, Some(Apply(Select(New(TypeIdent(modCls)), cls.primaryConstructor), Nil))) + * List(modValDef, modClassDef) + * ``` + * + * @param module the module symbol (of the module lazy val) + * @param parents parents of the module class + * @param body body of the module class + * @return The module lazy val definition and module class definition. + * These should be added one after the other (in that order) in the body of a class or statements of a block. + * + * @syntax markdown + */ + // TODO add selfOpt: Option[ValDef]? + @experimental def module(module: Symbol, parents: List[Tree /* Term | TypeTree */], body: List[Statement]): (ValDef, ClassDef) } /** Makes extension methods on `ClassDef` available without any imports */ @@ -3660,14 +3684,11 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * val runSym = cls.declaredMethod("run").head * * val runDef = DefDef(runSym, _ => Some('{ println("run") }.asTerm)) - * 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 modDef = List(modVal, clsDef) + * val modDef = ClassDef.module(mod, parents, body = List(runDef)) * * val callRun = Apply(Select(Ref(mod), runSym), Nil) * - * Block(modDef, callRun) + * Block(modDef.toList, callRun) * ``` * constructs the equivalent to * ```scala diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index 3084a4d5a326..1bf4c2fb24b0 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -73,6 +73,7 @@ val experimentalDefinitionInLibrary = Set( // Need newClass variant that can add constructor parameters. // Need experimental annotation macros to check that design works. "scala.quoted.Quotes.reflectModule.ClassDefModule.apply", + "scala.quoted.Quotes.reflectModule.ClassDefModule.module", "scala.quoted.Quotes.reflectModule.SymbolModule.newClass", "scala.quoted.Quotes.reflectModule.SymbolModule.newModule", "scala.quoted.Quotes.reflectModule.SymbolModule.freshName", diff --git a/tests/run-macros/annot-add-global-object/Macro_1.scala b/tests/run-macros/annot-add-global-object/Macro_1.scala index e66414274e95..b8d1dfd7dc42 100644 --- a/tests/run-macros/annot-add-global-object/Macro_1.scala +++ b/tests/run-macros/annot-add-global-object/Macro_1.scala @@ -19,13 +19,10 @@ class addClass extends MacroAnnotation: 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 modDef = ClassDef.module(mod, parents, body = List(runDef)) val newDef = DefDef.copy(tree)(name, List(TermParamClause(Nil)), tpt, Some(Apply(Select(Ref(mod), runSym), Nil))) - List(modVal, clsDef, newDef) + modDef.toList ::: newDef :: Nil case _ => report.error("Annotation only supports `def` with one argument") List(tree) diff --git a/tests/run-macros/annot-add-local-object/Macro_1.scala b/tests/run-macros/annot-add-local-object/Macro_1.scala index e2cd05881bca..d02ee7600194 100644 --- a/tests/run-macros/annot-add-local-object/Macro_1.scala +++ b/tests/run-macros/annot-add-local-object/Macro_1.scala @@ -19,10 +19,7 @@ class addClass extends MacroAnnotation: 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 (modVal, clsDef) = ClassDef.module(mod, parents, body = List(runDef)) val newDef = DefDef.copy(tree)(name, List(TermParamClause(Nil)), tpt, Some(Apply(Select(Ref(mod), runSym), Nil))) List(modVal, clsDef, newDef) diff --git a/tests/run-macros/annot-add-nested-object/Macro_1.scala b/tests/run-macros/annot-add-nested-object/Macro_1.scala index e66414274e95..e10455e88678 100644 --- a/tests/run-macros/annot-add-nested-object/Macro_1.scala +++ b/tests/run-macros/annot-add-nested-object/Macro_1.scala @@ -19,10 +19,7 @@ class addClass extends MacroAnnotation: 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 (modVal, clsDef) = ClassDef.module(mod, parents, body = List(runDef)) val newDef = DefDef.copy(tree)(name, List(TermParamClause(Nil)), tpt, Some(Apply(Select(Ref(mod), runSym), Nil))) List(modVal, clsDef, newDef) From ecba50d8809383f75f8050091b0768bd0b707206 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 12 Jan 2023 13:20:34 +0100 Subject: [PATCH 11/13] Apply suggestions from code review Co-authored-by: Guillaume Martres --- library/src/scala/quoted/Quotes.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 183532c84797..7b133472506a 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -473,7 +473,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => def unapply(cdef: ClassDef): (String, DefDef, List[Tree /* Term | TypeTree */], Option[ValDef], List[Statement]) - /** Create the ValDef and ClassDef of a module. + /** Create the ValDef and ClassDef of a module (equivalent to an `object` declaration in source code). * * Equivalent to * ``` @@ -484,7 +484,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * List(modValDef, modClassDef) * ``` * - * @param module the module symbol (of the module lazy val) + * @param module the module symbol (created using `Symbol.newModule`) * @param parents parents of the module class * @param body body of the module class * @return The module lazy val definition and module class definition. @@ -3713,7 +3713,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * * This symbol starts without an accompanying definition. * It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing - * this symbol to the ClassDef and ValDef constructor. + * this symbol to `ClassDef.module`. * * @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be * direct or indirect children of the reflection context's owner. From 41a46fcfc77c5c4ab8f52ee7d01c96b7de18cf8d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 12 Jan 2023 14:59:43 +0100 Subject: [PATCH 12/13] Improve doc --- library/src/scala/quoted/Quotes.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 7b133472506a..2562ae02a7d1 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3666,7 +3666,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => @experimental def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol /** Generates a new module symbol with an associated module class symbol, - * This returns the module symbol. The module class can be accessed calling `moduleClass` on this symbol. + * this is equivalent to an `object` declaration in source code. + * This method returns the module symbol. The module class can be accessed calling `moduleClass` on this symbol. * * Example usage: * ```scala From 6c6dc775e67f4473a313ed8c0cb95aec6016a42b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 12 Jan 2023 15:39:47 +0100 Subject: [PATCH 13/13] Handle top-level class insertion using a MutableSymbolMap --- .../dotty/tools/dotc/transform/Inlining.scala | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index 87924c7932eb..f0ed7026ee91 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -14,6 +14,7 @@ import dotty.tools.dotc.inlines.Inlines import dotty.tools.dotc.ast.TreeMapWithImplicits import dotty.tools.dotc.core.DenotTransformers.IdentityDenotTransformer +import scala.collection.mutable.ListBuffer /** Inlines all calls to inline methods that are not in an inline method or a quote */ class Inlining extends MacroTransform { @@ -66,7 +67,7 @@ class Inlining extends MacroTransform { /** List of top level classes added by macro annotation in a package object. * These are added to the PackageDef that owns this particular package object. */ - private val topClasses = new collection.mutable.ListBuffer[Tree] + private val newTopClasses = MutableSymbolMap[ListBuffer[Tree]]() override def transform(tree: Tree)(using Context): Tree = { tree match @@ -82,10 +83,11 @@ class Inlining extends MacroTransform { val trees1 = trees.map(super.transform) // Find classes added to the top level from a package object - val (topClasses0, trees2) = + val (topClasses, trees2) = if ctx.owner.isPackageObject then trees1.partition(_.symbol.owner == ctx.owner.owner) else (Nil, trees1) - topClasses ++= topClasses0 + if topClasses.nonEmpty then + newTopClasses.getOrElseUpdate(ctx.owner.owner, new ListBuffer) ++= topClasses flatTree(trees2) else super.transform(tree) @@ -101,10 +103,13 @@ class Inlining extends MacroTransform { super.transform(tree)(using StagingContext.spliceContext) case _: PackageDef => super.transform(tree) match - case tree1: PackageDef if !topClasses.isEmpty => - val newStats = tree1.stats ::: topClasses.result() - topClasses.clear() - cpy.PackageDef(tree1)(tree1.pid, newStats) + case tree1: PackageDef => + newTopClasses.get(tree.symbol.moduleClass) match + case Some(topClasses) => + newTopClasses.remove(tree.symbol.moduleClass) + val newStats = tree1.stats ::: topClasses.result() + cpy.PackageDef(tree1)(tree1.pid, newStats) + case _ => tree1 case tree1 => tree1 case _ => super.transform(tree)