Skip to content

Commit cd0c0ab

Browse files
committed
[clang-tidy] add support for lambdas in use-trailing-return-type
1 parent eb71fdd commit cd0c0ab

9 files changed

+345
-12
lines changed

clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp

Lines changed: 151 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,30 @@
1717
#include <cctype>
1818
#include <optional>
1919

20+
namespace clang::tidy {
21+
22+
template <>
23+
struct OptionEnumMapping<
24+
modernize::UseTrailingReturnTypeCheck::TransformLambda> {
25+
static llvm::ArrayRef<std::pair<
26+
modernize::UseTrailingReturnTypeCheck::TransformLambda, StringRef>>
27+
getEnumMapping() {
28+
static constexpr std::pair<
29+
modernize::UseTrailingReturnTypeCheck::TransformLambda, StringRef>
30+
Mapping[] = {
31+
{modernize::UseTrailingReturnTypeCheck::TransformLambda::All,
32+
"all"},
33+
{modernize::UseTrailingReturnTypeCheck::TransformLambda::
34+
AllExceptAuto,
35+
"all_except_auto"},
36+
{modernize::UseTrailingReturnTypeCheck::TransformLambda::None,
37+
"none"}};
38+
return Mapping;
39+
}
40+
};
41+
42+
} // namespace clang::tidy
43+
2044
using namespace clang::ast_matchers;
2145

2246
namespace clang::tidy::modernize {
@@ -111,10 +135,17 @@ struct UnqualNameVisitor : public RecursiveASTVisitor<UnqualNameVisitor> {
111135
private:
112136
const FunctionDecl &F;
113137
};
138+
139+
AST_MATCHER(LambdaExpr, hasExplicitResultType) {
140+
return Node.hasExplicitResultType();
141+
}
142+
114143
} // namespace
115144

116145
constexpr llvm::StringLiteral ErrorMessageOnFunction =
117146
"use a trailing return type for this function";
147+
constexpr llvm::StringLiteral ErrorMessageOnLambda =
148+
"use a trailing return type for this lambda";
118149

