-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
Somehow I'd never paid attention to attemptNarrow, which we added in 2.1.0. It has some problems. For example it's trivially easy to make it crash at runtime:
scala> val e: Either[Any, Unit] = Left(List("abc"))
e: Either[Any,Unit] = Left(List(abc))
scala> import cats.implicits._
import cats.implicits._
scala> def x = e.attemptNarrow[List[Int]] match {
| case Right(Left(List(x))) => x
| case _ => 0
| }
x: Int
scala> x
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:99)
at .x(<console>:2)
... 36 elidedIt can also be used to launder a runtime type check:
scala> import cats.implicits._, scala.reflect.ClassTag
import cats.implicits._
import scala.reflect.ClassTag
scala> def isIt[A: ClassTag](any: Any): Boolean =
| (Left(any): Either[Any, Unit]).attemptNarrow[A].isRight
isIt: [A](any: Any)(implicit evidence$1: scala.reflect.ClassTag[A])Boolean
scala> val x = if (true) 1 else "abc"
x: Any = 1
scala> isIt[String](x)
res0: Boolean = false
scala> isIt[Int](x)
res1: Boolean = trueMaybe that's fine, and you should know that if you've got a ClassTag around you've given up any hope of parametricity, but that seems pretty far from the spirit of Cats.
@tpolecat requested tests for the erasure issues in the original PR, but unfortunately the tests that were added don't actually check for the relevant problems.
I guess the reasoning is that E is usually an exception type, and generally won't be generic? In any case I think we should make both the type class and syntax methods package-private in future 2.x releases and remove them in 3.0, or at least put an explicit <: Throwable constraint on them (which doesn't eliminate the possibility of runtime crashes, but does make them much less likely).