// RUN: %target-swift-emit-silgen -Xllvm -sil-print-types -module-name existential_erasure %s | %FileCheck %s

protocol P {
  func downgrade(_ m68k: Bool) -> Self
  func upgrade() throws -> Self
}
protocol Q {}

struct X: P, Q {
  func downgrade(_ m68k: Bool) -> X {
    return self
  }

  func upgrade() throws -> X {
    return self
  }
}

func makePQ() -> P & Q { return X() }

func useP(_ x: P) { }

func throwingFunc() throws -> Bool { return true }

// CHECK-LABEL: sil hidden [ossa] @$s19existential_erasure5PQtoPyyF : $@convention(thin) () -> () {
func PQtoP() {
  // CHECK: [[PQ_PAYLOAD:%.*]] = open_existential_addr immutable_access [[PQ:%.*]] : $*any P & Q to $*[[OPENED_TYPE:@opened\(.*, any P & Q\) Self]]
  // CHECK: [[P_PAYLOAD:%.*]] = init_existential_addr [[P:%.*]] : $*any P, $[[OPENED_TYPE]]
  // CHECK: copy_addr [[PQ_PAYLOAD]] to [init] [[P_PAYLOAD]]
  // CHECK: destroy_addr [[PQ]]
  // CHECK-NOT: destroy_addr [[P]]
  // CHECK-NOT: destroy_addr [[P_PAYLOAD]]
  // CHECK-NOT: deinit_existential_addr [[PQ]]
  // CHECK-NOT: destroy_addr [[PQ_PAYLOAD]]
  useP(makePQ())
}

// Make sure uninitialized existentials are properly deallocated when we
// have an early return.

// CHECK-LABEL: sil hidden [ossa] @$s19existential_erasure19openExistentialToP1yyAA1P_pKF
func openExistentialToP1(_ p: P) throws {
// CHECK: bb0(%0 : $*any P):
// CHECK:   [[OPEN:%.*]] = open_existential_addr immutable_access %0 : $*any P to $*[[OPEN_TYPE:@opened\(.*, any P\) Self]]
// CHECK:   [[RESULT:%.*]] = alloc_stack $any P
// CHECK:   [[FUNC:%.*]] = function_ref @$s19existential_erasure12throwingFuncSbyKF
// CHECK:   try_apply [[FUNC]]()
//
// CHECK: bb1([[SUCCESS:%.*]] : $Bool):
// CHECK:   [[METHOD:%.*]] = witness_method $[[OPEN_TYPE]], #P.downgrade : {{.*}}, [[OPEN]]
// CHECK:   [[RESULT_ADDR:%.*]] = init_existential_addr [[RESULT]] : $*any P, $[[OPEN_TYPE]]
// CHECK:   apply [[METHOD]]<[[OPEN_TYPE]]>([[RESULT_ADDR]], [[SUCCESS]], [[OPEN]])
// CHECK:   dealloc_stack [[RESULT]]
// CHECK:   return
//
// CHECK: bb2([[FAILURE:%.*]] : @owned $any Error):
// CHECK:   dealloc_stack [[RESULT]]
// CHECK:   throw [[FAILURE]]
//
  try useP(p.downgrade(throwingFunc()))
}

// CHECK-LABEL: sil hidden [ossa] @$s19existential_erasure19openExistentialToP2yyAA1P_pKF
func openExistentialToP2(_ p: P) throws {
// CHECK: bb0(%0 : $*any P):
// CHECK:   [[OPEN:%.*]] = open_existential_addr immutable_access %0 : $*any P to $*[[OPEN_TYPE:@opened\(.*, any P\) Self]]
// CHECK:   [[RESULT:%.*]] = alloc_stack $any P
// CHECK:   [[METHOD:%.*]] = witness_method $[[OPEN_TYPE]], #P.upgrade : {{.*}}, [[OPEN]]
// CHECK:   [[RESULT_ADDR:%.*]] = init_existential_addr [[RESULT]] : $*any P, $[[OPEN_TYPE]]
// CHECK:   try_apply [[METHOD]]<[[OPEN_TYPE]]>([[RESULT_ADDR]], [[OPEN]])
//
// CHECK: bb1
// CHECK:  dealloc_stack [[RESULT]]
// CHECK:  return
//
// CHECK: bb2([[FAILURE:%.*]]: @owned $any Error):
// CHECK:  deinit_existential_addr [[RESULT]]
// CHECK:  dealloc_stack [[RESULT]]
// CHECK:  throw [[FAILURE]]
//
  try useP(p.upgrade())
}

