Skip to content

Attempt to fix a potentially missing case in TypeComparer #5914

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 1 commit into from
Mar 27, 2019

Conversation

abgruszecki
Copy link
Contributor

This is the potentially missing case that I described in #5736 (comment).

@abgruszecki
Copy link
Contributor Author

@smarter to clarify, this is the case I was talking about. To see why it's necessary, consider that if tp1, tp2 are TypeParamRefs, constraint already contains tp1 <: tp2 and we're not in frozen constraint, we will try to record that tp1 <: tp2, do nothing because it's already recorded, and return true. However, if the constraint is frozen, we won't try to record anything and ultimately (I believe) we will return false.

Since I'm not that familiar with type inference, I have no idea what kind of code would require this and cannot test it.

@@ -451,7 +451,11 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
// widening in `fourthTry` before adding to the constraint.
if (frozenConstraint) isSubType(tp1, bounds(tp2).lo)
else isSubTypeWhenFrozen(tp1, tp2)
alwaysTrue || {
alwaysTrue ||
frozenConstraint && (tp1 match {
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be good to have a test case that fails if the case is missing! Failing that, one can try to instrument the code to see whether the new logic makes a difference anywhere in the existing code base.

Copy link
Contributor Author

@abgruszecki abgruszecki Feb 14, 2019

Choose a reason for hiding this comment

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

@odersky the following code fails without this case:

val a = newTypeVar(TypeBounds.empty)
val b = newTypeVar(TypeBounds.empty)
a <:< b
assert(a frozen_<:< b)

I think that seems potentially problematic. I believe I actually ran across this issue half a year ago, when I was trying to make my exhaustivity checker use Constraint as a constraint solver. That being said, I have 0 idea on how to make TypeComparer enter this case w/o manually triggering it.

EDIT: for clarity, I meant to hand off this PR to @smarter. I think it's useful to know that a potential problem exists and that we already have a fix for it, even if it doesn't impact the codebase yet. It seems to me that if (a <:< b) then a frozen_<:< b is a useful invariant to have.

@abgruszecki abgruszecki force-pushed the missing-frozen-constraint-case branch from 46ab58b to f6d037a Compare March 7, 2019 12:17
@abgruszecki
Copy link
Contributor Author

abgruszecki commented Mar 7, 2019

@odersky can I get a re-review on this? I checked if this code actually impacts the codebase and the answer is it does: see all occurrences of "!!! result affected !!!" in http://dotty-ci.epfl.ch/lampepfl/dotty/11090/4. The problem is that all those occurences that I've checked (and there are 34) seemingly make comparing a type parameter with itself slightly quicker:

            ==> isSubType A <:< A ?
              ==> isSubType A <:< A ?
                ==> isSubType A <:< A ?
                  ==> isSubType Any <:< A ?
                    ==> isSubType Any <:< Int ?
                    <== isSubType Any <:< Int  = false
                  <== isSubType Any <:< A  = false
                  ==> isSubType A <:< Int ?
                    ==> isSubType Any <:< Int ?
                    <== isSubType Any <:< Int  = false
                  <== isSubType A <:< Int  = false
                <== isSubType A <:< A  = true // would otherwise be false
              <== isSubType A <:< A  = true
            <== isSubType A <:< A  = true

Note that if not for this change, the entire operation would still return true, because we'd try to constrain A <: A and then ConstraintHandler would simply return true. Since I couldn't find a proper test case, I added a unit test.

To reiterate from our Monday meeting: I still think we should merge this PR - not having this case can result in very confusing results for someone trying to use frozen_<:< manually, little additional code is necessary and the invariant that this case satisfies (if t <:< u, then also t frozen_<:< u) is a very reasonable one.

@abgruszecki abgruszecki requested a review from odersky March 7, 2019 13:14
Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

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

Thanks for getting to the bottom of this. I agree it's better to add the code to avoid surprises.

@odersky odersky merged commit 1636a25 into scala:master Mar 27, 2019
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