Mark A. Donohoe
Mark A. Donohoe

Reputation: 30458

How is Array() ambiguous when the given argument is a known type?

I'm seeing something that doesn't make sense to me. Here's my code...

import Foundation

enum SeparatorPaddingLocation{
    case beforeSeparator
    case afterSeparator
}

struct SeparatorToken{
    let separator          : Character
    let formattedSeparator : String
    let paddingLocation    : SeparatorPaddingLocation
}

extension Array where Element == String {

    func aligned(separatorTokens:[SeparatorToken]) -> [String] {

        // This won't compile!
        let remainingSeparatorTokens = Array(separatorTokens.dropFirst())

        return self
    }
}

The 'let' causes this compile error even though dropFirst() returns an ArraySlice<SeparatorToken>.

Type of expression is ambiguous without more context

Why is this ambiguous? What am I missing?

I also tried fully qualifying it like so, but this didn't work either...

let remainingSeparatorTokens:[SeparatorToken] = Array(separatorTokens.dropFirst())

Upvotes: 1

Views: 308

Answers (3)

SaganRitual
SaganRitual

Reputation: 3203

This is not a bug, and there's nothing mysterious or bug-like going on.

Not a bug: it's the very reason the == operator is available in where clauses: to require Element to be of type String, as explained here. Search the page for "you can also write a generic where clauses that require Item to be a specific type".

Not bug-like: when you're inside the extension and call an Array initializer, the compiler takes your where clause to indicate what specialization of Array you want. Your where clause says String, so Array<String> it is. The compiler can't read your mind when you pass an ArraySlice<SeparatorToken> into an Array<String> initializer. Did you make a mistake, or did you really want an Array<ArraySlice<SeparatorToken>>? Hence the error message about ambiguity.

Here's a simplified and annotated version of OP's code, in case anyone finds it illuminating.

class Foo<T> {
    var theFoo: T!

    init() { theFoo = nil }
    init(_ aFoo: T) { theFoo = aFoo }
}

extension Foo where T == Int {
    func bar() {
        // This one fails, because our where clause
        // says this extension applies to Foo<Int>,
        // but now we're trying to assign it to a Foo<String>
        let aFoo: Foo<String> = Foo()

        // This one also fails, for the same reason.
        // It uses the other initializer, but we're
        // still inside the Foo<Int> extension.
        let bFoo: Foo<String> = Foo("bFoo")

        // This one works, because we're expressly
        // overriding the where clause
        let cFoo: Foo<String> = Foo<String>("cFoo")
    }
}

The ambiguity error makes sense. I told the compiler in my where clause to make this an Int extension, so it created Foo<Int>s. But then I tried to assign those to variables of type Foo<String>. The compiler couldn't tell whether I really wanted Foo<String> or I had made a mistake, so it gave up.

Upvotes: 1

Rob Napier
Rob Napier

Reputation: 299605

Inside of generic extensions, type parameters bind to their scope. I'm not sure this is really a good thing (or possibly if it's really a bug), but it shows up consistently, so the type parameter here implicitly wants to be String.

A simpler example:

let x = Array()  // error: generic parameter 'Element' could not be inferred

That makes sense. But put it in an extension:

extension Array {
    func f() {
        let x = Array()
    }
}

This compiles, and x is of type [Element].

In your case, you've set Element to String, so the call to Array.init wants to create a [String] and it's getting confused because SeparatorToken != String. You need to pass the type parameter explicitly (as you've discovered):

let remainingSeparatorTokens = Array<SeparatorToken>(separatorTokens.dropFirst())

Again, I don't really consider this a feature in Swift, and it may even be considered a bug. But it's very consistent.

Upvotes: 6

Sagar Chauhan
Sagar Chauhan

Reputation: 5823

I just run your code in playground and updated as follows, then error is gone:

import UIKit
import Foundation

enum SeparatorPaddingLocation{
    case beforeSeparator
    case afterSeparator
}

struct SeparatorToken{
    let separator          : Character
    let formattedSeparator : String
    let paddingLocation    : SeparatorPaddingLocation
}

extension Array where Element == String {

    func aligned(separatorTokens:[SeparatorToken]) -> [String] {

        // I update code here
        let remainingSeparatorTokens = Array<SeparatorToken>(separatorTokens.dropFirst())

        return self
    }
}

As per swift, Array required type when you going to initialise with Array.

I hope this will work for you.

For more details about array visit: https://developer.apple.com/documentation/swift/array

Upvotes: 1

Related Questions