4

When assigning a tuple of, say, Int members, to a tuple of an (heterogeneous) protocol type, to which Int conforms, it is seemingly only allowed to perform this assignment via explicit member-by-member assignment.

protocol MyType {}
extension Int: MyType {}

let intPair = (1, 2)
var myTypePair : (MyType, MyType)

// OK
myTypePair = (intPair.0, intPair.1)

// OK
let intPairToMyTypePair : ((Int, Int)) -> (MyType, MyType) = { ($0.0, $0.1) }
myTypePair = intPairToMyTypePair(intPair)

// For all below: 
// "Error: cannot express tuple conversion '(Int, Int)' to '(MyType, MyType)'"
myTypePair = intPair
myTypePair = (intPair as! (MyType, MyType))
    /* warning: forced cast from '(Int, Int)' to '(MyType, MyType)' 
                always succeeds <-- well, not really */

if let _ = intPair as? (MyType, MyType) { }
    /* warning: conditional cast from '(Int, Int)' to '(MyType, MyType)' 
                always succeeds */

The peculiarity is that for the casting cases above the Swift compiler warns that

warning: forced/conditional cast from '(Int, Int)' to '(MyType, MyType)' always succeeds

Which it clearly does not:

error: cannot express tuple conversion '(Int, Int)' to '(MyType, MyType)'

Question: What is the issue here, is the non-allowance of direct assignment as intended? If so, what's up with the warning message that the casting will always succeed?

(Edit addition)

To clarify further the peculiar behaviour I'm asking about here: I wonder why the follow snippet prints "bar" even if Swift warns us that the 'is' case is always true:

let intPair = (1, 2)

switch intPair {
case is (MyType, MyType): print("foo") /* warning: 'is' test is always true */
case _ : print("bar")
}
    // "bar"

The analogous case for an array is straightforward with explanatory errors, but I don't know if this is a valid comparison, as tuples are more of anonymous structures rather than some cousin to Array.

let intArr = [Int](1...5)
var myTypeArr : [MyType]

myTypeArr = intArr
    /* error: cannot assign value of type '[Int]' to type '[MyType]' */

if let _ = intArr as? [MyType] { }
    /* error: 'MyType' is not a subtype of 'Int' */
dfrib
  • 70,367
  • 12
  • 127
  • 192

2 Answers2

0

The warning is true in this case because intPair is (Int, Int) which conforms to (MyType, MyType). So that cast will always succeed. Because of strong type checks compiler knows that this particular instance will always succeed.

Why myTypePair = intPair errors is because the myTypePair is a more generic than intPair which is more concrete. So the types don't match and hence it cannot assign.

Pradeep K
  • 3,671
  • 1
  • 11
  • 15
  • But the peculiarity is that `if let a = (intPair as? (MyType, MyType)) { }` will not fall through, i.e., will not enter the `if let` block, even if the cast is prompted to always succeed (which it seemingly does not). – dfrib Feb 02 '16 at 15:30
  • In my case `if let _ = intPair as? (MyType, MyType)` indicates error as well a warning. The code does not compile in this case. – Pradeep K Feb 02 '16 at 15:38
  • Yes that is exactly my point with this question: the warning tells us the cast will always succeed, but the subsequent assignment still fails, prompting _"cannot express tuple conversion `'(Int, Int)'` to `'(MyType, MyType)'`"_ (which should not be possible if the cast really succeeds). – dfrib Feb 02 '16 at 15:41
0

I just did a testing about Array of Swift with class type. I think I may help on the Array part. But all my answer is my personal understanding based on my test.

All the type you used in your code is Swift type without anything to do with Objective-C bridge. In this case, Swift behaves like Java. Check the Java code below:

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<B> b = new ArrayList<B>();
        ArrayList<A> a = b; // compile error: incompatible types: java.util.ArrayList<B> cannot be converted to java.util.ArrayList<A>
    }
}
class A {
}
class B extends A {
}

you can check the comment of this answer for the reason. I think swift do this with the same reason for Swift Value Type.

Note here I highlight Value Type. Since for Class Type, the thing changes. check the swift code below:

class A {}
let a = [A()]
let o: [AnyObject] = a // there is no problem for this!

Actually, all the class inherit from AnyObject or has @objc attribute will be handle with NSArray underneath. You can check swift document for more detail.

Hope this can help, and If you found anything wrong, please correct me!

Community
  • 1
  • 1
Ypy
  • 56
  • 5
  • Thanks for your answer. The `Array` example in my question, however, was mostly to show an example of how I believe the error messages _should_ look like (informative), whereas for the tuple example they're totally off (saying cast will always pass when it never does). Note that your final example above works for some native Swift _value_ types as well, e.g. `String` or `Int` (types that conform to `_ObjectiveCBridgeable`), as these are bridged _"behind-the-hood"_ to reference Obj-C types. <...> – dfrib Mar 26 '16 at 09:28
  • <...> And we can actually conform our own value types to this internal protocol, to make use of this seamless bridging for such value types of our own. See e.g. [this thread](http://stackoverflow.com/questions/35893517) regarding implementing such a bridging. Finally, I still believe the compiler warning (_"... always succeeds"_) is to be considered somewhat of a bug, much like the case you cover in [your question here](http://stackoverflow.com/questions/36173726/swift-use-is-for-function-type-compiler-behavior-is-different-with-runtime). – dfrib Mar 26 '16 at 09:30
  • @dfri It should be a bug. For the reason, I think compiler is using `type inference` to check while runtime is using `dynamicType`. – Ypy Mar 28 '16 at 08:51