119150
static SourceLocation expandIfMacroId(SourceLocation Loc,
120151
const SourceManager &SM) {
@@ -329,6 +360,48 @@ findReturnTypeAndCVSourceRange(const FunctionDecl &F, const TypeLoc &ReturnLoc,
329360
return ReturnTypeRange;
330361
}
331362

363+
static SourceLocation findLambdaTrailingReturnInsertLoc(
364+
const CXXMethodDecl *Method, const SourceManager &SM,
365+
const LangOptions &LangOpts, const ASTContext &Ctx) {
366+
// 'requires' keyword is present in lambda declaration
367+
if (Method->getTrailingRequiresClause()) {
368+
SourceLocation ParamEndLoc;
369+
if (Method->param_empty())
370+
ParamEndLoc = Method->getBeginLoc();
371+
else
372+
ParamEndLoc = Method->getParametersSourceRange().getEnd();
373+
374+
std::pair<FileID, unsigned> ParamEndLocInfo =
375+
SM.getDecomposedLoc(ParamEndLoc);
376+
StringRef Buffer = SM.getBufferData(ParamEndLocInfo.first);
377+
378+
Lexer Lexer(SM.getLocForStartOfFile(ParamEndLocInfo.first), LangOpts,
379+
Buffer.begin(), Buffer.data() + ParamEndLocInfo.second,
380+
Buffer.end());
381+
382+
Token Token;
383+
while (!Lexer.LexFromRawLexer(Token)) {
384+
if (Token.is(tok::raw_identifier)) {
385+
IdentifierInfo &Info = Ctx.Idents.get(StringRef(
386+
SM.getCharacterData(Token.getLocation()), Token.getLength()));
387+
Token.setIdentifierInfo(&Info);
388+
Token.setKind(Info.getTokenID());
389+
}
390+
391+
if (Token.is(tok::kw_requires))
392+
return Token.getLocation().getLocWithOffset(-1);
393+
}
394+
395+
return {};
396+
}
397+
398+
// If no requires clause, insert before the body
399+
if (const Stmt *Body = Method->getBody())
400+
return Body->getBeginLoc().getLocWithOffset(-1);
401+
402+
return {};
403+
}
404+
332405
static void keepSpecifiers(std::string &ReturnType, std::string &Auto,
333406
SourceRange ReturnTypeCVRange, const FunctionDecl &F,
334407
const FriendDecl *Fr, const ASTContext &Ctx,
@@ -382,14 +455,43 @@ static void keepSpecifiers(std::string &ReturnType, std::string &Auto,
382455
}
383456
}
384457

458+
UseTrailingReturnTypeCheck::UseTrailingReturnTypeCheck(
459+
StringRef Name, ClangTidyContext *Context)
460+
: ClangTidyCheck(Name, Context),
461+
TransformFunctions(Options.get("TransformFunctions", true)),
462+
TransformLambdas(Options.get("TransformLambdas", TransformLambda::All)) {
463+
464+
if (TransformFunctions == false && TransformLambdas == TransformLambda::None)
465+
this->configurationDiag(
466+
"The check 'modernize-use-trailing-return-type' will not perform any "
467+
"analysis because 'TransformFunctions' and 'TransformLambdas' are "
468+
"disabled.");
469+
}
470+
471+
void UseTrailingReturnTypeCheck::storeOptions(
472+
ClangTidyOptions::OptionMap &Opts) {
473+
Options.store(Opts, "TransformFunctions", TransformFunctions);
474+
Options.store(Opts, "TransformLambdas", TransformLambdas);
475+
}
476+
385477
void UseTrailingReturnTypeCheck::registerMatchers(MatchFinder *Finder) {
386-
auto F = functionDecl(
387-
unless(anyOf(hasTrailingReturn(), returns(voidType()),
388-
cxxConversionDecl(), cxxMethodDecl(isImplicit()))))
389-
.bind("Func");
478+
auto F =
479+
functionDecl(
480+
unless(anyOf(
481+
hasTrailingReturn(), returns(voidType()), cxxConversionDecl(),
482+
cxxMethodDecl(
483+
anyOf(isImplicit(),
484+
hasParent(cxxRecordDecl(hasParent(lambdaExpr()))))))))
485+
.bind("Func");
486+
487+
if (TransformFunctions) {
488+
Finder->addMatcher(F, this);
489+
Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this);
490+
}
390491

391-
Finder->addMatcher(F, this);
392-
Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this);
492+
if (TransformLambdas != TransformLambda::None)
493+
Finder->addMatcher(
494+
lambdaExpr(unless(hasExplicitResultType())).bind("Lambda"), this);
393495
}
394496

395497
void UseTrailingReturnTypeCheck::registerPPCallbacks(
@@ -401,8 +503,13 @@ void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
401503
assert(PP && "Expected registerPPCallbacks() to have been called before so "
402504
"preprocessor is available");
403505

404-
const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func");
506+
if (const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("Lambda")) {
507+
diagOnLambda(Lambda, Result);
508+
return;
509+
}
510+
405511
const auto *Fr = Result.Nodes.getNodeAs<FriendDecl>("Friend");
512+
const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func");
406513
assert(F && "Matcher is expected to find only FunctionDecls");
407514

408515
// Three-way comparison operator<=> is syntactic sugar and generates implicit
@@ -495,4 +602,41 @@ void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
495602
<< FixItHint::CreateInsertion(InsertionLoc, " -> " + ReturnType);
496603
}
497604

