swiftoverridingswift-protocolsswift-extensions

How to override a protocol's default method behavior of a 3rd party library?


// 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 😣

enter image description here


Solution

  • 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.