// RUN: %check_clang_tidy -std=c++20 %s misc-coroutine-hostile-raii %t \
// RUN:   -config="{CheckOptions: {\
// RUN:             misc-coroutine-hostile-raii.RAIITypesList: 'my::Mutex; ::my::other::Mutex', \
// RUN:             misc-coroutine-hostile-raii.AllowedAwaitablesList: 'safe::awaitable; ::transformable::awaitable' \
// RUN:             }}"

namespace std {

template <typename R, typename...> struct coroutine_traits {
  using promise_type = typename R::promise_type;
};

template <typename Promise = void> struct coroutine_handle;

template <> struct coroutine_handle<void> {
  static coroutine_handle from_address(void *addr) noexcept {
    coroutine_handle me;
    me.ptr = addr;
    return me;
  }
  void operator()() { resume(); }
  void *address() const noexcept { return ptr; }
  void resume() const {  }
  void destroy() const { }
  bool done() const { return true; }
  coroutine_handle &operator=(decltype(nullptr)) {
    ptr = nullptr;
    return *this;
  }
  coroutine_handle(decltype(nullptr)) : ptr(nullptr) {}
  coroutine_handle() : ptr(nullptr) {}
  //  void reset() { ptr = nullptr; } // add to P0057?
  explicit operator bool() const { return ptr; }

protected:
  void *ptr;
};

template <typename Promise> struct coroutine_handle : coroutine_handle<> {
  using coroutine_handle<>::operator=;

  static coroutine_handle from_address(void *addr) noexcept {
    coroutine_handle me;
    me.ptr = addr;
    return me;
  }

  Promise &promise() const {
    return *reinterpret_cast<Promise *>(
        __builtin_coro_promise(ptr, alignof(Promise), false));
  }
  static coroutine_handle from_promise(Promise &promise) {
    coroutine_handle p;
    p.ptr = __builtin_coro_promise(&promise, alignof(Promise), true);
    return p;
  }
};

struct suspend_always {
  bool await_ready() noexcept { return false; }
  void await_suspend(std::coroutine_handle<>) noexcept {}
  void await_resume() noexcept {}
};
} // namespace std

struct ReturnObject {
    struct promise_type {
        ReturnObject get_return_object() { return {}; }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() {}
        std::suspend_always yield_value(int value) { return {}; }
    };
};

#define SCOPED_LOCKABLE __attribute__ ((scoped_lockable))

namespace absl {
class SCOPED_LOCKABLE Mutex {};
using Mutex2 = Mutex;
} // namespace absl

ReturnObject BasicWarning() {
  absl::Mutex mtx;
  // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: 'mtx' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
  int no_warning;
  {
    co_yield 1;
    // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here
  }
}

ReturnObject BasicNoWarning() {
  co_yield 1;
  {  absl::Mutex no_warning; }
  int no_warning;
  {
    co_yield 1;
    absl::Mutex no_warning;
  }
  co_yield 1;
}

ReturnObject scopedLockableTest() {
    co_yield 0;
    absl::Mutex a;
    // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 'a' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
    absl::Mutex2 b;
    // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: 'b' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
    {
        absl::Mutex no_warning_1;
        { absl::Mutex no_warning_2; }
    }

    co_yield 1;
    // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here
    absl::Mutex c;
    // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 'c' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
    co_await std::suspend_always{};
    // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here
    for(int i=1; i<=10; ++i ) {
      absl::Mutex d;
      // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 'd' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
      co_await std::suspend_always{};
      // CHECK-MESSAGES: :[[@LINE-1]]:7: note: suspension point is here
      co_yield 1;
      absl::Mutex no_warning_3;
    }
    if (true) {
      absl::Mutex e;
      // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 'e' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
      co_yield 1;
      // CHECK-MESSAGES: :[[@LINE-1]]:7: note: suspension point is here
      absl::Mutex no_warning_4;
    }
    absl::Mutex no_warning_5;
}

// ================================================================================
// Safe awaitable
// ================================================================================
namespace safe {
  struct awaitable {
  bool await_ready() noexcept { return false; }
  void await_suspend(std::coroutine_handle<>) noexcept {}
  void await_resume() noexcept {}
};
} // namespace safe
ReturnObject RAIISafeSuspendTest() {
  absl::Mutex a;
  co_await safe::awaitable{};
  using other = safe::awaitable;
  co_await other{};
} 

// ================================================================================
// Safe transformable awaitable
// ================================================================================
struct transformable { struct awaitable{}; };
using alias_transformable_awaitable = transformable::awaitable;
struct UseTransformAwaitable {
  struct promise_type {
    UseTransformAwaitable get_return_object() { return {}; }
    std::suspend_always initial_suspend() { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }
    void unhandled_exception() {}
    std::suspend_always await_transform(transformable::awaitable) { return {}; }
  };
};

auto retAwaitable() { return transformable::awaitable{}; }
UseTransformAwaitable RAIISafeSuspendTest2() {
  absl::Mutex a;
  co_await retAwaitable();
  co_await transformable::awaitable{};
  co_await alias_transformable_awaitable{};
}

// ================================================================================
// Lambdas
// ================================================================================
void lambda() {
  absl::Mutex no_warning;
  auto lambda = []() -> ReturnObject {
    co_await std::suspend_always{};
    absl::Mutex a;
    // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 'a' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
    co_yield 1;
    // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here
    co_await std::suspend_always{};
    co_yield 1;
  };
  absl::Mutex no_warning_2;
}

// ================================================================================
// Denylisted RAII
// ================================================================================
template<class T>
ReturnObject raii_in_template(){
  T a;
  // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'a' holds a lock across a suspension point of coroutine and could be unlocked by a different thread [misc-coroutine-hostile-raii]
  co_yield 1;
  // CHECK-MESSAGES: :[[@LINE-1]]:3: note: suspension point is here
}
void foo_template() { raii_in_template<absl::Mutex>(); }

namespace my {
class Mutex{};
namespace other {
class Mutex{};
} // namespace other

using Mutex2 = Mutex;
} // namespace my

ReturnObject denyListTest() {
    my::Mutex a;
    // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: 'a' persists across a suspension point of coroutine [misc-coroutine-hostile-raii]
    my::other::Mutex b;
    // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 'b' persists across a suspension point of coroutine [misc-coroutine-hostile-raii]
    my::Mutex2 c;
    // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 'c' persists across a suspension point of coroutine [misc-coroutine-hostile-raii]
    co_yield 1;
    // CHECK-MESSAGES: :[[@LINE-1]]:5: note: suspension point is here
}

ReturnObject referenceTest(my::Mutex& ref) {
    my::Mutex& a = ref;
    co_yield 1;
}
ReturnObject pointerTest(my::Mutex* ref) {
    my::Mutex* a = ref;
    co_yield 1;
}

ReturnObject functionArgTest(my::Mutex ref) {
    co_yield 1;
}
