Skip to content
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

Add two casts to satisfy Dotty #3230

Merged
merged 1 commit into from
Dec 31, 2019

Conversation

travisbrown
Copy link
Contributor

These two casts are necessary to compile cats-core with Dotty. I've been using them locally in my cross-compilation experiments, but wanted to understand for myself why Dotty wants them before opening a PR.

I've just been taking a closer look and have convinced myself that in both cases Dotty is doing the right thing, and Scala 2 only compiles the current code because of soundness bugs.

In AndThen, the issue is that the compiler is inferring the E to be Any in the Concat case, which isn't sound in general. Consider the following simplification:

scala> case class Foo[A](f: A => Any)
defined class Foo

scala> def whatever(x: Any) = x match { case Foo(f) => f("") }
                                                         ^
       error: type mismatch;
        found   : String("")
        required: Nothing

Inferring Nothing here is the safe thing to do, and this failure makes sense. You can trick the compiler into inferring the A to be Any, though, simply by having it appear in a non-contravariant position in another member:

scala> case class Foo[A](a: A, f: A => Any)
defined class Foo

scala> def whatever(x: Any) = x match { case Foo(_, f) => f("") }
whatever: (x: Any)Any

…which will fail at runtime on e.g. Foo[Int]:

scala> whatever(Foo[Int](1, identity))
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
  at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:100)
  at scala.runtime.java8.JFunction1$mcII$sp.apply(JFunction1$mcII$sp.scala:17)
  at .whatever(<console>:1)
  ... 29 elided

Dotty fixes this and refuses to compile the second Foo, and it fails on AndThen for the same reason:

[error] -- [E007] Type Mismatch Error: /home/travis/projects/cats/core/src/main/scala/cats/data/AndThen.scala:125:32 
[error] 125 |          right = inner.andThenF(right)
[error]     |                  ^^^^^^^^^^^^^^^^^^^^^
[error]     |                  Found:    cats.data.AndThen[E$4, Any]
[error]     |                  Required: cats.data.AndThen[Any, Any]

The Eval code also compiles only because the Scala 2 compiler is too willing to infer Any. Here's a simplified example that shows the unsoundness:

trait Foo[+A] {
  def result: Option[A]
}

case class Bar[A]() extends Foo[A] {
  var result: Option[A] = None
}

def whatever(foo: Foo[Any]): Unit = foo match {
  case b @ Bar() => b.result = Some("foo")
}

And then:

scala> val bar = Bar[Int]()
bar: Bar[Int] = Bar()

scala> whatever(bar)

scala> bar.result.get
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
  at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:100)
  ... 29 elided

In context both of these cases are optimizations where we're already doing a lot of casting, and personally I think it's reasonable to add two more casts to satisfy the Dotty compiler.

@codecov-io
Copy link

codecov-io commented Dec 30, 2019

Codecov Report

Merging #3230 into master will not change coverage.
The diff coverage is 100%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #3230   +/-   ##
=======================================
  Coverage   93.05%   93.05%           
=======================================
  Files         376      376           
  Lines        7412     7412           
  Branches      192      200    +8     
=======================================
  Hits         6897     6897           
  Misses        515      515
Flag Coverage Δ
#scala_version_212 93.38% <100%> (ø) ⬆️
#scala_version_213 92.82% <100%> (ø) ⬆️
Impacted Files Coverage Δ
core/src/main/scala/cats/Eval.scala 98.83% <100%> (ø) ⬆️
core/src/main/scala/cats/data/AndThen.scala 94.33% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 851f03b...06ae048. Read the comment docs.

Copy link
Member

@rossabaker rossabaker left a comment

Choose a reason for hiding this comment

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

Nice write-up!

@travisbrown travisbrown merged commit 4f5f7ac into typelevel:master Dec 31, 2019
@travisbrown travisbrown added this to the 2.2.0-M1 milestone Jan 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants