Oddity with throwing autoclosure

I've got a bit of a constructed scenario here that's causing a compiler error:

func handle(_ error: Error) {}

func attempt(_ process: @autoclosure () throws -> Void) {
    do {
        try process()
    } catch {
        handle(error)
    }
}

func receive(completionHandler: @escaping () -> Void) {}

func perform() throws {}

receive {
    attempt(try perform())
}

Basically, I have a sort of class-level error handler so I've written a wrapper for throwing calls that catches the error and calls to the error handler. To clean up the call site a little bit, I'm trying to use autoclosure. In the above code, when I call attempt(try perform()) I get a compiler error: Invalid conversion from throwing function of type '() throws -> ()' to non-throwing function type '() -> Void'. So it's complaining that the callback is being converted to a throwing function, but it shouldn't be since the attempt function should be swallowing that error.

Additionally, if I wrap that attempt call with a catch like this:

receive {
    do {
        attempt(try perform())
    } catch {}
}

I get a warning: 'catch' block is unreachable because no errors are thrown in 'do' block. Which is correct, because attempt is swallowing the error. But without the catch it thinks the function can throw.

Anybody run into this?

EDIT: I wanted to add that calling attempt from inside the completion handler in receive is the specific problem. If you call attempt directly instead of from a callback, the compiler error goes away and things work as expected.

1 Like

Many people. This is SR-487, first reported in 2016.

1 Like

Thanks!

I wonder if we could "fix" this by making it into an error, and say that a function that takes a throwing autoclosure must itself be throwing.

The reason for the bug is that today the compiler assumes that it can determine if a block of statements throws without having to type check them first, by looking for try. This sounds like a good property to have in general, but if a throwing autoclosure can be passed to a non-throwing function, this property no longer holds because we don't know which expressions are wrapped in autoclosures until after type checking.

No, because if you throw other errors in the do-catch block the code works fine. So there is very likely already code out there where this is accepted and relied upon. (And XCTest is unfortunately one of the offenders: XCTAssertNoThrow(_:_:file:line:) | Apple Developer Documentation )