Skip to content

[clang-tidy] Add support for lambda-expression in use-trailing-return-type check #135383

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 151 additions & 7 deletions clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,30 @@
#include <cctype>
#include <optional>

namespace clang::tidy {

template <>
struct OptionEnumMapping<
modernize::UseTrailingReturnTypeCheck::TransformLambda> {
static llvm::ArrayRef<std::pair<
modernize::UseTrailingReturnTypeCheck::TransformLambda, StringRef>>
getEnumMapping() {
static constexpr std::pair<
modernize::UseTrailingReturnTypeCheck::TransformLambda, StringRef>
Mapping[] = {
{modernize::UseTrailingReturnTypeCheck::TransformLambda::All,
"all"},
{modernize::UseTrailingReturnTypeCheck::TransformLambda::
AllExceptAuto,
"all_except_auto"},
{modernize::UseTrailingReturnTypeCheck::TransformLambda::None,
"none"}};
return Mapping;
}
};

} // namespace clang::tidy

using namespace clang::ast_matchers;

namespace clang::tidy::modernize {
Expand Down Expand Up @@ -111,10 +135,17 @@ struct UnqualNameVisitor : public RecursiveASTVisitor<UnqualNameVisitor> {
private:
const FunctionDecl &F;
};

AST_MATCHER(LambdaExpr, hasExplicitResultType) {
return Node.hasExplicitResultType();
}

} // namespace

constexpr llvm::StringLiteral ErrorMessageOnFunction =
"use a trailing return type for this function";
constexpr llvm::StringLiteral ErrorMessageOnLambda =
"use a trailing return type for this lambda";

static SourceLocation expandIfMacroId(SourceLocation Loc,
const SourceManager &SM) {
Expand Down Expand Up @@ -329,6 +360,48 @@ findReturnTypeAndCVSourceRange(const FunctionDecl &F, const TypeLoc &ReturnLoc,
return ReturnTypeRange;
}

static SourceLocation findLambdaTrailingReturnInsertLoc(
const CXXMethodDecl *Method, const SourceManager &SM,
const LangOptions &LangOpts, const ASTContext &Ctx) {
// 'requires' keyword is present in lambda declaration
if (Method->getTrailingRequiresClause()) {
SourceLocation ParamEndLoc;
if (Method->param_empty())
ParamEndLoc = Method->getBeginLoc();
else
ParamEndLoc = Method->getParametersSourceRange().getEnd();

std::pair<FileID, unsigned> ParamEndLocInfo =
SM.getDecomposedLoc(ParamEndLoc);
StringRef Buffer = SM.getBufferData(ParamEndLocInfo.first);

Lexer Lexer(SM.getLocForStartOfFile(ParamEndLocInfo.first), LangOpts,
Buffer.begin(), Buffer.data() + ParamEndLocInfo.second,
Buffer.end());

Token Token;
while (!Lexer.LexFromRawLexer(Token)) {
if (Token.is(tok::raw_identifier)) {
IdentifierInfo &Info = Ctx.Idents.get(StringRef(
SM.getCharacterData(Token.getLocation()), Token.getLength()));
Token.setIdentifierInfo(&Info);
Token.setKind(Info.getTokenID());
}

if (Token.is(tok::kw_requires))
return Token.getLocation().getLocWithOffset(-1);
}

return {};
}

// If no requires clause, insert before the body
if (const Stmt *Body = Method->getBody())
return Body->getBeginLoc().getLocWithOffset(-1);

return {};
}

static void keepSpecifiers(std::string &ReturnType, std::string &Auto,
SourceRange ReturnTypeCVRange, const FunctionDecl &F,
const FriendDecl *Fr, const ASTContext &Ctx,
Expand Down Expand Up @@ -382,14 +455,43 @@ static void keepSpecifiers(std::string &ReturnType, std::string &Auto,
}
}

UseTrailingReturnTypeCheck::UseTrailingReturnTypeCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
TransformFunctions(Options.get("TransformFunctions", true)),
TransformLambdas(Options.get("TransformLambdas", TransformLambda::All)) {

if (TransformFunctions == false && TransformLambdas == TransformLambda::None)
this->configurationDiag(
"The check 'modernize-use-trailing-return-type' will not perform any "
"analysis because 'TransformFunctions' and 'TransformLambdas' are "
"disabled.");
}

void UseTrailingReturnTypeCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "TransformFunctions", TransformFunctions);
Options.store(Opts, "TransformLambdas", TransformLambdas);
}

