llvm/clang-tools-extra/test/clang-tidy/checkers/readability/const-return-type.cpp

// RUN: %check_clang_tidy -std=c++14-or-later %s readability-const-return-type %t

//  p# = positive test
//  n# = negative test

namespace std {
template< class T >
struct add_cv { typedef const volatile T type; };

template< class T> struct add_const { typedef const T type; };

template< class T> struct add_volatile { typedef volatile T type; };
}

const int p1() {
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const int' is 'const'-qualified at the top level, which may reduce code readability without improving const correctness
// CHECK-FIXES: int p1() {
  return 1;
}

const int p15();
// CHECK-FIXES: int p15();

template <typename T>
const int p31(T v) { return 2; }
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const int' is 'const'-qu
// CHECK-FIXES: int p31(T v) { return 2; }

// We detect const-ness even without instantiating T.
template <typename T>
const T p32(T t) { return t; }
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const T' is 'const'-qual
// CHECK-FIXES: T p32(T t) { return t; }

// However, if the return type is itself a template instantiation, Clang does
// not consider it const-qualified without knowing `T`.
template <typename T>
typename std::add_const<T>::type n15(T v) { return v; }

template <bool B>
struct MyStruct {};

template <typename A>
class Klazz {
public:
  Klazz(A) {}
};

class Clazz {
 public:
  Clazz *const p2() {
    // CHECK-MESSAGES: [[@LINE-1]]:3: warning: return type 'Clazz *const' is 'co
    // CHECK-FIXES: Clazz *p2() {
    return this;
  }

  Clazz *const p3();
  // CHECK-FIXES: Clazz *p3();

  const int p4() const {
    // CHECK-MESSAGES: [[@LINE-1]]:3: warning: return type 'const int' is 'const
    // CHECK-FIXES: int p4() const {
    return 4;
  }

  const Klazz<const int>* const p5() const;
  // CHECK-FIXES: const Klazz<const int>* p5() const;

  const Clazz operator++(int x) {  //  p12
  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: return type 'const Clazz' is 'const
  // CHECK-FIXES: Clazz operator++(int x) {
  }

  struct Strukt {
    int i;
  };

  const Strukt p6() {}
  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: return type 'const Strukt' i
  // CHECK-FIXES: Strukt p6() {}

  // No warning is emitted here, because this is only the declaration.  The
  // warning will be associated with the definition, below.
  const Strukt* const p7();
  // CHECK-FIXES: const Strukt* p7();

  // const-qualifier is the first `const` token, but not the first token.
  static const int p8() {}
  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: return type 'const int' is 'const'-
  // CHECK-FIXES: static int p8() {}

  static const Strukt p9() {}
  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: return type 'const Strukt' i
  // CHECK-FIXES: static Strukt p9() {}

  int n0() const { return 0; }
  const Klazz<const int>& n11(const Klazz<const int>) const;
};

Clazz *const Clazz::p3() {
  // CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'Clazz *const' is 'cons
  // CHECK-FIXES: Clazz *Clazz::p3() {
  return this;
}

const Klazz<const int>* const Clazz::p5() const {}
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Klazz<const int> *
// CHECK-FIXES: const Klazz<const int>* Clazz::p5() const {}

const Clazz::Strukt* const Clazz::p7() {}
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Clazz::Strukt *con
// CHECK-FIXES: const Clazz::Strukt* Clazz::p7() {}

Clazz *const p10();
// CHECK-FIXES: Clazz *p10();

Clazz *const p10() {
  // CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'Clazz *const' is 'cons
  // CHECK-FIXES: Clazz *p10() {
  return new Clazz();
}

const Clazz bar;
const Clazz *const p11() {
  // CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Clazz *const' is
  // CHECK-FIXES: const Clazz *p11() {
  return &bar;
}

const Klazz<const int> p12() {}
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Klazz<const int>'
// CHECK-FIXES: Klazz<const int> p12() {}

const Klazz<const Klazz<const int>> p33() {}
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Klazz<
// CHECK-FIXES: Klazz<const Klazz<const int>> p33() {}

const Klazz<const int>* const p13() {}
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Klazz<const int> *
// CHECK-FIXES: const Klazz<const int>* p13() {}

const Klazz<const int>* const volatile p14() {}
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Klazz<const int> *
// CHECK-FIXES: const Klazz<const int>* volatile p14() {}

const MyStruct<0 < 1> p34() {}
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const MyStruct<0 < 1>'
// CHECK-FIXES: MyStruct<0 < 1> p34() {}

MyStruct<0 < 1> const p35() {}
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const MyStruct<0 < 1>'
// CHECK-FIXES: MyStruct<0 < 1> p35() {}

Klazz<MyStruct<0 < 1> const> const p36() {}
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Klazz<const MyStru
// CHECK-FIXES: Klazz<MyStruct<0 < 1> const> p36() {}

const Klazz<MyStruct<0 < 1> const> *const p37() {}
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Klazz<const MyStru
// CHECK-FIXES: const Klazz<MyStruct<0 < 1> const> *p37() {}

Klazz<const MyStruct<0 < 1>> const p38() {}
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Klazz<const MyStru
// CHECK-FIXES: Klazz<const MyStruct<0 < 1>> p38() {}

const Klazz<const MyStruct<0 < 1>> p39() {}
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Klazz<
// CHECK-FIXES: Klazz<const MyStruct<0 < 1>> p39() {}

const Klazz<const MyStruct<(0 > 1)>> p40() {}
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Klazz<const MyStru
// CHECK-FIXES: Klazz<const MyStruct<(0 > 1)>> p40() {}

// re-declaration of p15.
const int p15();
// CHECK-FIXES: int p15();

