// RUN: %target-run-simple-swift
//
// RUN: %target-build-swift -swift-version 5 -O %s -o %t/a.swift5.O.out
// RUN: %target-codesign %t/a.swift5.O.out
// RUN: %target-run %t/a.swift5.O.out
//
// RUN: %target-build-swift -swift-version 5 -Onone %s -o %t/a.swift5.Onone.out
// RUN: %target-codesign %t/a.swift5.Onone.out
// RUN: %target-run %t/a.swift5.Onone.out
//
// REQUIRES: executable_test

import StdlibUnittest

let tupleCastTests = TestSuite("Tuple casting")

func anyToIntPoint(_ x: Any) -> (x: Int, y: Int) {
  return x as! (x: Int, y: Int)
}

func anyToIntPointOpt(_ x: Any) -> (x: Int, y: Int)? {
  return x as? (x: Int, y: Int)
}

func anyToInt2(_ x: Any) -> (Int, Int) {
  return x as! (Int, Int)
}

func anyToPartlyLabeled(_ x: Any) -> (first: Int, Int, third: Int) {
  return x as! (first: Int, Int, third: Int)
}

tupleCastTests.test("Adding/removing labels") {
  expectEqual("(x: 1, y: 2)",
              String(describing: anyToIntPoint((x: 1, y: 2))))
  expectEqual("(x: 3, y: 4)",
              String(describing: anyToIntPoint((3, 4))))
  expectEqual("(x: 5, y: 6)",
              String(describing: anyToIntPoint((x: 5, 6))))
  expectEqual("(x: 5, y: 6)",
              String(describing: anyToIntPoint((5, y: 6))))

  expectEqual("(1, 2)", String(describing: anyToInt2((1, 2))))
  expectEqual("(3, 4)", String(describing: anyToInt2((x: 3, y: 4))))
  expectEqual("(5, 6)", String(describing: anyToInt2((x: 5, 6))))
  expectEqual("(7, 8)", String(describing: anyToInt2((7, y: 8))))

  expectEqual("(first: 1, 2, third: 3)",
              String(describing: anyToPartlyLabeled((1, 2, 3))))
}

tupleCastTests.test("Label checks on casting") {
  expectTrue((x: 1, y: 2) is (Int, Int))
  expectTrue((x: 1, y: 2) is (x: Int, Int))
  expectTrue((x: 1, y: 2) is (Int, y: Int))
  expectTrue((x: 1, y: 2) is (x: Int, y: Int))

  expectFalse((x: 1, y: 2) is (x: Int, z: Int))
  expectFalse((x: 1, y: 2) is (a: Int, y: Int))
  expectFalse((x: 1, y: 2) is (a: Int, z: Int))
  expectFalse((x: 1, y: 2) is (Int, z: Int))
  expectFalse((x: 1, y: 2) is (a: Int, Int))
}

tupleCastTests.test("Incorrect labels conditional cast") {
  expectNil(anyToIntPointOpt((x: 1, z: 2)))
  expectEqual("Optional((x: 1, y: 2))",
              String(describing: anyToIntPointOpt((x: 1, y: 2))))
}

tupleCastTests
  .test("Incorrect labels forced cast")
  .crashOutputMatches("Could not cast value of type '(x: Swift.Int, z: Swift.Int)'")
  .code {
  expectCrashLater()
  _ = anyToIntPoint((x: 1, z: 2))
}

func castToThree<T, U, V>(_ x: Any, _: T.Type, _: U.Type, _: V.Type)
    -> (t: T, u: U, v: V) {
  return x as! (t: T, u: U, v: V)
}

func castToThreeOpt<T, U, V>(_ x: Any, _: T.Type, _: U.Type, _: V.Type)
    -> (t: T, u: U, v: V)? {
  return x as? (t: T, u: U, v: V)
}

class LifetimeA {
  var tracked: LifetimeTracked

  init(value: Int) { 
    tracked = LifetimeTracked(value)
  }
}

class LifetimeB : LifetimeA {
}

class LifetimeC : LifetimeA {
}

protocol P { }
extension LifetimeA : P { }

tupleCastTests.test("Elementwise tuple casts that succeed") {
  let abc: (P, Any, P) = (LifetimeA(value: 1),
                          LifetimeB(value: 2),
                          LifetimeC(value: 3))

  expectEqual(
    "(t: main.LifetimeA, u: main.LifetimeB, v: main.LifetimeC)",
    String(describing: castToThree(abc, LifetimeA.self, LifetimeA.self,
                                   LifetimeA.self)))

  expectEqual(
    "(t: main.LifetimeA, u: main.LifetimeB, v: main.LifetimeC)",
    String(describing: castToThree((LifetimeA(value: 1),
                                    LifetimeB(value: 2),
                                    LifetimeC(value: 3)) as (P, Any, P),
                                   LifetimeA.self, LifetimeA.self,
                                   LifetimeA.self)))
}

tupleCastTests.test("Elementwise tuple casts that conditionally fail") {
  let abc: (P, Any, P) = (LifetimeA(value: 1),
                          LifetimeB(value: 2),
                          LifetimeC(value: 3))
  expectNil(castToThreeOpt(abc, LifetimeA.self, LifetimeB.self, LifetimeB.self))
  expectNil(castToThreeOpt(abc, LifetimeA.self, LifetimeC.self, LifetimeC.self))
  expectNil(castToThreeOpt(abc, LifetimeC.self, LifetimeB.self, LifetimeC.self))
}

tupleCastTests
  .test("Elementwise tuple casts that crash (1/3)")
  .crashOutputMatches("Could not cast value of type 'main.LifetimeA'")
  .code {
  let abc: (P, Any, P) = (LifetimeA(value: 1),
                          LifetimeB(value: 2),
                          LifetimeC(value: 3))
  expectCrashLater()
  _ = castToThree(abc, LifetimeC.self, LifetimeB.self, LifetimeC.self)
}

tupleCastTests
  .test("Elementwise tuple casts that crash (2/3)")
  .crashOutputMatches("Could not cast value of type 'main.LifetimeB")
  .code {
  let abc: (P, Any, P) = (LifetimeA(value: 1),
                          LifetimeB(value: 2),
                          LifetimeC(value: 3))
  expectCrashLater()
  _ = castToThree(abc, LifetimeA.self, LifetimeC.self, LifetimeC.self)
}

tupleCastTests
  .test("Elementwise tuple casts that crash (3/3)")
  .crashOutputMatches("Could not cast value of type 'main.LifetimeC")
  .code {
  let abc: (P, Any, P) = (LifetimeA(value: 1),
                          LifetimeB(value: 2),
                          LifetimeC(value: 3))
  expectCrashLater()
  _ = castToThree(abc, LifetimeA.self, LifetimeB.self, LifetimeB.self)
}

runAllTests()