void UseTrailingReturnTypeCheck::registerMatchers(MatchFinder *Finder) {
auto F = functionDecl(
unless(anyOf(hasTrailingReturn(), returns(voidType()),
cxxConversionDecl(), cxxMethodDecl(isImplicit()))))
.bind("Func");
auto F =
functionDecl(
unless(anyOf(
hasTrailingReturn(), returns(voidType()), cxxConversionDecl(),
cxxMethodDecl(
anyOf(isImplicit(),
hasParent(cxxRecordDecl(hasParent(lambdaExpr()))))))))
.bind("Func");

if (TransformFunctions) {
Finder->addMatcher(F, this);
Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this);
}

Finder->addMatcher(F, this);
Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this);
if (TransformLambdas != TransformLambda::None)
Finder->addMatcher(
lambdaExpr(unless(hasExplicitResultType())).bind("Lambda"), this);
}

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

const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func");
if (const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("Lambda")) {
diagOnLambda(Lambda, Result);
return;
}

const auto *Fr = Result.Nodes.getNodeAs<FriendDecl>("Friend");
const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func");
assert(F && "Matcher is expected to find only FunctionDecls");

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

void UseTrailingReturnTypeCheck::diagOnLambda(
const LambdaExpr *Lambda,
const ast_matchers::MatchFinder::MatchResult &Result) {

const CXXMethodDecl *Method = Lambda->getCallOperator();
if (!Method || Lambda->hasExplicitResultType())
return;

const ASTContext *Ctx = Result.Context;
const QualType ReturnType = Method->getReturnType();

// We can't write 'auto' in C++11 mode, try to write generic msg and bail out.
if (ReturnType->isDependentType() &&
Ctx->getLangOpts().LangStd == LangStandard::lang_cxx11) {
if (TransformLambdas == TransformLambda::All)
diag(Lambda->getBeginLoc(), ErrorMessageOnLambda);
return;
}

if (ReturnType->isUndeducedAutoType() &&
TransformLambdas == TransformLambda::AllExceptAuto)
return;

const SourceLocation TrailingReturnInsertLoc =
findLambdaTrailingReturnInsertLoc(Method, *Result.SourceManager,
getLangOpts(), *Result.Context);

if (TrailingReturnInsertLoc.isValid())
diag(Lambda->getBeginLoc(), "use a trailing return type for this lambda")
<< FixItHint::CreateInsertion(
TrailingReturnInsertLoc,
" -> " +
ReturnType.getAsString(Result.Context->getPrintingPolicy()));
else
diag(Lambda->getBeginLoc(), ErrorMessageOnLambda);
}

} // namespace clang::tidy::modernize
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,25 @@ struct ClassifiedToken {
/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-trailing-return-type.html
class UseTrailingReturnTypeCheck : public ClangTidyCheck {
public:
UseTrailingReturnTypeCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
UseTrailingReturnTypeCheck(StringRef Name, ClangTidyContext *Context);
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus11;
}
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
Preprocessor *ModuleExpanderPP) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;

enum TransformLambda { All, AllExceptAuto, None };

private:
Preprocessor *PP = nullptr;
const bool TransformFunctions;
const TransformLambda TransformLambdas;