605+
void UseTrailingReturnTypeCheck::diagOnLambda(
606+
const LambdaExpr *Lambda,
607+
const ast_matchers::MatchFinder::MatchResult &Result) {
608+
609+
const CXXMethodDecl *Method = Lambda->getCallOperator();
610+
if (!Method || Lambda->hasExplicitResultType())
611+
return;
612+
613+
const ASTContext *Ctx = Result.Context;
614+
const QualType ReturnType = Method->getReturnType();
615+
616+
// We can't write 'auto' in C++11 mode, try to write generic msg and bail out.
617+
if (ReturnType->isDependentType() &&
618+
Ctx->getLangOpts().LangStd == LangStandard::lang_cxx11) {
619+
if (TransformLambdas == TransformLambda::All)
620+
diag(Lambda->getBeginLoc(), ErrorMessageOnLambda);
621+
return;
622+
}
623+
624+
if (ReturnType->isUndeducedAutoType() &&
625+
TransformLambdas == TransformLambda::AllExceptAuto)
626+
return;
627+
628+
const SourceLocation TrailingReturnInsertLoc =
629+
findLambdaTrailingReturnInsertLoc(Method, *Result.SourceManager,
630+
getLangOpts(), *Result.Context);
631+
632+
if (TrailingReturnInsertLoc.isValid())
633+
diag(Lambda->getBeginLoc(), "use a trailing return type for this lambda")
634+
<< FixItHint::CreateInsertion(
635+
TrailingReturnInsertLoc,
636+
" -> " +
637+
ReturnType.getAsString(Result.Context->getPrintingPolicy()));
638+
else
639+
diag(Lambda->getBeginLoc(), ErrorMessageOnLambda);
640+
}
641+
498642
} // namespace clang::tidy::modernize

clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,25 @@ struct ClassifiedToken {
2626
/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-trailing-return-type.html
2727
class UseTrailingReturnTypeCheck : public ClangTidyCheck {
2828
public:
29-
UseTrailingReturnTypeCheck(StringRef Name, ClangTidyContext *Context)
30-
: ClangTidyCheck(Name, Context) {}
29+
UseTrailingReturnTypeCheck(StringRef Name, ClangTidyContext *Context);
3130
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
3231
return LangOpts.CPlusPlus11;
3332
}
33+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
3434
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
3535
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
3636
Preprocessor *ModuleExpanderPP) override;
3737
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
3838

39+
enum TransformLambda { All, AllExceptAuto, None };
40+
3941
private:
4042
Preprocessor *PP = nullptr;
43+
const bool TransformFunctions;
44+
const TransformLambda TransformLambdas;
45+
46+
void diagOnLambda(const LambdaExpr *Lambda,
47+
const ast_matchers::MatchFinder::MatchResult &Result);
4148
};
4249

