-
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 the possibility to create a typeSymbol in the Quotes API #20347
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 |
---|---|---|
|
@@ -3963,6 +3963,42 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => | |
// Keep: `flags` doc aligned with QuotesImpl's `validBindFlags` | ||
def newBind(parent: Symbol, name: String, flags: Flags, tpe: TypeRepr): Symbol | ||
|
||
/** Generate a new type symbol for a type alias 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 TypeDef constructor. | ||
* | ||
* @param parent The owner of the type | ||
* @param name The name of the type | ||
* @param flags extra flags to with which symbol can be constructed. Can be `Private` | `Protected` | `Override` | `Final` | `Infix` | `Local` | ||
* @param tpe The rhs the type alias | ||
* @param privateWithin the symbol within which this new type 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 | ||
* direct or indirect children of the reflection context's owner. | ||
*/ | ||
@experimental | ||
// Keep: `flags` doc aligned with QuotesImpl's `validTypeAliasFlags` | ||
def newTypeAlias(parent: Symbol, name: String, flags: Flags, tpe: TypeRepr, privateWithin: Symbol): Symbol | ||
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. What if someone calls this method with a 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. Should we represent 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. Then we would have the same problem, with being able to put TypeBounds into TypeAlias. I think it's better to add an assertion to the method (but thank you for catching this!) 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. Wait, I guess we are still able to put TypeBounds into TypeBounds, so it would not be that different... Still, I am hesitant to changing the reflection api types hierarchy, if we have alternatives |
||
|
||
/** Generate a new type symbol for a type bounds 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 TypeDef constructor. | ||
* | ||
* @param parent The owner of the type | ||
* @param name The name of the type | ||
* @param flags extra flags to with which symbol can be constructed. `Deferred` flag will be added. Can be `Private` | `Protected` | `Override` | `Deferred` | `Final` | `Infix` | `Local` | ||
* @param tpe The bounds of the type | ||
* @param privateWithin the symbol within which this new type 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 | ||
* direct or indirect children of the reflection context's owner. | ||
*/ | ||
@experimental | ||
// Keep: `flags` doc aligned with QuotesImpl's `validBoundedTypeFlags` | ||
def newBoundedType(parent: Symbol, name: String, flags: Flags, tpe: TypeBounds, privateWithin: Symbol): Symbol | ||
|
||
/** Definition not available */ | ||
def noSymbol: Symbol | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
//> using options -experimental -Yno-experimental | ||
import scala.quoted.* | ||
|
||
inline def testConflictingBounds = ${ testConflictingBoundsImpl } | ||
inline def testConflictingBoundsWithTypeLambda = ${ testConflictingBoundsWithTypeLambdaImpl } | ||
|
||
transparent inline def transparentTestConflictingBounds = ${ testConflictingBoundsImpl } | ||
transparent inline def transparentTestConflictingBoundsWithTypeLambda = ${ testConflictingBoundsWithTypeLambdaImpl } | ||
|
||
|
||
def testConflictingBoundsImpl(using Quotes): Expr[Object] = { | ||
import quotes.reflect.* | ||
|
||
def makeType(owner: Symbol): Symbol = | ||
// type Foo >: Int <: String | ||
Symbol.newBoundedType( | ||
owner, | ||
"Foo", | ||
Flags.EmptyFlags, | ||
TypeBounds(TypeRepr.of[Int], TypeRepr.of[String]), | ||
Symbol.noSymbol | ||
) | ||
makeClass(makeType) | ||
} | ||
|
||
def testConflictingBoundsWithTypeLambdaImpl(using Quotes): Expr[Object] = { | ||
import quotes.reflect.* | ||
def makeType(owner: Symbol): Symbol = | ||
// type Foo >: [X] =>> Int <: Any | ||
Symbol.newBoundedType( | ||
owner, | ||
"Foo", | ||
Flags.EmptyFlags, | ||
TypeBounds(TypeLambda.apply(List("X"), _ => List(TypeBounds.empty), _ => TypeRepr.of[Int]), TypeRepr.of[Any]), | ||
Symbol.noSymbol | ||
) | ||
makeClass(makeType) | ||
} | ||
|
||
def makeClass(using quotes: Quotes)(typeCons: quotes.reflect.Symbol => quotes.reflect.Symbol) = { | ||
import quotes.reflect.* | ||
val clsSymbol = Symbol.newClass(Symbol.spliceOwner, "CLS", List(TypeRepr.of[Object]), sym => List(typeCons(sym)), None) | ||
val classDef: ClassDef = ClassDef(clsSymbol, List(TypeTree.of[Object]), List(TypeDef(clsSymbol.typeMember("Foo")))) | ||
|
||
Block(List(classDef), Apply(Select(New(TypeIdent(clsSymbol)), clsSymbol.primaryConstructor), List.empty)).asExprOf[Object] | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
//> using options -experimental -Yno-experimental | ||
def test = | ||
transparentTestConflictingBounds // error | ||
transparentTestConflictingBoundsWithTypeLambda // error | ||
// testConflictingBounds // should throw an error here also, to be implemented before stabilisation | ||
// testConflictingBoundsWithTypeLambda // should throw an error here also, to be implemented before stabilisation |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
//> using options -experimental -Yno-experimental | ||
import scala.quoted.* | ||
|
||
inline def testMacro = ${ testImpl } | ||
|
||
transparent inline def transparentTestMacro = ${ testImpl } | ||
|
||
def testImpl(using Quotes): Expr[Object] = { | ||
import quotes.reflect.* | ||
|
||
def makeBasicType(owner: Symbol): Symbol = | ||
Symbol.newBoundedType(owner, "tpe", Flags.EmptyFlags, TypeBounds.lower(TypeRepr.of[String]), Symbol.noSymbol) | ||
|
||
def makeTypesForClass(owner: Symbol): List[Symbol] = | ||
val typeLambda = TypeLambda.apply(List("X"), _ => List(TypeBounds.empty), _ => TypeRepr.of[Int]) | ||
List( | ||
makeBasicType(owner), | ||
// type Bla >: Nothing <: [X] =>> Int | ||
Symbol.newBoundedType( | ||
owner, | ||
"tpe1", | ||
Flags.EmptyFlags, | ||
TypeBounds.upper(typeLambda), | ||
Symbol.noSymbol | ||
), | ||
// type Bar >: [X] =>> Int <: [X] =>> Int | ||
Symbol.newBoundedType( | ||
owner, | ||
"tpe2", | ||
Flags.EmptyFlags, | ||
TypeBounds(typeLambda, typeLambda), | ||
Symbol.noSymbol | ||
) | ||
) | ||
|
||
val typeDef = TypeDef(makeBasicType(Symbol.spliceOwner)) | ||
// Expr printer does not work here, see comment: | ||
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. If this cannot be fixed right now, we should open an issue for it. (IMO we should just use the regular compiler type printer and get rid of the custom one used for quotes, it creates more problems than it solves, and we can make the regular type printer more flexible if needed). |
||
// https://github.com/scala/scala3/pull/20347#issuecomment-2096824617 | ||
println(typeDef.toString) | ||
assert(typeDef.toString == "TypeDef(tpe,TypeTree[TypeBounds(TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class java)),object lang),String),TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Any))])") | ||
|
||
val clsSymbol = Symbol.newClass(Symbol.spliceOwner, "CLS", List(TypeRepr.of[Object]), sym => makeTypesForClass(sym), None) | ||
val classDef: ClassDef = ClassDef(clsSymbol, List(TypeTree.of[Object]), List( | ||
TypeDef(clsSymbol.typeMember("tpe")), | ||
TypeDef(clsSymbol.typeMember("tpe1")), | ||
TypeDef(clsSymbol.typeMember("tpe2")), | ||
)) | ||
Block(List(classDef), Apply(Select(New(TypeIdent(clsSymbol)), clsSymbol.primaryConstructor), List.empty)).asExprOf[Object] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
//> using options -experimental -Yno-experimental | ||
def test = | ||
testMacro | ||
transparentTestMacro |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
//> using options -experimental -Yno-experimental | ||
import scala.quoted.* | ||
|
||
inline def testMacro = ${ testImpl } | ||
|
||
transparent inline def transparentTestMacro = ${ testImpl } | ||
|
||
def testImpl(using Quotes): Expr[Object] = { | ||
import quotes.reflect.* | ||
|
||
def makeBasicType(owner: Symbol): Symbol = | ||
Symbol.newTypeAlias(owner, "tpe", Flags.EmptyFlags, TypeRepr.of[String], Symbol.noSymbol) | ||
|
||
def makeTypesForClass(owner: Symbol): List[Symbol] = | ||
val typeLambda = TypeLambda.apply(List("X"), _ => List(TypeBounds.empty), _ => TypeRepr.of[Int]) | ||
List( | ||
makeBasicType(owner), | ||
// type Foo = [X] =>> Int | ||
Symbol.newTypeAlias( | ||
owner, | ||
"tpe1", | ||
Flags.EmptyFlags, | ||
typeLambda, | ||
Symbol.noSymbol | ||
), | ||
) | ||
|
||
val clsSymbol = Symbol.newClass(Symbol.spliceOwner, "CLS", List(TypeRepr.of[Object]), sym => makeTypesForClass(sym), None) | ||
val classDef: ClassDef = ClassDef(clsSymbol, List(TypeTree.of[Object]), List( | ||
TypeDef(clsSymbol.typeMember("tpe")), | ||
TypeDef(clsSymbol.typeMember("tpe1")), | ||
)) | ||
|
||
Block(List(classDef), Apply(Select(New(TypeIdent(clsSymbol)), clsSymbol.primaryConstructor), List.empty)).asExprOf[Object] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
//> using options -experimental -Yno-experimental | ||
def test = | ||
testMacro | ||
transparentTestMacro |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
//> using options -experimental -Yno-experimental | ||
import scala.quoted.* | ||
|
||
inline def testMacro = ${ testImpl } | ||
|
||
def testImpl(using Quotes): Expr[Unit] = { | ||
import quotes.reflect.* | ||
val sym = Symbol.newTypeAlias(Symbol.spliceOwner, "mytype", Flags.EmptyFlags, TypeRepr.of[String], Symbol.noSymbol) | ||
val typeDef = TypeDef(sym) | ||
assert(typeDef.show == "type mytype = java.lang.String") | ||
|
||
Block(List(typeDef), '{()}.asTerm).asExprOf[Unit] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
//> using options -experimental -Yno-experimental | ||
def test = testMacro |
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.
I suggest calling it
newAbstractType
instead.