-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Dotty style downwards comparisons for implicit search and overloading resolution #6037
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
Conversation
|
Nice!
I think |
| val YdisableFlatCpCaching = BooleanSetting ("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.") | ||
| val YpartialUnification = BooleanSetting ("-Ypartial-unification", "Enable partial unification in type constructor inference") | ||
| val Yvirtpatmat = BooleanSetting ("-Yvirtpatmat", "Enable pattern matcher virtualization") | ||
| val YdottySpecificity = BooleanSetting ("-Ydotty-specificity", "Enable Dotty compatible rules for overload selection by specifity") |
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.
selection by specifity")
That's how I usually pronounce 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.
Curs mi speling ...
retronym
left a 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.
I'd also like to see how this would be described in the spec, assuming it were to become default behaviour. Sometimes these concepts are easier to describe in comments in the implenentation than they are in the more limited vocabulary of the spec.
| val flippedParams = sym.rawInfo.typeParams.map { param => | ||
| val flippedFlags = if(param.isContravariant) (param.flags&(~CONTRAVARIANT))|COVARIANT else param.flags | ||
| flippedClassSym.newTypeParameter(param.name.toTypeName, param.pos, flippedFlags) setInfo param.info | ||
| } |
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.info might refer to the original type parameters, so more substitution is needed. Cloning the class upfront takes care of all the rewiring.
scala> class C[-A <: C[A]]
defined class C
scala> val CClass = symbolOf[C[_]]
CClass: $r.intp.global.TypeSymbol = class C
scala> val CClass_clone = CClass.cloneSymbol(CClass.owner)
CClass_clone: CClass.TypeOfClonedSymbol = class C
scala> CClass_clone.rawInfo.typeParams.foreach(sym => if (sym.isContravariant) sym.resetFlag(Flag.CONTRAVARIANT).setFlag(Flag.COVARIANT))
scala> CClass_clone.defString
res3: String = class C[+A <: C[A]] extends AnyRef
scala> {settings.uniqid.value = true; println(CClass.defString); println(CClass_clone.defString); settings.uniqid.value = false}
class C#132392[-A#132393 <: C#132392[A#132393]] extends AnyRef#1932
class C#145639[+A#145640 <: C#132392[A#145640]] extends AnyRef#1932
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.
Thanks. That simplifies things quite a bit.
| val flippedSym = | ||
| subst.get(sym) match { | ||
| case None => | ||
| val flippedClassSym = sym.owner.newClassSymbol(sym.name.toTypeName, sym.pos, sym.flags) |
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.
Surely the new class symbols created by flip will always be treated as unrelated by the subsequent <:<? Maybe that gives the expected result for the test cases.
Dotty's implementation appears to make a type alias instead.
class C[-A]
...
type C'[+A] = C[A] // synthetic (ill-varying) type alias
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 don't think that's quite what's going on in Dotty's implementation. In the flip in Dotty the TypeMap traversal appears to traverse through to the type members which correspond to the type params of the TypeRef in the scala implementation. These type members then have their variance individually flipped, via a type alias, as you say. But I think the mechanism in scalac has to be different ... we have to work at the TypeRef/type params level.
On your first point, yes you're right that new symbols will be unrelated by <:< except in the trivial case where the outer type constructor is the same in both cases (here the subst map ensures that the same flipped symbol is used). I'm fairly confident that my implementation here matches the one in Dotty, but it's possible that the Dotty implementation gets this wrong as well.
I think however, that in cases where the outer type constructor differs, we will always want to say that neither is specific as the other. The following is the main problem case (included in the latest commit, Dotty agrees with the implementation here),
trait Z[-T]
trait Quux[-T] extends Z[T]
class A
class B extends AGiven the above, what is the relative specificity of Quux[B] and Z[A]? Both Dotty and this implementation say that neither is as specific as the other even though Z[B] is as specific as Z[A] and Quux[B] is a subtype of Z[B]. @smarter what are your thoughts on 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.
Intuitively I would expect Quux[B] to be more specific than Z[A] for the reasons you gave. The fact that it doesn't in dotty seems like an oversight to me /cc @odersky .
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.
@retronym is there any way of preserving the subtype relationships of the cloned outer ClassSymbols?
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've opened scala/scala3#2974, you might want to weight in on whether you find the top-level-only-flipping semantics sensible or not.
| case _ => existentialAbstraction(undef1, tpe1) <:< existentialAbstraction(undef2, tpe2) | ||
| case _ => | ||
|
|
||
| if (settings.YdottySpecificity) { |
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 approach looks somewhat expensive, but we could cache the flipped version of each contravariant class symbol by spending a few fields in ClassSymbol.
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.
Yes, this also helps.
| trait Z[-T] | ||
|
|
||
| @RunWith(classOf[JUnit4]) | ||
| class InferencerTests extends BytecodeTesting { |
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.
👍 for unit tests!
|
Could you try making |
|
@smarter aside from the inevitable issues with |
|
Quick and dirty PoC of a contravariant I made It compiles and partests successfully. |
Would this work? def max[U <: T](x: U, y: U): U = if (gteq(x, y)) x else y |
Yes, but it'd be a bit more work. The nested |
|
Latest commit fixes the problem that @retronym pointed out by normalizing the LHS to the type constructor of the RHS via |
|
I'd prefer to have this target 2.13, as it's about exploring dotty compatibility. |
|
I'm fine with this going into 2.13.x. Would you like me to change The Dotty bug corresponding to the issue that @retronym raised has been fixed with same test case as included in this PR so we can be reasonably confident that the semantics match: scala/scala3#2974. |
|
Thanks, yes, let's standardize on |
|
This is great :-) Cats can make all these typeclasses contravariant now! |
|
Rebased against 2.13.x ... @adriaanm where does this leave |
|
I would vote for making this unconditional so that it can influence the design of the collection-strawman |
a93b22a to
f1230da
Compare
|
I would be very happy to make this unconditional ... @adriaanm? |
|
More work is needed before we can get to deciding whether this can go in and which flag (if any) it should be gated by. In short, 1) a correct implementation and 2) a detailed explanation of its pros & cons, including how it affects existing code bases and future ones (the new collections). First, the code has evolved in dotty since the commit you've ported: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/typer/Applications.scala#L1138. Also, your port of the original commit is not obviously correct to me, and it didn't carry over the nice comment that accompanies the dotty implementation. Can you explain your implementation strategy and why it's different from dotty's? The higher-level concern is that this illustrates this is evolving in dotty. Are we ready to call the latest approach ready for production? We also need to evaluate how it affects existing code bases. If this helps the new collections design -- great! That's a good motivator and test bed, but it's not a sufficient condition. |
The only change to the behavior of dotty here was a fix for scala/scala3#2974 and Miles updated his PR to match the agreed-upon behavior. No other changes in this area are planned, though of course we need to see how well this works in practice. I think getting this PR in scalac and seeing what happens on the community build would be very helpful for that. |
|
Ok, thanks for the background. I'm all for experimentation here, and alignment with dotty, but this is a tricky part of the language and I would like to see a deep dive in how this is implemented and how it will impact existing code. I still don't understand how the code I linked to and the code in this PR relate. What does |
Yeah basically you replace all type arguments |
|
The impact will be similar, though not quite as significant, as partial unification ... people have been working around the old specificity behaviour wrt to contravariant type classes in a variety of ad hoc and more or less unsatisfactory ways for years. This change in Dotty and Scala gives people the behaviour they expected all along and makes the workarounds unnecessary. Existing code will break, as was the case with partial unification, but I think that's a price worth paying, and I'm fairly confident that most people working with contravariant type classes would agree. |
|
Since we now have a process to discuss changes that narrow the gap between Scala 2 and Scala 3, I suggest we follow this process with this pull request as well. Let's have this change on the agenda of one of the next SIP meetings? |
bb98a06 to
3474809
Compare
|
Rebased, fixed tests broken by 2.13.x deprecations. More importantly, changed to match the current Dotty semantics which are now to flip variances throughout the type, not just at the top level. @smarter, I'd welcome your feedback on this. @adriaanm do you want this behind an |
|
Hey ho ... this should be OK after squashing ... |
3474809 to
856f30d
Compare
|
Squashed. |
|
My current position is that this should go under It's too late for this to influence the collections, so I don't think it's urgent from a 2.13 release planning perspective. |
|
If you're happy to merge it with the |
|
👌
…On Fri, Aug 3, 2018 at 21:07 Miles Sabin ***@***.***> wrote:
If you're happy to merge it with the -Xsource:3.0 logic then I should be
able to find time for it next week.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#6037 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAFjyxPb1Rmu6LEd6Kheov5q-Nnq4vUuks5uNJ9VgaJpZM4O1sJH>
.
|
This is a backport of the following Dotty change to scalac, scala/scala3@8954026 As in the Dotty implementation the specificity comparison which is used for overload resolution and implicit selection is now performed as-if all contravariant positions in type constructors were covariant. Fixes scala/bug#2509. Fixes scala/bug#7768.
856f30d to
f4a8bf8
Compare
|
@adriaanm done. Merge when you're happy :-) I've taken out the spec change for now because having the language conditionalized on the language version was a bit awkward. We should deal with that as part of the SIP for the feature. |
|
@milessabin any idea why that might be...? |
|
🤣 Con is a reserved file name on windows 😱 |
|
oh LOL, it must be because |
|
jinx |
CON you call something CON on Windows? No, you CONnot. references scala#6037
|
this leaves me puzzled how Shapeless is able to call something but, a glance at the source shows that Shapeless's PR with fix: #7024 |
|
Oh. My. Goodness. |
|
Narrator: There comes a moment in everyone's life when they must choose what kind of a person they're going to be. A moment where steel is tempered in the unyielding fires of decision. A moment where all the churning demons of ten thousand unanswered slights rage as one and demand retribution. In that moment, our hero knew: he wasn't a Windows user. |
This is a backport of scala/scala3@8954026 to scalac. As in the Dotty implementation the specificity comparison is as-if all contravariant positions in type constructors were covariant. The feature is enabled by specifying
-Xsource:3.0.Initially this broke
test/files/run/t2030.scalain exactly the same way as in Dotty as reported by @smarter here. Since then the landing of the new collections has fixed this.This fixes scala/bug#2509. It also aligns with the contravariant behaviour hoped for in scala/bug#7768, although it differs on the covariant behaviour, which is unchanged. I think that's a reasonable outcome and that scala/bug#7768 can be declared as fixed.