Why doesn’t Swift call my overloaded method with a

2019-02-10 11:56发布

问题:

I use Decodable to decode a simple struct from JSON. This works by conforming to a Decodable protocol:

extension BackendServerID: Decodable {

    static func decode(_ json: Any) throws -> BackendServerID {
        return try BackendServerID(
            id: json => "id",
            name: json => "name"
        )
    }
}

I’d like to be able to call decode with a String, though, so I have added an extension:

extension Decodable {

    static func decode(_ string: String) throws -> Self {
        let jsonData = string.data(using: .utf8)!
        let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
        return try decode(jsonObject)
    }
}

Then I would like to decode the objects like this:

XCTAssertNoThrow(try BackendServerID.decode("{\"id\": \"foo\", \"name\": \"bar\"}"))

This doesn’t work as expected, though, since somehow the decode(Any) method gets called instead of decode(String). What am I doing wrong? (When I clarify the call by renaming my custom method to decodeString, it works correctly.)

回答1:

I would agree that this behaviour is surprising, and you may well want to file a bug over it.

From quickly looking through the source of CSRanking.cpp, which is the part of type checker implementation that deals with the "rankings" for different declarations when it comes to overload resolution – we can see that in the implementation of:

/// \brief Determine whether the first declaration is as "specialized" as
/// the second declaration.
///
/// "Specialized" is essentially a form of subtyping, defined below.
static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
                                  ValueDecl *decl1, ValueDecl *decl2) {

The type checker considers an overload in a concrete type to be more "specialised" than an overload in a protocol extension (source):

  // Members of protocol extensions have special overloading rules.
  ProtocolDecl *inProtocolExtension1 = outerDC1
                                         ->getAsProtocolExtensionContext();
  ProtocolDecl *inProtocolExtension2 = outerDC2
                                         ->getAsProtocolExtensionContext();
  if (inProtocolExtension1 && inProtocolExtension2) {
    // Both members are in protocol extensions.
    // Determine whether the 'Self' type from the first protocol extension
    // satisfies all of the requirements of the second protocol extension.
    bool better1 = isProtocolExtensionAsSpecializedAs(tc, outerDC1, outerDC2);
    bool better2 = isProtocolExtensionAsSpecializedAs(tc, outerDC2, outerDC1);
    if (better1 != better2) {
      return better1;
    }
  } else if (inProtocolExtension1 || inProtocolExtension2) {
    // One member is in a protocol extension, the other is in a concrete type.
    // Prefer the member in the concrete type.
    return inProtocolExtension2;
  }

And when performing overload resolution, the type checker will keep track of a "score" for each potential overload, picking the one with the highest. When a given overload is considered more "specialised" than another, its score will be incremented, therefore meaning that it will be favoured. There are other factors that can affect an overload's score, but isDeclAsSpecializedAs appears to be the deciding factor in this particular case.

So, if we consider a minimal example, similar to the one @Sulthan gives:

protocol Decodable {
    static func decode(_ json: Any) throws -> Self
}

struct BackendServerID {}

extension Decodable {
    static func decode(_ string: String) throws -> Self {
        return try decode(string as Any)
    }
}

extension BackendServerID : Decodable {
    static func decode(_ json: Any) throws -> BackendServerID {
        return BackendServerID()
    }
}

let str = try BackendServerID.decode("foo")

When calling BackendServerID.decode("foo"), the overload in the concrete type BackendServerID is preferred to the overload in the protocol extension (the fact that the BackendServerID overload is in an extension of the concrete type doesn't make a difference here). In this case, this is regardless of whether one is more specialised when it comes to the function signature itself. The location matters more.

(Although the function signature does matter if generics are involved – see tangent below)

It's worth noting that in this case we can force Swift to use the overload we want by casting the method at the call:

let str = try (BackendServerID.decode as (String) throws -> BackendServerID)("foo")

This will now call the overload in the protocol extension.

If the overloads were both defined in BackendServerID:

extension BackendServerID : Decodable {
    static func decode(_ json: Any) throws -> BackendServerID {
        return BackendServerID()
    }

    static func decode(_ string: String) throws -> BackendServerID {
        return try decode(string as Any)
    }
}

let str = try BackendServerID.decode("foo")

The the above condition in the type checker implementation won't be triggered, as neither are in a protocol extension – therefore when it comes to overload resolution, the more "specialised" overload will be solely based on signatures. Therefore the String overload will be called for a String argument.


(Slight tangent regarding generic overloads...)

It's worth noting that there are (lots of) other rules in the type checker for whether one overload is considered more "specialised" than another. One of these is preferring non-generic overloads to generic overloads (source):

  // A non-generic declaration is more specialized than a generic declaration.
  if (auto func1 = dyn_cast<AbstractFunctionDecl>(decl1)) {
    auto func2 = cast<AbstractFunctionDecl>(decl2);
    if (func1->isGeneric() != func2->isGeneric())
      return func2->isGeneric();
  }

This condition is implemented higher up than the protocol extension condition – therefore if you were to change the decode(_:) requirement in the protocol such that it used a generic placeholder:

protocol Decodable {
    static func decode<T>(_ json: T) throws -> Self
}

struct BackendServerID {}

extension Decodable {
    static func decode(_ string: String) throws -> Self {
        return try decode(string as Any)
    }
}

extension BackendServerID : Decodable {
    static func decode<T>(_ json: T) throws -> BackendServerID {
        return BackendServerID()
    }
}

let str = try BackendServerID.decode("foo")

The String overload will now be called instead of the generic one, despite being in a protocol extension.


So really, as you can see, there are lots of complicated factors that determine which overload to call. Really the best solution in this case, as others have already said, is to explicitly disambiguate the overloads by giving your String overload an argument label:

extension Decodable {
    static func decode(jsonString: String) throws -> Self {
        // ...
    }
}

// ...

let str = try BackendServerID.decode(jsonString: "{\"id\": \"foo\", \"name\": \"bar\"}")

Not only does this clear up the overload resolution, it also makes the API clearer. With just decode("someString"), it wasn't clear exactly what format the string should be in (XML? CSV?). Now it's perfectly clear that it expects a JSON string.



回答2:

Let's consider the minimal example:

protocol Decodable {
    static func decode(_ json: Any) throws -> Self
}

struct BackendServerID {
}

extension Decodable {
    static func decode(_ string: String) throws -> Self {
        return try decode(string)
    }
}

extension BackendServerID : Decodable {
    static func decode(_ json: Any) throws -> BackendServerID {
        return BackendServerID()
    }
}

The implementation of decode in BackendServerId replaces the default implementation of Decodable.decode (the parameters are covariant, similar case as overriding). Your use case would work only if both functions were declared on the same level, e.g.:

extension BackendServerID : Decodable {
    static func decode(_ json: Any) throws -> BackendServerID {
        return BackendServerID()
    }

    static func decode(_ string: String) throws -> Self {
        return try decode(string as Any)
    }
}

Also note the as Any which is necessary to prevent recursion.

To prevent confusion, you should name functions that accept string and Any differently, e.g decode(string:) and decode(json:).



回答3:

I think you should override decode(Any) or you can do something like that

extension Decodable {

    static func decode(String string: String) throws -> Self {
        let jsonData = string.data(using: .utf8)!
        let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
        return try decode(jsonObject)
    }
}

Here your define new method decode(String string: String) so decode(Any) method will not called.



回答4:

Swift is supposed to call the most specific implementation, you can try that in a playground to confirm; so your expectation is correct.

In your case, I suspect that the issue lies in access control levels.

In this Decodable library, method func decode(_ json: Any) is declared as public, thus it's available in your tests code.

On the other hand, your own method func decode(_ string: String) doesn't seem to be public, and then is internal by default, and not accessible in your tests code.

To fix that, either import your app's framework using @testable (which makes all internal symbols available), or declare the method public.



回答5:

It seems you are adding your functions to two different "things", the first function is added to BackendServerID and returns a BackendServerID, the second function is added to the Decodable protocol and returns a Decodable. The following will work in a Playground:

protocol Decodable {
    static func decode(_ json: Any)
}

extension Decodable {
    static func decode(_ json: String) {
        print("Hi, I am String-Json: ", json)
    }

    static func decode(_ json: Int8) {
        print("Hi, I am Int8-Json: ", json)
    }

    static func decode(_ json: Any) {
        print("Hi, I am Any-Json: ", "(I do not know how to print whatever you gave me)")
    }
}

extension Decodable {
    static func decode(_ json: Int) {
        print("Hi, I am Int-Json: ", json)
    }
}

class JSONParser : Decodable {
}

let five : Int8 = 5
JSONParser.decode(Int(five))
JSONParser.decode(five)
JSONParser.decode("five")
JSONParser.decode(5.0)

It will print

Hi, I am Int-Json:  5
Hi, I am Int8-Json:  5
Hi, I am String-Json:  five
Hi, I am Any-Json:  (I do not know how to print whatever you gave me)

and I think that should be what you expect.

However, your two static functions do not have exactly the same signatures and even if they have they are not considered to "overload" the same function. Paraphrasing @Sulthan slightly I tried

protocol Decodable {
    static func decode(_ json: Any) throws -> Self
}

struct BackendServerID {
}

extension Decodable {
    static func decode(_ string: String) throws -> BackendServerID {
        print("decoding as String: ", string)
        return BackendServerID()
    }
}

extension BackendServerID : Decodable {
    static func decode(_ json: Any) throws -> BackendServerID {
        print("decoding as Any: ", "(no idea what I can do with this)")
        return BackendServerID()
    }
}

try BackendServerID.decode("hello")

and I got

decoding as Any:  (no idea what I can do with this)

(as you would probably have expected by now). The Decodable function is "shadowed" and static functions are not accessible through their protocol type, but if I rename it as

extension Decodable {
    static func decodeS(_ string: String) throws -> BackendServerID {
        print("decoding as String: ", string)
        return BackendServerID()
    }
}

I can do

try BackendServerID.decode("hello")
try BackendServerID.decodeS("hello")

and get the expected result

decoding as Any:  (no idea what I can do with this)
decoding as String:  hello

On the other hand you can do

extension BackendServerID : Decodable {
    static func decode(_ json: Any) throws -> BackendServerID {
        print("decoding as Any: ", "(no idea what I can do with this)")
        return BackendServerID()
    }
}

extension BackendServerID {
    static func decode(_ string: String) throws -> BackendServerID {
        print("decoding as String: ", string)
        return BackendServerID()
    }
}

try BackendServerID.decode("hello")
try BackendServerID.decode(5)

and get

decoding as String:  hello
decoding as Any:  (no idea what I can do with this)

with an overloaded function (but it won't accept another : Decodable on the second extension). However, extensions of concrete types and of protocols do not mix which is very probably a good thing (TM).

Btw: I tried to coax it, but while

extension Decodable {
    static func decode(_ string: String) throws -> Self {
        print("decoding as String: ", string)
        return try BackendServerID.decode(string as Any) as! Self
    }
}

try BackendServerID.decode("hello")
try BackendServerID.decode(5)

would compile it only returned

decoding as Any:  (no idea what I can do with this)
decoding as Any:  (no idea what I can do with this)

since the String version on Decodable remained buried. But in any case it is nice to see how flexible Swift can switch on parameter types.

However you will probably be disappointed by

let five : Any = "five"
try BackendServerID.decode(five)

printing

decoding as Any:  (no idea what I can do with this)

so your whole dispatching is happening in a static way. If you are given an Any there seems to be no way to avoid switching on it to determine the dynamic type.