Skip to content

Commit ae6cedc

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

8 files changed

+309
-8
lines changed

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

+134-3
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+
"AllExceptAuto"},
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,6 +135,11 @@ 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 Message =
@@ -383,14 +412,40 @@ void UseTrailingReturnTypeCheck::keepSpecifiers(
383412
}
384413
}
385414

415+
UseTrailingReturnTypeCheck::UseTrailingReturnTypeCheck(
416+
StringRef Name, ClangTidyContext *Context)
417+
: ClangTidyCheck(Name, Context),
418+
TransformFunctions(Options.get("TransformFunctions", true)),
419+
TransformLambdas(Options.get("TransformLambdas", TransformLambda::All)) {
420+
421+
if (TransformFunctions == false && TransformLambdas == TransformLambda::None)
422+
this->configurationDiag(
423+
"The check 'modernize-use-trailing-return-type' will not perform any "
424+
"analysis because 'TransformFunctions' and 'TransformLambdas' are "
425+
"disabled.");
426+
}
427+
428+
void UseTrailingReturnTypeCheck::storeOptions(
429+
ClangTidyOptions::OptionMap &Opts) {
430+
Options.store(Opts, "TransformFunctions", TransformFunctions);
431+
Options.store(Opts, "TransformLambdas", TransformLambdas);
432+
}
433+
386434
void UseTrailingReturnTypeCheck::registerMatchers(MatchFinder *Finder) {
387435
auto F = functionDecl(
388436
unless(anyOf(hasTrailingReturn(), returns(voidType()),
389437
cxxConversionDecl(), cxxMethodDecl(isImplicit()))))
390438
.bind("Func");
391439

392-
Finder->addMatcher(F, this);
393-
Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this);
440+
if (TransformFunctions) {
441+
Finder->addMatcher(F, this);
442+
Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this);
443+
}
444+
445+
if (TransformLambdas != TransformLambda::None) {
446+
Finder->addMatcher(
447+
lambdaExpr(unless(hasExplicitResultType())).bind("Lambda"), this);
448+
}
394449
}
395450

396451
void UseTrailingReturnTypeCheck::registerPPCallbacks(
@@ -402,8 +457,13 @@ void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
402457
assert(PP && "Expected registerPPCallbacks() to have been called before so "
403458
"preprocessor is available");
404459

405-
const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func");
460+
if (const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("Lambda")) {
461+
diagOnLambda(Lambda, Result);
462+
return;
463+
}
464+
406465
const auto *Fr = Result.Nodes.getNodeAs<FriendDecl>("Friend");
466+
const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func");
407467
assert(F && "Matcher is expected to find only FunctionDecls");
408468

409469
// Three-way comparison operator<=> is syntactic sugar and generates implicit
@@ -494,4 +554,75 @@ void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
494554
<< FixItHint::CreateInsertion(InsertionLoc, " -> " + ReturnType);
495555
}
496556

557+
void UseTrailingReturnTypeCheck::diagOnLambda(
558+
const LambdaExpr *Lambda,
559+
const ast_matchers::MatchFinder::MatchResult &Result) {
560+
561+
const CXXMethodDecl *Method = Lambda->getCallOperator();
562+
if (!Method || Lambda->hasExplicitResultType())
563+
return;
564+
565+
const QualType ReturnType = Method->getReturnType();
566+
if (ReturnType->isUndeducedAutoType() &&
567+
TransformLambdas == TransformLambda::AllExceptAuto)
568+
return;
569+
570+
const SourceLocation TrailingReturnInsertLoc =
571+
findLambdaTrailingReturnInsertLoc(Method, *Result.SourceManager,
572+
getLangOpts(), *Result.Context);
573+
574+
if (TrailingReturnInsertLoc.isValid())
575+
diag(Lambda->getBeginLoc(), "use a trailing return type for this lambda")
576+
<< FixItHint::CreateInsertion(
577+
TrailingReturnInsertLoc,
578+
" -> " +
579+
ReturnType.getAsString(Result.Context->getPrintingPolicy()));
580+
else
581+
diag(Lambda->getBeginLoc(), "use a trailing return type for this lambda");
582+
}
583+
584+
SourceLocation UseTrailingReturnTypeCheck::findLambdaTrailingReturnInsertLoc(
585+
const CXXMethodDecl *Method, const SourceManager &SM,
586+
const LangOptions &LangOpts, const ASTContext &Ctx) {
587+
// 'requires' keyword is present in lambda declaration
588+
if (Method->getTrailingRequiresClause()) {
589+
SourceLocation ParamEndLoc;
590+
if (Method->param_empty()) {
591+
ParamEndLoc = Method->getBeginLoc();
592+
} else {
593+
ParamEndLoc = Method->getParametersSourceRange().getEnd();
594+
}
595+
596+
std::pair<FileID, unsigned> ParamEndLocInfo =
597+
SM.getDecomposedLoc(ParamEndLoc);
598+
StringRef Buffer = SM.getBufferData(ParamEndLocInfo.first);
599+
600+
Lexer Lexer(SM.getLocForStartOfFile(ParamEndLocInfo.first), LangOpts,
601+
Buffer.begin(), Buffer.data() + ParamEndLocInfo.second,
602+
Buffer.end());
603+
604+
Token Token;
605+
while (!Lexer.LexFromRawLexer(Token)) {
606+
if (Token.is(tok::raw_identifier)) {
607+
IdentifierInfo &Info = Ctx.Idents.get(StringRef(
608+
SM.getCharacterData(Token.getLocation()), Token.getLength()));
609+
Token.setIdentifierInfo(&Info);
610+
Token.setKind(Info.getTokenID());
611+
}
612+
613+
if (Token.is(tok::kw_requires)) {
614+
return Token.getLocation().getLocWithOffset(-1);
615+
}
616+
}
617+
618+
return {};
619+
}
620+
621+
// If no requires clause, insert before the body
622+
if (const Stmt *Body = Method->getBody())
623+
return Body->getBeginLoc().getLocWithOffset(-1);
624+
625+
return {};
626+
}
627+
497628
} // namespace clang::tidy::modernize

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

