// RUN: %target-run-simple-swift(-swift-version 4)
// RUN: %target-run-simple-swift(-swift-version 5)

// REQUIRES: executable_test

import StdlibUnittest


var ThrowingInitTestSuite = TestSuite("ThrowingInit")

enum E : Error {
  case X
}

func unwrap(_ b: Bool) throws -> Int {
  if b {
    throw E.X
  }
  return 0
}

class Bear {
  let x: LifetimeTracked

  /* Designated */
  init(n: Int) {
    x = LifetimeTracked(0)
  }

  init(n: Int, before: Bool) throws {
    if before {
      throw E.X
    }
    self.x = LifetimeTracked(0)
  }

  init(n: Int, after: Bool) throws {
    self.x = LifetimeTracked(0)
    if after {
      throw E.X
    }
  }

  init(n: Int, before: Bool, after: Bool) throws {
    if before {
      throw E.X
    }
    self.x = LifetimeTracked(0)
    if after {
      throw E.X
    }
  }

  /* Convenience */
  convenience init(before: Bool) throws {
    try unwrap(before)
    self.init(n: 0)
  }

  convenience init(before2: Bool) throws {
    try self.init(n: unwrap(before2))
  }

  convenience init(before: Bool, before2: Bool) throws {
    try unwrap(before)
    try self.init(n: unwrap(before2))
  }

  convenience init(during: Bool) throws {
    try self.init(n: 0, after: during)
  }

  convenience init(before: Bool, during: Bool) throws {
    try unwrap(before)
    try self.init(n: 0, after: during)
  }

  convenience init(after: Bool) throws {
    self.init(n: 0)
    try unwrap(after)
  }

  convenience init(before: Bool, after: Bool) throws {
    try unwrap(before)
    self.init(n: 0)
    try unwrap(after)
  }

  convenience init(during: Bool, after: Bool) throws {
    try self.init(n: 0, after: during)
    try unwrap(after)
  }

  convenience init(before: Bool, during: Bool, after: Bool) throws {
    try unwrap(before)
    try self.init(n: 0, after: during)
    try unwrap(after)
  }

  convenience init(before: Bool, before2: Bool, during: Bool, after: Bool) throws {
    try unwrap(before)
    try self.init(n: unwrap(before2), after: during)
    try unwrap(after)
  }
}

class PolarBear : Bear {
  let y: LifetimeTracked

  /* Designated */
  override init(n: Int) {
    self.y = LifetimeTracked(0)
    super.init(n: n)
  }

  override init(n: Int, before: Bool) throws {
    if before {
      throw E.X
    }
    self.y = LifetimeTracked(0)
    super.init(n: n)
  }

  init(n: Int, during: Bool) throws {
    self.y = LifetimeTracked(0)
    try super.init(n: n, before: during)
  }

  init(n: Int, before: Bool, during: Bool) throws {
    self.y = LifetimeTracked(0)
    if before {
      throw E.X
    }
    try super.init(n: n, before: during)
  }

  override init(n: Int, after: Bool) throws {
    self.y = LifetimeTracked(0)
    super.init(n: n)
    if after {
      throw E.X
    }
  }

  init(n: Int, during: Bool, after: Bool) throws {
    self.y = LifetimeTracked(0)
    try super.init(n: n, before: during)
    if after {
      throw E.X
    }
  }

  override init(n: Int, before: Bool, after: Bool) throws {
    if before {
      throw E.X
    }
    self.y = LifetimeTracked(0)
    super.init(n: n)
    if after {
      throw E.X
    }
  }

  init(n: Int, before: Bool, during: Bool, after: Bool) throws {
    if before {
      throw E.X
    }
    self.y = LifetimeTracked(0)
    try super.init(n: n, before: during)
    if after {
      throw E.X
    }
  }
}

class GuineaPig<T> : Bear {
  let y: LifetimeTracked
  let t: T

  init(t: T, during: Bool) throws {
    self.y = LifetimeTracked(0)
    self.t = t
    try super.init(n: 0, before: during)
  }
}

struct Chimera {
  let x: LifetimeTracked
  let y: LifetimeTracked

  init(before: Bool) throws {
    if before {
      throw E.X
    }
    x = LifetimeTracked(0)
    y = LifetimeTracked(0)
  }

  init(during: Bool) throws {
    x = LifetimeTracked(0)
    if during {
      throw E.X
    }
    y = LifetimeTracked(0)
  }

  init(before: Bool, during: Bool) throws {
    if before {
      throw E.X
    }
    x = LifetimeTracked(0)
    if during {
      throw E.X
    }
    y = LifetimeTracked(0)
  }

