Skip to content

Fix type inference for HLists and HMaps #2045

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 16 commits into from
Mar 9, 2017

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Mar 2, 2017

Two main fixes and some cleanups:

  • Systematically support dependent implicit methods (the test uses them)
  • Make &-simplification not forget constraints

If an applied type has a refinement, it was
printed before as one large refinement type
including the type parameter bindings.
No reason why we should not - normalize handles implicit
methods just fine. This fixes type errors in test HLists.scala.
We approximate dependencies to parameters by Wildcards. This was already
done in one place, is now done in other places as well, instead of doing nothing for
dependent methods.
We previously tried to force S1 and S2 be the same type
when encountering a lub like `T1 { A = S1 } & T2 { A = S2 }`.
The comments in this commit explain why this is unsound, so
this rewrite is now made subject to a new config option, which
is off by default.

I verified that the new behavior does not affect the performance of
the junit tests.
The HLists test brought out the unsoundness of alias
rewriting in glbs which is tackled in the last commit.
These now compile with the changes to dependent methods, except
for one which is invalid under dotty.
Variance changes quite a few things for type inference, so
it's good to check a non-variant version as well.
Need to use fresh PolyParams instead of WildcardTypes
if constraint is committable.
Turned out hmaps.scala requires the arg alignment to compile.
So we have our first counterexample that we cannot drop this
hack. Now it is made safe in the sense that no constraints get
lost anymore.
I believe this worked only accidentally because we matched
more things with wildcards which turned out to be flawed. The test
errors show some funky _#_ types, so not sure whether the tests
are still valid or not. Moved back to pending awaiting further
resolution.
Type inference tends to take quite different paths for non-variant
and variant data structures. Since, non-variant hmap has already exposed quite
a few problems, it's good to test it also in the covariant case.
@OlivierBlanvillain OlivierBlanvillain self-requested a review March 2, 2017 10:42
@retronym
Copy link
Member

retronym commented Mar 2, 2017

You might like to include some tests from SI-5070.

Scalac is too pessimistic it determining if a method is dependent, dotty's implementation looks better.

// should compile
trait Web {
  type LocalName
}
trait Companion1[A]
trait WebDSL[W <: Web] {
  trait LocalNameCompanion extends Companion1[W#LocalName] {
    type A = String
  }
  implicit val LocalName: LocalNameCompanion
}
object Test {
  def t[W <: Web](implicit webDSL: WebDSL[W]): Unit = {
    import webDSL._
    implicitly[LocalNameCompanion] // succeeds
    implicitly[Companion1[W#LocalName]] // fails
  }
}

I see from trying to compile the first example that this is not allowed in dotty for other reasons: "W#LocalName: W is not a legal path".

trait A {
  type T
}
 
object O {
  implicit def b(implicit x: A): x.T = error("")
}
 
class Test {
  import O._
  implicit val a: A = new A {}
  implicitly[a.T]
  implicitly[a.T](b(a))
}
// variation that typechecks in dotty but fails in scalac. Due to scalac's approximation of `isImmediatelyDependent`
class Test {
  def foo(implicit x: A) = {
    object O {
      implicit def b(implicit y: x.type): y.T = ???
    }

    import O._
    b(x): x.T
    implicitly[x.T]
  }
}
trait A {
  type T
}

FYI Here's an attempted fix for SI-5070 in scalac scala/scala@2.12.x...retronym:ticket/5070

// But without it we would replace the two aliases by
// `T { X >: S1 | S2 <: S1 & S2 }`, which looks weird and is probably
// not what's intended.
// Given two refinements `T1 { X = S1 }` and `T2 { X = S2 }` rwrite to
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: "rwrite"

and a typo fixed
@odersky
Copy link
Contributor Author

odersky commented Mar 2, 2017

@retronym I added the original SI-5070 as well as an adapted version of SI-5643 as new tests.

try
if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound)
else oldBounds.derivedTypeBounds(lo | bound, hi)
finally homogenizeArgs = saved
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this state be stored in Context instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this would make the logic more complicated.

}
if (homogenizeArgs &&
isNonvariantAlias(rinfo1) && isNonvariantAlias(rinfo2))
isSameType(rinfo1, rinfo2)
Copy link
Contributor

Choose a reason for hiding this comment

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

else clause missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, we perform sameType only for its effect on the constraint

@@ -18,7 +17,7 @@ object Sessions {

// friendly interface to the theory
def runSession[S, D: Session[S]#HasDual](session: S, dual: D) =
?[Session[S]#HasDual[D]].run(session, dual)
implicitly[Session[S]#HasDual[D]].run(session, dual)
Copy link
Contributor

Choose a reason for hiding this comment

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

Was keeping this test pending intentional? Does this PR change how implicitly deals with type member?

I remember seeing something similar to this def ? being use to get more precise types:

trait A { type Out }

implicit val a = new A { type Out = String }

val i1: A { type Out = String } = implicitly[A]
// error: type mismatch;
// found   : A
// required: A{type Out = String}

def ?[T <: AnyRef](implicit w: T): w.type = w

val i2: A { type Out = String } = ?[A]  // works fine!

Copy link
Contributor Author

@odersky odersky Mar 3, 2017

Choose a reason for hiding this comment

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

No, that's an oversight. We should keep the test as it was in pending. It worked after the first commits but then stopped working again after the final commits. Given the error message it could well be something related to existential types which dotty no longer supports, so I did not investigate further. I'll change the test to what it was an add a comment.

@retronym
Copy link
Member

retronym commented Mar 2, 2017

/cc @adriaanm, this might be a slice of dotc that interests you.

@odersky odersky merged commit 8e5c9c4 into scala:master Mar 9, 2017
@liufengyun liufengyun mentioned this pull request Mar 14, 2017
@allanrenucci allanrenucci deleted the fix-hlist-hmap branch December 14, 2017 19:20
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.

4 participants