I use Decodable to decode a simple struct from JSON. This works by conforming to a Decodable
protocol:
extension BackendServerID: Decodable {
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 switch
ing on it to determine the dynamic type.
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.
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.
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:)
.
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
.