Add support for C++20 concepts and decltype to modernize-use-trailing-return-type.

This commit is contained in:
Bernhard Manfred Gruber 2020-08-15 10:40:22 -04:00 committed by Aaron Ballman
parent 85d381eb02
commit 345053390a
5 changed files with 149 additions and 37 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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.

View File

@ -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; };{{$}}

View File

@ -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);