New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SR-1729] Wrong == operator is called for subclasses of a class that implements Equatable #44338
Comments
This is correct behavior in today's Swift: overload resolution is done at compile time, and operators are never dynamically dispatched even when used in protocols. If you want dynamic dispatch behavior, you'll have to use a helper function like NSObject's isEqual(_:). There are a few open discussions about changing this behavior in some way; if we decide not to, we should see if we can make it more obvious that this is happening. |
Comment by Jon Nørrelykke (JIRA) The result of that design decision is that |
@belkadan Are you sure that operators are never dynamically dispatched to? The following example dispatches to Foo's == overload (I don't see how this could be achieved with static dispatch without specialising the generic function): struct Foo : Equatable {
static func ==(lhs: Foo, rhs: Foo) -> Bool {
print("foo's ==")
return true
}
}
func areEqual<T : Equatable>(lhs: T, rhs: T) -> Bool {
return lhs == rhs // dynamically dispatch
}
let f = Foo()
_ = areEqual(lhs: f, rhs: f) // foo's overload is called I think the problem is that an == overload for a subclass isn't added to the protocol witness table when its superclass already conforms to Equatable (or the subclass doesn't even get it's own protocol witness table for Equatable conformance in the first place?). |
Sorry, I meant == isn't dispatched like a class method; you are correct that it is dispatched dynamically like a protocol requirement. But yes, the subclass does not get to provide new members to satisfy the conformance. This is important because a protocol can be added to a base class in one module and a subclass created in another module. (It's not the only design decision, but it is the design we have today, and any change would have to go through the Swift Evolution Process.) |
Note that we now allow protocols to be defined inside types, but class operators still need to be final. However at least now the syntax makes sense for allowing overridable operators to be defined; I think at this point it's just a matter of implementation. |
Comment by Ryan Lovelett (JIRA) For my team, another point of consideration is a class that extends class SubClass : NSManagedObject {
...
static func == (lhs: SubClass, rhs: SubClass) -> Bool {
...
}
// ❌ Crashes at runtime with: ❌
// "Class 'SubClass' for entity 'SubClass' has an illegal override of NSManagedObject -isEqual:"
override func isEqual(_ object: Any?) -> Bool {
guard let rhs = object as? SubClass else {
return false
}
return self == rhs
}
} This basically means that any custom sub-class of To me, it certainly is not obvious at the call site which path the code will take. I think this violates the principle of least astonishment. I suppose it is worth noting that in Swift 3.1 |
Comment by Grigory Entin (JIRA) I wonder if some kind of diagnostics, or anything at compile time would be possible in this case. At quick glance, it looks like it makes sense either to make/treat such methods (utilizing default protocol implementation) as "final" in base class (because effectively you can not override them at all, even though they're "present" in the base class) or at least provide a warning on attempt to define the matching method in the subclass (I don't know how such a warning could be suppressed though). I believe this thing (mixing subclasses and protocols/different kinds of "dynamic dispatch" for protocols and classes) should be covered somehow in the documentation, to avoid the confusion. |
Environment
Swift 2.2
Additional Detail from JIRA
md5: d6f63b5ec7206d796d6d0269a6b932b1
Issue Description:
The small Playground example below shows the problem.
{{import UIKit
class A: Equatable {
let value1: Int
init(value1: Int) {
self.value1 = value1
}
}
func ==(lhs: A, rhs: A) -> Bool {
return lhs.value1 == rhs.value1
}
class B: A {
let value2: Int
init(value1: Int, value2: Int) {
self.value2 = value2
super.init(value1: value1)
}
}
func ==(lhs: B, rhs: B) -> Bool {
return lhs.value1 == rhs.value1 && lhs.value2 == rhs.value2
}
let b1 = B(value1: 1, value2: 2)
let b2 = B(value1: 1, value2: 2)
let b3 = B(value1: 1, value2: 3)
if b1 == b2 {
"The b1 and b2 are indeed equal"
} else {
"Failed: The b1 and b2 should be equal"
}
// Using != operator fails
if b1 != b3 {
"The b1 and b2 are indeed different"
} else {
"Failed: The b1 and b3 should be different"
}
// Explicitly using == operator works fine
if !(b1 == b3) {
"The b1 and b2 are indeed different"
} else {
"Failed: The b1 and b3 should be different"
}
}}
The text was updated successfully, but these errors were encountered: