Variable overloading with different types (not expected)

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

I don’t think it’s overloading the variable but shadowing it in the conformance. As only the shadowed var has a matching type it’s able to use it instead of the field in the struct.

The casting results you see are due to dynamic dispatch

Still I don’t get how the language allows to have multiple variables with the same name and different types. If it was a desired feature, why wouldn’t it allow to do it in the single block of a struct definition?

Regarding dynamic dispatch, isn’t that only working with classes? Here we have structs and protocols

Well shadowing can be convenient, just like in

var someVar: SomeType? = …
if let someVar {
// here someVar identifier is shadowing the var above and use a different type (non optional SomeType)
}

I agree the behavior in you case can be surprising.

Also, protocol methods are called with dynamic dispatch wether they are from a class or not.

2 Likes