-
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
Type inference issue involve type alias and higher-kinded type #11499
Comments
The problem isn't specific to extension methods or opaque types, it can be reproduced when writing: import cats._
object data {
type OptionT[F[_], A] = F[Option[A]]
def fold[F[_], A, B](value: OptionT[F, A])(default: => B)(f: A => B)(using F: Functor[F]): F[B] =
F.map(value)(_.fold(default)(f))
def cata[F[_], A, B](value: OptionT[F, A])(default: => B, f: A => B)(using F: Functor[F]): F[B] =
fold(value)(default)(f)(using F)
} But inference works fine when dealiasing manually: import cats._
object data {
def fold[F[_], A, B](value: F[Option[A]])(default: => B)(f: A => B)(using F: Functor[F]): F[B] =
F.map(value)(_.fold(default)(f))
def cata[F[_], A, B](value: F[Option[A]])(default: => B, f: A => B)(using F: Functor[F]): F[B] =
fold(value)(default)(f)(using F)
} |
Minimized without the cats dependency: trait Functor[F[_]]
object data {
type OptionT[F[_], A] = F[Option[A]]
def fold[F[_], A, B](value: OptionT[F, A])(f: Functor[F]): F[B] = ???
def cata[F[_], A, B](value: OptionT[F, A])(f: Functor[F]): F[B] =
fold(value)(f) // error
} This compiles with Scala 2. |
I did some investigations on this issue. Minimized a bit to: type X[F[_]]
type G[F[_], A] = F[String]
def g[M[_]](value: G[M, Int], f: X[M]): Unit = ???
def h[L[_]](value: G[L, Int], f: X[L]): Unit =
g(value, f) // error Apparently the typing of G[L, Int] <:< G[M, Int]
G[L, Int] <:< M[String]
[A] =>> G[L, A] <:< M // wrong, turned Int into a hole First, the compiler dealiases the second The compareAppliedTypeParamRef method above does not do the right thing here. Apparently it ends up trying to compare type constructors So that the comparison is done now between: [A] =>> G[L, A] <:< M Which succeeds and creates a lower bound constraint on type X[F[_]]
type G[A, F[_]] = F[String]
def g[M[_]](value: G[Int, M], f: X[M]): Unit = ???
def h[L[_]](value: G[Int, L], f: X[L]): Unit =
g(value, f) This is because the hole is now created where it should be: in place of I believe the error could be avoided in several ways. First, we could make sure the holes are inferred in an intelligent way rather than simply turning extra arguments into the holes. Second, we may want to try to dealias G[L, Int] <:< G[M, Int]
G[L, Int] <:< M[String]
L[String] <:< M[String] Maybe canInstantiate can be the right place for doing this dealias: Because it happens after the I'm not sure which way is the best fix. |
Higher-order unification is an extremely hard problem in general (cf https://en.wikipedia.org/wiki/Unification_(computer_science)#Higher-order_unification), the current solution was chosen because it does "the right thing" in many situations and it's easy enough to understand what it does to adapt code to work with it, so any attempt at changing it is likely to break more code than it fixes, see the infamous scala/bug#2712.
I haven't looked at the details here but in general dealiasing too early can break inference since people sometimes intentionally use aliases to change what gets inferred: https://github.com/lampepfl/dotty/blob/f8bd01c6219942b33bccd098b8bf5412ec22c264/compiler/src/dotty/tools/dotc/core/TypeApplications.scala#L324-L327 See also the discussion in #10221 |
Maybe a stupid question but why are we dealiasing instead of unifying |
Before we dealias, we should be compared the applied types as written, it looks like this is done but failing, looking around this seems to fix it: diff --git compiler/src/dotty/tools/dotc/core/TypeComparer.scala compiler/src/dotty/tools/dotc/core/TypeComparer.scala
index 23cd8b25d25..46c3be62047 100644
--- compiler/src/dotty/tools/dotc/core/TypeComparer.scala
+++ compiler/src/dotty/tools/dotc/core/TypeComparer.scala
@@ -973,7 +973,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
/** True if `tp1` and `tp2` have compatible type constructors and their
* corresponding arguments are subtypes relative to their variance (see `isSubArgs`).
*/
- def isMatchingApply(tp1: Type): Boolean = tp1 match {
+ def isMatchingApply(tp1: Type): Boolean = tp1.widen match {
case tp1 @ AppliedType(tycon1, args1) =>
// We intentionally do not automatically dealias `tycon1` or `tycon2` here.
// `TypeApplications#appliedTo` already takes care of dealiasing type now running the tests to see if this break anything, but this looks promising :) |
Unfortunately this change breaks one patmat test, when I run -- [E029] Pattern Match Exhaustivity Warning: tests/patmat/i6197d.scala:5:28 ----------------------------------------------------------------------------------------------------------------------------------------------------------------
5 |def bar(x: Array[String]) = x match {
| ^
| match may not be exhaustive.
|
| It would fail on pattern case: _: Array[String] But this warning disappears after applying the diff in my previous comment, digging a bit it seems that in Typer we generate a symbol for |
I think I know what causes the problem. It seems that we continue mishandling ConstraintHandling in GadtConstraint, and that before the change you propose could work, we'd need #11988 first. Reg. our previous discussion - the bit of code you quote does seem to be (at least somewhat) necessary. It seems that some checks in the compiler don't look up GADT constraints (match types, for instance) and if we remove that bit of code, we can't compile stdlib anymore. |
Fixes scala#11499 (see the discussion there for details on what happened in the test case before this change).
As discussed in https://gitter.im/lampepfl/dotty?at=6033a71be634904e60b71e22
Compiler version
3.0.0-RC1
Minimized code
Output
The text was updated successfully, but these errors were encountered: