// MARK: - Third Party Library
public protocol TestProtocol: ExpressibleByStringLiteral {
init(str: String)
}
extension TestProtocol {
public init(stringLiteral value: String) {
self.init(str: value)
}
}
public struct Test {
public let str: String
public init(str: String) {
self.str = str
}
}
extension Test: TestProtocol {}
// MARK: - My Swift Package
extension Test {
public init(stringLiteral value: String) {
self.init(str: "Roman")
}
}
let test: Test = "Test"
print(test.str)
This is playground-friendly code snippet and it works as expected. I'm overriding the initializer so that the code always prints "Roman" no matter what we're really assigning.
But as soon as I move "Third Party Library" code into playground's "Sources" folder (to imitate the usage of third party library) the behavior changes: my overrided method is just ignored 😣
This is not possible. The two modules are compiled separately, and when
extension Test: TestProtocol {}
is compiled, the compiler needs to generate the protocol witness table of Test
for the conformance of TestProtocol
. This table basically stores which thing in Test
implements which requirement in TestProtocol
. Callers that don't know the concrete type of something of type TestProtocol
would use this table to find the actual implementation they should call, e.g.
func f<T: TestProtocol>(_ string: TestProtocol.StringLiteralType) -> T {
// compiler would generate code here that looks up the protocol witness table of T
// and finds the correct implementation of init to call
T.init(stringLiteral: string)
}
Although the compiler could just transform let test: Test = "Test"
to let test: Test = Test(stringLiteral: "Test")
(which would have called the init
in the Test
extension), it is instead implemented to call the protocol witness of ExpressibleByStringLiteral.init(stringLiteral:)
.
When generating the protocol witness table, the compiler obviously will not see the one you declared in extension Test
, because the files are compiled separately.
Of course, if the compiler had seen the one declared in extension Test
, it would have chosen that one instead of the default implementation.
So nothing outside the library can change what let test: Test = "Test"
does.