// RUN: %target-typecheck-verify-swift

// REQUIRES: objc_interop
import Foundation

struct S0 {
  init!(int: Int) { }
  init! (uint: UInt) { }
  init !(float: Float) { }

  init?(string: String) { }
  init ?(double: Double) { }
  init ? (char: Character) { }
}

struct S1<T> {
  init?(value: T) { }
}

class DuplicateDecls {
  init!() { } // expected-note{{'init()' previously declared here}}
  init?() { } // expected-error{{invalid redeclaration of 'init()'}}

  init!(string: String) { } // expected-note{{'init(string:)' previously declared here}}
  init(string: String) { } // expected-error{{invalid redeclaration of 'init(string:)'}}

  init(double: Double) { } // expected-note{{'init(double:)' previously declared here}}
  init?(double: Double) { } // expected-error{{invalid redeclaration of 'init(double:)'}}
}

// Construct via a failable initializer.
func testConstruction(_ i: Int, s: String) {
  let s0Opt = S0(string: s)
  assert(s0Opt != nil)
  var _: S0 = s0Opt // expected-error{{value of optional type 'S0?' not unwrapped; did you mean to use '!' or '?'?}} {{20-20=!}}
  
  let s0IUO = S0(int: i)
  assert(s0IUO != nil)
  
  _ = s0IUO
}

// ----------------------------------------------------------------------------
// Superclass initializer chaining
// ----------------------------------------------------------------------------
class Super {
  init?(fail: String) { }
  init!(failIUO: String) { }
  init() { } // expected-note 2{{non-failable initializer 'init()' overridden here}}
}

class Sub : Super {
  override init() { super.init() } // okay, never fails

  init(nonfail: Int) { // expected-note{{propagate the failure with 'init?'}}{{7-7=?}}
    super.init(fail: "boom") // expected-error{{a non-failable initializer cannot chain to failable initializer 'init(fail:)' written with 'init?'}}
    // expected-note@-1{{force potentially-failing result with '!'}}{{29-29=!}}
  }

  convenience init(forceNonfail: Int) {
    self.init(nonfail: forceNonfail)! // expected-error{{cannot force unwrap value of non-optional type '()'}} {{37-38=}}
  }

  init(nonfail2: Int) { // okay, traps on nil
    super.init(failIUO: "boom")
  }

  init(nonfail3: Int) {
    super.init(fail: "boom")!
  }

  override init?(fail: String) {
    super.init(fail: fail) // okay, propagates ?
  }

  init?(fail2: String) { // okay, propagates ! as ?
    super.init(failIUO: fail2)
  }

  init?(fail3: String) { // okay, can introduce its own failure
    super.init()
  }

  override init!(failIUO: String) {
    super.init(failIUO: failIUO) // okay, propagates !
  }

  init!(failIUO2: String) { // okay, propagates ? as !
    super.init(fail: failIUO2)
  }

  init!(failIUO3: String) { // okay, can introduce its own failure
    super.init()
  }
}

// ----------------------------------------------------------------------------
// Initializer delegation
// ----------------------------------------------------------------------------
extension Super {
  convenience init(convenienceNonFailNonFail: String) { // okay, non-failable
    self.init()
  }

  convenience init(convenienceNonFailFail: String) { // expected-note{{propagate the failure with 'init?'}}{{19-19=?}}
    self.init(fail: convenienceNonFailFail) // expected-error{{a non-failable initializer cannot delegate to failable initializer 'init(fail:)' written with 'init?'}}
    // expected-note@-1{{force potentially-failing result with '!'}}{{44-44=!}}
  }

  convenience init(convenienceNonFailFailForce: String) {
    self.init(fail: convenienceNonFailFailForce)!
  }

  convenience init(convenienceNonFailFailIUO: String) { // okay, trap on failure
    self.init(failIUO: convenienceNonFailFailIUO) 
  }

  convenience init?(convenienceFailNonFail: String) { 
    self.init() // okay, can introduce its own failure
  }

  convenience init?(convenienceFailFail: String) {
    self.init(fail: convenienceFailFail) // okay, propagates ?
  }

