`ExpressibleByXxxLiteral` gotcha

I'd be using ExpressibleByIntegerLiteral as an example but this gotcha applies to other literal types as well. Consider the following example:

struct Foo {
    var value: Int
    init(_ value: Int) {
        self.value = value
        print("good old init of mine")
    }
}

extension Foo: ExpressibleByIntegerLiteral {
    init(integerLiteral value: Int) {
        print("init with integerLiteral")
        self.value = value
    }
}

let variableNotLiteral = 123

var foo1: Foo = 123                      // init with integerLiteral
var foo2: Foo = Foo(integerLiteral: 123) // init with integerLiteral
var foo3: Foo = Foo(integerLiteral: variableNotLiteral) // init with integerLiteral
var foo4: Foo = Foo(123)                // init with integerLiteral
var foo5: Foo = .init(123)              // good old init of mine
var foo6: Foo = Foo(123 as Int)         // good old init of mine
var foo7: Foo = Foo(Int(123))           // good old init of mine
var foo8: Foo = Foo(variableNotLiteral) // good old init of mine

Everything here is good and unsurprising except for the foo4 :

  • the expected "good old init of mine" initialiser is not called.
  • I don't see this behaviour documented in ExpressibleByIntegerLiteral so I am unaware of it.
  • There was no ambiguity warning or error during compilation to bring my attention to this issue.

You'd think changing from Foo(123) to .init(123) or vice versa is benign... yet here it's an observable behavioural change. Beware!

This is SE-0213.

Yes, this part is surprising to me as well.

2 Likes