From 48453537c90278e366c8ae17ab0398ac92d20854 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki <nicolas.stucki@gmail.com> Date: Mon, 11 Oct 2021 11:34:14 +0200 Subject: [PATCH] Add reflect `ClassDef.apply` and `Symbol.newClass` --- .../src/dotty/tools/dotc/core/Symbols.scala | 2 +- .../quoted/runtime/impl/QuotesImpl.scala | 24 +++++- library/src/scala/quoted/Quotes.scala | 85 +++++++++++++++++-- project/MiMaFilters.scala | 8 ++ .../neg-macros/newClassExtendsNoParents.check | 19 +++++ .../newClassExtendsNoParents/Macro_1.scala | 23 +++++ .../newClassExtendsNoParents/Test_2.scala | 1 + .../neg-macros/newClassExtendsOnlyTrait.check | 19 +++++ .../newClassExtendsOnlyTrait/Macro_1.scala | 31 +++++++ .../newClassExtendsOnlyTrait/Test_2.scala | 1 + 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 ++ .../newClassExtendsClassParams.check | 4 + .../newClassExtendsClassParams/Macro_1.scala | 27 ++++++ .../newClassExtendsClassParams/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 +++ 22 files changed, 408 insertions(+), 10 deletions(-) create mode 100644 tests/neg-macros/newClassExtendsNoParents.check create mode 100644 tests/neg-macros/newClassExtendsNoParents/Macro_1.scala create mode 100644 tests/neg-macros/newClassExtendsNoParents/Test_2.scala create mode 100644 tests/neg-macros/newClassExtendsOnlyTrait.check create mode 100644 tests/neg-macros/newClassExtendsOnlyTrait/Macro_1.scala create mode 100644 tests/neg-macros/newClassExtendsOnlyTrait/Test_2.scala 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/newClassExtendsClassParams.check create mode 100644 tests/run-macros/newClassExtendsClassParams/Macro_1.scala create mode 100644 tests/run-macros/newClassExtendsClassParams/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 b19c8cae3105..e61ba1d6201e 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -577,7 +577,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 e97aa061c3f5..9fd7b3b7dce1 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -227,6 +227,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), tpd.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)) @@ -259,6 +264,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) )) @@ -1803,7 +1809,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 @@ -2453,6 +2459,20 @@ 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 = + assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class") + val cls = dotc.core.Symbols.newNormalizedClassSymbol( + owner, + name.toTypeName, + dotc.core.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 = @@ -2621,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..45c7e5fc8c94 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,19 +3534,73 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** The class Symbol of a global class definition */ def classSymbol(fullName: String): Symbol + /** Generates a new class symbol for a class with a parameterless constructor. + * + * Example usage: + * ```scala + * val name: String = ... + * 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 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] + * ``` + * constructs the equivalent to + * ```scala + * '{ + * class `name`() extends Object with Foo { + * def foo(): Unit = println("Calling foo") + * } + * new `name`(): Foo + * } + * ``` + * + * @param parent The owner of the class + * @param name The name of the class + * @param parents The parent classes of the class + * @param decls The member declarations of the class provided the symbol of this class + * @param self The self type of the class if it has one + * + * 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 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. + */ + @experimental def newClass(parent: 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. - * It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing - * this symbol to the DefDef 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. - */ + * + * To define a member method of a class, use the `newMethod` within the `decls` function of `newClass`. + * + * @param parent The owner of the method + * @param name The name of the method + * @param tpe The type of the method (MethodType, PolyType, ByNameType) + * + * 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 DefDef 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. + */ def newMethod(parent: Symbol, name: String, tpe: TypeRepr): Symbol /** Works as the other newMethod, but with additional parameters. * + * To define a member method of a class, use the `newMethod` within the `decls` function of `newClass`. + * + * @param parent The owner of the method + * @param name The name of the method + * @param tpe The type of the method (MethodType, PolyType, ByNameType) * @param flags extra flags to with which the symbol should be constructed * @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. */ @@ -3559,6 +3614,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * * Note: Also see reflect.let * + * @param parent The owner of the /var/lazy val + * @param name The name of the val/var/lazy val + * @param tpe The type of the val/var/lazy val * @param flags extra flags to with which the symbol should be constructed * @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. * @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be @@ -3572,7 +3630,10 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing * this symbol to the BindDef constructor. * + * @param parent The owner of the binding + * @param name The name of the binding * @param flags extra flags to with which the symbol should be constructed + * @param tpe The type of the binding * @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. */ @@ -3807,6 +3868,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 73b7e9ca674e..aff1a9baadb7 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -19,6 +19,14 @@ object MiMaFilters { ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#CompilationInfoModule.XmacroSettings"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.deriving.Mirror.fromProductTyped"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.deriving.Mirror.fromTuple"), + 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/neg-macros/newClassExtendsNoParents.check b/tests/neg-macros/newClassExtendsNoParents.check new file mode 100644 index 000000000000..c9637033d50e --- /dev/null +++ b/tests/neg-macros/newClassExtendsNoParents.check @@ -0,0 +1,19 @@ + +-- Error: tests/neg-macros/newClassExtendsNoParents/Test_2.scala:1:25 -------------------------------------------------- +1 |def test: Any = makeClass("foo") // error + | ^^^^^^^^^^^^^^^^ + | Exception occurred while executing macro expansion. + | java.lang.AssertionError: assertion failed: First parent must be a class + | at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$Symbol$.newClass(QuotesImpl.scala:2464) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$Symbol$.newClass(QuotesImpl.scala:2463) + | at Macro_1$package$.makeClassExpr(Macro_1.scala:11) + | at Macro_1$package$.inline$makeClassExpr(Macro_1.scala:4) + | + |--------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from Macro_1.scala:3 +3 |inline def makeClass(inline name: String): Any = ${ makeClassExpr('name) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/newClassExtendsNoParents/Macro_1.scala b/tests/neg-macros/newClassExtendsNoParents/Macro_1.scala new file mode 100644 index 000000000000..170e2f7d3f4b --- /dev/null +++ b/tests/neg-macros/newClassExtendsNoParents/Macro_1.scala @@ -0,0 +1,23 @@ +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.empty[Tree] // BUG: first parent is not a class + def decls(cls: Symbol): List[Symbol] = Nil + + val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = Nil, decls, selfInfo = None) + val clsDef = ClassDef(cls, parents, body = List()) + val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Object]) + + Block(List(clsDef), newCls).asExpr + + // '{ + // class `name`() { + // def foo(): Unit = println("Calling `name`.foo") + // } + // new `name`() + // } +} diff --git a/tests/neg-macros/newClassExtendsNoParents/Test_2.scala b/tests/neg-macros/newClassExtendsNoParents/Test_2.scala new file mode 100644 index 000000000000..eada3ba7e67a --- /dev/null +++ b/tests/neg-macros/newClassExtendsNoParents/Test_2.scala @@ -0,0 +1 @@ +def test: Any = makeClass("foo") // error diff --git a/tests/neg-macros/newClassExtendsOnlyTrait.check b/tests/neg-macros/newClassExtendsOnlyTrait.check new file mode 100644 index 000000000000..140da03c93a1 --- /dev/null +++ b/tests/neg-macros/newClassExtendsOnlyTrait.check @@ -0,0 +1,19 @@ + +-- Error: tests/neg-macros/newClassExtendsOnlyTrait/Test_2.scala:1:25 -------------------------------------------------- +1 |def test: Foo = makeClass("foo") // error + | ^^^^^^^^^^^^^^^^ + | Exception occurred while executing macro expansion. + | java.lang.AssertionError: assertion failed: First parent must be a class + | at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$Symbol$.newClass(QuotesImpl.scala:2464) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$Symbol$.newClass(QuotesImpl.scala:2463) + | at Macro_1$package$.makeClassExpr(Macro_1.scala:12) + | at Macro_1$package$.inline$makeClassExpr(Macro_1.scala:4) + | + |--------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from Macro_1.scala:3 +3 |inline def makeClass(inline name: String): Foo = ${ makeClassExpr('name) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/newClassExtendsOnlyTrait/Macro_1.scala b/tests/neg-macros/newClassExtendsOnlyTrait/Macro_1.scala new file mode 100644 index 000000000000..8ff570b2d9e2 --- /dev/null +++ b/tests/neg-macros/newClassExtendsOnlyTrait/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[Foo]) // BUG: first parent is not a class + 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/neg-macros/newClassExtendsOnlyTrait/Test_2.scala b/tests/neg-macros/newClassExtendsOnlyTrait/Test_2.scala new file mode 100644 index 000000000000..d8636c811214 --- /dev/null +++ b/tests/neg-macros/newClassExtendsOnlyTrait/Test_2.scala @@ -0,0 +1 @@ +def test: Foo = makeClass("foo") // error 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..e88f63b52122 --- /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 Object, 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/newClassExtendsClassParams.check b/tests/run-macros/newClassExtendsClassParams.check new file mode 100644 index 000000000000..38089ae4874a --- /dev/null +++ b/tests/run-macros/newClassExtendsClassParams.check @@ -0,0 +1,4 @@ +Calling Foo.foo with i = 1 +class Test_2$package$foo$1 +Calling Foo.foo with i = 1 +class Test_2$package$bar$1 diff --git a/tests/run-macros/newClassExtendsClassParams/Macro_1.scala b/tests/run-macros/newClassExtendsClassParams/Macro_1.scala new file mode 100644 index 000000000000..7444ce4f0c6d --- /dev/null +++ b/tests/run-macros/newClassExtendsClassParams/Macro_1.scala @@ -0,0 +1,27 @@ +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('{ new Foo(1) }.asTerm) + 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 = Nil) + val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Foo]) + + Block(List(clsDef), newCls).asExprOf[Foo] + + // '{ + // class `name`() extends Foo(3) + // new `name`() + // } +} + +class Foo(i: Int) { + def foo(): Unit = println(s"Calling Foo.foo with i = $i") +} diff --git a/tests/run-macros/newClassExtendsClassParams/Test_2.scala b/tests/run-macros/newClassExtendsClassParams/Test_2.scala new file mode 100644 index 000000000000..1db791749bab --- /dev/null +++ b/tests/run-macros/newClassExtendsClassParams/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)