// Same as above but for boxed existentials

extension Error {
  func returnOrThrowSelf() throws -> Self {
    throw self
  }
}

// CHECK-LABEL: sil hidden [ossa] @$s19existential_erasure12errorHandlerys5Error_psAC_pKF
func errorHandler(_ e: Error) throws -> Error {
// CHECK: bb0([[ARG:%.*]] : @guaranteed $any Error):
// CHECK:  debug_value [[ARG]] : $any Error
// CHECK:  [[OPEN:%.*]] = open_existential_box [[ARG]] : $any Error to $*[[OPEN_TYPE:@opened\(.*, any Error\) Self]]
// CHECK:  [[FUNC:%.*]] = function_ref @$ss5ErrorP19existential_erasureE17returnOrThrowSelf{{[_0-9a-zA-Z]*}}F
// CHECK:  [[RESULT:%.*]] = alloc_existential_box $any Error, $[[OPEN_TYPE]]
// CHECK:  [[ADDR:%.*]] = project_existential_box $[[OPEN_TYPE]] in [[RESULT]] : $any Error
// CHECK:  store [[RESULT]] to [init] [[RESULT_BUF:%.*]] :
// CHECK:  try_apply [[FUNC]]<[[OPEN_TYPE]]>([[ADDR]], [[OPEN]])
//
// CHECK: bb1
// CHECK:  [[RESULT2:%.*]] = load [take] [[RESULT_BUF]]
// CHECK:  return [[RESULT2]] : $any Error
//
// CHECK: bb2([[FAILURE:%.*]] : @owned $any Error):
// CHECK:  [[RESULT3:%.*]] = load [take] [[RESULT_BUF]]
// CHECK:  dealloc_existential_box [[RESULT3]]
// CHECK:  throw [[FAILURE]] : $any Error
//
  return try e.returnOrThrowSelf()
}


// rdar://problem/22003864 -- SIL verifier crash when init_existential_addr
// references dynamic Self type
class EraseDynamicSelf {
  required init() {}

// CHECK-LABEL: sil hidden [ossa] @$s19existential_erasure16EraseDynamicSelfC7factoryACXDyFZ : $@convention(method) (@thick EraseDynamicSelf.Type) -> @owned EraseDynamicSelf
// CHECK:  [[ANY:%.*]] = alloc_stack $Any
// CHECK:  init_existential_addr [[ANY]] : $*Any, $@dynamic_self EraseDynamicSelf
//
  class func factory() -> Self {
    let instance = self.init()
    let _: Any = instance
    return instance
  }

// CHECK-LABEL: sil hidden [ossa] @$s19existential_erasure16EraseDynamicSelfC23eraseToClassExistentialyXlyF : $@convention(method) (@guaranteed EraseDynamicSelf) -> @owned AnyObject
// CHECK: bb0([[ARG:%.*]] : @guaranteed $EraseDynamicSelf):
// CHECK:  debug_value [[ARG]] : $EraseDynamicSelf, let, name "self", argno 1
// CHECK:  [[METATYPE:%.*]] = metatype $@thick @dynamic_self EraseDynamicSelf.Type
// CHECK:  [[UPCAST:%.*]] = upcast [[METATYPE]] : $@thick @dynamic_self EraseDynamicSelf.Type to $@thick EraseDynamicSelf.Type
// CHECK:  [[METHOD:%.*]] = class_method [[UPCAST]] : $@thick EraseDynamicSelf.Type, #EraseDynamicSelf.factory
// CHECK:  [[INSTANCE:%.*]] = apply [[METHOD]]([[UPCAST]]) : $@convention(method) (@thick EraseDynamicSelf.Type) -> @owned EraseDynamicSelf
// CHECK:  [[EXISTENTIAL:%.*]] = init_existential_ref [[INSTANCE]] : $EraseDynamicSelf : $@dynamic_self EraseDynamicSelf, $AnyObject
// CHECK:  return [[EXISTENTIAL]] : $AnyObject
  func eraseToClassExistential() -> AnyObject {
    return Self.factory()
  }
}
