There are two main problems with what you are trying to do here.
1. Overload resolution favors supertypes for additional advertising
You have specified your overload == for Item! parameters Item! , not Item . Thus, the type checker weighs more in favor of statically sending overload NSObject for == , since it seems that the type checker supports a subclass for conversions over the superclass compared to additional advertising (I could not find a source to confirm this, although )
Usually you do not need to define your own overload to handle options. By matching this type with Equatable , you automatically get an overload == , which handles equality checking between optional instances of this type.
A simpler example demonstrating the advantage of superclass overloading over additional subclass overloading would be:
// custom operator just for testing. infix operator <===> class Foo {} class Bar : Foo {} func <===>(lhs: Foo, rhs: Foo) { print("Foo overload") } func <===>(lhs: Bar?, rhs: Bar?) { print("Bar overload") } let b = Bar() b <===> b // Foo overload
Should Overload Bar? changed to Bar - overload will be called instead.
Therefore, you must change your overload to use the Item parameters instead. Now you can use this overload to compare two instances of Item for equality. However, this will not completely solve your problem due to the following problem.
2. Subclasses cannot directly re-implement protocol requirements
Item does not directly Equatable . Instead, it inherits from NSObject , which already matches Equatable . Its implementation == just goes to isEqual(_:) - which by default compares memory addresses (i.e. checks if these two instances are exactly the same instance).
This means that if you overload == for Item , this overload cannot be dynamically dispatched. This is because Item does not get its own protocol witness table for Equatable - instead, it relies on NSObject PWT, which sends == to its overload, simply by calling isEqual(_:) .
(Protocol witness protocols are a mechanism used to provide dynamic sending with protocols - see this WWDC talk to them for more information.)
Thus, this will prevent your overload from being called in general contexts, including the aforementioned free == overload for options - an explanation of why it does not work when trying to compare Item? instances Item? .
This behavior can be seen in the following example:
class Foo : Equatable {} class Bar : Foo {} func ==(lhs: Foo, rhs: Foo) -> Bool {
So, even if you have to change your overload in such a way that it accepts the entry Item , if == has ever been called from the general context in an instance of Item , your overload will not be called. NSObject overload will be.
This behavior is somewhat unobvious and was filed as an error - SR-1729 . The rationale for this, as Jordan Rose explained, is as follows:
[...] A subclass cannot provide new members to meet requirements. This is important because a protocol can be added to a base class in one module and a subclass created in another module.
Which makes sense, since the module in which this subclass is located needs to be recompiled so that it can satisfy the requirements, which is likely to lead to problematic behavior.
However, it is worth noting that this restriction is indeed problematic with operator requirements, since other protocol requirements can usually be overridden by subclasses. In such cases, overriding implementations are added to the vtable subclass, which allows dynamic dispatch as expected. However, this is currently not possible for operators without using a helper method (for example, isEqual(_:) ).
Decision
So the solution is to override NSObject isEqual(_:) and the hash property instead of overload == (see this Q & A for how to do this). This ensures that your equality implementation will always be called regardless of context - since your override will be added to the vtable class, which will allow you to dynamically send messages.
The rationale for overriding hash as well as isEqual(_:) is that you need to keep the promise that if two objects are compared equal, their hashes must be the same. All sorts of oddities can occur differently if Item ever hashed.
Obviously, a solution for derived classes other than NSObject would have to define your own isEqual(_:) method, and subclasses override it (and then just have an overload chain == ).