-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add reflect ClassDef.apply
and Symbol.newClass
#14124
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that we're missing an API to create a TypeRef/TermRef from a specific prefix: with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh indeed, I missed that one, maybe each of their documentation should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
||
/** Term reference to the symbol usable in the scope of its owner. */ | ||
@experimental | ||
def termRef: TermRef | ||
end extension | ||
} | ||
|
||
|
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]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this is orthogonal but in tpd we have a New overload that takes constructor arguments this could be useful here too |
||
|
||
Block(List(clsDef), newCls).asExpr | ||
|
||
// '{ | ||
// class `name`() { | ||
// def foo(): Unit = println("Calling `name`.foo") | ||
// } | ||
// new `name`() | ||
// } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
def test: Any = makeClass("foo") // error |
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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
def test: Foo = makeClass("foo") // error |
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 |
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`() | ||
// } | ||
} |
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) | ||
} |
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This description should be expanded (or some examples should be added) to explain that the parents can either be TypeTrees or Terms, and the terms are
new
calls so that arguments can be passed to them.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done