diff --git a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h --- a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h @@ -49,10 +49,11 @@ 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); diff --git a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp --- a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp @@ -260,8 +260,8 @@ } 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). @@ -273,6 +273,35 @@ 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(); + if ((ATL && (ATL.isConstrained() || + ATL.getAutoKeyword() == AutoTypeKeyword::DecltypeAuto)) || + ReturnLoc.getAs()) { + 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 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; @@ -316,7 +345,7 @@ 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) { @@ -326,14 +355,14 @@ 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> 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,13 +396,13 @@ DeletedChars += TLengthWithWS; } - return true; + return; } 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); @@ -396,11 +425,17 @@ if (F->getLocation().isInvalid()) return; + // Skip functions which return just 'auto'. + const auto *AT = F->getDeclaredReturnType()->getAs(); + 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() != nullptr) { + F->getDeclaredReturnType()->isMemberPointerType()) { diag(F->getLocation(), Message); return; } @@ -434,7 +469,7 @@ // 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; diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize-use-trailing-return-type.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize-use-trailing-return-type.rst --- a/clang-tools-extra/docs/clang-tidy/checks/modernize-use-trailing-return-type.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize-use-trailing-return-type.rst @@ -11,58 +11,46 @@ Example ------- -.. code-block:: c++ +======================================== =============================================== +Before After +======================================== =============================================== +.. code-block:: c++ .. code-block:: c++ - int f1(); - inline int f2(int arg) noexcept; - virtual float f3() const && = delete; - -transforms to: - -.. code-block:: c++ - - auto f1() -> int; - inline auto f2(int arg) -> int noexcept; - virtual auto f3() const && -> float = delete; + int f1(); auto f1() -> int; + inline int f2(int arg) noexcept; inline auto f2(int arg) -> int noexcept; + virtual float f3() const && = delete; virtual auto f3() const && -> float = delete; +======================================== =============================================== 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. This location includes e.g. function parameter names and members of the enclosing class (including all inherited classes). Such a lookup is currently not implemented. -Given the following piece of code - -.. code-block:: c++ - - struct Object { long long value; }; - Object f(unsigned Object) { return {Object * 2}; } - class CC { - int Object; - struct Object m(); - }; - Object 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 - class CC { - int Object; - auto m() -> struct Object; - }; - auto CC::m() -> Object { 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. +For example, a careless rewrite would produce the following output: + +======================================== =============================================== +Before After +======================================== =============================================== +.. code-block:: c++ .. code-block:: c++ + + struct S { long long value; }; struct S { long long value; }; + S f(unsigned S) { return {S * 2}; } auto f(unsigned S) -> S { return {S * 2}; } // error + class CC { class CC { + int S; int S; + struct S m(); auto m() -> struct S; + }; }; + S CC::m() { return {0}; } auto CC::m() -> S { return {0}; } // error +======================================== =============================================== + +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. diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize-use-trailing-return-type.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize-use-trailing-return-type.cpp --- a/clang-tools-extra/test/clang-tidy/checkers/modernize-use-trailing-return-type.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize-use-trailing-return-type.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy -std=c++14,c++17 %s modernize-use-trailing-return-type %t -- -- -fdeclspec -fexceptions +// RUN: %check_clang_tidy -std=c++14-or-later %s modernize-use-trailing-return-type %t -- -- -fdeclspec -fexceptions // FIXME: Fix the checker to work in C++2a mode, it is performing a // use-of-uninitialized-value. @@ -215,18 +215,28 @@ // 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 decltype(std::declval(std::declval)) 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(std::declval)) { return f(t); }{{$}} +// CHECK-FIXES: {{^}}auto dec2(F f, T t) -> decltype(std::declval(std::declval)) { return f(t); }{{$}} template typename decltype(std::declval())::value_type dec3(); // CHECK-MESSAGES: :[[@LINE-1]]:50: warning: use a trailing return type for this function [modernize-use-trailing-return-type] @@ -531,6 +541,62 @@ return {0}; } +// +// Concepts +// + +#if __cplusplus > 201703L /* C++2a and later */ + +namespace std { +template +struct is_same { static constexpr auto value = false; }; + +template +struct is_same { static constexpr auto value = true; }; + +template +concept floating_point = std::is_same::value || std::is_same::value || std::is_same::value; +} + +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 +concept Concept = true; + +template +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 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 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 +int req1(T t) requires std::floating_point; +// 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;{{$}} + +template +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; };{{$}} + +#endif // // Samples which do not trigger the check @@ -544,7 +610,6 @@ template auto f(T t) -> int; auto ff(); -decltype(auto) fff(); void c(); void c(int arg);