// RUN: %batch-code-completion -debug-forbid-typecheck-prefix FORBIDDEN

enum E {
  case e
  case f(Int)
}

struct SomeError: Error {}

func ifExprDotReturn() -> E {
  if .random() {
    .#^DOT1?check=DOT^#
  } else {
    .e
  }
}

func switchExprDotReturn() -> E {
  switch Bool.random() {
  case true:
    .e
  case false:
    .#^DOT2?check=DOT^#
  }
}

func ifExprDotClosureReturn() -> E {
  let x: E = {
    if .random() {
      .e
    } else {
      .#^DOT3?check=DOT^#
    }
  }()
  return x
}

func switchExprDotClosureReturn() -> E {
  let x: E = {
    switch Bool.random() {
    case true:
      .#^DOT4?check=DOT^#
    case false:
      .e
    }
  }()
  return x
}

func ifExprBranchInferenceReturn1() -> E {
  let fn = {
    if .random() {
      E.e
    } else {
      .#^DOT5?check=DOT^#
    }
  }
  return fn()
}

func switchExprBranchInferenceReturn1() -> E {
  let fn = {
    switch Bool.random() {
    case true:
      E.e
    case false:
      .#^DOT6?check=DOT^#
    }
  }
  return fn()
}

func ifExprBranchInferenceReturn2() -> E {
  let fn = {
    if .random() {
      .#^DOT7?check=DOT^#
    } else {
      E.e
    }
  }
  return fn()
}

func switchExprBranchInferenceReturn2() -> E {
  let fn = {
    switch Bool.random() {
    case true:
      .#^DOT8?check=DOT^#
    case false:
      E.e
    }
  }
  return fn()
}

func ifExprBinding() -> E {
  let x: E = 
    if .random() {
      .e
    } else {
      .#^DOT9?check=DOT^#
    }
  return x
}

func switchExprBinding() -> E {
  let x: E =
    switch Bool.random() {
      case true:
        .e
      case false:
        .#^DOT10?check=DOT^#
    }
  return x
}

struct NO {
  // Triggering interface type computation of this will cause an assert to fire,
  // ensuring we don't attempt to type-check any references to it.
  static var TYPECHECK = FORBIDDEN
}

func testSkipTypechecking1() -> E {
  // Make sure we don't try to type-check the first branch, as it doesn't
  // contribute to the type.
  if .random() {
    _ = NO.TYPECHECK
    throw SomeError()
  } else {
    .#^DOT11?check=DOT^#
  }
}

func testSkipTypechecking2() -> E {
  // Make sure we don't try to type-check the second branch, as it doesn't
  // contribute to the type.
  if .random() {
    .#^DOT12?check=DOT^#
  } else {
    _ = NO.TYPECHECK
    throw SomeError()
  }
}

func testSkipTypechecking3() -> E {
  // Make sure we don't try to type-check the first branch, as it doesn't
  // contribute to the type.
  switch Bool.random() {
  case true:
    _ = NO.TYPECHECK
    throw SomeError()
  case false:
    .#^DOT13?check=DOT^#
  }
}

func testSkipTypechecking4() -> E {
  // Make sure we don't try to type-check the second branch, as it doesn't
  // contribute to the type.
  switch Bool.random() {
  case true:
    .#^DOT14?check=DOT^#
  case false:
    _ = NO.TYPECHECK
    throw SomeError()
  }
}

func takesE(_ e: E) {}