+13-2
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,22 @@ struct ClassifiedToken {
2727
/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-trailing-return-type.html
2828
class UseTrailingReturnTypeCheck : public ClangTidyCheck {
2929
public:
30-
UseTrailingReturnTypeCheck(StringRef Name, ClangTidyContext *Context)
31-
: ClangTidyCheck(Name, Context) {}
30+
UseTrailingReturnTypeCheck(StringRef Name, ClangTidyContext *Context);
3231
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
3332
return LangOpts.CPlusPlus11;
3433
}
34+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
3535
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
3636
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
3737
Preprocessor *ModuleExpanderPP) override;
3838
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
3939

40+
enum TransformLambda { All, AllExceptAuto, None };
41+
4042
private:
4143
Preprocessor *PP = nullptr;
44+
const bool TransformFunctions;
45+
const TransformLambda TransformLambdas;
4246

4347
SourceLocation findTrailingReturnTypeSourceLocation(
4448
const FunctionDecl &F, const FunctionTypeLoc &FTL, const ASTContext &Ctx,
@@ -56,6 +60,13 @@ class UseTrailingReturnTypeCheck : public ClangTidyCheck {
5660
SourceRange ReturnTypeCVRange, const FunctionDecl &F,
5761
const FriendDecl *Fr, const ASTContext &Ctx,
5862
const SourceManager &SM, const LangOptions &LangOpts);
63+
64+
void diagOnLambda(const LambdaExpr *Lambda,
65+
const ast_matchers::MatchFinder::MatchResult &Result);
66+
SourceLocation findLambdaTrailingReturnInsertLoc(const CXXMethodDecl *Method,
67+
const SourceManager &SM,
68+
const LangOptions &LangOpts,
69+
const ASTContext &Ctx);
5970
};
6071

6172
} // namespace clang::tidy::modernize

clang-tools-extra/docs/ReleaseNotes.rst

+6
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,12 @@ Changes in existing checks
185185
<clang-tidy/checks/modernize/use-std-numbers>` check to support math
186186
functions of different precisions.
187187

188+
- Improved :doc:`modernize-use-trailing-return-type
189+
<clang-tidy/checks/modernize/use-trailing-return-type>` check by adding
190+
support for modifying lambda signatures to use trailing return type and adding
191+
two new options: `TransformFunctions` and `TransformLambdas` to control
192+
whether function declarations and lambdas should be transformed by the check.
193+
188194
- Improved :doc:`performance-move-const-arg
189195
<clang-tidy/checks/performance/move-const-arg>` check by fixing false
190196
negatives on ternary operators calling ``std::move``.

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

+25-1
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.
88+
* `AllExceptAuto` - Transform all lambda expressions except those whose return
89+
type can not be deduced (``auto`` type).
90+
* `None` - Do not transform any lambda expressions.
91+
92+
Default is `All`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %check_clang_tidy -std=c++20 %s modernize-use-trailing-return-type %t
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)