-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Overconstrained GADT bounds in presence of bad bounds #11545
Comments
The culprit is clearly 3. In Scala/pDOT, you can't escape type members with members that themselves end up having bad bounds (as you say, bad bounds can still be crafted). So we have to live with bad bounds. In the pattern match, we know In DOT-like terms, we have |
@liufengyun the issue is not truly solved, as you can see in this slight variaton: class test0 {
type AA
trait S[A]
trait Inv[A]
class P[X] extends S[Inv[X] & AA]
}
@main def test: Unit =
new test0:
type AA = Inv[String]
def patmat[A, Y](s: S[Inv[A] & Y]): A = s match {
case p: P[x] =>
"Hello"
}
val got: Int = patmat[Int, AA](new P) // ClassCastException: String cannot be cast to Integer Should it be reopened? I'm aware that @odersky said in #11549 there were still holes to be fixed by the community, but this particular problem is never going to be fixed completely. The theory of DOT shows we can't avoid bad bounds, so we have to be sound in their presence, and GADT reasoning is currently not. |
@LPTK In the example, it seems to me type inference (GADT reasoning) is doing the right thing:
If I understand correctly, handling bad bounds in type inference is more convoluted and has performance penalties. Another possibility is to add the inferred GADT bounds that are valid to the method type parameters (if they are valid in the method scope). This needs to propagate F-bounds outward and do the join. I'm not sure how practical it is. /cc: @abgruszecki |
How do you justify that step? Similar to what I've said above, This is not about handling bad bounds in type inference, this is about the soundness of type checking GADTs. I wonder why you think the compiler is doing the right thing, despite the fact that executing the program leads to a |
Indeed it does not hold generally. I read your previous comment but mistakenly thought there's a way out that only resorts to subtyping. Nevertheless, given that |
That's not true. Again, referring to my message above, taking PS: To specifically answer your argument about |
If there is no rule says that they hold, they don't hold ;-) |
You have it backwards. The burden of proof is on you to show that it cannot possibly hold, otherwise this GADT reasoning is unsound, as we can see from the |
I'm reopening this. @LPTK correctly observed that we're still unsound. The correct solution seems to be to check whether we're in GADT constraint inference mode before (approximately) branching on |
That's not quite true. DOT says that good bounds are only guaranteed for members of values. It puts the burden of guaranteeing good bounds on when we construct values. So that's what goes wrong here. Type parameters of classes are treated like member types. Non-variant type parameters are treated like type aliases and we need to have guarantee good bounds for them. Meanwhile #11995 shows that my fix caused important code out there to fail. So that does not seem to work either. It looks like an important and urgent problem and it would be good if the people discussing here could invest themselves. How can we let real world code compile and still ensure soundness? If you have some time any contribution to this issue and #11995 would be much appreciated. |
Yes, of course. I meant that we can't avoid bad bounds in general, more specifically in the members of types for which we don't have values around. My point stands that the problem is never going to be solved by filling holes which are only special cases of a fundamentally unavoidable property.
By removing the ad-hoc "fixes" and confronting the reality that we can't infer |
This was closed accidentally. Looking at the issue, I think what is happening is that we take a wrong step in TypeComparer. I'll have to investigate where exactly. I'd guess that we do something that is correct as far as type inference goes, but gives wrong results for inferring GADT constraints. Specifically, the GADT logic should run the following type comparison : |
I think whatever we do, this should not be part of 3.0.0. We had two previous soundness patches that we had to fix or revert for RC3. Restricting the range of programs that are compiled needs to be well considered, and we need time for the community to experiment. |
I don't think this is a language design issue that the community needs to experiment with. Instead, this is a clear-cut constraint solving bug. Here is what I gather the issue is:
What Dotty currently does is to approximate a GADT constraint in the wrong direction, as if it was a type inference constraint. Looking at @abgruszecki's PR, it is clear that the culprit is the use of |
As a meta-remark: I'm very suspicious about the use of the type comparer to deal with GADT constraints. Every place where constraints are approximated during type comparison is a potential soundness hole, unless it has specific logic to avoid the approximation or do it in the other direction in case we are in GADT mode. I'm pretty sure everything would be a lot simpler and, more importantly, less bug-prone, if the GADT constraint solving algorithm was completely separate. In particular, there are many complexities and heuristics needed for type inference that are unnecessary or plain unsound when used for GADT reasoning. |
We do have that logic, |
Also |
There's also def testSubType(tp1: Type, tp2: Type): CompareResult =
GADTused = false
if !topLevelSubType(tp1, tp2) then CompareResult.Fail
else if GADTused then CompareResult.OKwithGADTUsed
else CompareResult.OK which seems excitedly on point for what one might want in order to use the subtyping algorithm, biased towards GADT reasoning ( |
Compiler version
3.0.0-RC1 (Scastie)
Minimized code
Output
Click to expand
Expectation
The code should be rejected, but it is unclear what part is problematic:
new P
due to introducing the bad boundsS[Inv[Int] & Inv[String]]
extends S[Inv[X] & Inv[String]]
clause, which may lead to bad bounds ifX != String
x = A = String
I think that the problem comes from 1 (and not necessarily from 3). I believe, however, that this is not sufficient, as it seems that bad bounds can still be crafted.
Translating the above into pDOT shows that we can allow 3. if we require a witness value for
Inv[A] & Y
. Then, the above would not cause aClassCastException
since providing a value forInv[Int] & Inv[String]
is not possible.The text was updated successfully, but these errors were encountered: