4

I am attempting this technique:

class Pet {}

class Dog: Pet {}

class House {
    func getPets() -> [Pet] {
        return [Pet]()
    }
}

class DogHouse: House {
    override func getPets() -> [Dog] {
        return [Dog]()
    }
}

The DogHouse class overrides the House getPets method in a way that strictly meets the API requirement of House getPets.

However, Swift does not appreciate that [Dog] isa [Pet] and it produces the error Method does not override any method from its superclass.

Is there any way for a subclass to implement API with more generic inputs or more restrictive outputs than its superclass?

William Entriken
  • 37,208
  • 23
  • 149
  • 195
  • Have I answered your question? – Alexander Mar 06 '17 at 07:37
  • There's no real reason why this shouldn't be possible – the Swift compiler can specially deal with conversions between arrays of subtypes to arrays of supertypes. This just seems to be an edge case due to the fact that `Array` is generic and therefore seen by the compiler to be invariant (which generics are), even though it can work some magic to make it appear covariant in most places (see for example [this Q&A](http://stackoverflow.com/q/37188580/2976878)). See also [this related bug report](https://bugs.swift.org/browse/SR-4075). – Hamish Mar 06 '17 at 08:11

2 Answers2

5

To answer the question officially asked: Yes, Swift allows more "restricted" return types in return types. This property is formally called return type Covariance. Consider this example, which is compilable Swift code:

class Pet {}

class Dog: Pet {}

class House {
    func getPets() -> Pet {
        return Pet()
    }
}

class DogHouse: House {
    override func getPets() -> Dog {
        return Dog()
    }
}

However, the issue here is that Array<Dog> is not a "more restricted" type than Array<Pet>, and conversely, Array<Pet> is not a generalization of Array<Dog>. Formally, Array<Dog> is not a covariant of Array<Pet>.

To illustrate why that is, consider this example:

class House<T> {
    var occupants = [T]()

    func addOccupant(_ o: T) {
        occupants.append(o)
    }
}

class Pet {}
class Dog: Pet {}
class Cat: Pet {}

class PetHouseBuilder {
    func buildHouse() -> House<Pet> {
        return House()
    }
}

class DogHouseBuilder: PetHouseBuilder {
    // Suppose this were legal
    override func buildHouse() -> House<Dog> {
        return House()
    }
}

// The concrete return type of the object is `House<Dog>`, but
// `PetHouseBuilder.buildHouse` has a static return type of `House<Pet>`,
// so `petHouse` will have an inferred static type of `House<Pet>`
let petHouse = PetHouseBuilder().buildHouse()

let vulnerableLittle = Cat()
petHouse.addOccupant(vulnerableLittle)
// Oh boy, now there's a kitten in the dog house ☠️
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • @Hamish Good point. I had converted this idea over from Java, where I had originally seen it, but the value semantics of array invalidate it. – Alexander Mar 06 '17 at 08:19
  • @Hamish Can you think of a better example? – Alexander Mar 06 '17 at 08:20
  • I'm afraid not (I think OP's code should compile) – to just show why generics are invariant, you'll have to involve a generic reference type (but this logic can be applied to arbitrary generic value types as well, as they can contain any number of generic reference types themselves – just not to Array specifically, as it has copy on write value semantics). – Hamish Mar 06 '17 at 08:33
  • @Hamish I think I've got a working example, that relies on a generic reference-type – Alexander Mar 06 '17 at 08:44
  • Yeah, that works (or should I say "doesn't work" :P). Although it doesn't prove that OP's code shouldn't compile, just that arbitrary generic types shouldn't be allowed to be covariant (`Array` can safely be an exception to this rule due to its value semantics). – Hamish Mar 06 '17 at 08:55
  • Are you suggesting that `Array` should get special treatment? – Alexander Mar 06 '17 at 09:01
  • `Array` *does* get special treatment. Try converting an `Array` to an `Array` – the compiler will let you, despite the fact that `Array` is generic and therefore invariant. The compiler just does a magic conversion for you. I go into this in more detail in [this Q&A](http://stackoverflow.com/q/37188580/2976878). – Hamish Mar 06 '17 at 09:08
  • @Hamish Ah yes, I've seen and upvoted) this answer already :D So you suggest this special treatment should extend to cover this covariant overridden return type case? – Alexander Mar 06 '17 at 09:10
  • Yes, I don't see why not. From the [linked bug report in my comment under the question](https://bugs.swift.org/browse/SR-4075), Jordan Rose says "*There's no reason why this couldn't work. Once we added the general array-of-concrete-type-to-array-of-protocol-type conversion we already gave up on trying to distinguish these cases. We should be able to treat array element upcasts like any other conversion.*" – Hamish Mar 06 '17 at 09:17
  • @Hamish Makes sense, I think I would agree, though I don't know enough to say for sure – Alexander Mar 06 '17 at 09:25
0

@Alexander

Hi, probably you know, why your example not works with protocols?

protocol IPet {}

protocol IDog: IPet {}

class Pet: IPet {}

class Dog: Pet, IDog {}

class House {
    func getPets() -> IPet {
        return Pet()
    }
}

class DogHouse: House {
    override func getPets() -> IDog //Method does not override any method from its superclass
    { 
        return Dog()
    }
}

Thanks.

Igor
  • 1,537
  • 10
  • 12