Why doesn’t `ExpressibleByDictionaryLiteral` work without an explicit `as` coercion?

i have a type BSON.Fields, which is ExpressibleByDictionaryLiteral:

extension BSON.Fields:ExpressibleByDictionaryLiteral
{
    @inlinable public
    init(dictionaryLiteral:(String, BSON.Value<[UInt8]>)...)
    {
        self.init(dictionaryLiteral)
    }
}

it also conforms to a protocol BSONEncodable:

extension BSON.Fields:BSONEncodable
{
    ...
}

finally, it has a subscript that can be assigned to with some optional BSONEncodable value:

extension BSON.Fields
{
    @inlinable public
    subscript<Encodable>(key:String) -> Encodable?
        where Encodable:BSONEncodable
    {
        get
        {
            nil
        }
        set(value)
        {
            ...
        }
    }
}

based on this, i would expect it to be possible to assign to this subscript with a dictionary literal, which should generate a nested instance of BSON.Fields.

this works:

outer["a"] = ["a": 1, "b": 2, "c": 3] as BSON.Fields

but this doesn’t:

outer["b"] = ["a": 1, "b": 2, "c": 3]
//           ^~~~~~~~~~~~~~~~~~~~~~~~
// error: cannot assign value of type '[String : Int]' to subscript
// of type 'Encodable'

the fixit is quite perplexing:

Tests/BSONEncoding/Main.swift:44:21:
error: generic parameter 'Encodable' could not be inferred
                outer["b"] = ["a": 1, "b": 2, "c": 3]
                ^
Sources/BSONEncoding/Encoding/BSON.Fields.swift:12:5:
note: in call to 'subscript(_:)'
    subscript<Encodable>(key:String) -> Encodable? 
        where Encodable:BSONEncodable
    ^
Tests/BSONEncoding/Main.swift:44:31:
error: cannot assign value of type '[String : Int]' to subscript
of type 'Encodable'
                outer["b"] = ["a": 1, "b": 2, "c": 3]
                             ^~~~~~~~~~~~~~~~~~~~~~~~
                                                      as! Encodable

because the type is not Encodable, it is some BSONEncodable and Encodable is just the name of the generic parameter, and force-casting with as! Encodable will not help.

curiously, array literals do not have this problem:

outer["c"] = [1, 2, 3]

because Array is BSONEncodable:

extension Array:BSONEncodable where Element:BSONEncodable
{
    ...
}

and Array can come from an array literal without an as coercion.

What if there were two types that were BSONEncodable? How would the compiler pick?

The compiler has a special case to try Array and Dictionary in the absence of other information for array and dictionary literals, respectively, but other than that it won't go searching for every type it knows in order to find a possible match.

EDIT: You could help guide the compiler here by adding a second subscript that takes BSON.Fields directly, at the cost of "now you have overloads".

4 Likes

im confused as to why the compiler treats these cases differently, the only difference seems to be the type context changed from some BSONEncodable to BSON.Fields. so it would not have to search through every ExpressibleByDictionaryLiteral type or every BSONEncodable type, it would just have to search through the set intersection of them, which right now, i only have one of them.

The compiler does not keep a list of “every type in the program”, or even “every type statically known to conform to a particular protocol”. It’s not impossible—it already has to keep track of “every extension for a particular type”—but it’s not part of the design today, and that kind of exhaustive search is the wrong direction to go in for a fast type-checker. (Imagine if this was part of a larger expression.)

10 Likes