-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #10129: Fix comparisons of applied type aliases #10221
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
// This leads to the constraint Lifted[U] <: Lifted[Int]. If we just | ||
// check the arguments this gives `U <: Int`. But this is wrong. Dealiasing | ||
// `Lifted` gives `Err | U <: Err | Int`, hence it should be `U <: Err | Int`. |
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'm not necessarily against changing it now if we believe it's worth it, but that was actually an intentional behavior, see the justification in 5fb13aa:
I think the new behavior is arguably better since using type aliases now
gives you more control on how type inference proceeds, even if it means
that some things that used to typecheck don't anymore.
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 think we should not simply infer something that does not follow from the constraint. Or at least we should do it under the strict control of either
. i10129 is arguably a very natural way to express things.
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.
Or, let's put it that way. The correct thing to do is to dealias. Unfortunately that breaks hk type inference. But hk type inference always was an ad-hoc best effort thing (some might call it a kludge). It's clear we can't do hk type inference in general. So it's good to push this as far as we can, but it should not compromise the correct behavior for other constraints.
test performance please |
performance test scheduled: 1 job(s) in queue, 0 running. |
Performance test finished successfully: Visit http://dotty-bench.epfl.ch/10221/ to see the changes. Benchmarks is based on merging with master (5c84e42) |
// that is taken care of by the outer inFrozenGadtIf test. The problem is | ||
// that hk-alias-unification.scala relies on the right isSubArgs being performed | ||
// when constraining the result type of a method. But there we are only allowed | ||
// to use a necessaryEither, since it might be that an implicit conversion will |
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.
There's no implicit conversion in hk-alias-unification.scala, just implicit search.
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.
You are right. The change was actually introduced here: #8635
It was a way to avoid overcommitting when constraining the result type. Unfortunately, it seems that this causes the problems we encounter here. So, what to do? All this is getting more and more ad-hoc, which is not very satisfactory.
// be inserted on the result. So strictly speaking by going to a sufficientEither | ||
// we overshoot and narrow the constraint unnecessarily. On the other hand, 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.
In situation where the gadt constraints aren't frozen, isn't this narrowing problematic? /cc @abgruszecki
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 believe not, since we are in a frozen Gadt state anyway.
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.
Ah, we're in inFrozenGadtIf(!tyconIsInjective)
, and tyconIsInjective
won't be true if both constructors are type aliases, so gadt constraints are frozen indeed, although it's not immediately obvious from the code.
// which demands a sufficientEither anyway. | ||
sufficientEither( | ||
isSubArgs(args1, args2, tp1, tparams), | ||
recur(tp1.superType, tp2.superType)) |
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.
We check above that tycon1sym.isAliasType
so tp1.superType
should be equivalent to tp1
, on the other hand I don't see a similar check for tp2
, so it seems that tp2.superType
could in fact be an actual supertype of tp2
.
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.
The code is only hit if the two type constructors are the same.
case tp1a as AppliedType(_, args1a) if args1a.eqElements(args1) => | ||
tp2.superType match | ||
case tp2a as AppliedType(_, args2a) if args2a.eqElements(args2) => | ||
return recur(tp1a, tp2a) |
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 branch is never taken (I added an assert here and verified that our whole CI passes with it), because we eagerly dealias in appliedTo
unless it affects type inference: https://github.com/lampepfl/dotty/blob/8e9ac7519b56a452348b878979f56ff01308cc8e/compiler/src/dotty/tools/dotc/core/TypeApplications.scala#L319-L325
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.
Ah, good to know!
// There's one test in shapeless/deriving that had to be disabled | ||
// (modules/deriving/src/test/scala/shapeless3/deriving/deriving.scala, value v7 in the functor test). |
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.
FYI @milessabin, how important is that particular test?
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.
That's an important test. It demonstrates the ability to derive functor instances for nested type constructors.
It would at least be good to know why v7
fails whereas v6
succeeds. Both involve recursion. The only difference is that the former failing one has an additional non-recursive wrapper, which is itself successfully derived in v3
.
That's the second time I get a conflict because of shapeless which causes me a huge effort to re-sync. I'll do that one last time and then merge since I don't have the time to go through these steps a third time. |
The failure with shapeless here is demonstrating what this change is forcing us to give up. That being the case, it would help to have a motivation for the change and a sketch of a workaround. |
The motivation is issue #10129. Here we have type Lifted[A] = Err | A and the constraint Lifted[X] <: Lifted[Int] It's simply incorrect to conclude from this that I don't know what else one could do here. |
That sounds completely reasonable. What's not clear to me is why that breaks the test in shapeless. Other than involving hk types it doesn't appear to be related in any obvious way to the example you're fixing here. You suggest this is an inference problem ... is there an explicit type annotation or parameter which could be added to the test which would fix it? What is the compilation error in shapeless for that test? |
It's hard to diagnose since it is in auto-generated code from deriving, I believe. What must have happened is that there is a type alias somewhere such as type T[X] = Any and a constraint gets generated such as T[F[A]] <: T[List{X]] In this case we'd conclude so far that The RHS of |
There are uses of a type Const[c] = [t] =>> c which could produce something similar to your Are you saying that this sort of construct can't be relied on any more? Can you suggest an alternative encoding? |
Yes, |
I think this comes down to a design decision: do we want type aliases to be a way to drive type inference, or do we want to always infer the minimum set of constraints? The former let library authors craft aliases to improve inference in their APIs and hopefully provide a better UX, but it's ill-defined, whereas the latter can be unintuitive for users but it's a clear criterion compiler developers can refer to when making changes to type inference, so it reduces the risk that we end up piling up heuristics after heuristics without really knowing what we're doing. |
No, it can't. |
A trick might be to write |
Const[c] >: [t] =>> c <: [t] =>> c Yes, that looks promising. |
How should we proceed? We don't seem to have an alternative to the fix in this PR. I propose we get it in and then see what needs to be done to bring back the failing test in shapeless. |
I'm still unclear why this test fails but others don't.
|
- streamline comparisons of applied types - optimize sufficientEither to not try the second alternative if the first succeeds without narrowing the constraint.
Given type F[Xs] = ... F[As] <: F[Bs] Do we dealias `F` or check the arguments directly? It turns out that neither choice is correct in all instances. Checking the arguments directly is necessary for hk type inference, but may narrow the constraint too much. The solution is to go to an either that tries both options.
Good news! I found a way to do what we want and still keep all shapeless tests. Digging deeper I found that the issue was not what I initially suspected. I suspected that a constraint generated by a isSubArgs was rolled back by the test of the dealiased types. But what happened instead was that we had a failing
If we simply fail The fix is simple: Fail if |
Awesome ... thanks for digging in to this! 😄 |
Given
Do we dealias
F
or check the arguments directly? It turns out that neither choiceis correct in all instances. Checking the arguments directly is necessary for hk
type inference, but may narrow the constraint too much. The solution is to use
an
either
that tries both alternatives.