Skip to content

Commit

Permalink
Merge pull request #14124 from dotty-staging/add-reflect-ClassDef-apply
Browse files Browse the repository at this point in the history
Add reflect `ClassDef.apply` and `Symbol.newClass`
  • Loading branch information
nicolasstucki authored Mar 24, 2022
2 parents 0e97414 + 36853ff commit a5d5fe5
Show file tree
Hide file tree
Showing 20 changed files with 403 additions and 11 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 26 additions & 1 deletion compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)
))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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)

Expand Down
114 changes: 105 additions & 9 deletions library/src/scala/quoted/Quotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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])
}
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
10 changes: 10 additions & 0 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
23 changes: 23 additions & 0 deletions tests/neg-macros/newClassExtendsNoParents/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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`()
// }
}
1 change: 1 addition & 0 deletions tests/neg-macros/newClassExtendsNoParents/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
def test: Any = makeClass("foo") // error
31 changes: 31 additions & 0 deletions tests/neg-macros/newClassExtendsOnlyTrait/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions tests/neg-macros/newClassExtendsOnlyTrait/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
def test: Foo = makeClass("foo") // error
4 changes: 4 additions & 0 deletions tests/run-macros/newClass.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Constructing foo
class Test_2$package$foo$1
Constructing bar
class Test_2$package$bar$1
21 changes: 21 additions & 0 deletions tests/run-macros/newClass/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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`()
// }
}
6 changes: 6 additions & 0 deletions tests/run-macros/newClass/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@main def Test: Unit = {
val foo = makeClass("foo")
println(foo.getClass)
val bar = makeClass("bar")
println(bar.getClass)
}
4 changes: 4 additions & 0 deletions tests/run-macros/newClassExtends.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Calling foo.foo
class Test_2$package$foo$1
Calling bar.foo
class Test_2$package$bar$1
Loading

0 comments on commit a5d5fe5

Please sign in to comment.