// RUN: %clang_cc1 -std=c++2c -verify -emit-llvm -triple=x86_64-pc-linux-gnu %s -o - | FileCheck %s
// RUN: %clang_cc1 -std=c++2c -verify -fsyntax-only -fexperimental-new-constant-interpreter -triple=x86_64-pc-linux-gnu %s
// expected-no-diagnostics

namespace std {

template <typename T> struct tuple_size;

template <int, typename> struct tuple_element;

} // namespace std

namespace Case1 {

struct S {
  int a, b;
  bool flag = false;

  constexpr explicit operator bool() {
    flag = true;
    return a != b;
  }

  constexpr operator int() {
    flag = true;
    return a * b;
  }

  constexpr bool operator==(S rhs) const {
    return a == rhs.a && b == rhs.b;
  }

  template <int I>
  constexpr int& get() {
    if (!flag)
      return a = a + b;
    return I == 0 ? a : b;
  }
};

} // namespace Case1

template <> struct std::tuple_size<Case1::S> {
  static const int value = 2;
};

template <int I> struct std::tuple_element<I, Case1::S> {
  using type = int;
};

namespace Case1 {

void foo() {
  if (S s(1, 2); auto [a, b] = s) {
    __builtin_assume(a == 1);
    __builtin_assume(b == 2);
  }
// CHECK: %[[call:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
// CHECK: br i1 %[[call]], label {{.*}}, label {{.*}}

  if (auto [a, b] = S(1, 2)) {
    __builtin_assume(a == 1);
    __builtin_assume(b == 2);
  }
// CHECK: %[[call2:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
// CHECK: br i1 %[[call2]], label {{.*}}, label {{.*}}

  if (S s(3, 4); auto& [a, b] = s) {
    __builtin_assume(a == 3);
    __builtin_assume(b == 4);
  }
// CHECK: %[[call3:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
// CHECK: br i1 %[[call3]], label {{.*}}, label {{.*}}

  while (auto [i, j] = S(5, 6))
    break;

// CHECK: while.cond{{.*}}:
// CHECK: %[[call4:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
// CHECK: br i1 %[[call4]], label {{.*}}, label {{.*}}

  S s(7, 8);
  while (auto& [i, j] = s)
    break;

// CHECK: while.cond{{.*}}:
// CHECK: %[[call5:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
// CHECK: br i1 %[[call5]], label {{.*}}, label {{.*}}

  for (int k = 0; auto [i, j] = S(24, 42); ++k)
    break;

// CHECK: for.cond{{.*}}:
// CHECK: %[[call6:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
// CHECK: br i1 %[[call6]], label {{.*}}, label {{.*}}

  for (S s(114, 514); auto& [i, j] = s; ++i)
    break;

// CHECK: for.cond{{.*}}:
// CHECK: %[[call7:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
// CHECK: br i1 %[[call7]], label {{.*}}, label {{.*}}

  switch (S s(10, 11); auto& [i, j] = s) {
    case 10 * 11:
      __builtin_assume(i == 10);
      __builtin_assume(j == 11);
      break;
    default:
      break;
  }

// CHECK: %[[call8:.+]] = call {{.*}} i32 @_ZN5Case11ScviEv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv
// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv
// CHECK: switch i32 %[[call8]], label {{.*}}

}

constexpr int bar(auto) {
  constexpr auto value = [] {
    if (S s(1, 2); auto [i, j] = s)
      return S(i, j);
    return S(0, 0);
  }();
  static_assert(value == S(1, 2));

  constexpr auto value2 = [] {
    if (auto [a, b] = S(1, 2))
      return S(a, b);
    return S(0, 0);
  }();
  static_assert(value2 == S(1, 2));

  constexpr auto value3 = [] {
    if (auto&& [a, b] = S(3, 4))
      return S(a, b);
    return S(0, 0);
  }();
  static_assert(value3 == S(3, 4));

  constexpr auto value4 = [] {
    S s(7, 8);
    int cnt = 0;
    while (auto& [i, j] = s) {
      s.flag = false;
      ++i, ++j;
      if (++cnt == 10)
        break;
    }
    return s;
  }();
  static_assert(value4 == S(17, 18));

  constexpr auto value5 = [] {
    S s(3, 4);
    for (int cnt = 0; auto& [x, y] = s; s.flag = false, ++cnt) {
      if (cnt == 3)
        break;
      ++x, ++y;
    }
    return s;
  }();
  static_assert(value5 == S(6, 7));

  constexpr auto value6 = [] {
    switch (auto [x, y] = S(3, 4)) {
      case 3 * 4:
        return S(x, y);
      default:
        return S(y, x);
    }
  }();
  static_assert(value6 == S(3, 4));

  return 42;
}

constexpr int value = bar(1);

#if 0

// FIXME: This causes clang to ICE, though this is not a regression.
constexpr int ice(auto) {
  if constexpr (S s(1, 2); auto [i, j] = s) {
    static_assert(i == 1);
  }
  return 42;
}

constexpr int value2 = ice(1);

#endif

} // namespace Case1