4350
} // namespace clang::tidy::modernize

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,13 @@ Changes in existing checks
236236
<clang-tidy/checks/modernize/use-std-numbers>` check to support math
237237
functions of different precisions.
238238

239+
- Improved :doc:`modernize-use-trailing-return-type
240+
<clang-tidy/checks/modernize/use-trailing-return-type>` check by adding
241+
support to modernize lambda signatures to use trailing return type and adding
242+
two new options: `TransformFunctions` and `TransformLambdas` to control
243+
whether function declarations and lambdas should be transformed by the check.
244+
Fixed false positives when lambda was matched as a function in C++11 mode.
245+
239246
- Improved :doc:`performance-move-const-arg
240247
<clang-tidy/checks/performance/move-const-arg>` check by fixing false
241248
negatives on ternary operators calling ``std::move``.

clang-tools-extra/docs/clang-tidy/checks/modernize/use-trailing-return-type.rst

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
modernize-use-trailing-return-type
44
==================================
55

6-
Rewrites function signatures to use a trailing return type
6+
Rewrites function and lambda signatures to use a trailing return type
77
(introduced in C++11). This transformation is purely stylistic.
88
The return type before the function name is replaced by ``auto``
99
and inserted after the function parameter list (and qualifiers).
@@ -16,6 +16,7 @@ Example
1616
int f1();
1717
inline int f2(int arg) noexcept;
1818
virtual float f3() const && = delete;
19+
auto lambda = []() {};
1920

2021
transforms to:
2122

@@ -24,6 +25,7 @@ transforms to:
2425
auto f1() -> int;
2526
inline auto f2(int arg) -> int noexcept;
2627
virtual auto f3() const && -> float = delete;
28+
auto lambda = []() -> void {};
2729

2830
Known Limitations
2931
-----------------
@@ -66,3 +68,25 @@ a careless rewrite would produce the following output:
6668
This code fails to compile because the S in the context of f refers to the equally named function parameter.
6769
Similarly, the S in the context of m refers to the equally named class member.
6870
The check can currently only detect and avoid a clash with a function parameter name.
71+
72+
Options
73+
-------
74+
75+
.. option:: TransformFunctions
76+
77+
When set to `true`, function declarations will be transformed to use trailing
78+
return. Default is `true`.
79+
80+
.. option:: TransformLambdas
81+
82+
Controls how lambda expressions are transformed to use trailing
83+
return type. Possible values are:
84+
85+
* `all` - Transform all lambda expressions without an explicit return type
86+
to use trailing return type. If type can not be deduced, ``auto`` will be
87+
used since C++14 and generic message will be emitted otherwise.
88+
* `all_except_auto` - Transform all lambda expressions except those whose return
89+
type can not be deduced.
90+
* `none` - Do not transform any lambda expressions.
91+
92+
Default is `all`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// RUN: %check_clang_tidy -std=c++14-or-later %s modernize-use-trailing-return-type %t -- -- -fno-delayed-template-parsing
2+
3+
namespace std {
4+
template <typename T>
5+
class vector {};
6+
7+
class string {};
8+
} // namespace std
9+
10+
void test_lambda_positive() {
11+
auto l1 = [](auto x) { return x; };
12+
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
13+
// CHECK-FIXES: {{^}} auto l1 = [](auto x) -> auto { return x; };{{$}}
14+
}
15+
16+
template <template <typename> class C>
17+
void test_lambda_positive_template() {
18+
auto l1 = []() { return C<int>{}; };
19+
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
20+
// CHECK-FIXES: {{^}} auto l1 = []() -> auto { return C<int>{}; };{{$}}
21+
auto l2 = []() { return 0; };
22+
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
23+
// CHECK-FIXES: {{^}} auto l2 = []() -> auto { return 0; };{{$}}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-use-trailing-return-type %t -- -- -fno-delayed-template-parsing
2+
3+
namespace std {
4+
template <typename T, typename U>
5+
struct is_same { static constexpr auto value = false; };
6+
7+
template <typename T>
8+
struct is_same<T, T> { static constexpr auto value = true; };
9+
10+
template <typename T>
11+
concept floating_point = std::is_same<T, float>::value || std::is_same<T, double>::value || std::is_same<T, long double>::value;
12+
}
13+
14+
void test_lambda_positive() {
15+
auto l1 = []<typename T, typename U>(T x, U y) { return x + y; };
16+
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
17+
// CHECK-FIXES: {{^}} auto l1 = []<typename T, typename U>(T x, U y) -> auto { return x + y; };{{$}}
18+
auto l2 = [](auto x) requires requires { x + x; } { return x; };
19+
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
20+
// CHECK-FIXES: {{^}} auto l2 = [](auto x) -> auto requires requires { x + x; } { return x; };{{$}}
21+
auto l3 = [](auto x) requires std::floating_point<decltype(x)> { return x; };
22+
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
23+
// CHECK-FIXES: {{^}} auto l3 = [](auto x) -> auto requires std::floating_point<decltype(x)> { return x; };{{$}}
24+
auto l4 = [](int x) consteval { return x; };
25+
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
26+
// CHECK-FIXES: {{^}} auto l4 = [](int x) consteval -> int { return x; };{{$}}
27+
// Complete complex example
28+
auto l5 = []<typename T, typename U>(T x, U y) constexpr noexcept
29+
requires std::floating_point<T> && std::floating_point<U>
30+
{ return x * y; };
31+
// CHECK-MESSAGES: :[[@LINE-3]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
32+
// CHECK-FIXES: {{^}} auto l5 = []<typename T, typename U>(T x, U y) constexpr noexcept{{$}}
33+
// CHECK-FIXES: {{^}} -> auto requires std::floating_point<T> && std::floating_point<U>{{$}}
34+
// CHECK-FIXES: {{^}} { return x * y; };{{$}}
35+
}

0 commit comments

Comments
 (0)