Initialization error when forwarding the initializers required by protocol ExpressibleByArrayLiteral or ExpressibleByDictionaryLiteral

I am exploring the possibility to have a protocol that can be initialized with basic type literals if its associatedtype is expressible by the required type.

public protocol WrappedValueLiteralExpressible {
    associatedtype WrappedValue
    var value: WrappedValue { get }
    init(value: WrappedValue)
}

For ExpressibleByBooleanLiteral, ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByUnicodeScalarLiteral, ExpressibleByExtendedGraphemeClusterLiteral, ExpressibleByStringLiteral, the following extensions work fine:

extension WrappedValueLiteralExpressible where WrappedValue: ExpressibleByBooleanLiteral {
    public init(booleanLiteral value: WrappedValue.BooleanLiteralType) {
        self.init(value: WrappedValue(booleanLiteral: value))
    }
}

extension WrappedValueLiteralExpressible where WrappedValue: ExpressibleByIntegerLiteral {
    public init(integerLiteral value: WrappedValue.IntegerLiteralType) {
        self.init(value: WrappedValue(integerLiteral: value))
    }
}

extension WrappedValueLiteralExpressible where WrappedValue: ExpressibleByFloatLiteral {
    public init(floatLiteral value: WrappedValue.FloatLiteralType) {
        self.init(value: WrappedValue(floatLiteral: value))
    }
}

extension WrappedValueLiteralExpressible where WrappedValue: ExpressibleByUnicodeScalarLiteral {
    public init(unicodeScalarLiteral value: WrappedValue.UnicodeScalarLiteralType) {
        self.init(value: WrappedValue(unicodeScalarLiteral: value))
    }
}

extension WrappedValueLiteralExpressible where WrappedValue: ExpressibleByExtendedGraphemeClusterLiteral {
    public init(extendedGraphemeClusterLiteral value: WrappedValue.ExtendedGraphemeClusterLiteralType) {
        self.init(value: WrappedValue(extendedGraphemeClusterLiteral: value))
    }
}

extension WrappedValueLiteralExpressible where WrappedValue: ExpressibleByStringLiteral {
    public init(stringLiteral value: WrappedValue.StringLiteralType) {
        self.init(value: WrappedValue(stringLiteral: value))
    }
}

However, for ExpressibleByArrayLiteral and ExpressibleByDictionaryLiteral, a compiling error

Non-nominal type 'Self.WrappedValue' does not support explicit initialization

will prompt with following extension:

extension WrappedValueLiteralExpressible where WrappedValue: ExpressibleByArrayLiteral {
    public init(arrayLiteral elements: WrappedValue.ArrayLiteralElement...) {
        self.init(value: WrappedValue(arrayLiteral: elements))
    }
}

extension WrappedValueLiteralExpressible where WrappedValue: ExpressibleByDictionaryLiteral {
    public init(arrayLiteral elements: (WrappedValue.Key, WrappedValue.Value)...) {
        self.init(value: WrappedValue(dictionaryLiteral: elements))
    }
}

This is where I got confused. If WrappedValue conforms to ExpressibleByArrayLiteral, then the required initializer (init(arrayLiteral elements: Self.ArrayLiteralElement...)) must be sufficient to initialize an instance of type WrappedValue, and that instance is all it needs to initialize an instance of any type that conforms to protocol WrappedValueLiteralExpressible.

So why is there that error, and how does error get generated?

The diagnostic is a bit better on master (thanks to @owenv):

error: cannot pass array of type '[Self.WrappedValue.ArrayLiteralElement]' as variadic arguments of type 'Self.WrappedValue.ArrayLiteralElement'

1 Like

This is the well-known variadic forwarding issue. A minimal example of your situation is:

struct Foo<T> {
  var base: T
}

extension Foo: ExpressibleByArrayLiteral where T: ExpressibleByArrayLiteral {
  init(arrayLiteral elements: T.ArrayLiteralElement...) {
    // Cannot invoke 'init' with an argument list of type '(arrayLiteral: [T.ArrayLiteralElement])'
    base = T.init(arrayLiteral: elements)
  }
}

Here are some previous threads about the topic:

[Idea] Passing an Array to Variadic Functions (55 comments, April 2016)
[Discussion] Variadics as an Attribute (7 comments, February 2017)
Explicit array splat for variadic functions (29 comments, March 2018)
Variadic Parameters that Accept Array Inputs (52 comments, May 2018)
Another attempt at passing arrays as varargs (with implementation) (126 comments, July 2019)
Yet another varargs “splat” proposal (9 comments, September 2019)

2 Likes

This is interesting, so the literal parameter elements actually gets converted to an Array before initialization.

I was wondering if the following change makes more sense to ExpressibleByArrayLiteral.

public protocol ExpressibleByArrayLiteral {
    associatedtype ArrayLiteralElement
    init(array: [ArrayLiteralElement])
}

public extension ExpressibleByArrayLiteral {
    init(arrayLiteral elements: ArrayLiteralElement...) {
        self.init(array: elements)
    }
}

So that any type conforms to ExpressibleByArrayLiteral only needs to implement init(array: [ArrayLiteralElement]), and get the literal initialization for free.

Normally, to add the new protocol requirement init(array: [ArrayLiteralElement]) in an ABI stable way, you would need to provide a default implementation. Because the compiler has special knowledge of ExpressibleByArrayLiteral and due to some implementation details of variadic arguments, it might be viable in this case though.

That said, I think it would be better to spend time on a more general-purpose solution to varargs forwarding, instead of just working around it in this one case. The feature is 90% implemented, I just don't think it's likely to move forward until the details of tuple splatting have been sorted out.

1 Like

Yes, I agree. Have been reading through the threads @Nevin posted, a lot of good stuff there.

Thanks for these.