How to explain this behaviour of overloaded generic functions?

How come this code:

protocol SomeProtocol {}
extension Int: SomeProtocol {}

func testA<T>(v: T) {
    let protocolConformanceSuffix = v is SomeProtocol ? "yet v conforms to SomeProtocol!?" : "since v doesn't conform to SomeProtocol."
    print("  testA: called version WITHOUT SomeProtocol constraint \(protocolConformanceSuffix)")
}
func testA<T: SomeProtocol>(v: T) {
    print("  testA: called version WITH SomeProtocol constraint.")
}
func testB<T>(v: T) {
    if v is SomeProtocol { print("  testB: v conforms to SomeProtocol, now calling testA ...") }
    testA(v)
}

print("\nCalling testA(Float(1.2)):")
testA(Float(1.2))

print("\nCalling testA(1234):")
testA(1234)

print("\nCalling testB(1234):")
testB(1234)


produces the following output:

Calling testA(Float(1.2)):
  testA: called version WITHOUT SomeProtocol constraint since v doesn't conform to SomeProtocol.

Calling testA(1234):
  testA: called version WITH SomeProtocol constraint.

Calling testB(1234):
  testB: v conforms to SomeProtocol, now calling testA ...
  testA: called version WITHOUT SomeProtocol constraint yet v conforms to SomeProtocol!?

?


I would have expected calling testA through testB would be no different than calling testA directly, thus I'm surprised by the result of the last test, callling testB(1234).


Can someone please explain why testB(1234) results in a call to testA<T> rather than testA<T: SomeProtocol> even though the argument (1234) evidently conforms to SomeProtocol? The function is even able to say so itself.


