Closure typed throw

I am trying the typed throw feature on swift 6.
when the closure throws a typed error, the error became any Error.
Is it a bug?

enum SomeError: Error {
    case unknown
}

func throwCallBack<E: Error>(callback: () throws(E) -> Void) throws(E) {} 

func foo() {
    do {
        try throwCallBack {
            throw SomeError.unknown
        }
    } catch {
        // `e` is `any Error`
        let e = error
    }
}
1 Like

I think the code above you provided had some misunderstanding about Typed throws.

According to the “typed throws” Proposal.

Function declarations must always explicitly specify whether they throw, optionally providing a specific thrown error type.

// For Functions expressions, these are equivalent.
func throwCallBack(callback: () throws -> Void) throws {} 
func throwCallBack<E: Error>(callback: () throws(E) -> Void) throws(E) {} 
// In your case, If you wanna a typed throws function, it should be like:
func throwCallBack(callback: () throws(SomeError) -> Void) throws(SomeError) {} 

// For Closures expressions are like: 
{ () -> Bool in true }
{ () throws -> Bool in true }
{ () throws(CatError) -> Bool in true }

But there's some specified rules for inferring the error types.

With typed throws, the closure type could be inferred to have a typed error by considering all of the throwing sites that aren't caught (let each have a thrown type Ei) and then inferring the closure's thrown error type to be errorUnion(E1, E2, ... EN).

Swift 6: This inference rule will change the thrown error types of existing closures that throw concrete types. For example, the following closure:

{ if Int.random(in: 0..<24) < 20 { throw CatError.asleep } }

will currently be inferred as throws. With the rule specified here, it will be inferred as throws(CatError). This could break some code that depends on the precisely inferred type. To prevent this from becoming a source compatibility problem, we apply the same rule as for do...catch statements to limit inference: throw statements within the closure body are treated as having the type any Error in Swift 5. This way, one can only infer a more specific thrown error type in a closure when the try operations are calling functions that make use of typed errors.

Note that one can explicitly specify the thrown error type of a closure to disable this type inference, which has the nice effect of also providing a contextual type for throw statements:

{ () throws(CatError) in if Int.random(in: 0..<24) < 20 { throw .asleep } }

1 Like

The example you provided above. the function shown is a concrete type.
I know concrete-type it works as expected.

But my example is generic.
It can't be inferred. but the placeholder for the two throws are E. So, I expect it can be inferred.

So, are you saying the inference won't work for generic?

I believe that part of the feature is gated behind the FullTypedThrows feature, which isn’t yet fully implemented and won’t be on by default.

3 Likes

It's not the generic part that's the problem. The problem is just that closures need their errors explicitly typed.*

do {
  try throwCallBack { () throws(SomeError) in
    throw .unknown
  }
} catch {
  error // SomeError
}

There is implicit typing going on there. The fully explicit syntax expands on the do statement.

do throws(SomeError) {
  try throwCallBack { () throws(SomeError) in
    throw .unknown
  }
} catch {
  error // SomeError
}

* That is, they do if they throw. there's no problem if you don't actually throw errors.

// Compiles.
_ = { } as () throws(SomeError) -> _

// Invalid conversion of thrown error type 'any Error' to 'SomeError'
_ = { throw SomeError.unknown } as () throws(SomeError) -> _
1 Like