SE-0413: Typed throws

Three questions regarding this amendment:

First, regarding this rule—

If a throws-clause is present, then there must be at least one catch-clause.

Why this requirement? Could it not be useful for a non-catching do for the purposes of closure type inference?

Second—

How do nested throws affect type inference? The original proposal writes of func callCat() throws(CatError) -> Cat:

The function can only throw instances of CatError. This provides contextual type information for all throw sites, so we can write .sleeps instead of the more verbose CatError.sleeps that's needed with untyped throws.

But suppose we have:

func callCat() throws(CatError) -> Cat {
  do throws(DogError) {
    throw .sleeps  // (1)
  } catch {
    throw .sleeps  // (2)
  }
  fatalError()
}

Am I correct in deducing that DogError.sleeps is thrown at (1), which is caught as an error of static type DogError and handled by rethrowing CatError.sleeps at (2)?

Third—

throws-clause not being part of this amendment (and hence not in the diff), I'm assuming this permits:

do throws /* note: no type */ { ... } catch { ... }

If so, does this "force box" the error—i.e., is it equivalent to do throws(any Error)...—or is it always equivalent to bare do { ... } catch { ... }?

Actually, it would seem to me that the latter wouldn't be logical, particular for nested type inference purposes. For instance, given:

// --- This all works in Swift 5.9 ---
struct E: Error { }
enum F: Error { case e }

extension E {
    static var e: F { F.e } // note this twist.
}

extension Error where Self == E {
    static var e: E { E() } // yes, I'm making your life difficult.
}

func e() -> Error { .e }  // returns a boxed `E`
func f() -> Error { F.e } // returns a boxed `F`
func g() throws { throw .e } // throws error of type `E`

// --- As originally proposed here in SE-0413 ---
func h() throws(F) { throw .e } // throws error of type `F`

func i() throws(F) {
  do { throw .e } // throws error of type `F`
}

Can you explain how the following behaves?

func j() throws {
  do throws(F) {
    throw .e // throws error of type `F`
  } catch {
    throw .e // throws error of type `E` (I think?)
  }
}

// --- What happens here? ---

func k() throws(F) {
  do throws(E) {
    throw .e // (5)
  } catch is E {
    throw .e // (6)
  }
  // ...
}

func l() throws(F) {
  do throws {
    throw .e // (7)
  } catch is E {
    throw .e // (8)
  }
  // ...
}

func m() throws(F) {
  do throws(any Error) {
    throw .e // (9)
  } catch is E {
    throw .e // (10)
  }
  // ...
}

func n() throws(F) {
  do {
    throw .e // (11)
  } catch is E {
    throw .e // (12)
  }
  // ...
}
5 Likes