Bright
Bright

Reputation: 5741

Cannot express tuple conversion '([Subclass], Int, String)' to '([Superclass], Int, String)'

Original

In my project I have a Iterator class, which has a function:

func iterateItems<T: Items>(iterationItems: [T], removeItem: (T) -> Void, addItem: (T) -> Void, calculateEfficiency: () -> Void) -> [T] {

    ...
    return bestComposition as! [T]
}

And in its subclass WPCalculator I run it this way:

func iterateWPItems() -> [Items] {
    return iterateItems(iterationItems: WeaponItems.weaponItems, removeItem: removeWeaponItem, addItem: addWeaponItem, calculateEfficiency: calcWeaponDemage)
}

New code

Everything worked fine that way. Now I want to change the iterateItems function to this:

func iterateItems<T: Items>(iterationItems: [T], removeItem: (T) -> Void, addItem: (T) -> Void, calculateEfficiency: () -> Void) -> ([T], Int, String) {

    ...
    return (bestComposition as! [T], bestBuildCost, customMessage)
}

Then I updated WPCalculator accordingly:

func iterateWPItems() -> ([Items], Int, String) {
    return iterateItems(iterationItems: WeaponItems.weaponItems, removeItem: removeWeaponItem, addItem: addWeaponItem, calculateEfficiency: calcWeaponDemage)
}

Now I get an error: Cannot express tuple conversion '([WeaponItems], Int, String)' to '([Items], Int, String)'

The argument passed to iterateWPItems is an array of type WeaponItems, which is a subclass of Items, it worked fine in the original version, where Swift seems to have inferred the subclass-to-superclass conversion, but when I put it in a tuple in the new code, it doesn't work.

Why is that? How do I solve this problem?

Edit:

WeaponItems:

class WeaponItems: Items {

var weaponPower, attackSpeed, criticalChance, criticalDamage, armorPierce: Double

init(name: String, index: Int, price: Int, weaponPower: Double = 0, attackSpeed: Double = 0, criticalChance: Double = 0, criticalDamage: Double = 0, armorPierce: Double = 0, image: UIImage){

    self.weaponPower = weaponPower
    self.attackSpeed = attackSpeed
    self.criticalChance = criticalChance
    self.criticalDamage = criticalDamage
    self.armorPierce = armorPierce
    super.init(name: name, index: index, price: price, image: image)
}
...
}

Items:

class Items {

let name: String
let index: Int
let price: Int
let image: UIImage

init(name: String, index: Int, price: Int, image: UIImage) {
    self.name = name
    self.index = index
    self.price = price
    self.image = image
}
...
}

Upvotes: 1

Views: 1569

Answers (1)

0x416e746f6e
0x416e746f6e

Reputation: 10136

The problem has indeed to do with the fact that although Swift can implicitly convert [WeaponItems] into [Items], it fails to do so when these types come as the components of tuples. E.g. see this: Tuple "upcasting" in Swift

There are several ways to "solve" this problem.

Easiest is:

func iterateWPItems() -> ([Items], Int, String) {
    let (composition, cost, message) = iterateItems(iterationItems: WeaponItems.weaponItems, removeItem: removeWeaponItem, addItem: addWeaponItem, calculateEfficiency: calcWeaponDemage)
    return (composition, cost, message)
}

Alternatively, you can change iterateItems to return just the tuple you expect, that is ([Items], Int, String):

func iterateItems<T: Items>(iterationItems: [T], removeItem: (T) -> Void, addItem: (T) -> Void, calculateEfficiency: () -> Void) -> ([Items], Int, String) {
    ...
    return (bestComposition as! [Items], bestBuildCost, customMessage)
}

Better still, from the way it looks I do not see why iterateItems has to be a generic method. If that's indeed the case, then simply changing it to:

func iterateItems(iterationItems: [Items], removeItem: (Items) -> Void, addItem: (Items) -> Void, calculateEfficiency: () -> Void) -> ([Items], Int, String) {
    ...
    return (bestComposition as! [Items], bestBuildCost, customMessage)
}

.. should also help.

Upvotes: 1

Related Questions