Typed throw inference bug

I am seeing an error which does not seem to make sense. I even tried to explicitly add an annotation, but for some reason the compiler is inferring any Error

This is a problem, the types are required in embedded mode here — even ignoring that it's the standard library which should just include a typed initializer.

public extension Array {
    init<E: Error>(
        throwing: E.Type,
        unsafeUninitializedCapacity: Int,
        initializingWith initializer: (_ buffer: inout UnsafeMutableBufferPointer<Element>, _ initializedCount: inout Int) throws(E) -> Void
    ) throws(E) {
        var error: E?
        self.init(unsafeUninitializedCapacity: unsafeUninitializedCapacity) { buffer, initializedCount in
            do throws(E) {
                try initializer(&buffer, &initializedCount)
            } catch let e {
                error = e
            }
        }
        
        if let error { throw error }
    }
    
    init<E: Error>(count: Index, _ body: (_ index: Index) throws(E) -> Element) throws(E) {
        // Invalid conversion of thrown error type 'any Error' to 'E'
        try self.init(throwing: E.self, unsafeUninitializedCapacity: count) { buffer, initializedCount in
            for index in 0..<count {
                buffer[index] = try body(index)
            }
        }
    }
}

Is this expected? I find it odd, I did not experience such inference issues before and I did not change my toolchain as far as I know.

Adding explicit throws(E) to the closure should do the trick.

    init<E: Error>(count: Index, _ body: (_ index: Index) throws(E) -> Element) throws(E) {
        try self.init(throwing: E.self, unsafeUninitializedCapacity: count) { (buffer, initializedCount) throws(E) in
            for index in 0..<count {
                buffer[index] = try body(index)
            }
        }
    }
3 Likes

That's overkill when the metatype is supplied as a parameter—all the compiler needs is the hint that you're using a typed throw—not the specific type. So you can use a placeholder. (Also, E implicitly conforms to Error, so : Error is not needed either.)

init<E>(count: Index, _ body: (_ index: Index) throws(E) -> Element) throws(E) {
  try self.init(throwing: E.self, unsafeUninitializedCapacity: count) { 
    buffer, initializedCount throws(_) in // Note the underscore.

Unfortunately, there's no good solution to adding typed throws to code you don't control. The force cast is an easy fix, but you either need to supply that metatype argument, or you need to give the overload a different signature. And if you do the latter, then, you need to be explicit. :confounded_face:

init<E>(
  overloadNonsense: Void,
  unsafeUninitializedCapacity: Int,
  initializingWith initializer: (
    _ buffer: inout UnsafeMutableBufferPointer<Element>,
    _ initializedCount: inout Int
  ) throws(E) -> Void
) throws(E) {
  do {
    try self.init(
      unsafeUninitializedCapacity: unsafeUninitializedCapacity,
      initializingWith: initializer
    )
  } catch { throw error as! E }
}

init<E>(count: Index, _ body: (_ index: Index) throws(E) -> Element) throws(E) {
  try self.init(overloadNonsense: (), unsafeUninitializedCapacity: count) {
    buffer, initializedCount throws(E) in // No underscore possible.
1 Like