  convenience init?(convenienceFailFailIUO: String) { // okay, propagates ! as ?
    self.init(failIUO: convenienceFailFailIUO) 
  }

  convenience init!(convenienceFailIUONonFail: String) { 
    self.init() // okay, can introduce its own failure
  }

  convenience init!(convenienceFailIUOFail: String) {
    self.init(fail: convenienceFailIUOFail) // okay, propagates ? as !
  }

  convenience init!(convenienceFailIUOFailIUO: String) { // okay, propagates !
    self.init(failIUO: convenienceFailIUOFailIUO) 
  }
}

struct SomeStruct {
   init(nonFail: Int) { // expected-note{{propagate the failure with 'init?'}}{{8-8=?}}
    self.init(fail: nonFail) // expected-error{{a non-failable initializer cannot delegate to failable initializer 'init(fail:)' written with 'init?'}}
    // expected-note@-1{{force potentially-failing result with '!'}}{{29-29=!}}
  }

   init(nonFail2: Int) {
    self.init(fail: nonFail2)!
  }

  init?(fail: Int) {}
}

// ----------------------------------------------------------------------------
// Initializer overriding
// ----------------------------------------------------------------------------
class Sub2 : Super {
  override init!(fail: String) { // okay to change ? to !
    super.init(fail: fail) 
  }
  override init?(failIUO: String) { // okay to change ! to ?
    super.init(failIUO: failIUO)
  }

  override init() { super.init() } // no change
}

// Dropping optionality
class Sub3 : Super {
  override init(fail: String) { // okay, strengthened result type
    super.init()
  }

  override init(failIUO: String) { // okay, strengthened result type
    super.init()
  }

  override init() { } // no change
}

// Adding optionality
class Sub4 : Super {
  override init?(fail: String) { super.init() }
  override init!(failIUO: String) { super.init() }

  override init?() { // expected-error{{failable initializer 'init()' cannot override a non-failable initializer}}
    super.init()
  }
}

class Sub5 : Super {
  override init?(fail: String) { super.init() }
  override init!(failIUO: String) { super.init() }

  override init!() { // expected-error{{failable initializer 'init()' cannot override a non-failable initializer}}
    super.init()
  }
}

// ----------------------------------------------------------------------------
// Initializer conformances
// ----------------------------------------------------------------------------
protocol P1 {
  init(string: String)
}

@objc protocol P1_objc {
  init(string: String)
}

protocol P2 {
  init?(fail: String)
}

protocol P3 {
  init!(failIUO: String)
}

class C1a : P1 {
  required init?(string: String) { } // expected-error{{non-failable initializer requirement 'init(string:)' cannot be satisfied by a failable initializer ('init?')}}
}

class C1b : P1 {
  required init!(string: String) { } // okay
}

class C1b_objc : P1_objc {
  @objc required init!(string: String) { } // expected-error{{non-failable initializer requirement 'init(string:)' in Objective-C protocol cannot be satisfied by a failable initializer ('init!')}}
}

class C2a : P2 {
  required init(fail: String) { } // okay to remove failability
}

class C2b : P2 {
  required init?(fail: String) { } // okay, ? matches
}

class C2c : P2 {
  required init!(fail: String) { } // okay to satisfy init? with init!
}

class C3a : P3 {
  required init(failIUO: String) { } // okay to remove failability
}

class C3b : P3 {
  required init?(failIUO: String) { } // okay to satisfy ! with ?
}

class C3c : P3 {
  required init!(failIUO: String) { } // okay, ! matches
}


// ----------------------------------------------------------------------------
// Initiating failure
// ----------------------------------------------------------------------------
struct InitiateFailureS {
  init(string: String) { // expected-note{{use 'init?' to make the initializer 'init(string:)' failable}}{{7-7=?}}
    return (nil) // expected-error{{only a failable initializer can return 'nil'}}
  }

  init(int: Int) {
    return 0 // expected-error{{'nil' is the only return value permitted in an initializer}}
  }

  init?(double: Double) {
    return nil // ok
  }

  init!(char: Character) {
    return nil // ok
  }
}
