Skip to content

Commit a5d5fe5

Browse files
Merge pull request #14124 from dotty-staging/add-reflect-ClassDef-apply
Add reflect `ClassDef.apply` and `Symbol.newClass`
2 parents 0e97414 + 36853ff commit a5d5fe5

File tree

20 files changed

+403
-11
lines changed

20 files changed

+403
-11
lines changed

compiler/src/dotty/tools/dotc/core/Symbols.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ object Symbols {
575575
def complete(denot: SymDenotation)(using Context): Unit = {
576576
val cls = denot.asClass.classSymbol
577577
val decls = newScope
578-
denot.info = ClassInfo(owner.thisType, cls, parentTypes.map(_.dealias), decls)
578+
denot.info = ClassInfo(owner.thisType, cls, parentTypes.map(_.dealias), decls, selfInfo)
579579
}
580580
}
581581
newClassSymbol(owner, name, flags, completer, privateWithin, coord, assocFile)

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
230230
end ClassDefTypeTest
231231

232232
object ClassDef extends ClassDefModule:
233+
def apply(cls: Symbol, parents: List[Tree], body: List[Statement]): ClassDef =
234+
val untpdCtr = untpd.DefDef(nme.CONSTRUCTOR, Nil, tpd.TypeTree(dotc.core.Symbols.defn.UnitClass.typeRef), tpd.EmptyTree)
235+
val ctr = ctx.typeAssigner.assignType(untpdCtr, cls.primaryConstructor)
236+
tpd.ClassDefWithParents(cls.asClass, ctr, parents, body)
237+
233238
def copy(original: Tree)(name: String, constr: DefDef, parents: List[Tree], selfOpt: Option[ValDef], body: List[Statement]): ClassDef = {
234239
val dotc.ast.Trees.TypeDef(_, originalImpl: tpd.Template) = original
235240
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
262267

263268
object DefDef extends DefDefModule:
264269
def apply(symbol: Symbol, rhsFn: List[List[Tree]] => Option[Term]): DefDef =
270+
assert(symbol.isTerm, s"expected a term symbol but received $symbol")
265271
withDefaultPos(tpd.DefDef(symbol.asTerm, prefss =>
266272
xCheckMacroedOwners(xCheckMacroValidExpr(rhsFn(prefss)), symbol).getOrElse(tpd.EmptyTree)
267273
))
@@ -1049,6 +1055,9 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
10491055
object TypeTree extends TypeTreeModule:
10501056
def of[T <: AnyKind](using tp: scala.quoted.Type[T]): TypeTree =
10511057
tp.asInstanceOf[TypeImpl].typeTree
1058+
def ref(sym: Symbol): TypeTree =
1059+
assert(sym.isType, "Expected a type symbol, but got " + sym)
1060+
tpd.ref(sym)
10521061
end TypeTree
10531062

10541063
given TypeTreeMethods: TypeTreeMethods with
@@ -1806,7 +1815,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
18061815
(x.prefix, x.name.toString)
18071816
end TermRef
18081817

1809-
type TypeRef = dotc.core.Types.NamedType
1818+
type TypeRef = dotc.core.Types.TypeRef
18101819

18111820
object TypeRefTypeTest extends TypeTest[TypeRepr, TypeRef]:
18121821
def unapply(x: TypeRepr): Option[TypeRef & x.type] = x match
@@ -2456,6 +2465,20 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
24562465
def requiredModule(path: String): Symbol = dotc.core.Symbols.requiredModule(path)
24572466
def requiredMethod(path: String): Symbol = dotc.core.Symbols.requiredMethod(path)
24582467
def classSymbol(fullName: String): Symbol = dotc.core.Symbols.requiredClass(fullName)
2468+
2469+
def newClass(owner: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol =
2470+
assert(parents.nonEmpty && !parents.head.typeSymbol.is(dotc.core.Flags.Trait), "First parent must be a class")
2471+
val cls = dotc.core.Symbols.newNormalizedClassSymbol(
2472+
owner,
2473+
name.toTypeName,
2474+
dotc.core.Flags.EmptyFlags,
2475+
parents,
2476+
selfType.getOrElse(Types.NoType),
2477+
dotc.core.Symbols.NoSymbol)
2478+
cls.enter(dotc.core.Symbols.newConstructor(cls, dotc.core.Flags.Synthetic, Nil, Nil))
2479+
for sym <- decls(cls) do cls.enter(sym)
2480+
cls
2481+
24592482
def newMethod(owner: Symbol, name: String, tpe: TypeRepr): Symbol =
24602483
newMethod(owner, name, tpe, Flags.EmptyFlags, noSymbol)
24612484
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
26242647
def companionClass: Symbol = self.denot.companionClass
26252648
def companionModule: Symbol = self.denot.companionModule
26262649
def children: List[Symbol] = self.denot.children
2650+
def typeRef: TypeRef = self.denot.typeRef
2651+
def termRef: TermRef = self.denot.termRef
26272652

26282653
def show(using printer: Printer[Symbol]): String = printer.show(self)
26292654

library/src/scala/quoted/Quotes.scala

Lines changed: 105 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,15 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
460460

461461
/** Methods of the module object `val ClassDef` */
462462
trait ClassDefModule { this: ClassDef.type =>
463+
/** Create a class definition tree
464+
*
465+
* @param cls The class symbol. A new class symbol can be created using `Symbol.newClass`.
466+
* @param parents The parents trees class. The trees must align with the parent types of `cls`.
467+
* Parents can be `TypeTree`s if they don't have term parameter,
468+
* otherwise the can be `Term` containing the `New` applied to the parameters of the extended class.
469+
* @param body List of members of the class. The members must align with the members of `cls`.
470+
*/
471+
@experimental def apply(cls: Symbol, parents: List[Tree /* Term | TypeTree */], body: List[Statement]): ClassDef
463472
def copy(original: Tree)(name: String, constr: DefDef, parents: List[Tree /* Term | TypeTree */], selfOpt: Option[ValDef], body: List[Statement]): ClassDef
464473
def unapply(cdef: ClassDef): (String, DefDef, List[Tree /* Term | TypeTree */], Option[ValDef], List[Statement])
465474
}
@@ -1719,6 +1728,13 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
17191728
trait TypeTreeModule { this: TypeTree.type =>
17201729
/** Returns the tree of type or kind (TypeTree) of T */
17211730
def of[T <: AnyKind](using Type[T]): TypeTree
1731+
1732+
/** Returns a type tree reference to the symbol
1733+
*
1734+
* @param sym The type symbol for which we are creating a type tree reference.
1735+
*/
1736+
@experimental
1737+
def ref(typeSymbol: Symbol): TypeTree
17221738
}
17231739

17241740
/** Makes extension methods on `TypeTree` available without any imports */
@@ -2571,6 +2587,11 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
25712587
def typeSymbol: Symbol
25722588
def termSymbol: Symbol
25732589
def isSingleton: Boolean
2590+
2591+
/** The type of `member` as seen from prefix `self`.
2592+
*
2593+
* Also see `typeRef` and `termRef`
2594+
*/
25742595
def memberType(member: Symbol): TypeRepr
25752596

25762597
/** The base classes of this type with the class itself as first element. */
@@ -3578,19 +3599,73 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
35783599
/** The class Symbol of a global class definition */
35793600
def classSymbol(fullName: String): Symbol
35803601

3602+
/** Generates a new class symbol for a class with a parameterless constructor.
3603+
*
3604+
* Example usage:
3605+
* ```
3606+
* val name: String = "myClass"
3607+
* val parents = List(TypeTree.of[Object], TypeTree.of[Foo])
3608+
* def decls(cls: Symbol): List[Symbol] =
3609+
* List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit])))
3610+
*
3611+
* val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfInfo = None)
3612+
* val fooSym = cls.declaredMethod("foo").head
3613+
*
3614+
* val fooDef = DefDef(fooSym, argss => Some('{println(s"Calling foo")}.asTerm))
3615+
* val clsDef = ClassDef(cls, parents, body = List(fooDef))
3616+
* val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Foo])
3617+
*
3618+
* Block(List(clsDef), newCls).asExprOf[Foo]
3619+
* ```
3620+
* constructs the equivalent to
3621+
* ```
3622+
* '{
3623+
* class myClass() extends Object with Foo {
3624+
* def foo(): Unit = println("Calling foo")
3625+
* }
3626+
* new myClass(): Foo
3627+
* }
3628+
* ```
3629+
*
3630+
* @param parent The owner of the class
3631+
* @param name The name of the class
3632+
* @param parents The parent classes of the class. The first parent must not be a trait.
3633+
* @param decls The member declarations of the class provided the symbol of this class
3634+
* @param selfType The self type of the class if it has one
3635+
*
3636+
* This symbol starts without an accompanying definition.
3637+
* It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing
3638+
* this symbol to the ClassDef constructor.
3639+
*
3640+
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
3641+
* direct or indirect children of the reflection context's owner.
3642+
*/
3643+
@experimental def newClass(parent: Symbol, name: String, parents: List[TypeRepr], decls: Symbol => List[Symbol], selfType: Option[TypeRepr]): Symbol
3644+
35813645
/** Generates a new method symbol with the given parent, name and type.
3582-
*
3583-
* This symbol starts without an accompanying definition.
3584-
* It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing
3585-
* this symbol to the DefDef constructor.
3586-
*
3587-
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
3588-
* direct or indirect children of the reflection context's owner.
3589-
*/
3646+
*
3647+
* To define a member method of a class, use the `newMethod` within the `decls` function of `newClass`.
3648+
*
3649+
* @param parent The owner of the method
3650+
* @param name The name of the method
3651+
* @param tpe The type of the method (MethodType, PolyType, ByNameType)
3652+
*
3653+
* This symbol starts without an accompanying definition.
3654+
* It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing
3655+
* this symbol to the DefDef constructor.
3656+
*
3657+
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
3658+
* direct or indirect children of the reflection context's owner.
3659+
*/
35903660
def newMethod(parent: Symbol, name: String, tpe: TypeRepr): Symbol
35913661

35923662
/** Works as the other newMethod, but with additional parameters.
35933663
*
3664+
* To define a member method of a class, use the `newMethod` within the `decls` function of `newClass`.
3665+
*
3666+
* @param parent The owner of the method
3667+
* @param name The name of the method
3668+
* @param tpe The type of the method (MethodType, PolyType, ByNameType)
35943669
* @param flags extra flags to with which the symbol should be constructed
35953670
* @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol.
35963671
*/
@@ -3604,6 +3679,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
36043679
*
36053680
* Note: Also see reflect.let
36063681
*
3682+
* @param parent The owner of the val/var/lazy val
3683+
* @param name The name of the val/var/lazy val
3684+
* @param tpe The type of the val/var/lazy val
36073685
* @param flags extra flags to with which the symbol should be constructed
36083686
* @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol.
36093687
* @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 =>
36173695
* It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing
36183696
* this symbol to the BindDef constructor.
36193697
*
3698+
* @param parent The owner of the binding
3699+
* @param name The name of the binding
36203700
* @param flags extra flags to with which the symbol should be constructed
3701+
* @param tpe The type of the binding
36213702
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
36223703
* direct or indirect children of the reflection context's owner.
36233704
*/
@@ -3679,9 +3760,11 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
36793760
*
36803761
* symbol.tree.tpe
36813762
*
3682-
* It should be replaced by the following code:
3763+
* It should be replaced by one of the following:
36833764
*
36843765
* tp.memberType(symbol)
3766+
* symbol.typeRef
3767+
* symbol.termRef
36853768
*
36863769
*/
36873770
def tree: Tree
@@ -3880,6 +3963,19 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
38803963
@experimental
38813964
def asQuotes: Nested
38823965

3966+
/** Type reference to the symbol usable in the scope of its owner.
3967+
*
3968+
* To get a reference to a symbol from a specific prefix `tp`, use `tp.select(symbol)` instead.
3969+
* @see TypeReprMethods.select
3970+
*
3971+
* @pre symbol.isType returns true
3972+
*/
3973+
@experimental
3974+
def typeRef: TypeRef
3975+
3976+
/** Term reference to the symbol usable in the scope of its owner. */
3977+
@experimental
3978+
def termRef: TermRef
38833979
end extension
38843980
}
38853981

project/MiMaFilters.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ object MiMaFilters {
2121
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#CompilationInfoModule.XmacroSettings"),
2222
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.deriving.Mirror.fromProductTyped"),
2323
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.deriving.Mirror.fromTuple"),
24+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"),
25+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"),
26+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"),
27+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"),
28+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"),
29+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"),
30+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"),
31+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"),
32+
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"),
33+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"),
2434

2535
// Private to the compiler - needed for forward binary compatibility
2636
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.since"),
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import scala.quoted.*
2+
3+
inline def makeClass(inline name: String): Any = ${ makeClassExpr('name) }
4+
private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = {
5+
import quotes.reflect.*
6+
7+
val name = nameExpr.valueOrAbort
8+
val parents = List.empty[Tree] // BUG: first parent is not a class
9+
def decls(cls: Symbol): List[Symbol] = Nil
10+
11+
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = Nil, decls, selfType = None)
12+
val clsDef = ClassDef(cls, parents, body = List())
13+
val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Object])
14+
15+
Block(List(clsDef), newCls).asExpr
16+
17+
// '{
18+
// class `name`() {
19+
// def foo(): Unit = println("Calling `name`.foo")
20+
// }
21+
// new `name`()
22+
// }
23+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
def test: Any = makeClass("foo") // error
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import scala.quoted.*
2+
3+
inline def makeClass(inline name: String): Foo = ${ makeClassExpr('name) }
4+
private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Foo] = {
5+
import quotes.reflect.*
6+
7+
val name = nameExpr.valueOrAbort
8+
val parents = List(TypeTree.of[Foo]) // BUG: first parent is not a class
9+
def decls(cls: Symbol): List[Symbol] =
10+
List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit])))
11+
12+
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None)
13+
val fooSym = cls.declaredMethod("foo").head
14+
15+
val fooDef = DefDef(fooSym, argss => Some('{println(s"Calling ${$nameExpr}.foo")}.asTerm))
16+
val clsDef = ClassDef(cls, parents, body = List(fooDef))
17+
val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Foo])
18+
19+
Block(List(clsDef), newCls).asExprOf[Foo]
20+
21+
// '{
22+
// class `name`() extends Foo {
23+
// def foo(): Unit = println("Calling `name`.foo")
24+
// }
25+
// new `name`()
26+
// }
27+
}
28+
29+
trait Foo {
30+
def foo(): Unit
31+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
def test: Foo = makeClass("foo") // error

tests/run-macros/newClass.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Constructing foo
2+
class Test_2$package$foo$1
3+
Constructing bar
4+
class Test_2$package$bar$1
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.quoted.*
2+
3+
inline def makeClass(inline name: String): Any = ${ makeClassExpr('name) }
4+
private def makeClassExpr(nameExpr: Expr[String])(using Quotes): Expr[Any] = {
5+
import quotes.reflect.*
6+
7+
val name = nameExpr.valueOrAbort
8+
val parents = List(TypeTree.of[Object])
9+
def decls(cls: Symbol): List[Symbol] = Nil
10+
11+
val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None)
12+
13+
val clsDef = ClassDef(cls, parents, body = List('{println(s"Constructing ${$nameExpr}")}.asTerm))
14+
val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[Object])
15+
16+
Block(List(clsDef), newCls).asExpr
17+
// '{
18+
// class `name`() { println("Constructing `name`") }
19+
// new `name`()
20+
// }
21+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@main def Test: Unit = {
2+
val foo = makeClass("foo")
3+
println(foo.getClass)
4+
val bar = makeClass("bar")
5+
println(bar.getClass)
6+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Calling foo.foo
2+
class Test_2$package$foo$1
3+
Calling bar.foo
4+
class Test_2$package$bar$1

0 commit comments

Comments
 (0)