Skip to content

Fix #6047: Implement variance rules for match types #6050

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 12 commits into from
Mar 17, 2019

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Mar 9, 2019

No description provided.

odersky added 3 commits March 9, 2019 12:59
That way, we can control that the variance of a case is 0.
The precise error message i3430.scala depends on the order in which files
are found. This is OK. But unfortunately it means there's no easy way to
write a check file. My (local) testCompilation, CI test, and CI bootstrapped
tests all disagree pairwise which two implicit instances are reported as
ambiguous. We could invest in a more specialized test that just checks
that an ambiguity is reported no matter what the participants. But my
patience has run out, so it would be good if someone else who picks
this up could do it.
@odersky odersky changed the title Make matchCases tail-recursive Fix #6047: Implement variance rules for match types Mar 9, 2019
odersky added 3 commits March 9, 2019 15:51
I believe these were lost when we changed to the new SourcePos scheme. At least nobody
was setting the `outer` part of a SourcePosition anymore. We now do this at the latest
possible stage, when generating MessageContainers in Reporter.
The run-test-picklin.blacklist got a new entry where before and after
pickling differ in whether a match type is reduced or not. It's currently
not clear how to write pickling tests that are independent of this detail.
odersky added 4 commits March 10, 2019 14:43
Since we don't use function types for cases anymore, we have to insert
the `=>` explicitly.
They might be recursive, so we can't blindly expand them for variance checking.
Instead use normalize to follow possible reductions.
result

def recur(cases: List[Type]): Type = cases match {
case cas :: cases1 => matchCase(cas).getOrElse(recur(cases1))
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the inFrozenConstraint be pushed inside? The way this is written, it's possible that matchCase(case1) returns None while still infer new constrains, which will then be visible when calling matchCase(case2) to matchCase(caseN).

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 don't see how that could happen. Once the constraint is frozen is stays so (in the same typeComparer). Which constraints would be computed by matchCase(case1) and propagated to matchCase(case2)?

variance = -variance
val restpe = tp.resultType
val saved = variance
variance = if (defn.MatchCase.isInstance(restpe)) 0 else -variance
Copy link
Contributor

Choose a reason for hiding this comment

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

Having to do this kind of distinction in TypeMap makes me wonder if reusing LambdaType-s to encode cases of MatchType is the correct approach. Maybe MatchCase should simply be its own independent type...

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's a lot of machinery associated with LambdaType which would have to be duplicated. So I think what we have is the lesser evil.

@@ -28,6 +28,7 @@ object Formatting {
case arg: Showable =>
try arg.show
catch {
case ex: CyclicReference => "..."
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this going to be visible to the user? Either way we should probably have something more grepable than "..."

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 would kick in if we hit a Cyclic Reference during printing. The idea is that printing should try to do the best it can rather than quitting with an exception. But I'll make clearer that we elided a cyclic reference.

else foldOver(status, tp)
else sym.info match {
case MatchAlias(_) => foldOver(status, tp)
case TypeAlias(alias) => this(status, alias)
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not very clear to me than the new code for TypeAlias-es is equivalent to the previous one. Is .bounds.hi and .alias the same thing here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, it's the same thing. The previous code was a bit outdated.


/** A type constructor for a case in a match type.
*/
final abstract class MatchCase[Pat, +Body]
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we really need a runtime class for that?

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 don't need a runtime class. But it's easiest to get the definition that way. The class will never be referenced at runtime anyway.

else {
def fallbackApply(): Expr[Elem[Tup, N]] = nValue match {
case Some(n) => quoted.QuoteError("index out of bounds: " + n)
case None => '{dynamicApply($tup, $n)}
case None => '{dynamicApply($tup, $n)}.asInstanceOf // TODO: check that variance is OK here
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you might be able to avoid the casts by changing the signature of dynamicApply to the following:

def dynamicApply[This <: NonEmptyTuple, N <: Int] (self: This, n: N): Elem[This, N] = {
  type Result = Elem[This, N]
  ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That works 👍

@odersky odersky merged commit b537220 into scala:master Mar 17, 2019
@allanrenucci allanrenucci deleted the fix-#6047 branch March 17, 2019 18:18
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