  init(after: Bool) throws {
    x = LifetimeTracked(0)
    y = LifetimeTracked(0)
    if after {
      throw E.X
    }
  }

  init(before: Bool, after: Bool) throws {
    if before {
      throw E.X
    }
    x = LifetimeTracked(0)
    y = LifetimeTracked(0)
    if after {
      throw E.X
    }
  }

  init(during: Bool, after: Bool) throws {
    x = LifetimeTracked(0)
    if during {
      throw E.X
    }
    y = LifetimeTracked(0)
    if after {
      throw E.X
    }
  }

  init(before: Bool, during: Bool, after: Bool) throws {
    if before {
      throw E.X
    }
    x = LifetimeTracked(0)
    if during {
      throw E.X
    }
    y = LifetimeTracked(0)
    if after {
      throw E.X
    }
  }
}

func mustThrow<T>(_ f: () throws -> T) {
  do {
    _ = try f()
    preconditionFailure("Didn't throw")
  } catch {}
}

ThrowingInitTestSuite.test("DesignatedInitSuccess_Root") {
  _ = try! Bear(n: 0, before: false)
  _ = try! Bear(n: 0, after: false)
  _ = try! Bear(n: 0, before: false, after: false)
}

ThrowingInitTestSuite.test("DesignatedInitFailure_Root") {
  mustThrow { try Bear(n: 0, before: true) }
  mustThrow { try Bear(n: 0, after: true) }
  mustThrow { try Bear(n: 0, before: true, after: false) }
  mustThrow { try Bear(n: 0, before: false, after: true) }
}

ThrowingInitTestSuite.test("DesignatedInitSuccess_Derived") {
  _ = try! PolarBear(n: 0, before: false)
  _ = try! PolarBear(n: 0, during: false)
  _ = try! PolarBear(n: 0, before: false, during: false)
  _ = try! PolarBear(n: 0, during: false, after: false)
  _ = try! PolarBear(n: 0, before: false, after: false)
  _ = try! PolarBear(n: 0, before: false, during: false, after: false)
}

ThrowingInitTestSuite.test("DesignatedInitFailure_Derived") {
  mustThrow { try PolarBear(n: 0, before: true) }
  mustThrow { try PolarBear(n: 0, during: true) }
  mustThrow { try PolarBear(n: 0, before: true, during: false) }
  mustThrow { try PolarBear(n: 0, before: false, during: true) }
  mustThrow { try PolarBear(n: 0, after: true) }
  mustThrow { try PolarBear(n: 0, during: true, after: false) }
  mustThrow { try PolarBear(n: 0, during: false, after: true) }
  mustThrow { try PolarBear(n: 0, before: true, after: false) }
  mustThrow { try PolarBear(n: 0, before: false, after: true) }
  mustThrow { try PolarBear(n: 0, before: true, during: false, after: false) }
  mustThrow { try PolarBear(n: 0, before: false, during: true, after: false) }
  mustThrow { try PolarBear(n: 0, before: false, during: false, after: true) }
}

ThrowingInitTestSuite.test("DesignatedInitSuccess_DerivedGeneric") {
  _ = try! GuineaPig(t: LifetimeTracked(0), during: false)
}

ThrowingInitTestSuite.test("DesignatedInitFailure_DerivedGeneric") {
  mustThrow { try GuineaPig(t: LifetimeTracked(0), during: true) }
}

ThrowingInitTestSuite.test("ConvenienceInitSuccess_Root") {
  _ = try! Bear(before: false)
  _ = try! Bear(before2: false)
  _ = try! Bear(before: false, before2: false)
  _ = try! Bear(during: false)
  _ = try! Bear(before: false, during: false)
  _ = try! Bear(after: false)
  _ = try! Bear(before: false, after: false)
  _ = try! Bear(during: false, after: false)
  _ = try! Bear(before: false, during: false, after: false)
  _ = try! Bear(before: false, before2: false, during: false, after: false)
}

ThrowingInitTestSuite.test("ConvenienceInitFailure_Root") {
  mustThrow { try Bear(before: true) }
  mustThrow { try Bear(before2: true) }
  mustThrow { try Bear(before: true, before2: false) }
  mustThrow { try Bear(before: false, before2: true) }
  mustThrow { try Bear(during: true) }
  mustThrow { try Bear(before: true, during: false) }
  mustThrow { try Bear(before: false, during: true) }
  mustThrow { try Bear(after: true) }
  mustThrow { try Bear(before: true, after: false) }
  mustThrow { try Bear(before: false, after: true) }
  mustThrow { try Bear(during: true, after: false) }
  mustThrow { try Bear(during: false, after: true) }
  mustThrow { try Bear(before: true, during: false, after: false) }
  mustThrow { try Bear(before: false, during: true, after: false) }
  mustThrow { try Bear(before: false, during: false, after: true) }
  mustThrow { try Bear(before: true, before2: false, during: false, after: false) }
  mustThrow { try Bear(before: false, before2: true, during: false, after: false) }
  mustThrow { try Bear(before: false, before2: false, during: true, after: false) }
  mustThrow { try Bear(before: false, before2: false, during: false, after: true) }
}

