Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Array() coercion ambiguous without more context — but only in extension

Tags:

arrays

swift

It is legal to say this (arr is an Array):

let arrenum = Array(arr.enumerated())

So why isn't it legal to say this?

extension Array {
    func f() {
        let arrenum = Array(self.enumerated())
        // error: type of expression is ambiguous without more context
    }
}

EDIT It seems this is a workaround:

extension Array {
    func f() {
        typealias Tup = (offset:Index, element:Element)
        let arrenum = Array<Tup>(self.enumerated())
    }
}

But why is that needed? (And is it right?)

like image 886
matt Avatar asked Aug 09 '18 13:08

matt


1 Answers

This is a known bug (SR-1789). Swift currently has a feature where you can refer to a generic type within its own body without having to repeat its placeholder type(s) – the compiler will infer them for you to be the same as the type of self.

For example:

struct S<T> {
  func foo(_ other: S) { // parameter inferred to be `S<T>`.
    let x = S() // `x` inferred to be `S<T>`.
  }
}

extension S {
  func bar(_ other: S) {} // same in extensions too.
}

This is pretty convenient, but the bug you're running into is the fact that Swift will always make this inference, even if it's incorrect.

So, in your example:

extension Array {
    func f() {
        let arrenum = Array(self.enumerated())
        // error: type of expression is ambiguous without more context
    }
}

Swift interprets the code as let arrenum = Array<Element>(self.enumerated()), as you're in the body of Array<Element>. This is incorrect, because enumerated() yields a sequence of offset-element tuple pairs – Swift should have inferred Array to be Array<(offset: Int, element: Element)> instead.

One workaround, which you've already discovered, is to explicitly specify the placeholder type in order to prevent the compiler from making this incorrect inference.

extension Array {
  func f() {
    let arrenum = Array<(offset: Int, element: Element)>(self.enumerated())
  }
}

Another possible workaround appears to be using the fully-qualified type, for example:

extension Array {
  func f() {
    let arrenum = Swift.Array(self.enumerated())
  }
}

as it appears Swift doesn't do the same inference for fully-qualified types (I'm not sure if you should rely on this fact though).

Finally it's worth noting that instead of doing a call to Array's initialiser, you could use map(_:) instead to avoid the issue entirely:

extension Array {
  func f() {
    let arrenum = self.enumerated().map { $0 }
  }
}

which, like the initialiser call, will give you back an array of offset-element pairs.

like image 69
Hamish Avatar answered Oct 23 '22 03:10

Hamish