Comparing incompatible types with ===

Hi all,

class A { }
class A1: A { }
class A2: A { }

let a1 = A1()
let a2 = A2()
let a: A = a2

print(a1 === a)
print(a2 === a)
print(a1 === a2) // here

Is there a use case in Swift where comparing 2 instances that have incompatible static types (like a1 and a2 in that example) with the === operator could return true?
If there isn't, could the compiler maybe produce an error here?
And if there is one, could the compiler maybe produce a warning, which you could possibly silence with a cast to AnyObject?

Thanks

1 Like

FWIW, === is implemented in the standard library, not baked into the compiler.

We do special-case certain things and error at compile-time if we know a certain expression is going to trap at runtime (e.g. integer overflow). However, I can't remember examples where we emit an error when the answer is a known-fixed value...

2 Likes

I think it is impossible for === to be rewritten to be good enough for you. Add another =!

infix operator ==== : ComparisonPrecedence

/// Like `===`, but requiring actual matching operands.
public func ==== <Object: AnyObject>(object0: Object?, object1: Object?) -> Bool {
  object0 === object1
}

infix operator !=== : ComparisonPrecedence

/// Like `!==`, but requiring actual matching operands.
public func !=== <Object: AnyObject>(object0: Object?, object1: Object?) -> Bool {
  object0 !== object1
}
1 Like

Thank you, I'll try to use that in my projects :+1:
Unlike ===, this custom operator produces an error when comparing unrelated instances (e.g. a ==== b), but no errors when comparing siblings (e.g. a1 ==== a2 or a1 ==== a3):

class A { }        //       A        B
class A1: A { }    //     /  \
class A2: A { }    //   A1   A2
class A3: A2 { }   //         |
class B { }        //        A3

let a = A()
let a1 = A1()
let a2 = A2()
let a3 = A3()
let b = B()

a ===  b // No error
a ==== b // ERROR: Binary operator '====' cannot be applied to operands of type 'A' and 'B'

a1 ===  a2 // No error
a1 ==== a2 // Also no error
a1 ==== a3 // No error again :(

I don’t believe so. The compiler assumes that 2 pointers to incompatible types cannot alias (TBAA). That’s one of the reasons memory (re-)binding can be a bit awkward.

So I think would be reasonable to at least emit a warning.

1 Like

One example is the "casting oracle" that tells you when a cast will never succeed i.e.

_ = 1 as? String
// Warning: Cast from 'Int' to unrelated type 'String' always fails

This in principle is very similar to what the OP is asking for here.

But like you say, this is still special-cased in the compiler. We don't yet have a way of erroring or warning from Swift code at compile time, though we're getting closer to it with the compile-time evaluation functionality we now use for OSLog.

A first step might be to switch the current implementation from what is there now, to an @_transparent (i.e. inlined even in debug builds) function like

@_transparent
@_alwaysEmitIntoClient
// the original impl also needs @_disfavoredOverload
public func === <T: AnyObject, U: AnyObject> (lhs: T?, rhs: U?) -> Bool {
  guard T.self == U.self else { /* some day magic goes here */ return false }
  switch (lhs, rhs) {
  case let (l?, r?):
    return ObjectIdentifier(l) == ObjectIdentifier(r)
  case (nil, nil):
    return true
  default:
    return false
  }
}

and then add a similar warning to the casting one.

3 Likes