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

Don't infer Null in fromTryCatch #592

Merged
merged 4 commits into from
Nov 7, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions core/src/main/scala/cats/NotNull.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package cats

/**
* An instance of `NotNull[A]` indicates that `A` does not have a static type
* of `Null`.
*
* This can be useful in preventing `Null` from being inferred when a type
* parameter is omitted.
*/
sealed trait NotNull[A]

object NotNull {
/**
* Since NotNull is just a marker trait with no functionality, it's safe to
* reuse a single instance of it. This helps prevent unnecessary allocations.
*/
private[this] val singleton: NotNull[Any] = new NotNull[Any] {}

private[this] def ambiguousException: Exception = new Exception("An instance of NotNull[Null] was used. This should never happen. Both ambiguous NotNull[Null] instances should always be in scope if one of them is.")

implicit def `If you are seeing this, you probably need to add an explicit type parameter somewhere, beause Null is being inferred.`: NotNull[Null] = throw ambiguousException

implicit def ambiguousNull2: NotNull[Null] = throw ambiguousException

implicit def notNull[A]: NotNull[A] = singleton.asInstanceOf[NotNull[A]]
}
16 changes: 11 additions & 5 deletions core/src/main/scala/cats/data/Validated.scala
Original file line number Diff line number Diff line change
Expand Up @@ -228,22 +228,28 @@ trait ValidatedFunctions {
* the resulting `Validated`. Uncaught exceptions are propagated.
*
* For example: {{{
* val result: Validated[NumberFormatException, Int] = fromTryCatch[NumberFormatException] { "foo".toInt }
* val result: Validated[NumberFormatException, Int] = catchOnly[NumberFormatException] { "foo".toInt }
* }}}
*/
def fromTryCatch[T >: Null <: Throwable]: FromTryCatchPartiallyApplied[T] = new FromTryCatchPartiallyApplied[T]
def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] = new CatchOnlyPartiallyApplied[T]