void diagOnLambda(const LambdaExpr *Lambda,
const ast_matchers::MatchFinder::MatchResult &Result);
};

} // namespace clang::tidy::modernize
Expand Down
7 changes: 7 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,13 @@ Changes in existing checks
<clang-tidy/checks/modernize/use-std-numbers>` check to support math
functions of different precisions.

- Improved :doc:`modernize-use-trailing-return-type
<clang-tidy/checks/modernize/use-trailing-return-type>` check by adding
support to modernize lambda signatures to use trailing return type and adding
two new options: `TransformFunctions` and `TransformLambdas` to control
whether function declarations and lambdas should be transformed by the check.
Fixed false positives when lambda was matched as a function in C++11 mode.

- Improved :doc:`performance-move-const-arg
<clang-tidy/checks/performance/move-const-arg>` check by fixing false
negatives on ternary operators calling ``std::move``.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
modernize-use-trailing-return-type
==================================

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

transforms to:

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

Known Limitations
-----------------
Expand Down Expand Up @@ -66,3 +68,25 @@ a careless rewrite would produce the following output:
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.

Options
-------

.. option:: TransformFunctions

When set to `true`, function declarations will be transformed to use trailing
return. Default is `true`.

.. option:: TransformLambdas

Controls how lambda expressions are transformed to use trailing
return type. Possible values are:

* `all` - Transform all lambda expressions without an explicit return type
to use trailing return type. If type can not be deduced, ``auto`` will be
used since C++14 and generic message will be emitted otherwise.
* `all_except_auto` - Transform all lambda expressions except those whose return
type can not be deduced.
* `none` - Do not transform any lambda expressions.

Default is `all`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// RUN: %check_clang_tidy -std=c++14-or-later %s modernize-use-trailing-return-type %t -- -- -fno-delayed-template-parsing

namespace std {
template <typename T>
class vector {};

class string {};
} // namespace std

void test_lambda_positive() {
auto l1 = [](auto x) { return x; };
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
// CHECK-FIXES: {{^}} auto l1 = [](auto x) -> auto { return x; };{{$}}
}

template <template <typename> class C>
void test_lambda_positive_template() {
auto l1 = []() { return C<int>{}; };
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
// CHECK-FIXES: {{^}} auto l1 = []() -> auto { return C<int>{}; };{{$}}
auto l2 = []() { return 0; };
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
// CHECK-FIXES: {{^}} auto l2 = []() -> auto { return 0; };{{$}}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-use-trailing-return-type %t -- -- -fno-delayed-template-parsing

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

void test_lambda_positive() {
auto l1 = []<typename T, typename U>(T x, U y) { return x + y; };
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
// CHECK-FIXES: {{^}} auto l1 = []<typename T, typename U>(T x, U y) -> auto { return x + y; };{{$}}
auto l2 = [](auto x) requires requires { x + x; } { return x; };
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
// CHECK-FIXES: {{^}} auto l2 = [](auto x) -> auto requires requires { x + x; } { return x; };{{$}}
auto l3 = [](auto x) requires std::floating_point<decltype(x)> { return x; };
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
// CHECK-FIXES: {{^}} auto l3 = [](auto x) -> auto requires std::floating_point<decltype(x)> { return x; };{{$}}
auto l4 = [](int x) consteval { return x; };
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
// CHECK-FIXES: {{^}} auto l4 = [](int x) consteval -> int { return x; };{{$}}
// Complete complex example
auto l5 = []<typename T, typename U>(T x, U y) constexpr noexcept
requires std::floating_point<T> && std::floating_point<U>
{ return x * y; };
// CHECK-MESSAGES: :[[@LINE-3]]:13: warning: use a trailing return type for this lambda [modernize-use-trailing-return-type]
// CHECK-FIXES: {{^}} auto l5 = []<typename T, typename U>(T x, U y) constexpr noexcept{{$}}
// CHECK-FIXES: {{^}} -> auto requires std::floating_point<T> && std::floating_point<U>{{$}}
// CHECK-FIXES: {{^}} { return x * y; };{{$}}
}
Loading