-
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 3 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 | ||||
---|---|---|---|---|---|---|
|
@@ -2649,6 +2649,16 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler | |||||
def newBind(owner: Symbol, name: String, flags: Flags, tpe: TypeRepr): Symbol = | ||||||
checkValidFlags(flags.toTermFlags, Flags.validBindFlags) | ||||||
dotc.core.Symbols.newSymbol(owner, name.toTermName, flags | dotc.core.Flags.Case, tpe) | ||||||
|
||||||
def newTypeAlias(owner: Symbol, name: String, flags: Flags, tpe: TypeRepr, privateWithin: Symbol): Symbol = | ||||||
checkValidFlags(flags.toTypeFlags, Flags.validTypeAliasFlags) | ||||||
assert(!tpe.isInstanceOf[Types.TypeBounds], "Passed `tpe` into newTypeAlias should not represent TypeBounds") | ||||||
dotc.core.Symbols.newSymbol(owner, name.toTypeName, flags | dotc.core.Flags.Deferred, dotc.core.Types.TypeAlias(tpe), privateWithin) | ||||||
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. As mentioned by hamza:
Suggested change
|
||||||
|
||||||
def newBoundedType(owner: Symbol, name: String, flags: Flags, tpe: TypeBounds, 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. I suggest calling it |
||||||
checkValidFlags(flags.toTypeFlags, Flags.validBoundedTypeFlags) | ||||||
dotc.core.Symbols.newSymbol(owner, name.toTypeName, flags, tpe, privateWithin) | ||||||
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. This one should be Deferred:
Suggested change
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. |
||||||
|
||||||
def noSymbol: Symbol = dotc.core.Symbols.NoSymbol | ||||||
|
||||||
private inline def checkValidFlags(inline flags: Flags, inline valid: Flags): Unit = | ||||||
|
@@ -2989,6 +2999,13 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler | |||||
|
||||||
// Keep: aligned with Quotes's `newBind` doc | ||||||
private[QuotesImpl] def validBindFlags: Flags = Case // Flags that could be allowed: Implicit | Given | Erased | ||||||
|
||||||
// Keep: aligned with Quotes's 'newBoundedType' doc | ||||||
private[QuotesImpl] def validBoundedTypeFlags: Flags = Private | Protected | Override | Deferred | Final | Infix | Local | ||||||
|
||||||
// Keep: aligned with Quotes's `newTypeAlias` doc | ||||||
private[QuotesImpl] def validTypeAliasFlags: Flags = Private | Protected | Override | Final | Infix | Local | ||||||
|
||||||
end Flags | ||||||
|
||||||
given FlagsMethods: FlagsMethods with | ||||||
|
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,22 @@ | ||
//> 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 makeType(owner: Symbol): Symbol = | ||
Symbol.newBoundedType(owner, "mytype", Flags.EmptyFlags, TypeBounds.lower(TypeRepr.of[String]), Symbol.noSymbol) | ||
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. Would be good to have some tests with type lambdas as bounds, that is the equivalent of: type Foo = [X] =>> Int
type Bla >: Nothing <: [X] =>> Int
type Bar >: [X] =>> Int <: [X] =>> Int It's also important that error-checking runs the same bound-checks than regular code: type Foo1 >: Int <: String // error
type Foo2 >: [X] =>> Int <: Any // error: kind mismatch |
||
|
||
val typeDef = TypeDef(makeType(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 | ||
assert(typeDef.toString == "TypeDef(mytype,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 => List(makeType(sym)), None) | ||
val classDef: ClassDef = ClassDef(clsSymbol, List(TypeTree.of[Object]), List(TypeDef(clsSymbol.typeMember("mytype")))) | ||
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,18 @@ | ||
//> 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 makeType(owner: Symbol): Symbol = | ||
Symbol.newTypeAlias(owner, "mytype", Flags.EmptyFlags, TypeRepr.of[String], Symbol.noSymbol) | ||
|
||
val clsSymbol = Symbol.newClass(Symbol.spliceOwner, "CLS", List(TypeRepr.of[Object]), sym => List(makeType(sym)), None) | ||
val classDef: ClassDef = ClassDef(clsSymbol, List(TypeTree.of[Object]), List(TypeDef(clsSymbol.typeMember("mytype")))) | ||
|
||
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.
TypeAlias
are notDeferred
and cannot beDeferred
since they have a defined member (rhs)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 means we should also change the
Flags.validTypeFlags
to reflect this