(It's as if testB somehow removes the SomeProtocol conformance from v, even though it doesn't ... or it does it only statically and then it can be recovered/seen again at runtime ... but why should any of this need to be dynamic ... I'm not getting this.)


(Xcode7 beta 2)

In the testB() function, the v parameter will always be considered to be of the type explictly stated in the function declaration, regardless of what it is at runtime. The compiler is deciding which version of testA() to call, and all it knows about v is that it is type Any.

Except the following version of testB() doesn't call the <T: SomeProtocol> version of the the function either...


func testB<T>(v: T) {
    if let x: SomeProtocol = v as? SomeProtocol {
        print("  testB: v conforms to SomeProtocol, now calling testA ...")
        testA(x)
    }
    else {testA(v)}
}



Could you just write the second version of testA() as

func testA(v: SomeProtocol) {
    print("  testA: called version WITH SomeProtocol constraint.")
}

or does that not work with the actual non-example thing you are trying to accomplish?

LCS wrote:

In the testB() function, the v parameter will always be considered to be of the type explictly stated in the function declaration, regardless of what it is at runtime. The compiler is deciding which version of testA() to call, and all it knows about v is that it is type Any.


Shouldn't it be "all it knows about v and T and thus (v: T) for some particular call of testB<T>(v: T) is that the type of v is that of T for that particular call"?

So for testB(1234) it would be able to know that v is an Int (rather than just Any), isn't that the point of using generics here?

Or are you are saying this:

func testB<T>(v: T) { testA(v) }

is exactly the same as:

func testB(v: Any) { testA(v) }

?

I thought we could think of the top version (the generic one) as letting the compiler propagate what ever type T happens to be at some call site to its testA(v) call, as if the compiler wrote us a new version of testB for each and every argument type T that we happen to use when calling testB. So effectively, according to my expectations, the compiler could just optimize away testB (if written as the top version here) and call testA directly, but as my original example shows, this is not the way it is, and I don't know if that is a bug or if I'm not understanding Swift's generics (parameterized polymorphism) correctly.



LCS wrote:

Except the following version of testB() doesn't call the <T: SomeProtocol> version of the the function either...

  1. func testB<T>(v: T) {
  2. if let x: SomeProtocol = v as? SomeProtocol {
  3. print(" testB: v conforms to SomeProtocol, now calling testA ...")
  4. testA(x)
  5. }
  6. else {testA(v)}
  7. }


Exactly! Now you see the point of my question / what confuses me. I just want to know if there is a compiler bug behind this behaviour, and if it's not a bug, I'd like to update my understanding of Swift generics so as to not be confused by this behaviour.

In my original example I think the compiler should be able to work everything out statically, and I can't understand why testB<T>(v: T) { testA(v) } when called like this: testB(1234) would not produce the same results as when making that same call to testA directly.



LCS wrote:

Could you just write the second version of testA() as

  1. func testA(v: SomeProtocol) {
  2. print(" testA: called version WITH SomeProtocol constraint.")
  3. }

or does that not work with the actual non-example thing you are trying to accomplish?


No, that would not be the same. I don't want anything to be done dynamically, and as I said, I think the compiler should be able to resolve everything in my original example statically, although for some reason it doesn't seem able to do that when routing the call to testA through testB, this is what surprises me. Note that my only goal here is to have a clear understanding of Swift's generics, and the sole pupose of the example code in my original post is to highlight some (to me) surprising behaviour.

Please reread my original post, and try to make sense of (especially the last line of) the program's output. The only explanation I can think of is that the compiler is not able to resolve everything in that example program at compile time, but I can't see any reason for why it shouldn't be able to do just that. Or, it's like the compiler sometimes forgets that Int conforms to SomeProtocol, more specifically it seems to forget it when deciding what version of TestA to call from within the scope of the testB<Int> function, but it does not forget it when deciding what version of testA to call from the global scope.

Yeah, this is the thing I hate the most about Swift. Overloaded functions are statically picked at compile-time based on how you declared the type, not at runtime depending on the data.


C# has the dynamic keyword which does this.

You're right that I probably wasn't thinking about generic functions the right way when I was writing my first post.

I also could have written what I meant more clearly too. But I blame Xcode for enabling my confirmation bias 🙂


I had tried three tests:

let outsideFunc: (Int)->() = testA
print("\nCalling outsideFunc(1234):")
outsideFunc(1234)

//Calling outsideFunc(1234):
//testA: called version WITH SomeProtocol constraint.


func getAnyTFunc<T>(v: T) -> (T)->()
{
    let temp: (T)->() = testA
    return temp
}
let insideAnyTFunc: (Int)->() = getAnyTFunc(1234)
print("\nCalling insideAnyTFunc(1234):")
insideAnyTFunc(1234)

//Calling insideAnyTFunc(1234):
//testA: called version WITHOUT SomeProtocol constraint yet v conforms to SomeProtocol!?


func getRestrictedFunc<T: SomeProtocol>(v: T) -> (T)->()
{
    let temp: (T)->() = testA
    return temp
}
let insideRestrictedFunc: (Int)->() = getRestrictedFunc(1234)
print("\nCalling insideRestrictedFunc(1234):")
insideRestrictedFunc(1234)

//Calling insideRestrictedFunc(1234):
//testA: called version WITH SomeProtocol constraint.



After messing around a bit more, the bug seems to be related to how the compiler is interpreting the type.


let xAsProtocolType: SomeProtocol = 1234
testA(xAsProtocolType)
// testA: called version WITHOUT SomeProtocol constraint yet v conforms to SomeProtocol!?

let xAsIntType: Int = 1234
testA(xAsIntType)
// testA: called version WITH SomeProtocol constraint.


If it sees the explicit type of the parameter as its actual Type which conforms to SomeProtocol, it calls the correct T:SomeProtocol version.

But if it sees the explicit type of the parameter as the protocol-based Type, it incorrectly calls the unrestricted T:Any version instead.

Funny how opinions vary: for me it is one of Swift strong points.

For any non-@objc protocol type Foo, Swift does not allow you to pass an value of the existential type Foo (i.e. a value typed as Foo, as opposed to a value whose type is bound by Foo) to a function that expects a generic type T bound by Foo.


protocol Foo {
    func foo() -> String
}


extension Int: Foo {
    func foo() -> String {
        return String(self)
    }
}


func doFoo<T: Foo>(x: T) {
    print(x.foo())
}


doFoo(1234) // this works
doFoo(1234 as Foo) // error: generic parameter 'T' is constrained to a non-@objc protocol type 'Foo' and cannot be bound to an existential type


This is why Swift is resolving the overloads the way it does.

Magicman wrote:

Yeah, this is the thing I hate the most about Swift. Overloaded functions are statically picked at compile-time based on how you declared the type, not at runtime depending on the data.


I think you must have read your hate into my question, because what you say you hate is not at all the same thing that I am asking about / surprised by, it seems to be the opposite in fact.


I totally expect and want the function calls, types and protocol conformances etc of my example program in the original post to be resolved statically (at compile time). I'm just surprised by the fact that the compiler doesn't seem to do that the way I expected (again, at compile time).


This thread is not about any perceived limitations when it comes to doing dynamic / runtime stuff a la eg Objective C in Swift. I love the approach Swift takes in making the compiler know and do as much work as possible at compile time, it's one of its defining features and what will enable it to become a really great and fast systems and general purpose programming (as well as being nice to program in). I suppose you either already can or eventually will be able to do anything dynamic at runtime that you can do in eg Objective C in Swift. But all this is a totally different topic.

Please bring any further debate about this to another thread as it has nothing to do with the topic of this thread.

LCS wrote:

After messing around a bit more, the bug seems to be related to how the compiler is interpreting the type. /.../ If it sees the explicit type of the parameter as its actual Type which conforms to SomeProtocol, it calls the correct T:SomeProtocol version.

But if it sees the explicit type of the parameter as the protocol-based Type, it incorrectly calls the unrestricted T:Any version instead.


Yes, this seems like a reasonable explanation. I wonder if this is a known and prioritized issue or what would be a sufficiently small/elegant/clear demonstration of it. I don't like filing bug reports, especially complicated ones that are bound to be misinterpreted.


Eridius wrote:

For any non-@objc protocol type Foo, Swift does not allow you to pass a value of the existential type Foo (i.e. a value typed as Foo, as opposed to a value whose type is bound by Foo) to a function that expects a generic type T bound by Foo. /.../ This is why Swift is resolving the overloads the way it does.


So the question becomes: Why does the compiler think that the T for the testB(1234) call below should be of the existential type SomeProtocol rather than just Int? Would that be its, seemingly clumsy, way of handling the fact that Int happens to conform to SomeProtocol. If so, then what about all the other protocols that Int conforms to, why would it pick SomeProtocol over eg IntegerType or IntegerArithmeticType or Comparable? That doesn't sound right ...

protocol SomeProtocol {}
extension Int: SomeProtocol {}

func testA<T>(v: T) {
    let protocolConformanceSuffix = v is SomeProtocol ? "yet v conforms to SomeProtocol!?" : "since v doesn't conform to SomeProtocol."
    print("  testA: called version WITHOUT SomeProtocol constraint \(protocolConformanceSuffix)")
}
func testA<T: SomeProtocol>(v: T) {
    print("  testA: called version WITH SomeProtocol constraint.")
}
func testB<T>(v: T) {
    if v is SomeProtocol { print("  testB: v conforms to SomeProtocol, now calling testA ...") }
    testA(v)
}

testB(1234)
// Will print:
// testB: v conforms to SomeProtocol, now calling testA ...
// testA: called version WITHOUT SomeProtocol constraint yet v conforms to SomeProtocol!?


And, sort of the same question in different clothes, why and how does the compiler see this:

func testB<T>(v: T) { testA(v) }
testB(1234)

as anything different than simply this:

testA(1234)

?


I mean, the way testB is written there, I would assume that it could always be treated as just a meaningless forward/wrapper that can be optimized away, since all that testB really does is to propagate whatever argument (of whatever type, T) to testA.

Perhaps this observation can increase our understanding in some way:

func test<T: IntegerType>(t: T) {
    print("\(t) is of type \(T.self) which conforms to IntegerType.")
}

func test<T: Any>(t: T) {
    print("\(t) is of type \(T.self) which conforms to Any (as does any other type).")
}

func propagatesGenericArgsAsExpected<T, R>(v: T, fn: T -> R) -> R {
    return fn(v)
}

func propagatesGenericArgInSurprisingWay<T>(v: T) -> Void {
    return test(v)
}

test(1234)
// Prints: 1234 is of type Swift.Int which conforms to IntegerType.

propagatesGenericArgsAsExpected(1234, fn: test)
// Prints: 1234 is of type Swift.Int which conforms to IntegerType.

propagatesGenericArgInSurprisingWay(1234)
// Prints: 1234 is of type Swift.Int which conforms to Any (as does any other type).
How to explain this behaviour of overloaded generic functions?
 
 
Q