Add support for C++20 concepts and decltype to modernize-use-trailing-return-type.
This commit is contained in:
parent
85d381eb02
commit
345053390a
|
@ -261,8 +261,8 @@ static bool hasAnyNestedLocalQualifiers(QualType Type) {
|
|||
}
|
||||
|
||||
SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange(
|
||||
const FunctionDecl &F, const ASTContext &Ctx, const SourceManager &SM,
|
||||
const LangOptions &LangOpts) {
|
||||
const FunctionDecl &F, const TypeLoc &ReturnLoc, const ASTContext &Ctx,
|
||||
const SourceManager &SM, const LangOptions &LangOpts) {
|
||||
|
||||
// We start with the range of the return type and expand to neighboring
|
||||
// qualifiers (const, volatile and restrict).
|
||||
|
@ -274,6 +274,35 @@ SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange(
|
|||
return {};
|
||||
}
|
||||
|
||||
// If the return type is a constrained 'auto' or 'decltype(auto)', we need to
|
||||
// include the tokens after the concept. Unfortunately, the source range of an
|
||||
// AutoTypeLoc, if it is constrained, does not include the 'auto' or
|
||||
// 'decltype(auto)'. If the return type is a plain 'decltype(...)', the
|
||||
// source range only contains the first 'decltype' token.
|
||||
auto ATL = ReturnLoc.getAs<AutoTypeLoc>();
|
||||
if ((ATL && (ATL.isConstrained() ||
|
||||
ATL.getAutoKeyword() == AutoTypeKeyword::DecltypeAuto)) ||
|
||||
ReturnLoc.getAs<DecltypeTypeLoc>()) {
|
||||
SourceLocation End =
|
||||
expandIfMacroId(ReturnLoc.getSourceRange().getEnd(), SM);
|
||||
SourceLocation BeginNameF = expandIfMacroId(F.getLocation(), SM);
|
||||
|
||||
// Extend the ReturnTypeRange until the last token before the function
|
||||
// name.
|
||||
std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(End);
|
||||
StringRef File = SM.getBufferData(Loc.first);
|
||||
const char *TokenBegin = File.data() + Loc.second;
|
||||
Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
|
||||
TokenBegin, File.end());
|
||||
Token T;
|
||||
SourceLocation LastTLoc = End;
|
||||
while (!Lexer.LexFromRawLexer(T) &&
|
||||
SM.isBeforeInTranslationUnit(T.getLocation(), BeginNameF)) {
|
||||
LastTLoc = T.getLocation();
|
||||
}
|
||||
ReturnTypeRange.setEnd(LastTLoc);
|
||||
}
|
||||
|
||||
// If the return type has no local qualifiers, it's source range is accurate.
|
||||
if (!hasAnyNestedLocalQualifiers(F.getReturnType()))
|
||||
return ReturnTypeRange;
|
||||
|
@ -317,7 +346,7 @@ SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange(
|
|||
return ReturnTypeRange;
|
||||
}
|
||||
|
||||
bool UseTrailingReturnTypeCheck::keepSpecifiers(
|
||||
void UseTrailingReturnTypeCheck::keepSpecifiers(
|
||||
std::string &ReturnType, std::string &Auto, SourceRange ReturnTypeCVRange,
|
||||
const FunctionDecl &F, const FriendDecl *Fr, const ASTContext &Ctx,
|
||||
const SourceManager &SM, const LangOptions &LangOpts) {
|
||||
|
@ -327,14 +356,14 @@ bool UseTrailingReturnTypeCheck::keepSpecifiers(
|
|||
if (!F.isConstexpr() && !F.isInlineSpecified() &&
|
||||
F.getStorageClass() != SC_Extern && F.getStorageClass() != SC_Static &&
|
||||
!Fr && !(M && M->isVirtualAsWritten()))
|
||||
return true;
|
||||
return;
|
||||
|
||||
// Tokenize return type. If it contains macros which contain a mix of
|
||||
// qualifiers, specifiers and types, give up.
|
||||
llvm::Optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
|
||||
classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
|
||||
if (!MaybeTokens)
|
||||
return false;
|
||||
return;
|
||||
|
||||
// Find specifiers, remove them from the return type, add them to 'auto'.
|
||||
unsigned int ReturnTypeBeginOffset =
|
||||
|
@ -367,14 +396,12 @@ bool UseTrailingReturnTypeCheck::keepSpecifiers(
|
|||
ReturnType.erase(TOffsetInRT, TLengthWithWS);
|
||||
DeletedChars += TLengthWithWS;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UseTrailingReturnTypeCheck::registerMatchers(MatchFinder *Finder) {
|
||||
auto F = functionDecl(unless(anyOf(hasTrailingReturn(), returns(voidType()),
|
||||
returns(autoType()), cxxConversionDecl(),
|
||||
cxxMethodDecl(isImplicit()))))
|
||||
auto F = functionDecl(
|
||||
unless(anyOf(hasTrailingReturn(), returns(voidType()),
|
||||
cxxConversionDecl(), cxxMethodDecl(isImplicit()))))
|
||||
.bind("Func");
|
||||
|
||||
Finder->addMatcher(F, this);
|
||||
|
@ -397,11 +424,17 @@ void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
|
|||
if (F->getLocation().isInvalid())
|
||||
return;
|
||||
|
||||
// Skip functions which return just 'auto'.
|
||||
const auto *AT = F->getDeclaredReturnType()->getAs<AutoType>();
|
||||
if (AT != nullptr && !AT->isConstrained() &&
|
||||
AT->getKeyword() == AutoTypeKeyword::Auto &&
|
||||
!hasAnyNestedLocalQualifiers(F->getDeclaredReturnType()))
|
||||
return;
|
||||
|
||||
// TODO: implement those
|
||||
if (F->getDeclaredReturnType()->isFunctionPointerType() ||
|
||||
F->getDeclaredReturnType()->isMemberFunctionPointerType() ||
|
||||
F->getDeclaredReturnType()->isMemberPointerType() ||
|
||||
F->getDeclaredReturnType()->getAs<DecltypeType>() != nullptr) {
|
||||
F->getDeclaredReturnType()->isMemberPointerType()) {
|
||||
diag(F->getLocation(), Message);
|
||||
return;
|
||||
}
|
||||
|
@ -435,7 +468,7 @@ void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
|
|||
// discards user formatting and order of const, volatile, type, whitespace,
|
||||
// space before & ... .
|
||||
SourceRange ReturnTypeCVRange =
|
||||
findReturnTypeAndCVSourceRange(*F, Ctx, SM, LangOpts);
|
||||
findReturnTypeAndCVSourceRange(*F, FTL.getReturnLoc(), Ctx, SM, LangOpts);
|
||||
if (ReturnTypeCVRange.isInvalid())
|
||||
return;
|
||||
|
||||
|
|
|
@ -50,10 +50,11 @@ private:
|
|||
const SourceManager &SM,
|
||||
const LangOptions &LangOpts);
|
||||
SourceRange findReturnTypeAndCVSourceRange(const FunctionDecl &F,
|
||||
const TypeLoc &ReturnLoc,
|
||||
const ASTContext &Ctx,
|
||||
const SourceManager &SM,
|
||||
const LangOptions &LangOpts);
|
||||
bool keepSpecifiers(std::string &ReturnType, std::string &Auto,
|
||||
void keepSpecifiers(std::string &ReturnType, std::string &Auto,
|
||||
SourceRange ReturnTypeCVRange, const FunctionDecl &F,
|
||||
const FriendDecl *Fr, const ASTContext &Ctx,
|
||||
const SourceManager &SM, const LangOptions &LangOpts);
|
||||
|
|
|
@ -29,10 +29,10 @@ Known Limitations
|
|||
-----------------
|
||||
|
||||
The following categories of return types cannot be rewritten currently:
|
||||
|
||||
* function pointers
|
||||
* member function pointers
|
||||
* member pointers
|
||||
* decltype, when it is the top level expression
|
||||
|
||||
Unqualified names in the return type might erroneously refer to different entities after the rewrite.
|
||||
Preventing such errors requires a full lookup of all unqualified names present in the return type in the scope of the trailing return type location.
|
||||
|
@ -43,26 +43,26 @@ Given the following piece of code
|
|||
|
||||
.. code-block:: c++
|
||||
|
||||
struct Object { long long value; };
|
||||
Object f(unsigned Object) { return {Object * 2}; }
|
||||
struct S { long long value; };
|
||||
S f(unsigned S) { return {S * 2}; }
|
||||
class CC {
|
||||
int Object;
|
||||
struct Object m();
|
||||
int S;
|
||||
struct S m();
|
||||
};
|
||||
Object CC::m() { return {0}; }
|
||||
S CC::m() { return {0}; }
|
||||
|
||||
a careless rewrite would produce the following output:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
struct Object { long long value; };
|
||||
auto f(unsigned Object) -> Object { return {Object * 2}; } // error
|
||||
struct S { long long value; };
|
||||
auto f(unsigned S) -> S { return {S * 2}; } // error
|
||||
class CC {
|
||||
int Object;
|
||||
auto m() -> struct Object;
|
||||
int S;
|
||||
auto m() -> struct S;
|
||||
};
|
||||
auto CC::m() -> Object { return {0}; } // error
|
||||
auto CC::m() -> S { return {0}; } // error
|
||||
|
||||
This code fails to compile because the Object in the context of f refers to the equally named function parameter.
|
||||
Similarly, the Object in the context of m refers to the equally named class member.
|
||||
The check can currently only detect a clash with a function parameter name.
|
||||
This code fails to compile because the S in the context of f refers to the equally named function parameter.
|
||||
Similarly, the S in the context of m refers to the equally named class member.
|
||||
The check can currently only detect and avoid a clash with a function parameter name.
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
// RUN: %check_clang_tidy -std=c++20 %s modernize-use-trailing-return-type %t
|
||||
|
||||
namespace std {
|
||||
template <typename T, typename U>
|
||||
struct is_same { static constexpr auto value = false; };
|
||||
|
||||
template <typename T>
|
||||
struct is_same<T, T> { static constexpr auto value = true; };
|
||||
|
||||
template <typename T>
|
||||
concept floating_point = std::is_same<T, float>::value || std::is_same<T, double>::value || std::is_same<T, long double>::value;
|
||||
}
|
||||
|
||||
//
|
||||
// Concepts
|
||||
//
|
||||
|
||||
std::floating_point auto con1();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}auto con1() -> std::floating_point auto;{{$}}
|
||||
|
||||
std::floating_point auto con1() { return 3.14f; }
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}auto con1() -> std::floating_point auto { return 3.14f; }{{$}}
|
||||
|
||||
namespace a {
|
||||
template <typename T>
|
||||
concept Concept = true;
|
||||
|
||||
template <typename T, typename U>
|
||||
concept BinaryConcept = true;
|
||||
}
|
||||
|
||||
a::Concept decltype(auto) con2();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:27: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}auto con2() -> a::Concept decltype(auto);{{$}}
|
||||
|
||||
a::BinaryConcept<int> decltype(auto) con3();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:38: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}auto con3() -> a::BinaryConcept<int> decltype(auto);{{$}}
|
||||
|
||||
const std::floating_point auto* volatile con4();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:42: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}auto con4() -> const std::floating_point auto* volatile;{{$}}
|
||||
|
||||
template <typename T>
|
||||
int req1(T t) requires std::floating_point<T>;
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}auto req1(T t) -> int requires std::floating_point<T>;{{$}}
|
||||
|
||||
template <typename T>
|
||||
T req2(T t) requires requires { t + t; };
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}auto req2(T t) -> T requires requires { t + t; };{{$}}
|
|
@ -1,6 +1,4 @@
|
|||
// RUN: %check_clang_tidy -std=c++14,c++17 %s modernize-use-trailing-return-type %t -- -- -fdeclspec -fexceptions
|
||||
// FIXME: Fix the checker to work in C++20 mode, it is performing a
|
||||
// use-of-uninitialized-value.
|
||||
// RUN: %check_clang_tidy -std=c++14-or-later %s modernize-use-trailing-return-type %t -- -- -fdeclspec -fexceptions -DCOMMAND_LINE_INT=int
|
||||
|
||||
namespace std {
|
||||
template <typename T>
|
||||
|
@ -11,6 +9,8 @@ namespace std {
|
|||
|
||||
class string;
|
||||
|
||||
class ostream;
|
||||
|
||||
template <typename T>
|
||||
auto declval() -> T;
|
||||
}
|
||||
|
@ -215,18 +215,28 @@ struct A e13();
|
|||
// CHECK-FIXES: {{^}}auto e13() -> struct A;{{$}}
|
||||
|
||||
//
|
||||
// decltype (unsupported if top level expression)
|
||||
// deduced return types
|
||||
//
|
||||
|
||||
const auto ded1();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}auto ded1() -> const auto;{{$}}
|
||||
const auto& ded2();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}auto ded2() -> const auto&;{{$}}
|
||||
|
||||
decltype(auto) ded3();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}auto ded3() -> decltype(auto);{{$}}
|
||||
|
||||
|
||||
decltype(1 + 2) dec1() { return 1 + 2; }
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// TODO: source range of DecltypeTypeLoc not yet implemented
|
||||
// _HECK-FIXES: {{^}}auto dec1() -> decltype(1 + 2) { return 1 + 2; }{{$}}
|
||||
// CHECK-FIXES: {{^}}auto dec1() -> decltype(1 + 2) { return 1 + 2; }{{$}}
|
||||
template <typename F, typename T>
|
||||
decltype(std::declval<F>(std::declval<T>)) dec2(F f, T t) { return f(t); }
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:44: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// TODO: source range of DecltypeTypeLoc not yet implemented
|
||||
// _HECK-FIXES: {{^}}auto dec2(F f, T t) -> decltype(std::declval<F>(std::declval<T>)) { return f(t); }{{$}}
|
||||
// CHECK-FIXES: {{^}}auto dec2(F f, T t) -> decltype(std::declval<F>(std::declval<T>)) { return f(t); }{{$}}
|
||||
template <typename T>
|
||||
typename decltype(std::declval<T>())::value_type dec3();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:50: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
|
@ -463,6 +473,13 @@ CONST_CAT int& h19();
|
|||
CONST_F_MACRO int& h19();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}auto h19() -> CONST_F_MACRO int&;{{$}}
|
||||
// Macro COMMAND_LINE_INT is defined on the command line via: -DCOMMAND_LINE_INT=int
|
||||
const COMMAND_LINE_INT& h20();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}auto h20() -> const COMMAND_LINE_INT&;{{$}}
|
||||
decltype(COMMAND_LINE_INT{}) h21();
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:30: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}auto h21() -> decltype(COMMAND_LINE_INT{});{{$}}
|
||||
|
||||
//
|
||||
// Name collisions
|
||||
|
@ -531,6 +548,14 @@ Object DD::g() {
|
|||
return {0};
|
||||
}
|
||||
|
||||
//
|
||||
// bug 44206, no rewrite should happen due to collision with parameter name
|
||||
//
|
||||
|
||||
using std::ostream;
|
||||
ostream& operator<<(ostream& ostream, int i);
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use a trailing return type for this function [modernize-use-trailing-return-type]
|
||||
// CHECK-FIXES: {{^}}ostream& operator<<(ostream& ostream, int i);{{$}}
|
||||
|
||||
//
|
||||
// Samples which do not trigger the check
|
||||
|
@ -544,7 +569,6 @@ auto f(int arg1, int arg2, int arg3, ...) -> int;
|
|||
template <typename T> auto f(T t) -> int;
|
||||
|
||||
auto ff();
|
||||
decltype(auto) fff();
|
||||
|
||||
void c();
|
||||
void c(int arg);
|
||||
|
|
Loading…
Reference in New Issue