Failure when checking if protocol exists

I just encountered one weird problem related to protocol checking. Here is the swift code using Xcode playground.

//-------------------------------------------------------------------------------
protocol IGenericCallback {
  @discardableResult
  func Callback(_ objects: Any...) -> Int
}
//-------------------------------------------------------------------------------
class CGenericCallback: IGenericCallback {
  private let Closure: (_ objects: Any...) -> Int
  init(closure: @escaping (_ objects: Any...) -> Int) {
    Closure = closure
  }
  @discardableResult
  func Callback(_ objects: Any...) -> Int {
    // Array splatting is not supported yet. That's fine now.
    Closure(objects)
    return 0
  }
}
//-------------------------------------------------------------------------------
class PackCallback {
  var objectList: [Any]
  init(_ objects: Any...) {
    objectList = objects;
  }
}
//-------------------------------------------------------------------------------
func test(cb: IGenericCallback?, param: Any) {
  let pack = PackCallback(cb as Any, param)
  if cb is IGenericCallback {
    print("***** cb is IGenericCallback")
  }
  print("pack.objectList[0] = \(pack.objectList[0])")
  if pack.objectList[0] is IGenericCallback {
    print("***** pack.objectList[0] is IGenericCallback")
  }
  else {
    print("***** pack.objectList[0] is NOT IGenericCallback!! WHY?")
  }
}
//-------------------------------------------------------------------------------
test(cb: CGenericCallback.init(closure: {(objects: Any...) -> Int in
  print("pack callback done. count=\(objects.count)")
  return 0
}), param: 0)

I got the result:

***** cb is IGenericCallback
pack.objectList[0] = Optional(__lldb_expr_9.CGenericCallback)
***** pack.objectList[0] is NOT IGenericCallback!! WHY?

If I change "func test(cb: IGenericCallback?, param: Any)" to "func test(cb: IGenericCallback, param: Any)", the result is correct. But I don't know why? Can anyone help me? Thanks.

I think this is because Optional does not conform to your protocol (or P in my example below). Optional<CGenericCallback> does not imply that Optional<CGenericCallback>: CGenericCallback.

protocol P {}

class C: P {}

let c_1 = C()
let c_2: C? = c_1
let p_1: P = c_1
let p_2: P? = c_1

func test(_ any: Any, name: String) {
  any is C
    ? print("is C - \(name)")
    : print("is not C - \(name)")

  any is P
    ? print("is P - \(name)")
    : print("is not P - \(name)")
  print()
}

test(c_1, name: "c_1")
test(c_2 as Any, name: "c_2")
test(p_1, name: "p_1")
test(p_2 as Any, name: "p_2")

This prints:

is C - c_1
is P - c_1

is C - c_2
is not P - c_2

is C - p_1
is P - p_1

is C - p_2
is not P - p_2

If we now add this conditional conformance:

extension Optional: P where Wrapped: P {}

the result will look like this:

is C - c_1
is P - c_1

is C - c_2
is P - c_2

is C - p_1
is P - p_1

is C - p_2
is not P - p_2

However I'm not sure why it prints is not P - p_2, cc @Joe_Groff is this intended or a bug?

I think this is due the fact that protocols do not conform to themselves. Please correct me if I'm wrong.


Update:

If you swap the above extension with:

extension Optional: P where Wrapped == P {}

You'll get as the result:

is C - c_1
is P - c_1

is C - c_2
is not P - c_2

is C - p_1
is P - p_1

is C - p_2
is P - p_2

This is the exact opposite of the previous issue. That in fact means that extension Optional: P where Wrapped: P {} would fix both issues if P would conform to itself like Error protocol conforms to itself in Swift 5.

Yeah, it's a known bug that dynamic casting sometimes fails to unwrap Optionals when trying protocol casts. Relatedly, it also fails to bridge sometimes when bridging would enable a protocol cast to succeed.

Thanks, @DevAndArtist and @Joe_Groff ! It looks like that it should be a bug. I would expect the 4 cases in your example should be C and P. I will remove protocol and change it to class as a workaround. "class" seems performing correctly now. Hope the fix is coming soon.