-
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
Conversation
Note that I didn't add a test for
This is due to the fact that when a user writes the following code: TypeDef(X,TypeBoundsTree(Ident(Int),TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Any)],EmptyTree)) The same type tree generated with the Quotes API will the represented as this: TypeDef(X,TypeTree[TypeBounds(TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Int),TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),Any))]) Both representations are equivalent but due to how the printer is designed, we cannot show the bounds of the second representation at the moment |
38eab54
to
429ac32
Compare
|
||
def testImpl(using Quotes): Expr[Unit] = { | ||
import quotes.reflect.* | ||
val sym = Symbol.newType(Symbol.spliceOwner, "mytype", Flags.EmptyFlags, TypeRepr.of[String], Symbol.noSymbol) |
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.
In the compiler, the info
of a type symbol can either be a ClassInfo (if we're defining a class) or a TypeBounds (if we're defining a type parameter, a type alias, an abstract type member or an opaque type). So String
is not a valid info, it should be TypeAlias(String)
, but giving the user higher-level methods like newTypeAlias
and newTypeBounds
would be better.
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.
We should also have a test of the use case from the original discussion:
Two macro methods: both a transparent inline and an inline macro method returning a trait with a bounded type member and a type alias type member, so the incorrect stuff we generate there can be caught by -Ycheck and -Xcheck-macros
But great work so far! Especially with investigating the printer issue with the type bounds. I personally think showing the inferred stuff would be better than showing nothing, but this is something we can change in the future. The TypeAlias issue makes me wonder whether the previous uses of TypeDef.apply were correct (I thought the only way to use it was to get the symbol from elsewhere and effectively use it as a type alias, but I don't think we wrap it with |
|
||
inline def testMacro = ${ testImpl } | ||
|
||
def testImpl(using Quotes): Expr[Unit] = { |
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.
Missing @experimental
here and in def test I believe
This has been decided to be worked on in the 3.6.0 release. |
(@hamzaremmal, I know you are away temporarily so feel free to ignore this post for now - apologies for the notification) I remember mentioning that I would help with this and I never ended up getting around to it, until now (sorry about that). I wanted to add some more tests so that we can check if we fail While adding the -Xcheck-macro one, I discovered that the lack of Can I push onto this PR? I have the necessary changes basically done on my machine already, so there is no need to worry about any added work or anything like that, especially at the end of the current cycle. |
Hi @jchyb, sorry I was bit overwhelmed by work and forgot about this PR. Nevertheless, I added it in my TODO for next week but if you have a working version, feel free to push here and I would be happy to review it. |
efcdf18
to
2aee139
Compare
*/ | ||
@experimental | ||
// Keep: `flags` doc aligned with QuotesImpl's `validTypeFlags` | ||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
What if someone calls this method with a TypeBounds
?
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.
Should we represent TypeAlias
in the Quotes API as substype of TypeRepr
instead ?
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.
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 comment
The 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
|
||
def newTypeAlias(owner: Symbol, name: String, flags: Flags, tpe: TypeRepr, privateWithin: Symbol): Symbol = | ||
checkValidFlags(flags.toTypeFlags, Flags.validTypeFlags) | ||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
TypeAlias
are not Deferred
and cannot be Deferred
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
* @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 rhs the type alias | ||
* @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. |
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.
* @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. | |
* @param privateWithin the symbol within which this new type symbol should be private. May be noSymbol. |
* @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 method symbol should be private. May be noSymbol. |
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.
* @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. | |
* @param privateWithin the symbol within which this new type symbol should be private. May be noSymbol. |
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.
Otherwise LGTM.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned by hamza:
dotc.core.Symbols.newSymbol(owner, name.toTypeName, flags | dotc.core.Flags.Deferred, dotc.core.Types.TypeAlias(tpe), privateWithin) | |
dotc.core.Symbols.newSymbol(owner, name.toTypeName, flags, dotc.core.Types.TypeAlias(tpe), privateWithin) |
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) | ||
|
||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest calling it newAbstractType
instead.
|
||
def newBoundedType(owner: Symbol, name: String, flags: Flags, tpe: TypeBounds, privateWithin: Symbol): Symbol = | ||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
This one should be Deferred:
dotc.core.Symbols.newSymbol(owner, name.toTypeName, flags, tpe, privateWithin) | |
dotc.core.Symbols.newSymbol(owner, name.toTypeName, flags | dotc.core.Flags.Deferred, tpe, privateWithin) |
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.
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 comment
The 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
Symbol.newBoundedType(owner, "mytype", Flags.EmptyFlags, TypeBounds.lower(TypeRepr.of[String]), Symbol.noSymbol) | ||
|
||
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 comment
The 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).
@smarter Thank you for the review! |
Since the API is |
Agreed ! |
Closes #19448