[Xcode 11.4 beta] Should overriding methods be allowed to use subprotocol constraints?

The following worked in previous versions of Xcode, but fails to compile on the Xcode 11.4 beta.

protocol BaseProtocol { }
protocol SubProtocol: BaseProtocol { }

class FooClass {
    func makeViewController<U: BaseProtocol>(u: U) { fatalError() }
}

class FooSubClass: FooClass {
    override func makeViewController<U: SubProtocol>(u: U) { // Error here. Text below.
        fatalError()
    }
}

/*
 Overridden method 'makeViewController' has generic signature <U where U : SubProtocol> which is
 incompatible with base method's generic signature <U where U : BaseProtocol>; expected generic
 signature to be <U where U : BaseProtocol>
*/

Is this a regression or expected behaviour. If it's expected, is there a workaround?

I think this is the same issue discussed earlier.

2 Likes

There's some existing discussion about this change in this thread. It's intentional and the old behavior shouldn't have been allowed, because the override needs to accept all possible inputs that the base method does, which means it can't constraint the type further.

4 Likes

Thanks for the links! Makes sense to me.

I believe this was mentioned in the changelog.

2 Likes

If you do this, it should work:

let base: BaseProtocol = ...
let subFoo = FooSubClass(...)

(subFoo as FooClass).makeViewController(u: base)

Note that a SubFooClass instance is a FooClass. Therefore, it must be able to accept all kinds of input that the FooClass will. What should happen in your example? Should the superclass implementation be silently invoked when there is only a partial override in the subclass?

I think that would be too magical.

This current behaviour was a bug. It has been fixed.

If we want to consciously allow partial overrides, I think we should fix that as a separate feature, perhaps with a partial override keyword, and clear and well-documented semantics for behaviour. I'm not sure we even want that.

Probably it's better to model your FooClass as a protocol with associated type requirements, and implement your SubFooClass as an implementation with a specific constraint on the associated type.

1 Like

If your protocol does not have Self or associatedtype requirements, then you can perform a checked cast:

protocol BaseProtocol { }
protocol SubProtocol: BaseProtocol { }

class FooClass {
    func makeViewController<U: BaseProtocol>(u: U) { fatalError() }
}

class FooSubClass: FooClass {
    override func makeViewController<U: BaseProtocol>(u: U) {
        if let _u = u as? SubProtocol {
          ...
        }
    }
}