// RUN: %empty-directory(%t)
//
// RUN: %gyb %s -o %t/AnyHashableCasts.swift
// RUN: %line-directive %t/AnyHashableCasts.swift -- %target-build-swift -g -module-name a %t/AnyHashableCasts.swift -o %t.out
// RUN: %target-codesign %t.out
// RUN: %line-directive %t/AnyHashableCasts.swift -- %target-run %t.out
// RUN: %line-directive %t/AnyHashableCasts.swift -- %target-build-swift -g -O -module-name a %t/AnyHashableCasts.swift -o %t.out.optimized
// RUN: %target-codesign %t.out.optimized
// RUN: %line-directive %t/AnyHashableCasts.swift -- %target-run %t.out.optimized
// REQUIRES: executable_test

import StdlibUnittest

#if _runtime(_ObjC)
import Foundation
#endif

var AnyHashableCasts = TestSuite("AnyHashableCasts")

protocol Implemented {}
protocol Unimplemented {}
extension Int : Implemented {}

struct HashableStruct : Hashable {
  var value : Int
  static func ==(lhs: HashableStruct, rhs: HashableStruct) -> Bool {
    return lhs.value == rhs.value
  }
  func hash(into hasher: inout Hasher) {
    hasher.combine(value)
  }
}

class HashableClass : Hashable {
  var value : Int
  init(value v: Int) { self.value = v }
  static func ==(lhs: HashableClass, rhs: HashableClass) -> Bool {
    return lhs.value == rhs.value
  }
  func hash(into hasher: inout Hasher) {
    hasher.combine(value)
  }
}

enum HashableEnum : Hashable {
  case value(Int)
  static func ==(lhs: HashableEnum, rhs: HashableEnum) -> Bool {
    switch (lhs, rhs) {
    case (.value(let l), .value(let r)): return l == r
    }
  }
  func hash(into hasher: inout Hasher) {
    switch self {
    case .value(let v):
      hasher.combine(v)
    }
  }
}

func opaqueCast<T, U>(_ lhs: T, _ rhs: U.Type) -> U? {
  return lhs as? U
}

func matches<T: Equatable>(_ lhs: T, _ rhs: T) -> Bool {
  return lhs == rhs
}

func matches<T: Equatable, U>(_ lhs: U, _ rhs: T) -> Bool {
  if let x = lhs as? T {
    return x == rhs
  } else {
    return false
  }
}

%{
testCases = [
  ("5", "AnyHashable", "Int", "5"),
  ("5", "AnyHashable", "Any", "5"),
  ("5", "AnyHashable", "Implemented", "5"),
  ("5", "AnyHashable", "Unimplemented", False),
  ("5", "Int", "AnyHashable", "5"),
  ("5", "Any", "AnyHashable", "5"),
  ("AnyHashable(5)", "Any", "Int", "5"),
  ("HashableStruct(value: 5)", "HashableStruct", "AnyHashable",
   "AnyHashable(HashableStruct(value: 5))"),
  ("HashableStruct(value: 5)", "AnyHashable", "HashableStruct",
   "AnyHashable(HashableStruct(value: 5))"),
  ("HashableClass(value: 5)", "HashableClass", "AnyHashable",
   "AnyHashable(HashableClass(value: 5))"),
  ("HashableClass(value: 5)", "AnyHashable", "HashableClass",
   "AnyHashable(HashableClass(value: 5))"),
  ("HashableEnum.value(5)", "HashableEnum", "AnyHashable",
   "AnyHashable(HashableEnum.value(5))"),
  ("HashableEnum.value(5)", "AnyHashable", "HashableEnum",
   "AnyHashable(HashableEnum.value(5))"),
]
}%

% for valueExpr, coercedType, castType, expected in testCases:
AnyHashableCasts.test("${valueExpr} as ${coercedType} as? ${castType}") {
  do {
    let x = ${valueExpr}
    let y : ${coercedType} = x
    if let z = y as? ${castType} {
%if expected:
      expectTrue(matches(z, ${expected}))
%else:
      expectUnreachable()
%end
    } else {
%if expected:
      expectUnreachable()
%end
    }

    if let z = opaqueCast(y, (${castType}).self) {
%if expected:
      expectTrue(matches(z, ${expected}))
%else:
      expectUnreachable()
%end
    } else {
%if expected:
      expectUnreachable()
%end
    }
  }
}
% end

AnyHashableCasts.test("Casting to AnyHashable doesn't leak") {
  do {
    let tracked = LifetimeTracked(42)
    let anyHashable = AnyHashable(tracked)
    let anyObject = anyHashable as AnyObject
    _ = anyObject as? AnyHashable
  }
  expectEqual(LifetimeTracked.instances, 0)
}

#if _runtime(_ObjC)
// A wrapper type around Int that bridges to NSNumber.
struct IntWrapper1: _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable {
  let rawValue: Int
}

// A wrapper type around Int that bridges to NSNumber.
struct IntWrapper2: _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable {
  let rawValue: Int
}

