Skip to content

Fix bounds of encoded type variables in quote patterns #17956

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

Closed

Conversation

nicolasstucki
Copy link
Contributor

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 #17935 if this fix is not applied.

@nicolasstucki nicolasstucki self-assigned this Jun 12, 2023
@nicolasstucki nicolasstucki marked this pull request as ready for review June 12, 2023 11:09
var newBindingsRefs: List[Type] = mapping.values.toList.map(_.typeRef)
for newBindings <- mapping.values do
newBindings.info = newBindings.info.subst(oldBindings, newBindingsRefs)
ctx.gadtState.addToConstraint(newBindings) // This must be preformed after the info has been updated
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
ctx.gadtState.addToConstraint(newBindings) // This must be preformed after the info has been updated
ctx.gadtState.addToConstraint(newBindings) // This must be performed after the info has been updated

Comment on lines 356 to 357
var oldBindings: List[Symbol] = mapping.keys.toList
var newBindingsRefs: List[Type] = mapping.values.toList.map(_.typeRef)
Copy link
Member

Choose a reason for hiding this comment

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

Look like those don't need to be vars?

Suggested change
var oldBindings: List[Symbol] = mapping.keys.toList
var newBindingsRefs: List[Type] = mapping.values.toList.map(_.typeRef)
val oldBindings: List[Symbol] = mapping.keys.toList
val newBindingsRefs: List[Type] = mapping.values.toList.map(_.typeRef)

* This mapping retains the original type variable definition order.
*/
private def unapplyBindingsMapping(quoted: Tree)(using Context): collection.Map[Symbol, Bind] = {
val mapping = mutable.LinkedHashMap.empty[Symbol, Symbol]
Copy link
Member

Choose a reason for hiding this comment

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

It looks like we never actually query this map, we only call mapping.keys and mapping.values, could we replace it by directly defining oldBindings and newBindingsRefs as ListBuffers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is a mapping.contains(sym). I will simplify it to a LinkedHashSet and a ListBuffer.

@@ -288,7 +282,7 @@ trait QuotesAndSplices {
report.error(IllegalVariableInPatternAlternative(tdef.symbol.name), tdef.srcPos)
if variance == -1 then
tdef.symbol.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_fromAboveAnnot.typeRef)).withSpan(tdef.span)))
val bindingType = getBinding(tdef.symbol).symbol.typeRef
val bindingType = bindSymMapping(tdef.symbol).symbol.typeRef
Copy link
Member

Choose a reason for hiding this comment

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

If all we use bindSymMapping for is to get the symbol of the value, why do we need to have Bind nodes as values at all instead of symbols or typerefs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function returns them. But I would create them later. I will refactor this to make it simpler.

@smarter smarter assigned nicolasstucki and unassigned smarter Jun 21, 2023
@nicolasstucki nicolasstucki force-pushed the fix-quote-type-variable-bounds branch 2 times, most recently from e7d4857 to a096e75 Compare June 23, 2023 14:42
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.
Now, the mapping is a `SeqMap[Symbol, Symbol]` to convey its ordering
and the `Bind` is created when we are creating the tree of the quote
pattern.
@nicolasstucki nicolasstucki force-pushed the fix-quote-type-variable-bounds branch from a096e75 to 1f4d46c Compare June 26, 2023 12:49
@nicolasstucki nicolasstucki requested a review from smarter June 27, 2023 16:10
Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

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

Otherwise LGTM.

Comment on lines +350 to +351
val oldBindingsBuffer = mutable.LinkedHashSet.empty[Symbol]
val newBindingsBuffer = mutable.ListBuffer.empty[Symbol]
Copy link
Member

Choose a reason for hiding this comment

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

Now that we're returning a map from old bindings to new bindings without creating Bind nodes, I think the original idea of directly having a LinkedHashMap here makes more sense, sorry for the back and forth!

@smarter smarter assigned nicolasstucki and unassigned smarter Jun 28, 2023
nicolasstucki added a commit that referenced this pull request Jun 28, 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
Copy link
Contributor Author

The fix was merged in #17935, and the refactoring is now in #18104.

nicolasstucki added a commit that referenced this pull request Jun 30, 2023
Now, the mapping is a `SeqMap[Symbol, Symbol]` to convey its ordering
and the `Bind` is created when we are creating the tree of the quote
pattern.

Follow up of the refactoring in #17956
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.

2 participants