diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 000f84199c27..179445134b88 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 = diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index a9e2db108f40..a6579ebcb42b 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. diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index cac81eecc141..3a7890a9df8e 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -17,6 +17,10 @@ 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"), // 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) +}