// RUN: %check_clang_tidy %s readability-convert-member-functions-to-static %t

class DoNotMakeEmptyStatic {
  void emptyMethod() {}
  void empty_method_out_of_line();
};

void DoNotMakeEmptyStatic::empty_method_out_of_line() {}

class A {
  int field;
  const int const_field;
  static int static_field;

  void no_use() {
    // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'no_use' can be made static
    // CHECK-FIXES: {{^}}  static void no_use() {
    int i = 1;
  }

  int read_field() {
    return field;
  }

  void write_field() {
    field = 1;
  }

  int call_non_const_member() { return read_field(); }

  int call_static_member() {
    // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: method 'call_static_member' can be made static
    // CHECK-FIXES: {{^}}  static int call_static_member() {
    already_static();
  }

  int read_static() {
    // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: method 'read_static' can be made static
    // CHECK-FIXES: {{^}}  static int read_static() {
    return static_field;
  }
  void write_static() {
    // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'write_static' can be made static
    // CHECK-FIXES: {{^}}  static void write_static() {
    static_field = 1;
  }

  void static_nested() {
    // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'static_nested' can be made static
    // CHECK-FIXES: {{^}}  static void static_nested() {
    struct Nested {
      int Foo;
      int getFoo() { return Foo; }
    };
  }

  void write_nested() {
    struct Nested {
      int Foo;
      int getFoo() { return Foo; }
    };
    // Ensure we still detect usages of `this` once we leave the nested class definition.
    field = 1;
  }

  static int already_static() { return static_field; }

  int already_const() const { return field; }

  int already_const_convert_to_static() const {
    // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: method 'already_const_convert_to_static' can be made static
    // CHECK-FIXES: {{^}}  static int already_const_convert_to_static() {
    return static_field;
  }

  static int out_of_line_already_static();

  void out_of_line_call_static();
  // CHECK-FIXES: {{^}}  static void out_of_line_call_static();
  int out_of_line_const_to_static() const;
  // CHECK-FIXES: {{^}}  static int out_of_line_const_to_static() ;
};

int A::out_of_line_already_static() { return 0; }

void A::out_of_line_call_static() {
  // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: method 'out_of_line_call_static' can be made static
  // CHECK-FIXES: {{^}}void A::out_of_line_call_static() {
  already_static();
}

int A::out_of_line_const_to_static() const {
  // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'out_of_line_const_to_static' can be made static
  // CHECK-FIXES: {{^}}int A::out_of_line_const_to_static() {
  return 0;
}

struct KeepVirtual {
  virtual int f() { return 0; }
  virtual int h() const { return 0; }
};

struct KeepVirtualDerived : public KeepVirtual {
  int f() { return 0; }
  int h() const override { return 0; }
};

// Don't add 'static' to special member functions and operators.
struct KeepSpecial {
  KeepSpecial() { int L = 0; }
  ~KeepSpecial() { int L = 0; }
  int operator+() { return 0; }
  operator int() { return 0; }
};

void KeepLambdas() {
  using FT = int (*)();
  auto F = static_cast<FT>([]() { return 0; });
  auto F2 = []() { return 0; };
}

template <class Base>
struct KeepWithTemplateBase : public Base {
  int i;
  // We cannot make these methods static because they might need to override
  // a function from Base.
  int static_f() { return 0; }
};

template <class T>
struct KeepTemplateClass {
  int i;
  // We cannot make these methods static because a specialization
  // might use *this differently.
  int static_f() { return 0; }
};

struct KeepTemplateMethod {
  int i;
  // We cannot make these methods static because a specialization
  // might use *this differently.
  template <class T>
  static int static_f() { return 0; }
};

void instantiate() {
  struct S {};
  KeepWithTemplateBase<S> I1;
  I1.static_f();

  KeepTemplateClass<int> I2;
  I2.static_f();

  KeepTemplateMethod I3;
  I3.static_f<int>();
}

struct Trailing {
  auto g() const -> int {
    // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'g' can be made static
    // CHECK-FIXES: {{^}}  static auto g() -> int {
    return 0;
  }

  void vol() volatile {
    // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'vol' can be made static
    return;
  }

  void ref() const & {
    // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'ref' can be made static
    return;
  }
  void refref() const && {
    // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'refref' can be made static
    return;
  }

  void restr() __restrict {
    // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'restr' can be made static
    return;
  }
};

struct UnevaluatedContext {
  void f() { sizeof(this); }

  void noex() noexcept(noexcept(this));
};

struct LambdaCapturesThis {
  int Field;

  int explicitCapture() {
    return [this]() { return Field; }();
  }

  int implicitCapture() {
    return [&]() { return Field; }();
  }
};

struct NoFixitInMacro {
#define CONST const
  int no_use_macro_const() CONST {
    // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: method 'no_use_macro_const' can be made static
    return 0;
  }

#define ADD_CONST(F) F const
  int ADD_CONST(no_use_macro2()) {
    return 0;
  }

#define FUN no_use_macro()
  int i;
  int FUN {
    return i;
  }

#define T(FunctionName, Keyword) \
  Keyword int FunctionName() { return 0; }
#define EMPTY
  T(A, EMPTY)
  T(B, static)

#define T2(FunctionName) \
  int FunctionName() { return 0; }
  T2(A2)

#define VOLATILE volatile
  void volatileMacro() VOLATILE {
    // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'volatileMacro' can be made static
    return;
  }
};