const int p15() {
// CHECK-MESSAGES: [[@LINE-1]]:1: warning:
// CHECK-FIXES: int p15() {
  return 0;
}

// Exercise the lexer.

const /* comment */ /* another comment*/ int p16() { return 0; }
// CHECK-MESSAGES: [[@LINE-1]]:1: warning:
// CHECK-FIXES: /* comment */ /* another comment*/ int p16() { return 0; }

/* comment */ const
// CHECK-MESSAGES: [[@LINE-1]]:15: warning:
// CHECK-FIXES: /* comment */
// more
/* another comment*/ int p17() { return 0; }

// Test cases where the `const` token lexically is hidden behind some form of
// indirection.

// Regression tests involving macros, which are ignored by default because
// IgnoreMacros defaults to true.
#define CONCAT(a, b) a##b
CONCAT(cons, t) int n22(){}

#define CONSTINT const int
CONSTINT n23() {}

#define CONST const
CONST int n24() {}

#define CREATE_FUNCTION()                    \
const int n_inside_macro() { \
  return 1; \
}
CREATE_FUNCTION();

using ty = const int;
ty p21() {}

typedef const int ty2;
ty2 p22() {}

// Declaration uses a macro, while definition doesn't.  In this case, we won't
// fix the declaration, and will instead issue a warning.
CONST int p23();
// CHECK-NOTE: [[@LINE-1]]:1: note: could not transform this declaration

const int p23();
// CHECK-FIXES: int p23();

const int p23() { return 3; }
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const int' is 'const'-qu
// CHECK-FIXES: int p23() { return 3; }

int const p24() { return 3; }
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const int' is 'const'-qu
// CHECK-FIXES: int p24() { return 3; }

int const * const p25(const int* p) { return p; }
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const int *const' is 'co
// CHECK-FIXES: int const * p25(const int* p) { return p; }

// We cannot (yet) fix instances that use trailing return types, but we can
// warn.
auto p26() -> const int { return 3; }
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const int' is 'const'-qu
auto p27() -> int const { return 3; }
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const int' is 'const'-qu

std::add_const<int>::type p28() { return 3; }

// p29, p30 are based on
// llvm/projects/test-suite/SingleSource/Benchmarks/Misc-C++-EH/spirit.cpp:
template <class T>
Klazz<T const> const p29(T const &t) { return {}; }
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Klazz<const T>' is
// CHECK-FIXES: Klazz<T const> p29(T const &t) { return {}; }

Klazz<char const *> const p30(char const *s) { return s; }
// CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const Klazz<const char *
// CHECK-FIXES: Klazz<char const *> p30(char const *s) { return s; }

const int n1 = 1;
const Clazz n2 = Clazz();
const Clazz* n3 = new Clazz();
Clazz *const n4 = new Clazz();
const Clazz *const n5 = new Clazz();
constexpr int n6 = 6;
constexpr int n7() { return 8; }
const int eight = 8;
constexpr const int* n8() { return &eight; }
Klazz<const int> n9();
const Klazz<const int>* n10();
const Klazz<const int>& Clazz::n11(const Klazz<const int>) const {}

// Declaration only.
const int n14();

int **const * n_multiple_ptr();
int *const & n_pointer_ref();

class PVBase {
public:
  virtual const int getC() = 0;
  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: return type 'const int' is 'const'-qualified at the top level, which may reduce code readability without improving const correctness
  // CHECK-NOT-FIXES: virtual int getC() = 0;
};

class NVDerive : public PVBase {
public:
  // Don't warn about overridden methods, because it may be impossible to make
  // them non-const as the user may not be able to change the base class.
  const int getC() override { return 1; }
};

class NVDeriveOutOfLine : public PVBase {
public:
  // Don't warn about overridden methods, because it may be impossible to make
  // them non-const as one may not be able to change the base class
  const int getC();
};

const int NVDeriveOutOfLine::getC() { return 1; }

// Don't warn about const auto types, because it may be impossible to make them non-const
// without a significant semantics change. Since `auto` drops cv-qualifiers,
// tests check `decltype(auto)`.
decltype(auto) n16() {
  static const int i = 42;
  return i;
}

// Don't warn about `decltype(<expr>)` types
const int n17i = 1;
decltype(n17i) n17() {
  return 17;
}

// Do warn when on decltype types with the local const qualifier
// `const decltype(auto)` won't compile, so check only `const decltype(<expr>)`
const decltype(n17i) n18() {
  // CHECK-MESSAGES: [[@LINE-1]]:1: warning: return type 'const decltype(n17i)
  // CHECK-FIXES: decltype(n17i) n18() {
  return 18;
}

// `volatile` modifier doesn't affect the checker
volatile decltype(n17i) n19() {
  return 19;
}

// Don't warn about `__typeof__(<expr>)` types
__typeof__(n17i) n20() {
  return 20;
}

// Don't warn about `__typeof__(type)` types
__typeof__(const int) n21() {
  return 21;
}

template <typename T>
struct n25 {
  T foo() const { return 2; }
};
template struct n25<const int>;

template <typename T>
struct p41 {
  const T foo() const { return 2; }
  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: return type 'const
  // CHECK-MESSAGES: [[@LINE-2]]:3: warning: return type 'const
  // CHECK-FIXES: T foo() const { return 2; }
};
template struct p41<int>;

namespace PR73270 {
  template<typename K, typename V>
  struct Pair {
    using first_type = const K;
    using second_type = V;
  };

  template<typename PairType>
  typename PairType::first_type getFirst() {
    return {};
  }

  void test() {
    getFirst<Pair<int, int>>();
  }
}