Intriguing Xcode warning

I have an XCTest case that produces an intriguing warning:

    func testFutureValueWithTimeout() {
        let expectation = XCTestExpectation(description: "This should not fail")
        DispatchQueue.global().async {
            let expect = FutureResult<Int>()
            do {
                // This expectation is never fulfilled so should timeout
                XCTAssertThrowsError(try expect.valueWhenFulfilled(timeoutValue: .now() + 1.0))
            } catch {
                // Ignore
            }
            expectation.fulfill()
        }
        wait(for: [expectation], timeout: 2)
    }

This code produces the warning:

'catch' block is unreachable because no errors are thrown in 'do' block

The solution is obvious: "Remove the catch block"

    func testFutureValueWithTimeout() {
        let expectation = XCTestExpectation(description: "This should not fail")
        DispatchQueue.global().async {
            let expect = FutureResult<Int>()
            // This expectation is never fulfilled so should timeout
            XCTAssertThrowsError(try expect.valueWhenFulfilled(timeoutValue: .now() + 1.0))
            expectation.fulfill()
        }
        wait(for: [expectation], timeout: 2)
    }

which now fails with the message:

Invalid conversion from throwing function of type '() throws -> Void' to non-throwing function type '@convention(block) () -> Void'

Any suggestions?

Where does FutureResult come from?

This is a utility class around futures. I assumed it would not be relevant to explain this compiler warning.
The signature of the method valueWhenFulfilled is as follows:

public func valueWhenFulfilled(timeoutValue: DispatchTime) throws -> T

This method does throw in case of a timeout.

But the error is caught by XCTAssertThrowsError, so there's no need to surround it in a do-catch block. Just look at the function signature. It's not a throwing function:

func XCTAssertThrowsError<T>(
    _ expression: @autoclosure () throws -> T, 
    _ message: @autoclosure () -> String = "", 
    file: StaticString = #filePath, 
    line: UInt = #line, 
    _ errorHandler: (Error) -> Void = { _ in }
)

Well, it is. Show me the code for it.

I created a minimal version that still shows the compiler warning:

public enum FutureResultError: Error {
    case futureEndedInError
}


public class FutureResult<T> {

    public init() {
    }

    public func valueWhenFulfilled(timeoutValue: DispatchTime) throws -> T {
        throw(FutureResultError.futureEndedInError)
    }
}

With this code I get the same warning. Also the same error when I remove the catch block.

I do understand that XCTAssertThrowsError it is not supposed to throw an error. This is exactly why I am posting this question. It seems like the compiler is confused somehow.

Looks like a compiler bug. You should report it. Make sure the example you use is fully self-contained. For example:

struct SomeError: Error { }

func throwingFunction() throws {
    throw SomeError()
}

class TestCase: XCTestCase {

    func testMethod() {
        DispatchQueue.global().async {
            XCTAssertThrowsError(try throwingFunction())
        }
    }

}

Will do. It seem you already wrote the code that I need to submit :grinning:

Submitted: https://bugs.swift.org/browse/SR-14939

Terms of Service

Privacy Policy

Cookie Policy