This function should throw only ThrowsError type, however, when used, compiler says Thrown expression type 'any Error' cannot be converted to error type 'XXX'
You are not at the point. The generic function is declared (and compiled) as throwing ThrowsError, however when used, compiler ignores the exception type.
Yup, you're right—Task does not currently throw CancellationError on its own and I was misremembering. Need more coffee!
I believe @hibernat is not explicitly writing throws inside the body of the closure, in which case in Swift 6 mode the compiler does not infer an error type for the closure (beyond any Error). This is by design. See the relevant section in SE-0413.
Thanks, that is great response. However, why is this function accepted by the compiler? There is no inference needed, the exception type is declared, required is the same exception type for the perform throwing function/closure and the exception thrown by the function.
From what I see locally, this fails to compile in a way I'd expect:
func f() async throws(E) {
do throws(E) {
try await withSingleTaskInThrowingTaskGroup(cancellationError: E()) {
// ^ ^ ^ Thrown expression type 'any Error' cannot be converted to error type 'E'
try await g()
}
} catch {
}
}
This is because you didn't explicitly write () throws(E) in in the body of the closure you passed to withSingleTaskInThrowingTaskGroup(), which causes it to infer the type of the closure as throwing any Error, which is then incompatible with the expected type E (which is inferred from the explicit E() you passed as an argument to withSingleTaskInThrowingTaskGroup().)
If I add () throws(E) in, it compiles as expected:
Ran into this issue and though I understand the closure signature requirement, it still feels like a bug.
Case in point, consider the following:
struct FooError: Error { }
func a(_ block: () throws(FooError) -> Void) { }
func b(_ block: @autoclosure () throws(FooError) -> Void) { }
func c() throws(FooError) { }
a { try b() } // ⚠️ compiler error
a { () throws(FooError) -> Void in try c() } // ✅ works
a { () throws(_) -> Void in try c() } // ✅ works
b(try c()) // ✅ works
TL;DR - making the closure an @autoclosure produces the desired outcome of the error type being inferred (func b). The expectation is that func a would have the same inference behavior but it does not, as already discussed.