From 24586e15763d2f9fdceb821324cef9d54e9382af Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 11 Oct 2021 11:34:14 +0200 Subject: [PATCH] Add reflect `ClassDef.apply` --- .../src/dotty/tools/dotc/core/Symbols.scala | 2 +- .../quoted/runtime/impl/QuotesImpl.scala | 23 +++++- library/src/scala/quoted/Quotes.scala | 11 +++ project/MiMaFilters.scala | 8 +++ tests/run-macros/newClass.check | 4 ++ tests/run-macros/newClass/Macro_1.scala | 21 ++++++ tests/run-macros/newClass/Test_2.scala | 6 ++ tests/run-macros/newClassExtends.check | 4 ++ .../run-macros/newClassExtends/Macro_1.scala | 31 ++++++++ tests/run-macros/newClassExtends/Test_2.scala | 8 +++ tests/run-macros/newClassSelf.check | 8 +++ tests/run-macros/newClassSelf/Macro_1.scala | 71 +++++++++++++++++++ tests/run-macros/newClassSelf/Test_2.scala | 13 ++++ 13 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 tests/run-macros/newClass.check create mode 100644 tests/run-macros/newClass/Macro_1.scala create mode 100644 tests/run-macros/newClass/Test_2.scala create mode 100644 tests/run-macros/newClassExtends.check create mode 100644 tests/run-macros/newClassExtends/Macro_1.scala create mode 100644 tests/run-macros/newClassExtends/Test_2.scala create mode 100644 tests/run-macros/newClassSelf.check create mode 100644 tests/run-macros/newClassSelf/Macro_1.scala create mode 100644 tests/run-macros/newClassSelf/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 4fb481dfe2a9..713bd8c3b640 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -572,7 +572,7 @@ object Symbols { 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) + denot.info = ClassInfo(owner.thisType, cls, parentTypes.map(_.dealias), decls, selfInfo) } } newClassSymbol(owner, name, flags, completer, privateWithin, coord, assocFile) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 000f84199c27..261ff1b4d123 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -228,6 +228,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end ClassDefTypeTest object ClassDef extends ClassDefModule: + def apply(cls: Symbol, parents: List[Tree], body: List[Statement]): ClassDef = + val untpdCtr = untpd.DefDef(nme.CONSTRUCTOR, Nil, tpd.TypeTree(dotc.core.Symbols.defn.UnitClass.typeRef), untpd.EmptyTree) + val ctr = ctx.typeAssigner.assignType(untpdCtr, cls.primaryConstructor) + tpd.ClassDefWithParents(cls.asClass, ctr, parents, body) + def copy(original: Tree)(name: String, constr: DefDef, parents: List[Tree], selfOpt: Option[ValDef], body: List[Statement]): ClassDef = { val dotc.ast.Trees.TypeDef(_, originalImpl: tpd.Template) = original tpd.cpy.TypeDef(original)(name.toTypeName, tpd.cpy.Template(originalImpl)(constr, parents, derived = Nil, selfOpt.getOrElse(tpd.EmptyValDef), body)) @@ -260,6 +265,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object DefDef extends DefDefModule: def apply(symbol: Symbol, rhsFn: List[List[Tree]] => Option[Term]): DefDef = + assert(symbol.isTerm, s"expected a term symbol but received $symbol") withDefaultPos(tpd.DefDef(symbol.asTerm, prefss => xCheckMacroedOwners(xCheckMacroValidExpr(rhsFn(prefss)), symbol).getOrElse(tpd.EmptyTree) )) @@ -1804,7 +1810,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler (x.prefix, x.name.toString) end TermRef - type TypeRef = dotc.core.Types.NamedType + type TypeRef = dotc.core.Types.TypeRef object TypeRefTypeTest extends TypeTest[TypeRepr, TypeRef]: def unapply(x: TypeRepr): Option[TypeRef & x.type] = x match @@ -2454,6 +2460,19 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def requiredModule(path: String): Symbol = dotc.core.Symbols.requiredModule(path) def requiredMethod(path: String): Symbol = dotc.core.Symbols.requiredMethod(path) def classSymbol(fullName: String): Symbol = dotc.core.Symbols.requiredClass(fullName) + + def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfInfo: Option[TypeRepr]): Symbol = + val cls = dotc.core.Symbols.newNormalizedClassSymbol( + owner, + name.toTypeName, + Flags.EmptyFlags, + parents, + selfInfo.getOrElse(Types.NoType), + dotc.core.Symbols.NoSymbol) + cls.enter(dotc.core.Symbols.newConstructor(cls, dotc.core.Flags.Synthetic, Nil, Nil)) + for sym <- decls(cls) do cls.enter(sym) + cls + 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 = @@ -2622,6 +2641,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def companionClass: Symbol = self.denot.companionClass def companionModule: Symbol = self.denot.companionModule def children: List[Symbol] = self.denot.children + def typeRef: TypeRef = self.denot.typeRef + def termRef: TermRef = self.denot.termRef def show(using printer: Printer[Symbol]): String = printer.show(self) diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index a9e2db108f40..91ec03c74c68 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -464,6 +464,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val ClassDef` */ trait ClassDefModule { this: ClassDef.type => + @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]) } @@ -3533,6 +3534,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** The class Symbol of a global class definition */ def classSymbol(fullName: String): Symbol + @experimental def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfInfo: Option[TypeRepr]): Symbol + /** Generates a new method symbol with the given parent, name and type. * * This symbol starts without an accompanying definition. @@ -3807,6 +3810,14 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Case class or case object children of a sealed trait or cases of an `enum`. */ def children: List[Symbol] + + /** Type reference to the symbol */ + @experimental + def typeRef: TypeRef + + /** Term reference to the symbol */ + @experimental + def termRef: TermRef end extension } diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index cac81eecc141..67e839c1adf3 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -17,6 +17,14 @@ object MiMaFilters { ProblemFilters.exclude[MissingClassProblem]("scala.compiletime.ops.long$"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#CompilationInfoModule.XmacroSettings"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#CompilationInfoModule.XmacroSettings"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"), // Private to the compiler - needed for forward binary compatibility ProblemFilters.exclude[MissingClassProblem]("scala.annotation.since") diff --git a/tests/run-macros/newClass.check b/tests/run-macros/newClass.check new file mode 100644 index 000000000000..581350beea39 --- /dev/null +++ b/tests/run-macros/newClass.check @@ -0,0 +1,4 @@ +Constructing foo +class Test_2$package$foo$1 +Constructing bar +class Test_2$package$bar$1 diff --git a/tests/run-macros/newClass/Macro_1.scala b/tests/run-macros/newClass/Macro_1.scala new file mode 100644 index 000000000000..40ed128e43a0 --- /dev/null +++ b/tests/run-macros/newClass/Macro_1.scala @@ -0,0 +1,21 @@ +import scala.quoted.* + +inline def makeClass(inline name: String): Any = ${ makeClassExpr('name) } +private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + val parents = List(TypeTree.of[Object]) + def decls(cls: Symbol): List[Symbol] = Nil + + val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfInfo = None) + + val clsDef = ClassDef(cls, parents, body = List('{println(s"Constructing ${$nameExpr}")}.asTerm)) + val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Object]) + + Block(List(clsDef), newCls).asExpr + // '{ + // class `name`() { println("Constructing `name`") } + // new `name`() + // } +} diff --git a/tests/run-macros/newClass/Test_2.scala b/tests/run-macros/newClass/Test_2.scala new file mode 100644 index 000000000000..6868674547fe --- /dev/null +++ b/tests/run-macros/newClass/Test_2.scala @@ -0,0 +1,6 @@ +@main def Test: Unit = { + val foo = makeClass("foo") + println(foo.getClass) + val bar = makeClass("bar") + println(bar.getClass) +} diff --git a/tests/run-macros/newClassExtends.check b/tests/run-macros/newClassExtends.check new file mode 100644 index 000000000000..c8f9c6373f19 --- /dev/null +++ b/tests/run-macros/newClassExtends.check @@ -0,0 +1,4 @@ +Calling foo.foo +class Test_2$package$foo$1 +Calling bar.foo +class Test_2$package$bar$1 diff --git a/tests/run-macros/newClassExtends/Macro_1.scala b/tests/run-macros/newClassExtends/Macro_1.scala new file mode 100644 index 000000000000..58e568389fb4 --- /dev/null +++ b/tests/run-macros/newClassExtends/Macro_1.scala @@ -0,0 +1,31 @@ +import scala.quoted.* + +inline def makeClass(inline name: String): Foo = ${ makeClassExpr('name) } +private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Foo] = { + import quotes.reflect.* + + val name = nameExpr.valueOrAbort + val parents = List(TypeTree.of[Object], TypeTree.of[Foo]) + def decls(cls: Symbol): List[Symbol] = + List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]))) + + val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfInfo = None) + val fooSym = cls.declaredMethod("foo").head + + val fooDef = DefDef(fooSym, argss => Some('{println(s"Calling ${$nameExpr}.foo")}.asTerm)) + val clsDef = ClassDef(cls, parents, body = List(fooDef)) + val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Foo]) + + Block(List(clsDef), newCls).asExprOf[Foo] + + // '{ + // class `name`() extends Foo { + // def foo(): Unit = println("Calling `name`.foo") + // } + // new `name`() + // } +} + +trait Foo { + def foo(): Unit +} diff --git a/tests/run-macros/newClassExtends/Test_2.scala b/tests/run-macros/newClassExtends/Test_2.scala new file mode 100644 index 000000000000..1db791749bab --- /dev/null +++ b/tests/run-macros/newClassExtends/Test_2.scala @@ -0,0 +1,8 @@ +@main def Test: Unit = { + val foo: Foo = makeClass("foo") + foo.foo() + println(foo.getClass) + val bar: Foo = makeClass("bar") + bar.foo() + println(bar.getClass) +} diff --git a/tests/run-macros/newClassSelf.check b/tests/run-macros/newClassSelf.check new file mode 100644 index 000000000000..f18a088495bc --- /dev/null +++ b/tests/run-macros/newClassSelf.check @@ -0,0 +1,8 @@ +Calling Bar.bar +Calling Foo.foo +Calling Bar.bar +class Test_2$package$A$1 +Calling Bar.bar +Calling Foo.foo +Calling Bar.bar +class Test_2$package$B$1 diff --git a/tests/run-macros/newClassSelf/Macro_1.scala b/tests/run-macros/newClassSelf/Macro_1.scala new file mode 100644 index 000000000000..0a38034809ff --- /dev/null +++ b/tests/run-macros/newClassSelf/Macro_1.scala @@ -0,0 +1,71 @@ +import scala.quoted.* + +inline def makeClass(inline name: String): Bar = ${ makeClassExpr('name) } +private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Bar] = { + import quotes.reflect.* + val name = nameExpr.valueOrAbort + val fooDef = makeFoo() + val fooBarDef = makeFooBar(name, fooDef.symbol) + val newCls = makeNewFooBar(fooBarDef.symbol) + + Block(List(fooDef, fooBarDef), newCls).asExprOf[Bar] + // '{ + // class Foo { self: Bar => + // def foo(): Unit = bar() + // } + // class `name`() extends Foo with Bar + // new `name`() + // } +} + +/** Generate + * ``` + * class Foo { self: Bar => + * def foo(): Unit = bar() + * } + * ``` + */ +def makeFoo(using Quotes)(): quotes.reflect.ClassDef = { + import quotes.reflect.* + val parents = List(TypeTree.of[Object]) + def decls(cls: Symbol): List[Symbol] = + List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]))) + + val cls = Symbol.newClass(Symbol.spliceOwner, "Foo", parents = parents.map(_.tpe), decls, selfInfo = Some(TypeRepr.of[Bar])) + val fooSym = cls.declaredMethod("foo").head + val barSym = Symbol.classSymbol("Bar").declaredMethod("bar").head + + def fooRhs(args: List[List[Tree]]): Option[Term] = + val barCall = This(cls).select(barSym).appliedToNone.asExprOf[Unit] + Some('{ println("Calling Foo.foo"); $barCall }.asTerm) + + val fooDef = DefDef(fooSym, fooRhs) + ClassDef(cls, parents, body = List(fooDef)) +} + +/** Generate + * ``` + * class `name`() extends Foo with Bar + * ``` + */ +def makeFooBar(using Quotes)(name: String, fooCls: quotes.reflect.Symbol): quotes.reflect.ClassDef = { + import quotes.reflect.* + val parents = List(Inferred(fooCls.typeRef), TypeTree.of[Bar]) + def decls(cls: Symbol): List[Symbol] = Nil + val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfInfo = None) + ClassDef(cls, parents, body = Nil) +} + +/** Generate + * ``` + * new `name`() + * ``` + */ +def makeNewFooBar(using Quotes)(fooBarCls: quotes.reflect.Symbol): quotes.reflect.Term = { + import quotes.reflect.* + Typed(Apply(Select(New(TypeIdent(fooBarCls)), fooBarCls.primaryConstructor), Nil), TypeTree.of[Bar]) +} + +trait Bar { + def bar(): Unit = println("Calling Bar.bar") +} diff --git a/tests/run-macros/newClassSelf/Test_2.scala b/tests/run-macros/newClassSelf/Test_2.scala new file mode 100644 index 000000000000..0c6d522de58f --- /dev/null +++ b/tests/run-macros/newClassSelf/Test_2.scala @@ -0,0 +1,13 @@ +@main def Test: Unit = { + val a: Bar = makeClass("A") + a.bar() + callFoo(a) + println(a.getClass) + val b: Bar = makeClass("B") + b.bar() + callFoo(b) + println(b.getClass) +} + +def callFoo(x: Any): Unit = + x.getClass.getMethod("foo").invoke(x)