Overridden method has generic signature which is incompatible with base method's generic signature

974 Views Asked by At

I just updated to Xcode 11.4. I'm receiving the following error:

Overridden method 'equals' has generic signature <T where T : Gradient> which is incompatible with base method's generic signature <T where T : Fill>; expected generic signature to be <T where T : Fill>

enter image description here

class Fill: Equatable {
    func equals<T>(other: T) -> Bool where T: Fill { }
}

func ==<T> (lhs: T, rhs: T) -> Bool where T: Fill {
    return lhs.equals(other: rhs)
}

class Gradient: Fill {
    override func equals<T>(other: T) -> Bool where T: Gradient { }
}

How this changed?

3

There are 3 best solutions below

0
On

Please look at this thread https://forums.swift.org/t/method-override-with-a-generic-signature-with-requirements-not-imposed-by-the-base-method/33593

This compilation error prevents you from future crashes.

0
On

In my case I just fix this error by this:-

override func equals<T>(other: T) -> Bool where T: Fill {
    guard let other = other as? Gradient else { 
        return false 
    }
    ...
}

But there are some similar errors of Macaw, So just replace Gradient with your error issue and it fix.

0
On

The reason why you cannot change constraints like that is because doing so would violate Liskov substitution principle. The compiler has never checked the generic signature of overridden functions and this led to various runtime crashes, so I implemented this error in the Swift 5.2 compiler.

Let's take a look at your own example to understand what's wrong. One can write the following code:

let fill1: Fill = Fill()
let fill2: Fill = Gradient()
let isEqual = fill2.equals(other: fill1)

This is not correct, because Gradient.equals(other:) requires other to inherit from Gradient. However, because fill2 has a static type of Fill, I am able to bypass that constraint and pass a value of type Fill instead.

If Gradient.equals(other:) tries to access a property or a function on other which only exists on an instance of Gradient, your code would simply crash on runtime, because that property or function wouldn't be available on Fill:

class Fill: Equatable {
  func equals<T>(other: T) -> Bool where T: Fill { ... }
}

class Gradient: Fill {
  private(set) var id: String = UUID().uuidString
  override func equals<T>(other: T) -> Bool where T: Gradient {
    return id == other.id // crash if `other` is value of type `Fill`
  }
}

So, the compiler now prohibits you from adding incompatible constraints when overriding a function.

Now, there are basically two ways to fix the problem - change your code and avoid adding incompatible constraints in the overridden function or do a runtime check in the overridden function using as? or type(of:) to check if the passed value indeed satisfies the new constraints and then try using it:

override func equals<T>(other: T) -> Bool where T: Gradient {
  if type(of: other) == Fill.self { return false }
  return id == other.id // okay
}