// RUN: %check_clang_tidy %s cppcoreguidelines-avoid-const-or-ref-data-members %t
namespace std {
template <typename T>
struct unique_ptr {};

template <typename T>
struct shared_ptr {};
} // namespace std

namespace gsl {
template <typename T>
struct not_null {};
} // namespace gsl

struct Ok {
  int i;
  int *p;
  const int *pc;
  std::unique_ptr<int> up;
  std::shared_ptr<int> sp;
  gsl::not_null<int*> n;
};

struct ConstMember {
  const int c;
  // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: member 'c' of type 'const int' is const qualified [cppcoreguidelines-avoid-const-or-ref-data-members]
};

struct LvalueRefMember {
  int &lr;
  // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: member 'lr' of type 'int &' is a reference
};

struct ConstRefMember {
  const int &cr;
  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: member 'cr' of type 'const int &' is a reference
};

struct RvalueRefMember {
  int &&rr;
  // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: member 'rr' of type 'int &&' is a reference
};

struct ConstAndRefMembers {
  const int c;
  // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: member 'c' of type 'const int' is const qualified
  int &lr;
  // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: member 'lr' of type 'int &' is a reference
  const int &cr;
  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: member 'cr' of type 'const int &' is a reference
  int &&rr;
  // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: member 'rr' of type 'int &&' is a reference
};

struct Foo {};

struct Ok2 {
  Foo i;
  Foo *p;
  const Foo *pc;
  std::unique_ptr<Foo> up;
  std::shared_ptr<Foo> sp;
  gsl::not_null<Foo*> n;
};

struct ConstMember2 {
  const Foo c;
  // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: member 'c' of type 'const Foo' is const qualified
};

struct LvalueRefMember2 {
  Foo &lr;
  // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: member 'lr' of type 'Foo &' is a reference
};

struct ConstRefMember2 {
  const Foo &cr;
  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: member 'cr' of type 'const Foo &' is a reference
};

struct RvalueRefMember2 {
  Foo &&rr;
  // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: member 'rr' of type 'Foo &&' is a reference
};

struct ConstAndRefMembers2 {
  const Foo c;
  // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: member 'c' of type 'const Foo' is const qualified
  Foo &lr;
  // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: member 'lr' of type 'Foo &' is a reference
  const Foo &cr;
  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: member 'cr' of type 'const Foo &' is a reference
  Foo &&rr;
  // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: member 'rr' of type 'Foo &&' is a reference
};

using ConstType = const int;
using RefType = int &;
using ConstRefType = const int &;
using RefRefType = int &&;

struct WithAlias {
  ConstType c;
  // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: member 'c' of type 'ConstType' (aka 'const int') is const qualified
  RefType lr;
  // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: member 'lr' of type 'RefType' (aka 'int &') is a reference
  ConstRefType cr;
  // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: member 'cr' of type 'ConstRefType' (aka 'const int &') is a reference
  RefRefType rr;
  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: member 'rr' of type 'RefRefType' (aka 'int &&') is a reference
};

template <int N>
using Array = int[N];

struct ConstArrayMember {
  const Array<1> c;
  // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: member 'c' of type 'const Array<1>' (aka 'const int[1]') is const qualified
};

struct LvalueRefArrayMember {
  Array<2> &lr;
  // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: member 'lr' of type 'Array<2> &' (aka 'int (&)[2]') is a reference
};

struct ConstLvalueRefArrayMember {
  const Array<3> &cr;
  // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: member 'cr' of type 'const Array<3> &' (aka 'const int (&)[3]') is a reference
};

struct RvalueRefArrayMember {
  Array<4> &&rr;
  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: member 'rr' of type 'Array<4> &&' (aka 'int (&&)[4]') is a reference
};

template <typename T>
struct TemplatedOk {
  T t;
};

template <typename T>
struct TemplatedConst {
  T t;
  // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: member 't' of type 'const int' is const qualified
};

template <typename T>
struct TemplatedConstRef {
  T t;
  // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: member 't' of type 'const int &' is a reference
};

template <typename T>
struct TemplatedRefRef {
  T t;
  // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: member 't' of type 'int &&' is a reference
};

template <typename T>
struct TemplatedRef {
  T t;
  // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: member 't' of type 'int &' is a reference
};

TemplatedOk<int> t1{};
TemplatedConst<const int> t2{123};
TemplatedConstRef<const int &> t3{123};
TemplatedRefRef<int &&> t4{123};
TemplatedRef<int &> t5{t1.t};

