// RUN: %target-run-simple-swift
// REQUIRES: executable_test
// REQUIRES: objc_interop

import StdlibUnittest
import Foundation

// Make sure at the end of the file we run the tests.
defer { runAllTests() }

protocol P : class { }
protocol C : class { }

class Foo : NSObject {}

var Casts = TestSuite("Casts")

@inline(never)
func castit<ObjectType>(_ o: NSObject?, _ t: ObjectType.Type) -> ObjectType? {
  return o as? ObjectType
}

@inline(never)
func castitExistential<ObjectType>(_ o: C?, _ t: ObjectType.Type) -> ObjectType? {
  return o as? ObjectType
}

Casts.test("cast optional<nsobject> to protocol") {
  if let obj = castit(nil, P.self) {
    print("fail")
    expectUnreachable()
  } else {
    print("success")
  }
}

Casts.test("cast optional<nsobject> to protocol meta") {
  if let obj = castit(nil, P.Type.self) {
    print("fail")
    expectUnreachable()
  } else {
    print("success")
  }
}
Casts.test("cast optional<protocol> to protocol") {
  if let obj = castitExistential(nil, P.self) {
    print("fail")
    expectUnreachable()
  } else {
    print("success")
  }
}

Casts.test("cast optional<protocol> to class") {
  if let obj = castitExistential(nil, Foo.self) {
    print("fail")
    expectUnreachable()
  } else {
    print("success")
  }
}

Casts.test("cast optional<protocol> to protocol meta") {
  if let obj = castitExistential(nil, P.Type.self) {
    expectUnreachable()
    print("fail")
  } else {
    print("success")
  }
}

Casts.test("cast optional<protocol> to class meta") {
  if let obj = castitExistential(nil, Foo.Type.self) {
    expectUnreachable()
    print("fail")
  } else {
    print("success")
  }
}

@objc public class ParentType : NSObject {
  var a = LifetimeTracked(0)
}

public class ChildType : ParentType {
}

struct SwiftStructWrapper {
  var a = LifetimeTracked(0)
}

extension SwiftStructWrapper : _ObjectiveCBridgeable {
  typealias _ObjectiveCType = ParentType

  func _bridgeToObjectiveC() -> _ObjectiveCType {
    return ParentType()
  }

  static func _forceBridgeFromObjectiveC(
    _ source: _ObjectiveCType,
    result: inout Self?
  ) {}

  @discardableResult
  static func _conditionallyBridgeFromObjectiveC(
    _ source: _ObjectiveCType,
    result: inout Self?
  ) -> Bool { return false }

  @_effects(readonly)
  static func _unconditionallyBridgeFromObjectiveC(_ source: _ObjectiveCType?)
  -> Self {
    return SwiftStructWrapper()
  }
}

Casts.test("testConditionalBridgedCastFromSwiftToNSObjectDerivedClass") {
  autoreleasepool {
    let s = SwiftStructWrapper()
    let z = s as? ChildType
    print(z)
  }
  expectEqual(0, LifetimeTracked.instances)
}

protocol Q {}
class K { }
class D: Q {}
typealias AnyQ = Q & AnyObject
typealias KQ = K & Q

Casts.test("testCastProtocolCompoWithAnyObjectToProtocolCompoTypeSuperclass") {
  let shouldBeNil = (D() as AnyQ) as? KQ
  expectNil(shouldBeNil)
}

protocol QAny: AnyObject {}
typealias KQAny = K & QAny
class F: QAny {}

Casts.test("testCastProtocolWithAnyObjectToProtocolCompoTypeSuperclass") {
  let shouldBeNil = (F() as QAny) as? KQAny
  expectNil(shouldBeNil)
}
