Hello everyone,
While developing on an iOS codebase, I stumbled upon an issue I did not expect.
Premises:
In Swift, it shouldn't be possible to declare multiple variables with the same name and with different types. Overloading is something that only functions can do. In addition, when conforming to a protocol that requires a certain variable, if a struct/class/enum conforms to that protocol and declares the required variable with a different type, the compiler complains by saying that struct/class/enum does not conform to the protocol
Issue explanation:
It seems that the above premise is not always true, according to my observations. There is an edge case where It is possible to declare a variable multiple times with different types, and it is also possible to decide which implementation to use when we access that variable.
Steps to reproduce:
- declare protocol P which has required field F (only gettable)
- create extension to that protocol to provide default implementation for that field F
- declare struct S not conforming to the protocol yet, which implements a field with the same name of F but with a different type
- extend struct by conforming to protocol (can leave extension empty)
When accessing the variable, it will provide the value depending on the type we expect when accessing it. For example, if we want to access that variable in the context of a function's arguments or variable definition, where we expect a certain type, the variable definition corresponding to that type will be chosen
Example:
protocol TestProtocolString {
var requiredVariable: String { get }
}
extension TestProtocolString {
var requiredVariable: String { "Hello world" }
}
protocol TestProtocolBool {
var requiredVariable: Bool { get }
}
extension TestProtocolBool {
var requiredVariable: Bool { true }
}
struct TestStruct {
var requiredVariable: Int {
42
}
}
extension TestStruct: TestProtocolString, TestProtocolBool {
var getIntVariable: Int {
requiredVariable // interpreted as Int
}
var getStringVariable: String {
requiredVariable // interpreted as String
}
var getBoolVariable: Bool {
requiredVariable // interpreted as Bool
}
}
let s = TestStruct()
print(s.getIntVariable) // prints 42
print(s.getStringVariable) // prints "Hello World"
print(s.getBoolVariable) // prints true
Why is it a problem:
I consider this phenomenon an issue since it was unexpected to me and it caused a major bug in our codebase. This also happens when the type of a protocol P's required variable F is of type T, where T is a protocol too. Provided a default implementation for F of type T in an extension for P, if we later make a struct S conform to P, and declare a variable F' with the same name of F but with type T' which conforms to T, accessing F' will have two different results according to the type we are expecting:
- it will be F if we cast S as P
- it will be F' if we consider S as S
Final thoughts:
I rely quite heavily on compiler suggestions, when writing protocol implementations. I think that in this case, the compiler should have warned me that there was already a default implementation of a variable with the name of the one I am declaring somewhere else. Sure, I could have looked on the documentation or could have looked the protocol definition myself, but as a (lazy) developer, I trust the tools I use, at least for language specifications.
This issue did not raise any warning at compile time and run time, so it went unnoticed and caused a major bug where data where inconsistent, and it went latent for several weeks.
Am I missing some key points? Is this how it is intended to be?
I would appreciate your consideration and opinion.
Thanks to all