Skip to content

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

Merged
merged 3 commits into from
Jun 28, 2023
Merged

Conversation

nicolasstucki
Copy link
Contributor

@nicolasstucki nicolasstucki commented Jun 7, 2023

We add the definition of the QuotePattern AST. In this first step, keep the typing of quote patterns untouched and decode the resulting unapply into a QuotePattern. This AST is used to perform all transformations until the staging phase, here 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.

Based on #17956

@nicolasstucki nicolasstucki self-assigned this Jun 7, 2023
@nicolasstucki nicolasstucki force-pushed the quote-pattern-ast branch 17 times, most recently from bc086fd to 9a79ec7 Compare June 12, 2023 06:09
nicolasstucki added a commit to dotty-staging/dotty that referenced this pull request Jun 12, 2023
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.
@nicolasstucki nicolasstucki force-pushed the quote-pattern-ast branch 3 times, most recently from a4be2a7 to d59048c Compare June 13, 2023 10:53
@nicolasstucki nicolasstucki marked this pull request as ready for review June 14, 2023 11:01
@nicolasstucki nicolasstucki requested a review from smarter June 14, 2023 11:01
@@ -104,6 +105,19 @@ class CrossStageSafety extends TreeMapWithStages {
case _: DefDef if tree.symbol.isInlineMethod =>
tree

case tree: CaseDef if level == 0 =>
Copy link
Member

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?

Copy link
Contributor Author

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]] {
Copy link
Member

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?

Copy link
Contributor Author

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?

Copy link
Member

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

Copy link
Contributor Author

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 =
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@smarter smarter Jun 23, 2023

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

Copy link
Contributor Author

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
Copy link
Member

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?

Copy link
Contributor Author

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.

@smarter smarter assigned nicolasstucki and unassigned smarter Jun 21, 2023
nicolasstucki added a commit to dotty-staging/dotty that referenced this pull request Jun 23, 2023
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.
nicolasstucki added a commit to dotty-staging/dotty that referenced this pull request Jun 23, 2023
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.
nicolasstucki added a commit to dotty-staging/dotty that referenced this pull request Jun 26, 2023
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.
nicolasstucki added a commit to dotty-staging/dotty that referenced this pull request Jun 26, 2023
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.
@@ -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)
Copy link
Member

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?

Copy link
Contributor Author

@nicolasstucki nicolasstucki Jun 28, 2023

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.

Copy link
Member

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.

@nicolasstucki nicolasstucki enabled auto-merge June 28, 2023 13:07
@nicolasstucki nicolasstucki merged commit 08de2ba into scala:main Jun 28, 2023
@nicolasstucki nicolasstucki deleted the quote-pattern-ast branch June 28, 2023 14:33
@Kordyjan Kordyjan added this to the 3.4.0 milestone Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants