TypeTest on throw Exception

Im seeing warnings for a type test on an abstract type member in a try / catch case. I don’t think they should be there, since an appropriate type test is available.

def testThrows[E <: Exception](thunk: => Unit throws E)(using tt: TypeTest[Exception, E]): Unit =
  try
    thunk
  catch 
    // case tt(e: E) => println("testThrows caught TypeTest NPE")
    case e: E => println("testThrows caught NPE") // WARNING: think this can be properly checked though


def testReturns[E <: Exception](thunk: => Exception)(using tt: TypeTest[Exception, E]): Unit =
  val ex: Exception = thunk
  ex match 
    case e: E => println("testReturns matched NPE")
    //case tt(e: E) => println("testReturn matched TypeTest NPE")


def testExplicit: Unit = 
  testThrows[NullPointerException]:
    throw new NullPointerException()
  // testReturns: // ERROR: type inference chooses Nothing here instead of the precise type 
  testReturns[NullPointerException]:
    new NullPointerException()

Is there a way around this warning in testThrows without just suppressing it?

One other quick question, is there a reason that type inference chooses Nothing when a type parameter is an Exception? (swapping comments on line 25 and 26 in the example above)

Exception is not the super type of all exceptions. It’s Throwable. You need a TypeTest[Throwable, E].

1 Like

hmm, changing the type test and / or the type parameter lower bounds to throwable results in another error.

The capability to throw exception E is missing.
The capability can be provided by one of the following:
 - Adding a using clause `(using CanThrow[E])` to the definition of the enclosing method
 - Adding `throws E` clause after the result type of the enclosing method
 - Wrapping this piece of code with a `try` block that catches E

where:    E is a type in method testThrows with bounds <: Exception

This one doesnt make too much sense to me since it is in a try / catch.

Sorry for bumping this, but still seeing some really unintuitive (at least for me) behavior here. Any insight from anyone would be appreciated.

I have 3 different versions of a test_XXX function, each taking a thunk that can throw, and optionally a type test. The 2nd version (test_throwable) which i gathered to be the recommended one, does not compile, since somehow the inclusion of the TypeTest[Throwable, E] prevents the summoning of a valid CanThrow[E]. Why is this?

def test_basic[E <: Exception](thunk: => Unit throws E): Unit = 
  try 
    erased val can = summon[CanThrow[E]]
    thunk
  catch case e: E => ???

def test_throwable[E <: Exception](thunk: => Unit throws E)(using TypeTest[Throwable, E]): Unit = 
  try 
    erased val can = summon[CanThrow[E]]
    thunk
  catch case e: E => ???

def test_exception[E <: Exception](thunk: => Unit throws E)(using TypeTest[Exception, E]): Unit = 
  try 
    erased val can = summon[CanThrow[E]]
    thunk
  catch case e: E => ???

test_basic[Exception]:
  ???
test_throwable[Exception]:
  ???
test_exception[Exception]:
  ???

The other 2 version have match warnings (which is to be expected).

With the caveat that “I don’t know, but”, TIL that TypeTest is not used for catches.

The spec says that catch takes either an expression or cases, where the expected type is Throwable => pt. So you’d think that the cases you write would work just like any match.

I see both Scala 2 & 3 use ClassTag in catch.

I added guard if b to ensure it must do more than a “simple” catch, in case it mattered. Guards are not supported by safer exceptions. I removed the experimental features just to understand this much.

import scala.reflect.TypeTest

def b: Boolean = true

def test_exception[E <: Exception](thunk: => Unit)(using TypeTest[Exception, E]): Unit =
  try
    thunk
  catch case e: E => ???

//def test_exception2[E <: Exception](thunk: => Unit)(using TypeTest[Exception, E]): Unit =
def test_exception2[E](thunk: => Unit)(using TypeTest[Exception, E]): Unit =
  val e: Exception = NullPointerException()
  e match
  case _: E => println("ok")
  case _ => println("wut")
  try
    thunk
  //catch case e: E => throw ArrayIndexOutOfBoundsException()
  catch case e: E if b => throw ArrayIndexOutOfBoundsException()

@main def test =
  //test_exception2[NullPointerException]:
  test_exception2[String]:
    //throw Exception()
    throw null

Tried

scala-cli run --server=false -S 3.7.4-RC2 -Vprint:erasure x.scala
2 Likes

ah, not really getting to the answer of why the CanThrow[E] is not present, BUT its enough for a fix in my code. Matching on Exception and then having a second stage match to tighten down the bounds to E does work!

Thanks!