final class FromTryCatchPartiallyApplied[T] private[ValidatedFunctions] {
def apply[A](f: => A)(implicit T: ClassTag[T]): Validated[T, A] = {
final class CatchOnlyPartiallyApplied[T] private[ValidatedFunctions] {
def apply[A](f: => A)(implicit T: ClassTag[T], NT: NotNull[T]): Validated[T, A] =
try {
valid(f)
} catch {
case t if T.runtimeClass.isInstance(t) =>
invalid(t.asInstanceOf[T])
}
}
}

def catchNonFatal[A](f: => A): Validated[Throwable, A] =
try {
valid(f)
} catch {
case scala.util.control.NonFatal(t) => invalid(t)
}

/**
* Converts a `Try[A]` to a `Validated[Throwable, A]`.
*/
Expand Down
19 changes: 13 additions & 6 deletions core/src/main/scala/cats/data/Xor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -217,20 +217,27 @@ trait XorFunctions {
* the resulting `Xor`. Uncaught exceptions are propagated.
*
* For example: {{{
* val result: NumberFormatException Xor Int = fromTryCatch[NumberFormatException] { "foo".toInt }
* val result: NumberFormatException Xor Int = catching[NumberFormatException] { "foo".toInt }
* }}}
*/
def fromTryCatch[T >: Null <: Throwable]: FromTryCatchPartiallyApplied[T] =
new FromTryCatchPartiallyApplied[T]
def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] =
new CatchOnlyPartiallyApplied[T]

final class FromTryCatchPartiallyApplied[T] private[XorFunctions] {
def apply[A](f: => A)(implicit T: ClassTag[T]): T Xor A =
final class CatchOnlyPartiallyApplied[T] private[XorFunctions] {
def apply[A](f: => A)(implicit CT: ClassTag[T], NT: NotNull[T]): T Xor A =
try {
right(f)
} catch {
case t if T.runtimeClass.isInstance(t) =>
case t if CT.runtimeClass.isInstance(t) =>
left(t.asInstanceOf[T])
}
}

def catchNonFatal[A](f: => A): Throwable Xor A =
try {
right(f)
} catch {
case scala.util.control.NonFatal(t) => left(t)
}

/**
Expand Down
4 changes: 2 additions & 2 deletions docs/src/main/tut/traverse.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ import cats.std.list._
import cats.syntax.traverse._

def parseIntXor(s: String): Xor[NumberFormatException, Int] =
Xor.fromTryCatch[NumberFormatException](s.toInt)
Xor.catchOnly[NumberFormatException](s.toInt)

def parseIntValidated(s: String): ValidatedNel[NumberFormatException, Int] =
Validated.fromTryCatch[NumberFormatException](s.toInt).toValidatedNel
Validated.catchOnly[NumberFormatException](s.toInt).toValidatedNel

val x1 = List("1", "2", "3").traverseU(parseIntXor)
val x2 = List("1", "abc", "3").traverseU(parseIntXor)
Expand Down
11 changes: 9 additions & 2 deletions docs/src/main/tut/xor.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,13 +340,20 @@ val xor: Xor[NumberFormatException, Int] =
}
```

However, this can get tedious quickly. `Xor` provides a `fromTryCatch` method on its companion object
However, this can get tedious quickly. `Xor` provides a `catchOnly` method on its companion object
that allows you to pass it a function, along with the type of exception you want to catch, and does the
above for you.

```tut
val xor: Xor[NumberFormatException, Int] =
Xor.fromTryCatch[NumberFormatException]("abc".toInt)
Xor.catchOnly[NumberFormatException]("abc".toInt)
```

If you want to catch all (non-fatal) throwables, you can use `catchNonFatal`.

```tut
val xor: Xor[Throwable, Int] =
Xor.catchNonFatal("abc".toInt)
```

## Additional syntax
Expand Down
13 changes: 9 additions & 4 deletions tests/src/test/scala/cats/tests/ValidatedTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,21 @@ class ValidatedTests extends CatsSuite {
Applicative[Validated[String, ?]].ap2(Invalid("1"), Invalid("2"))(Valid(plus)) should === (Invalid("12"))
}

test("fromTryCatch catches matching exceptions") {
assert(Validated.fromTryCatch[NumberFormatException]{ "foo".toInt }.isInstanceOf[Invalid[NumberFormatException]])
test("catchOnly catches matching exceptions") {
assert(Validated.catchOnly[NumberFormatException]{ "foo".toInt }.isInstanceOf[Invalid[NumberFormatException]])
}

test("fromTryCatch lets non-matching exceptions escape") {
test("catchOnly lets non-matching exceptions escape") {
val _ = intercept[NumberFormatException] {
Validated.fromTryCatch[IndexOutOfBoundsException]{ "foo".toInt }
Validated.catchOnly[IndexOutOfBoundsException]{ "foo".toInt }
}
}

test("catchNonFatal catches non-fatal exceptions") {
assert(Validated.catchNonFatal{ "foo".toInt }.isInvalid)
assert(Validated.catchNonFatal{ throw new Throwable("blargh") }.isInvalid)
}

test("fromTry is invalid for failed try"){
forAll { t: Try[Int] =>
t.isFailure should === (Validated.fromTry(t).isInvalid)
Expand Down
13 changes: 9 additions & 4 deletions tests/src/test/scala/cats/tests/XorTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,21 @@ class XorTests extends CatsSuite {

checkAll("? Xor ?", BifunctorTests[Xor].bifunctor[Int, Int, Int, String, String, String])

test("fromTryCatch catches matching exceptions") {
assert(Xor.fromTryCatch[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]])
test("catchOnly catches matching exceptions") {
assert(Xor.catchOnly[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]])
}

test("fromTryCatch lets non-matching exceptions escape") {
test("catchOnly lets non-matching exceptions escape") {
val _ = intercept[NumberFormatException] {
Xor.fromTryCatch[IndexOutOfBoundsException]{ "foo".toInt }
Xor.catchOnly[IndexOutOfBoundsException]{ "foo".toInt }
}
}

test("catchNonFatal catches non-fatal exceptions") {
assert(Xor.catchNonFatal{ "foo".toInt }.isLeft)
assert(Xor.catchNonFatal{ throw new Throwable("blargh") }.isLeft)
}

test("fromTry is left for failed Try") {
forAll { t: Try[Int] =>
t.isFailure should === (Xor.fromTry(t).isLeft)
Expand Down