// Lambdas capturing const or ref members should not trigger warnings
void lambdas()
{
  int x1{123};
  const int x2{123};
  const int& x3{123};
  int&& x4{123};
  int& x5{x1};

  auto v1 = [x1]{};
  auto v2 = [x2]{};
  auto v3 = [x3]{};
  auto v4 = [x4]{};
  auto v5 = [x5]{};

  auto r1 = [&x1]{};
  auto r2 = [&x2]{};
  auto r3 = [&x3]{};
  auto r4 = [&x4]{};
  auto r5 = [&x5]{};

  auto iv = [=]{
    auto c1 = x1;
    auto c2 = x2;
    auto c3 = x3;
    auto c4 = x4;
    auto c5 = x5;
  };

  auto ir = [&]{
    auto c1 = x1;
    auto c2 = x2;
    auto c3 = x3;
    auto c4 = x4;
    auto c5 = x5;
  };
}

struct NonCopyableWithRef
{
  NonCopyableWithRef(NonCopyableWithRef const&) = delete;
  NonCopyableWithRef& operator=(NonCopyableWithRef const&) = delete;
  NonCopyableWithRef(NonCopyableWithRef&&) = default;
  NonCopyableWithRef& operator=(NonCopyableWithRef&&) = default;

  int& x;
  // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: member 'x' of type 'int &' is a reference
};

struct NonMovableWithRef
{
  NonMovableWithRef(NonMovableWithRef const&) = default;
  NonMovableWithRef& operator=(NonMovableWithRef const&) = default;
  NonMovableWithRef(NonMovableWithRef&&) = delete;
  NonMovableWithRef& operator=(NonMovableWithRef&&) = delete;

  int& x;
  // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: member 'x' of type 'int &' is a reference
};

struct NonCopyableNonMovableWithRef
{
  NonCopyableNonMovableWithRef(NonCopyableNonMovableWithRef const&) = delete;
  NonCopyableNonMovableWithRef(NonCopyableNonMovableWithRef&&) = delete;
  NonCopyableNonMovableWithRef& operator=(NonCopyableNonMovableWithRef const&) = delete;
  NonCopyableNonMovableWithRef& operator=(NonCopyableNonMovableWithRef&&) = delete;

  int& x; // OK, non copyable nor movable
};

struct NonCopyable
{
  NonCopyable(NonCopyable const&) = delete;
  NonCopyable& operator=(NonCopyable const&) = delete;
  NonCopyable(NonCopyable&&) = default;
  NonCopyable& operator=(NonCopyable&&) = default;
};

struct NonMovable
{
  NonMovable(NonMovable const&) = default;
  NonMovable& operator=(NonMovable const&) = default;
  NonMovable(NonMovable&&) = delete;
  NonMovable& operator=(NonMovable&&) = delete;
};

struct NonCopyableNonMovable
{
  NonCopyableNonMovable(NonCopyableNonMovable const&) = delete;
  NonCopyableNonMovable(NonCopyableNonMovable&&) = delete;
  NonCopyableNonMovable& operator=(NonCopyableNonMovable const&) = delete;
  NonCopyableNonMovable& operator=(NonCopyableNonMovable&&) = delete;
};

// Test inheritance
struct InheritFromNonCopyable : NonCopyable
{
  int& x;
  // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: member 'x' of type 'int &' is a reference
};

struct InheritFromNonMovable : NonMovable
{
  int& x;
  // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: member 'x' of type 'int &' is a reference
};

struct InheritFromNonCopyableNonMovable : NonCopyableNonMovable
{
  int& x;  // OK, non copyable nor movable
};

struct InheritBothFromNonCopyableAndNonMovable : NonCopyable, NonMovable
{
  int& x;  // OK, non copyable nor movable
};

// Test composition
struct ContainsNonCopyable
{
  NonCopyable x;
  int& y;
  // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: member 'y' of type 'int &' is a reference
};

struct ContainsNonMovable
{
  NonMovable x;
  int& y;
  // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: member 'y' of type 'int &' is a reference
};

struct ContainsNonCopyableNonMovable
{
  NonCopyableNonMovable x;
  int& y;  // OK, non copyable nor movable
};

struct ContainsBothNonCopyableAndNonMovable
{
  NonCopyable x;
  NonMovable y;
  int& z;  // OK, non copyable nor movable
};

// If copies are deleted and moves are not declared, moves are not implicitly declared,
// so the class is also not movable and we should not warn
struct NonCopyableMovesNotDeclared
{
  NonCopyableMovesNotDeclared(NonCopyableMovesNotDeclared const&) = delete;
  NonCopyableMovesNotDeclared& operator=(NonCopyableMovesNotDeclared const&) = delete;

  int& x;  // OK, non copyable nor movable
};

// If moves are deleted but copies are not declared, copies are implicitly deleted,
// so the class is also not copyable and we should not warn
struct NonMovableCopiesNotDeclared
{
  NonMovableCopiesNotDeclared(NonMovableCopiesNotDeclared&&) = delete;
  NonMovableCopiesNotDeclared& operator=(NonMovableCopiesNotDeclared&&) = delete;

  int& x;  // OK, non copyable nor movable
};