AnyHashableCasts.test("Wrappers around bridged integers") {
  let wrapper1: AnyHashable = IntWrapper1(rawValue: 42)
  let wrapper2: AnyHashable = IntWrapper2(rawValue: 42)
  let integer: AnyHashable = 42 as Int
  let byte: AnyHashable = 42 as UInt8
  let double: AnyHashable = 42.0 as Double
  let number: AnyHashable = 42 as NSNumber

  // Wrappers compare equal to their wrapped value as AnyHashable.
  expectEqual(wrapper1, wrapper2)
  expectEqual(wrapper1, integer)
  expectEqual(wrapper1, byte)
  expectEqual(wrapper1, double)
  expectEqual(wrapper1, number)

  // Original types are preserved in the base property.
  expectTrue(wrapper1.base is IntWrapper1)
  expectTrue(wrapper2.base is IntWrapper2)
  expectTrue(integer.base is Int)
  expectTrue(byte.base is UInt8)
  expectTrue(double.base is Double)
  expectTrue(number.base is NSNumber) // Through bridging

  // AnyHashable forms can be casted to any standard numeric type that can hold
  // their value.
  expectNotNil(wrapper1 as? IntWrapper1)
  expectNotNil(wrapper1 as? IntWrapper2)
  expectNotNil(wrapper1 as? Int)
  expectNotNil(wrapper1 as? UInt8)
  expectNotNil(wrapper1 as? Double)
  expectNotNil(wrapper1 as? NSNumber)

  expectNotNil(byte as? IntWrapper1)
  expectNotNil(byte as? IntWrapper2)
  expectNotNil(byte as? Int)
  expectNotNil(byte as? UInt8)
  expectNotNil(byte as? Double)
  expectNotNil(byte as? NSNumber)

  expectNotNil(integer as? IntWrapper1)
  expectNotNil(integer as? IntWrapper2)
  expectNotNil(integer as? Int)
  expectNotNil(integer as? UInt8)
  expectNotNil(integer as? Double)
  expectNotNil(integer as? NSNumber)

  expectNotNil(double as? IntWrapper1)
  expectNotNil(double as? IntWrapper2)
  expectNotNil(double as? Int)
  expectNotNil(double as? UInt8)
  expectNotNil(double as? Double)
  expectNotNil(double as? NSNumber)

  expectNotNil(number as? IntWrapper1)
  expectNotNil(number as? IntWrapper2)
  expectNotNil(number as? Int)
  expectNotNil(number as? UInt8)
  expectNotNil(number as? Double)
  expectNotNil(number as? NSNumber)

  // We can't cast to a numeric type that can't hold the value.
  let big: AnyHashable = Int32.max
  expectNotNil(big as? IntWrapper1)
  expectNotNil(big as? IntWrapper2)
  expectNotNil(big as? Int)
  expectNil(big as? UInt8) // <--
  expectNotNil(big as? Double)
  expectNotNil(big as? NSNumber)
}

// A wrapper type around a String that bridges to NSString.
struct StringWrapper1: _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable {
  let rawValue: String
}

// A wrapper type around a String that bridges to NSString.
struct StringWrapper2: _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable {
  let rawValue: String
}

AnyHashableCasts.test("Wrappers around bridged strings") {
  let wrapper1Hello: AnyHashable = StringWrapper1(rawValue: "hello")
  let wrapper2Hello: AnyHashable = StringWrapper2(rawValue: "hello")
  let stringHello: AnyHashable = "hello" as String
  let nsStringHello: AnyHashable = "hello" as NSString

  // Wrappers compare equal to their wrapped value as AnyHashable.
  expectEqual(wrapper1Hello, wrapper2Hello)
  expectEqual(wrapper1Hello, stringHello)
  expectEqual(wrapper1Hello, nsStringHello)
  expectEqual(wrapper2Hello, stringHello)
  expectEqual(wrapper2Hello, nsStringHello)
  expectEqual(stringHello, nsStringHello)

  // Type identity is maintained through the base property.
  expectTrue(wrapper1Hello.base is StringWrapper1)
  expectTrue(wrapper2Hello.base is StringWrapper2)
  expectTrue(stringHello.base is String)
  expectTrue(nsStringHello.base is NSString) // Through bridging

  // Swift wrapper's AnyHashable form doesn't enforce type identity.
  expectNotNil(wrapper1Hello as? StringWrapper1)
  expectNotNil(wrapper1Hello as? StringWrapper2)
  expectNotNil(wrapper1Hello as? String)
  expectNotNil(wrapper1Hello as? NSString)

  // String's AnyHashable form doesn't enforce type identity.
  expectNotNil(stringHello as? StringWrapper1)
  expectNotNil(stringHello as? StringWrapper2)
  expectNotNil(stringHello as? String)
  expectNotNil(stringHello as? NSString)

  // NSString's AnyHashable form doesn't enforce type identity.
  expectNotNil(nsStringHello as? StringWrapper1)
  expectNotNil(nsStringHello as? StringWrapper2)
  expectNotNil(nsStringHello as? String)
  expectNotNil(nsStringHello as? NSString)
}

#endif

runAllTests()
