-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add QuotePattern AST #17935
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 QuotePattern AST #17935
Conversation
bc086fd
to
9a79ec7
Compare
When we encode quote patterns into `unapply` methods, we need to create a copy of each type variable. One copy is kept within the quote(in the next stage) and the other is used in the `unapply` method to define the usual pattern type variables. When creating the latter we copied the symbols but did not update the infos. This implies that if type variables would be bounded by each other, the bounds of the copies would be the original types instead of the copies. We need to update those references. To update the info we now create all the symbols in one pass and the update all their infos in a second pass. This also implies that we cannot use the `newPatternBoundSymbol` to create the symbol as this constructor will register the info into GADT bounds. Instead we use the plain `newSymbol`. Then in the second pass, when we have updated the infos, we register the symbol into GADT bounds. Note that the code in the added test does compiles correctly, but it had the inconsistent bounds. This test is added in case we need to manually inspect the bounds latter. This test does fail to compile in scala#17935 if this fix is not applied.
a4be2a7
to
d59048c
Compare
@@ -104,6 +105,19 @@ class CrossStageSafety extends TreeMapWithStages { | |||
case _: DefDef if tree.symbol.isInlineMethod => | |||
tree | |||
|
|||
case tree: CaseDef if level == 0 => |
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 would go wrong if we instead directly did case tree: QuotePattern if level == 0
here instead of a few lines below in this case?
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.
The important this in this transformation is to introduce the new pattern variables (implicit Type[t]
) in the pattern scope. These are needed to transform the RHS of the case
. We need to encode the patterns before we look for the patterns variables to make sure they are in the context when needed.
An alternative would be implement this with a variant of the CaseDef
case in TreeMapWithImplicits
that collects the scope a second time after transforming the pattern. This would also need to include the context update in the CaseDef
case of TreeMapWithStages
. I chose not to do this because any change in the super-classes would need to be reflected here. This might be missed easily.
case _ => foldOver(syms, tree) | ||
} | ||
private object quotePatVars extends TreeAccumulator[List[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.
Instead of using a separate object, could we instead add a case SplitPattern(pat, _)
in the original TreeAccumulator that checks the level?
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 could. I was trying to keep keep the cases of patVars
to a minimum for the case where we do not have quoted patterns(most of them). Should I change 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.
Your call, I would optimize for what you think is the most readable
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 tried with levels. While that one is more concise and simple to read, I believe the current version makes the distinction between the two different traversal modes clearer.
type ThisTree[+T <: Untyped] = QuotePattern[T] | ||
|
||
/** Type of the quoted pattern */ | ||
def bodyType(using Context): Type = |
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 method and the one on Quote only make sense on typed trees, so to make them safe they should take a (using T <:< Type)
parameter or be defined in tpd.scala
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.
That would not work here
Should I rename it to bodyTypeOpt
? And do the same for Tree.Quote
.
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.
You could use promote(tree).bodyType there
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 did have to use a cast is another similar case in the refined printer
* | ||
* @param bindings Type variable definitions (`Bind` tree) | ||
* @param body Quoted pattern (without type variable definitions) | ||
* @param quotes A reference to the given `Quotes` instance in scope |
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.
Does this actually need to be stored in the tree or could we look for the given when we need it?
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 theory, we could look for it again. But I preferred to keep track of it to ensure we do not change the instance. Maybe a macro-generated quote pattern could go wrong. I need to analyze this in depth if we want to remove this instance. Would be nicer though.
When we encode quote patterns into `unapply` methods, we need to create a copy of each type variable. One copy is kept within the quote(in the next stage) and the other is used in the `unapply` method to define the usual pattern type variables. When creating the latter we copied the symbols but did not update the infos. This implies that if type variables would be bounded by each other, the bounds of the copies would be the original types instead of the copies. We need to update those references. To update the info we now create all the symbols in one pass and the update all their infos in a second pass. This also implies that we cannot use the `newPatternBoundSymbol` to create the symbol as this constructor will register the info into GADT bounds. Instead we use the plain `newSymbol`. Then in the second pass, when we have updated the infos, we register the symbol into GADT bounds. Note that the code in the added test does compiles correctly, but it had the inconsistent bounds. This test is added in case we need to manually inspect the bounds latter. This test does fail to compile in scala#17935 if this fix is not applied.
When we encode quote patterns into `unapply` methods, we need to create a copy of each type variable. One copy is kept within the quote(in the next stage) and the other is used in the `unapply` method to define the usual pattern type variables. When creating the latter we copied the symbols but did not update the infos. This implies that if type variables would be bounded by each other, the bounds of the copies would be the original types instead of the copies. We need to update those references. To update the info we now create all the symbols in one pass and the update all their infos in a second pass. This also implies that we cannot use the `newPatternBoundSymbol` to create the symbol as this constructor will register the info into GADT bounds. Instead we use the plain `newSymbol`. Then in the second pass, when we have updated the infos, we register the symbol into GADT bounds. Note that the code in the added test does compiles correctly, but it had the inconsistent bounds. This test is added in case we need to manually inspect the bounds latter. This test does fail to compile in scala#17935 if this fix is not applied.
When we encode quote patterns into `unapply` methods, we need to create a copy of each type variable. One copy is kept within the quote(in the next stage) and the other is used in the `unapply` method to define the usual pattern type variables. When creating the latter we copied the symbols but did not update the infos. This implies that if type variables would be bounded by each other, the bounds of the copies would be the original types instead of the copies. We need to update those references. To update the info we now create all the symbols in one pass and the update all their infos in a second pass. This also implies that we cannot use the `newPatternBoundSymbol` to create the symbol as this constructor will register the info into GADT bounds. Instead we use the plain `newSymbol`. Then in the second pass, when we have updated the infos, we register the symbol into GADT bounds. Note that the code in the added test does compiles correctly, but it had the inconsistent bounds. This test is added in case we need to manually inspect the bounds latter. This test does fail to compile in scala#17935 if this fix is not applied.
b783d0b
to
2a03623
Compare
When we encode quote patterns into `unapply` methods, we need to create a copy of each type variable. One copy is kept within the quote(in the next stage) and the other is used in the `unapply` method to define the usual pattern type variables. When creating the latter we copied the symbols but did not update the infos. This implies that if type variables would be bounded by each other, the bounds of the copies would be the original types instead of the copies. We need to update those references. To update the info we now create all the symbols in one pass and the update all their infos in a second pass. This also implies that we cannot use the `newPatternBoundSymbol` to create the symbol as this constructor will register the info into GADT bounds. Instead we use the plain `newSymbol`. Then in the second pass, when we have updated the infos, we register the symbol into GADT bounds. Note that the code in the added test does compiles correctly, but it had the inconsistent bounds. This test is added in case we need to manually inspect the bounds latter. This test does fail to compile in scala#17935 if this fix is not applied.
When we encode quote patterns into `unapply` methods, we need to create a copy of each type variable. One copy is kept within the quote(in the next stage) and the other is used in the `unapply` method to define the usual pattern type variables. When creating the latter we copied the symbols but did not update the infos. This implies that if type variables would be bounded by each other, the bounds of the copies would be the original types instead of the copies. We need to update those references. To update the info we now create all the symbols in one pass and the update all their infos in a second pass. This also implies that we cannot use the `newPatternBoundSymbol` to create the symbol as this constructor will register the info into GADT bounds. Instead we use the plain `newSymbol`. Then in the second pass, when we have updated the infos, we register the symbol into GADT bounds. Note that the code in the added test does compiles correctly, but it had the inconsistent bounds. This test is added in case we need to manually inspect the bounds latter. This test does fail to compile in scala#17935 if this fix is not applied.
We add the definition of the `QuotePattern` AST. In this first step, we keep the typing of quote patterns untouched and decode the resulting unapply into a `QuotePattern`. This AST is used to preform all transformations until the `staging` phase, where it is encoded into an `unapply` and a `Quote` AST. We also encode the AST into an `unapply` when we pickle the tree to keep the same pickled representation as before.
2a03623
to
351456e
Compare
@@ -734,7 +734,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { | |||
keywordStr("macro ") ~ toTextGlobal(call) | |||
case tree @ Quote(body, tags) => | |||
val tagsText = (keywordStr("<") ~ toTextGlobal(tags, ", ") ~ keywordStr(">")).provided(tree.tags.nonEmpty) | |||
val exprTypeText = (keywordStr("[") ~ toTextGlobal(tree.bodyType) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists) | |||
val exprTypeText = (keywordStr("[") ~ toTextGlobal(tpd.bodyType(tree.asInstanceOf[tpd.Quote])) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists) |
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 happens when I pretty-print an untyped Quote node?
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.
The provided
condition makes sure that the type exists. If it does not, the text and tpd.bodyType
is not evaluated as it is a by-name prefix.
The other solution to this would be to create bodyTypeOpt
. But this is the only use case for it.
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.
Ah I missed the provided, ok.
We add the definition of the
QuotePattern
AST. In this first step, keep the typing of quote patterns untouched and decode the resultingunapply
into aQuotePattern
. This AST is used to perform all transformations until thestaging
phase, here it is encoded into anunapply
and aQuote
AST. We also encode the AST into anunapply
when we pickle the tree to keep the same pickled representation as before.Based on #17956