diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 15b46f6c9cb9..7c3f1ae36017 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -575,7 +575,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 7dc347cdaea7..8193b5ef4ce6 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -230,6 +230,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)) @@ -262,6 +267,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) )) @@ -1049,6 +1055,9 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object TypeTree extends TypeTreeModule: def of[T <: AnyKind](using tp: scala.quoted.Type[T]): TypeTree = tp.asInstanceOf[TypeImpl].typeTree + def ref(sym: Symbol): TypeTree = + assert(sym.isType, "Expected a type symbol, but got " + sym) + tpd.ref(sym) end TypeTree given TypeTreeMethods: TypeTreeMethods with @@ -1806,7 +1815,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 @@ -2456,6 +2465,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], selfType: 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, + selfType.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 = @@ -2624,6 +2647,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 9b5b00a6718c..e7abfa801f83 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -460,6 +460,15 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val ClassDef` */ trait ClassDefModule { this: ClassDef.type => + /** Create a class definition tree + * + * @param cls The class symbol. A new class symbol can be created using `Symbol.newClass`. + * @param parents The parents trees class. The trees must align with the parent types of `cls`. + * Parents can be `TypeTree`s if they don't have term parameter, + * 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`. + */ + @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]) } @@ -1719,6 +1728,13 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => trait TypeTreeModule { this: TypeTree.type => /** Returns the tree of type or kind (TypeTree) of T */ def of[T <: AnyKind](using Type[T]): TypeTree + + /** Returns a type tree reference to the symbol + * + * @param sym The type symbol for which we are creating a type tree reference. + */ + @experimental + def ref(typeSymbol: Symbol): TypeTree } /** Makes extension methods on `TypeTree` available without any imports */ @@ -2571,6 +2587,11 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => def typeSymbol: Symbol def termSymbol: Symbol def isSingleton: Boolean + + /** The type of `member` as seen from prefix `self`. + * + * Also see `typeRef` and `termRef` + */ def memberType(member: Symbol): TypeRepr /** The base classes of this type with the class itself as first element. */ @@ -3578,19 +3599,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: + * ``` + * val name: String = "myClass" + * 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 + * ``` + * '{ + * class myClass() extends Object with Foo { + * def foo(): Unit = println("Calling foo") + * } + * new myClass(): Foo + * } + * ``` + * + * @param parent The owner of the class + * @param name The name of the class + * @param parents The parent classes of the class. The first parent must not be a trait. + * @param decls The member declarations of the class provided the symbol of this class + * @param selfType 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], selfType: 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. */ @@ -3604,6 +3679,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * * Note: Also see reflect.let * + * @param parent The owner of the val/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 @@ -3617,7 +3695,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. */ @@ -3679,9 +3760,11 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * * symbol.tree.tpe * - * It should be replaced by the following code: + * It should be replaced by one of the following: * * tp.memberType(symbol) + * symbol.typeRef + * symbol.termRef * */ def tree: Tree @@ -3880,6 +3963,19 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => @experimental def asQuotes: Nested + /** Type reference to the symbol usable in the scope of its owner. + * + * To get a reference to a symbol from a specific prefix `tp`, use `tp.select(symbol)` instead. + * @see TypeReprMethods.select + * + * @pre symbol.isType returns true + */ + @experimental + def typeRef: TypeRef + + /** Term reference to the symbol usable in the scope of its owner. */ + @experimental + def termRef: TermRef end extension } diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 060bf29eb301..20867b1a8cb7 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -21,6 +21,16 @@ 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"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"), // Private to the compiler - needed for forward binary compatibility ProblemFilters.exclude[MissingClassProblem]("scala.annotation.since"), diff --git a/tests/neg-macros/newClassExtendsNoParents/Macro_1.scala b/tests/neg-macros/newClassExtendsNoParents/Macro_1.scala new file mode 100644 index 000000000000..5280ffba0f53 --- /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, selfType = 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/Macro_1.scala b/tests/neg-macros/newClassExtendsOnlyTrait/Macro_1.scala new file mode 100644 index 000000000000..d1fd45399157 --- /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, selfType = 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..e7ae59c5331b --- /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, selfType = 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..99f639158761 --- /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, selfType = 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..f6eebc9487e5 --- /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, selfType = 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..864015c896f0 --- /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, selfType = 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(TypeTree.ref(fooCls), TypeTree.of[Bar]) + def decls(cls: Symbol): List[Symbol] = Nil + val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = 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)