ThrowingInitTestSuite.test("ConvenienceInitSuccess_Derived") {
  _ = try! PolarBear(before: false)
  _ = try! PolarBear(before2: false)
  _ = try! PolarBear(before: false, before2: false)
  _ = try! PolarBear(during: false)
  _ = try! PolarBear(before: false, during: false)
  _ = try! PolarBear(after: false)
  _ = try! PolarBear(before: false, after: false)
  _ = try! PolarBear(during: false, after: false)
  _ = try! PolarBear(before: false, during: false, after: false)
  _ = try! PolarBear(before: false, before2: false, during: false, after: false)
}

ThrowingInitTestSuite.test("ConvenienceInitFailure_Derived") {
  mustThrow { try PolarBear(before: true) }
  mustThrow { try PolarBear(before2: true) }
  mustThrow { try PolarBear(before: true, before2: false) }
  mustThrow { try PolarBear(before: false, before2: true) }
  mustThrow { try PolarBear(during: true) }
  mustThrow { try PolarBear(before: true, during: false) }
  mustThrow { try PolarBear(before: false, during: true) }
  mustThrow { try PolarBear(after: true) }
  mustThrow { try PolarBear(before: true, after: false) }
  mustThrow { try PolarBear(before: false, after: true) }
  mustThrow { try PolarBear(during: true, after: false) }
  mustThrow { try PolarBear(during: false, after: true) }
  mustThrow { try PolarBear(before: true, during: false, after: false) }
  mustThrow { try PolarBear(before: false, during: true, after: false) }
  mustThrow { try PolarBear(before: false, during: false, after: true) }
  mustThrow { try PolarBear(before: true, before2: false, during: false, after: false) }
  mustThrow { try PolarBear(before: false, before2: true, during: false, after: false) }
  mustThrow { try PolarBear(before: false, before2: false, during: true, after: false) }
  mustThrow { try PolarBear(before: false, before2: false, during: false, after: true) }
}

ThrowingInitTestSuite.test("InitSuccess_Struct") {
  _ = try! Chimera(before: false)
  _ = try! Chimera(during: false)
  _ = try! Chimera(before: false, during: false)
  _ = try! Chimera(after: false)
  _ = try! Chimera(before: false, after: false)
  _ = try! Chimera(during: false, after: false)
  _ = try! Chimera(before: false, during: false, after: false)
}

ThrowingInitTestSuite.test("InitFailure_Struct") {
  mustThrow { try Chimera(before: true) }
  mustThrow { try Chimera(during: true) }
  mustThrow { try Chimera(before: true, during: false) }
  mustThrow { try Chimera(before: false, during: true) }
  mustThrow { try Chimera(after: true) }
  mustThrow { try Chimera(before: true, after: false) }
  mustThrow { try Chimera(before: false, after: true) }
  mustThrow { try Chimera(during: true, after: false) }
  mustThrow { try Chimera(during: false, after: true) }
  mustThrow { try Chimera(before: true, during: false, after: false) }
  mustThrow { try Chimera(before: false, during: true, after: false) }
  mustThrow { try Chimera(before: false, during: false, after: true) }
}

// Specific regression tests:

// <https://bugs.swift.org/browse/SR-1714> - try? self.init(...)` in init? causes overrelease
class ThrowAndFailRoot {
  let x = LifetimeTracked(0)
}

class ThrowAndFailDerived: ThrowAndFailRoot {
  let name: String
  let y = LifetimeTracked(0)

  init(name: String) throws {
    self.name = name
    super.init()
    if name.isEmpty {
      struct EmptyError: Error {}
      throw EmptyError()
    }
  }

  convenience init?(b: Bool) {
    try? self.init(name: b ? "Bob" : "")
  }
}

ThrowingInitTestSuite.test("ThrowsAndFailableTest") {
  _ = ThrowAndFailDerived(b: true)!
  if let x = ThrowAndFailDerived(b: false) { preconditionFailure() }
}

// <https://bugs.swift.org/browse/SR-3132> - Invalid pointer dequeued from free list. Runtime crash on some weird code

runAllTests()