func testSkipTypechecking5() throws -> E {
  // Make sure we only type-check the first branch.
  if .random() {
    // We can still skip type-checking this tho.
    x = NO.TYPECHECK

    takesE(.#^DOT15?check=DOT^#)
    throw SomeError()
  } else {
    NO.TYPECHECK
  }
}

func testSkipTypechecking6() throws -> E {
  // Make sure we only type-check the first branch.
  switch Bool.random() {
  case true:
    // We can still skip type-checking this tho.
    x = NO.TYPECHECK

    takesE(.#^DOT16?check=DOT^#)
    throw SomeError()
  case false:
    NO.TYPECHECK
  }
}

enum F {
  case x
}

func takesIntOrDouble(_ i: Int) -> E { E.e }
func takesIntOrDouble(_ i: Double) -> F { F.x }

func testSkipTypechecking7() throws -> E {
  let fn = {
    switch Bool.random() {
    case true:
      .#^DOT17?check=DOT^#
    case false:
      // Make sure we don't end up with an ambiguity here.
      takesIntOrDouble(0)
    }
  }
  return fn()
}

func testSkipTypeChecking8() throws -> E {
  let e: E = if Bool.random() {
    takesE(.#^DOT18?check=DOT^#)
    throw NO.TYPECHECK
  } else {
    NO.TYPECHECK
  }
  return e
}

func testSkipTypeChecking8a() throws -> E {
  // Only need to type-check the inner function in this case.
  let e: E = if Bool.random() {
    func localFunc() {
      takesE(.#^DOT19?check=DOT^#)
    }
    throw NO.TYPECHECK
  } else {
    NO.TYPECHECK
  }
  return e
}

func takesArgAndClosure<T>(_ x: Int, fn: () -> T) -> T { fatalError() }

func testSkipTypeChecking9() -> E {
  // We need to type-check everything for this.
  if Bool.random() {
    .e
  } else {
    takesArgAndClosure(0) {
      .#^DOT20?check=DOT^#
    }
  }
}

func testSkipTypeChecking10() -> E {
  // Similar to the above case, we need to type-check everything for this since
  // the type-checking of 'takesArgAndClosure' is required to correctly handle
  // any potential captures in 'foo'.
  if Bool.random() {
    .e
  } else {
    takesArgAndClosure(0) {
      func foo() {
        // We can however skip unrelated elements in the local function.
        let x = NO.TYPECHECK
        if NO.TYPECHECK {
          takesE(NO.TYPECHECK)
        }
        takesE(.#^DOT21?check=DOT^#)
      }
      return .e
    }
  }
}

func testSkipTypeChecking11() -> E {
  // We only need to type-check the inner-most function for this.
  if Bool.random() {
    NO.TYPECHECK
  } else {
    if NO.TYPECHECK {
      func foo() {
        takesE(.#^DOT22?check=DOT^#)
      }
      throw NO.TYPECHECK
    } else {
      NO.TYPECHECK
    }
  }
}

func testSkipTypeChecking12() -> E {
  // Only need to type-check the condition here.
  if .#^DOT23?check=BOOL^# {
    NO.TYPECHECK
  } else {
    NO.TYPECHECK
  }
}

// BOOL:     Begin completions
// BOOL-DAG: Decl[Constructor]/CurrNominal/IsSystem/TypeRelation[Convertible]: init()[#Bool#]; name=init()
// BOOL-DAG: Decl[StaticMethod]/CurrNominal/Flair[ExprSpecific]/IsSystem/TypeRelation[Convertible]: random()[#Bool#]; name=random()

func testSkipTypeChecking13(_ e: E) -> E {
  // TODO: Currently this requires type-checking the bodies, but we ought to
  // be able to skip them. This is also an issue in closures.
  switch e {
  case .#^DOT24?check=DOT^#:
      .e
  default:
      .e
  }
}

func testSkipTypeChecking14() -> E {
  if Bool.random() {
    .#^DOT25?check=DOT^#
  } else if .random() {
    let x = NO.TYPECHECK
    throw x
  } else if .random() {
    let x = NO.TYPECHECK
    throw x
  } else {
    let x = NO.TYPECHECK
    throw x
  }
}

func testSkipTypeChecking15() -> E {
  switch Bool.random() {
  case true:
    .#^DOT26?check=DOT^#
  case _ where Bool.random():
    let x = NO.TYPECHECK
    throw x
  case _ where Bool.random():
    let x = NO.TYPECHECK
    throw x
  default:
    let x = NO.TYPECHECK
    throw x
  }
}

func testSkipTypechecking16(_ x: inout Int) -> E {
  switch Bool.random() {
  case true:
    .#^DOT27?check=DOT^#
  case false:
    x = 0
  }
}

// DOT:     Begin completions, 2 items
// DOT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: e[#E#]; name=e
// DOT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: f({#Int#})[#E#]; name=f()

// https://github.com/apple/swift/issues/71384
func testCompleteBinding1(_ e: E) -> Int {
  switch e {
  case .f(let foobar):
    #^BINDING1?check=BINDING^#
  case .e:
    0
  }
}

func testCompleteBinding2(_ x: Int?) -> Int {
  if let foobar = x {
    #^BINDING2?check=BINDING^#
  } else {
    0
  }
}

// BINDING-DAG: Decl[LocalVar]/Local/TypeRelation[Convertible]: foobar[#Int#]; name=foobar
