Skip to content

Commit 4a7cdb4

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

10 files changed

+362
-22
lines changed

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

Lines changed: 163 additions & 16 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+
"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,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

116-
constexpr llvm::StringLiteral Message =
145+
constexpr llvm::StringLiteral MessageFunction =
117146
"use a trailing return type for this function";
147+
constexpr llvm::StringLiteral MessageLambda =
148+
"use a trailing return type for this lambda";
118149

119150
static SourceLocation expandIfMacroId(SourceLocation Loc,
120151
const SourceManager &SM) {
@@ -242,7 +273,7 @@ UseTrailingReturnTypeCheck::classifyTokensBeforeFunctionName(
242273
const MacroInfo *MI = PP->getMacroInfo(&Info);
243274
if (!MI || MI->isFunctionLike()) {
244275
// Cannot handle function style macros.
245-
diag(F.getLocation(), Message);
276+
diag(F.getLocation(), MessageFunction);
246277
return std::nullopt;
247278
}
248279
}
@@ -254,7 +285,7 @@ UseTrailingReturnTypeCheck::classifyTokensBeforeFunctionName(
254285
if (std::optional<ClassifiedToken> CT = classifyToken(F, *PP, T))
255286
ClassifiedTokens.push_back(*CT);
256287
else {
257-
diag(F.getLocation(), Message);
288+
diag(F.getLocation(), MessageFunction);
258289
return std::nullopt;
259290
}
260291
}
@@ -283,7 +314,7 @@ SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange(
283314
if (ReturnTypeRange.isInvalid()) {
284315
// Happens if e.g. clang cannot resolve all includes and the return type is
285316
// unknown.
286-
diag(F.getLocation(), Message);
317+
diag(F.getLocation(), MessageFunction);
287318
return {};
288319
}
289320

@@ -383,14 +414,44 @@ void UseTrailingReturnTypeCheck::keepSpecifiers(
383414
}
384415
}
385416

417+
UseTrailingReturnTypeCheck::UseTrailingReturnTypeCheck(
418+
StringRef Name, ClangTidyContext *Context)
419+
: ClangTidyCheck(Name, Context),
420+
TransformFunctions(Options.get("TransformFunctions", true)),
421+
TransformLambdas(Options.get("TransformLambdas", TransformLambda::All)) {
422+
423+
if (TransformFunctions == false && TransformLambdas == TransformLambda::None)
424+
this->configurationDiag(
425+
"The check 'modernize-use-trailing-return-type' will not perform any "
426+
"analysis because 'TransformFunctions' and 'TransformLambdas' are "
427+
"disabled.");
428+
}
429+
430+
void UseTrailingReturnTypeCheck::storeOptions(
431+
ClangTidyOptions::OptionMap &Opts) {
432+
Options.store(Opts, "TransformFunctions", TransformFunctions);
433+
Options.store(Opts, "TransformLambdas", TransformLambdas);
434+
}
435+
386436
void UseTrailingReturnTypeCheck::registerMatchers(MatchFinder *Finder) {
387-
auto F = functionDecl(
388-
unless(anyOf(hasTrailingReturn(), returns(voidType()),
389-
cxxConversionDecl(), cxxMethodDecl(isImplicit()))))
390-
.bind("Func");
437+
auto F =
438+
functionDecl(
439+
unless(anyOf(
440+
hasTrailingReturn(), returns(voidType()), cxxConversionDecl(),
441+
cxxMethodDecl(
442+
anyOf(isImplicit(),
443+
hasParent(cxxRecordDecl(hasParent(lambdaExpr()))))))))
444+
.bind("Func");
445+
446+
if (TransformFunctions) {
447+
Finder->addMatcher(F, this);
448+
Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this);
449+
}
391450

392-
Finder->addMatcher(F, this);
393-
Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this);
451+
if (TransformLambdas != TransformLambda::None) {
452+
Finder->addMatcher(
453+
lambdaExpr(unless(hasExplicitResultType())).bind("Lambda"), this);
454+
}
394455
}
395456

396457
void UseTrailingReturnTypeCheck::registerPPCallbacks(
@@ -402,8 +463,13 @@ void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
402463
assert(PP && "Expected registerPPCallbacks() to have been called before so "
403464
"preprocessor is available");
404465

405-
const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func");
466+
if (const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("Lambda")) {
467+
diagOnLambda(Lambda, Result);
468+
return;
469+
}
470+
406471
const auto *Fr = Result.Nodes.getNodeAs<FriendDecl>("Friend");
472+
const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func");
407473
assert(F && "Matcher is expected to find only FunctionDecls");
408474

409475
// Three-way comparison operator<=> is syntactic sugar and generates implicit
@@ -423,7 +489,7 @@ void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
423489
if (F->getDeclaredReturnType()->isFunctionPointerType() ||
424490
F->getDeclaredReturnType()->isMemberFunctionPointerType() ||
425491
F->getDeclaredReturnType()->isMemberPointerType()) {
426-
diag(F->getLocation(), Message);
492+
diag(F->getLocation(), MessageFunction);
427493
return;
428494
}
429495

@@ -440,14 +506,14 @@ void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
440506
// FIXME: This may happen if we have __attribute__((...)) on the function.
441507
// We abort for now. Remove this when the function type location gets
442508
// available in clang.
443-
diag(F->getLocation(), Message);
509+
diag(F->getLocation(), MessageFunction);
444510
return;
445511
}
446512

447513
SourceLocation InsertionLoc =
448514
findTrailingReturnTypeSourceLocation(*F, FTL, Ctx, SM, LangOpts);
449515
if (InsertionLoc.isInvalid()) {
450-
diag(F->getLocation(), Message);
516+
diag(F->getLocation(), MessageFunction);
451517
return;
452518
}
453519

@@ -470,7 +536,7 @@ void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
470536
UnqualNameVisitor UNV{*F};
471537
UNV.TraverseTypeLoc(FTL.getReturnLoc());
472538
if (UNV.Collision) {
473-
diag(F->getLocation(), Message);
539+
diag(F->getLocation(), MessageFunction);
474540
return;
475541
}
476542

@@ -489,9 +555,90 @@ void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
489555
keepSpecifiers(ReturnType, Auto, ReturnTypeCVRange, *F, Fr, Ctx, SM,
490556
LangOpts);
491557

492-
diag(F->getLocation(), Message)
558+
diag(F->getLocation(), MessageFunction)
493559
<< FixItHint::CreateReplacement(ReturnTypeCVRange, Auto)
494560
<< FixItHint::CreateInsertion(InsertionLoc, " -> " + ReturnType);
495561
}
496562

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

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

Lines changed: 13 additions & 2 deletions
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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ 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 to modernize 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+
Fixed false positives when lambda was matched as a function in C++11 mode.
194+
188195
- Improved :doc:`performance-move-const-arg
189196
<clang-tidy/checks/performance/move-const-arg>` check by fixing false
190197
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 in since C++14 and generic message will be emitted otherwise.
88+
* `AllExceptAuto` - 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`.

clang-tools-extra/test/clang-tidy/checkers/modernize/use-trailing-return-type-cxx20.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %check_clang_tidy -std=c++20 %s modernize-use-trailing-return-type %t
1+
// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-use-trailing-return-type %t
22

33
namespace std {
44
template <typename T, typename U>

0 commit comments

Comments
 (0)