https://github.com/iamaayushrivastava updated https://github.com/llvm/llvm-project/pull/196893
>From 764b08cef88222650bc71f6978de72bff3f95c8a Mon Sep 17 00:00:00 2001 From: Aayush Shrivastava <[email protected]> Date: Mon, 11 May 2026 12:51:48 +0530 Subject: [PATCH 1/5] [clang-tidy] Add modernize-use-static-lambda check --- .../clang-tidy/modernize/CMakeLists.txt | 1 + .../modernize/ModernizeTidyModule.cpp | 3 + .../modernize/UseStaticLambdaCheck.cpp | 77 ++++++++++++++++++ .../modernize/UseStaticLambdaCheck.h | 38 +++++++++ .../docs/clang-tidy/checks/list.rst | 1 + .../checks/modernize/use-static-lambda.rst | 39 +++++++++ .../modernize/use-static-lambda-cxx20.cpp | 10 +++ .../checkers/modernize/use-static-lambda.cpp | 79 +++++++++++++++++++ 8 files changed, 248 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.h create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-static-lambda.rst create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda-cxx20.cpp create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt index 2c5c44db587fe..d8500fd2595b0 100644 --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -47,6 +47,7 @@ add_clang_library(clangTidyModernizeModule STATIC UseRangesCheck.cpp UseScopedLockCheck.cpp UseStartsEndsWithCheck.cpp + UseStaticLambdaCheck.cpp UseStdBitCheck.cpp UseStdFormatCheck.cpp UseStdNumbersCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp index cc13da7535bcb..dfb675f850d24 100644 --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -51,6 +51,7 @@ #include "UseStdFormatCheck.h" #include "UseStdNumbersCheck.h" #include "UseStdPrintCheck.h" +#include "UseStaticLambdaCheck.h" #include "UseStringViewCheck.h" #include "UseStructuredBindingCheck.h" #include "UseTrailingReturnTypeCheck.h" @@ -97,6 +98,8 @@ class ModernizeModule : public ClangTidyModule { "modernize-use-scoped-lock"); CheckFactories.registerCheck<UseStartsEndsWithCheck>( "modernize-use-starts-ends-with"); + CheckFactories.registerCheck<UseStaticLambdaCheck>( + "modernize-use-static-lambda"); CheckFactories.registerCheck<UseStdBitCheck>("modernize-use-std-bit"); CheckFactories.registerCheck<UseStdFormatCheck>("modernize-use-std-format"); CheckFactories.registerCheck<UseStdNumbersCheck>( diff --git a/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp new file mode 100644 index 0000000000000..25331baddee80 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp @@ -0,0 +1,77 @@ +#include "UseStaticLambdaCheck.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/LambdaCapture.h" +#include "clang/AST/TypeLoc.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +void UseStaticLambdaCheck::registerMatchers(MatchFinder *Finder) { + // Match lambdas that have no captures and no capture-default. + Finder->addMatcher( + lambdaExpr(unless(hasAnyCapture(lambdaCapture()))).bind("lambda"), this); +} + +void UseStaticLambdaCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda"); + if (!Lambda) + return; + + // Reject lambdas with a capture-default (e.g. [=] or [&] with no actual + // captures): they are not truly capture-free. + if (Lambda->getCaptureDefault() != LCD_None) + return; + + const CXXMethodDecl *CallOp = Lambda->getCallOperator(); + + // Already static + if (CallOp->isStatic()) + return; + + // `static` and `mutable` are mutually exclusive lambda-specifiers (C++23). + // A mutable lambda has a non-const call operator that is not already static. + if (!CallOp->isConst()) + return; + + const SourceLocation LambdaLoc = Lambda->getBeginLoc(); + if (LambdaLoc.isInvalid() || LambdaLoc.isMacroID()) + return; + + const SourceManager &SM = *Result.SourceManager; + const LangOptions &LangOpts = getLangOpts(); + + if (Lambda->hasExplicitParameters()) { + // Lambda has an explicit parameter list: [...](params) { ... }. + // Insert static right after the closing ). + const TypeSourceInfo *TSI = CallOp->getTypeSourceInfo(); + if (!TSI) + return; + auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>(); + if (!FTL) + return; + const SourceLocation RParenLoc = FTL.getRParenLoc(); + if (RParenLoc.isInvalid()) + return; + const SourceLocation InsertLoc = + Lexer::getLocForEndOfToken(RParenLoc, 0, SM, LangOpts); + diag(LambdaLoc, "lambda with empty capture list can be marked static") + << FixItHint::CreateInsertion(InsertLoc, " static"); + } else { + // Lambda has no explicit parameter list: [...] { ... }. + // We must add () as well since specifiers require a parameter list. + const SourceLocation IntroEnd = Lambda->getIntroducerRange().getEnd(); + if (IntroEnd.isInvalid()) + return; + const SourceLocation InsertLoc = + Lexer::getLocForEndOfToken(IntroEnd, 0, SM, LangOpts); + diag(LambdaLoc, "lambda with empty capture list can be marked 'static'") + << FixItHint::CreateInsertion(InsertLoc, "() static"); + } +} + +} // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.h new file mode 100644 index 0000000000000..abb5d6360668b --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.h @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTATICLAMBDACHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTATICLAMBDACHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::modernize { + +/// Finds non-capturing lambdas that can be marked ``static`` (C++23). +/// +/// A lambda with an empty capture list (``[]``) has no dependency on any +/// enclosing state. Marking it ``static`` communicates this clearly and +/// may allow the compiler to skip generating an implicit conversion-to- +/// function-pointer operator. +/// +/// For the user-facing documentation see: +/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-static-lambda.html +class UseStaticLambdaCheck : public ClangTidyCheck { +public: + UseStaticLambdaCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus23; + } +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTATICLAMBDACHECK_H diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index 053ce6f0779d9..ac4d9b7f6a033 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -326,6 +326,7 @@ Clang-Tidy Checks :doc:`modernize-use-ranges <modernize/use-ranges>`, "Yes" :doc:`modernize-use-scoped-lock <modernize/use-scoped-lock>`, "Yes" :doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, "Yes" + :doc:`modernize-use-static-lambda <modernize/use-static-lambda>`, "Yes" :doc:`modernize-use-std-bit <modernize/use-std-bit>`, "Yes" :doc:`modernize-use-std-format <modernize/use-std-format>`, "Yes" :doc:`modernize-use-std-numbers <modernize/use-std-numbers>`, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-static-lambda.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-static-lambda.rst new file mode 100644 index 0000000000000..5ff2721aa735d --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-static-lambda.rst @@ -0,0 +1,39 @@ +.. title:: clang-tidy - modernize-use-static-lambda + +modernize-use-static-lambda +=========================== + +Finds lambdas with an empty capture list (``[]``) that are not already marked +``static`` and suggests adding the ``static`` specifier (introduced in C++23). + +A non-capturing lambda does not depend on any enclosing state. Marking it +``static`` makes that property explicit in the source and allows the compiler +to omit the implicit conversion-to-function-pointer member. + +This check requires C++23 or later because ``static`` as a lambda-specifier +was introduced in C++23 (P2564R3). + +.. note:: + The ``static`` and ``mutable`` lambda-specifiers are mutually exclusive. + Mutable lambdas are not diagnosed even when their capture list is empty. + +Example +------- + +.. code-block:: c++ + + auto square = [](int x) { return x * x; }; + + auto answer = [] { return 42; }; + +transforms to: + +.. code-block:: c++ + + auto square = [](int x) static { return x * x; }; + + auto answer = []() static { return 42; }; + +Note that when the original lambda has no explicit parameter list, ``()`` is +inserted along with ``static``, because a parameter list is required whenever +lambda-specifiers are present. diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda-cxx20.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda-cxx20.cpp new file mode 100644 index 0000000000000..9b72d7644abe0 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda-cxx20.cpp @@ -0,0 +1,10 @@ +// RUN: %check_clang_tidy -std=c++20 %s modernize-use-static-lambda %t + +// The check is gated on C++23; no warnings should be emitted in C++20. + +void noWarnings() { + auto f1 = [](int x) { return x * x; }; + auto f2 = [] { return 42; }; + (void)f1; + (void)f2; +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda.cpp new file mode 100644 index 0000000000000..11dad0629b630 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda.cpp @@ -0,0 +1,79 @@ +// RUN: %check_clang_tidy -std=c++23-or-later %s modernize-use-static-lambda %t + +// Basic cases + +void basicCases() { + // Lambda with explicit params, no capture. + auto f1 = [](int x) { return x * x; }; + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] + // CHECK-FIXES: auto f1 = [](int x) static { return x * x; }; + + // Lambda with no params, no capture. + auto f2 = [] { return 42; }; + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] + // CHECK-FIXES: auto f2 = []() static { return 42; }; + + // Lambda with trailing return type. + auto f3 = [](int x) -> int { return x; }; + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] + // CHECK-FIXES: auto f3 = [](int x) static -> int { return x; }; + + // Lambda with noexcept. + auto f4 = []() noexcept { return 1; }; + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] + // CHECK-FIXES: auto f4 = []() static noexcept { return 1; }; +} + +// Already static: no warning + +void alreadyStatic() { + auto f = [](int x) static { return x * x; }; + (void)f; +} + +// Capturing lambdas: no warning + +void capturingLambdas() { + int x = 5; + + // Explicit capture by value. + auto f1 = [x]() { return x; }; + (void)f1; + + // Explicit capture by reference. + auto f2 = [&x]() { return x; }; + (void)f2; + + // Capture-default by value (even if nothing actually captured). + auto f3 = [=]() { return 0; }; + (void)f3; + + // Capture-default by reference (even if nothing actually captured). + auto f4 = [&]() { return 0; }; + (void)f4; +} + +// Mutable lambda: no warning (static and mutable are mutually exclusive) + +void mutableLambda() { + // Mutable with no capture is still excluded. + auto g = []() mutable { return 0; }; + (void)g; +} + +// Macro: no warning + +#define MAKE_LAMBDA [](int x) { return x; } + +void macroLambda() { + auto f = MAKE_LAMBDA; + (void)f; +} + +// constexpr lambda: warning (static + constexpr is valid in C++23) + +void constexprLambda() { + auto f = []() constexpr { return 42; }; + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] + // CHECK-FIXES: auto f = []() static constexpr { return 42; }; +} >From 38d3fe801804587e5f0ad88838f76319179641c9 Mon Sep 17 00:00:00 2001 From: Aayush Shrivastava <[email protected]> Date: Mon, 11 May 2026 13:11:27 +0530 Subject: [PATCH 2/5] Add license comments --- .../clang-tidy/modernize/UseStaticLambdaCheck.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp index 25331baddee80..24665882005b1 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp @@ -1,3 +1,11 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + #include "UseStaticLambdaCheck.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/LambdaCapture.h" @@ -74,4 +82,4 @@ void UseStaticLambdaCheck::check(const MatchFinder::MatchResult &Result) { } } -} // namespace clang::tidy::modernize +} // namespace clang::tidy::modernize \ No newline at end of file >From 4369134a2035fe7db8d8a6013de148ed26d75f9d Mon Sep 17 00:00:00 2001 From: Aayush Shrivastava <[email protected]> Date: Mon, 11 May 2026 13:21:46 +0530 Subject: [PATCH 3/5] [clang-tidy] Add release notes and fix include order for modernize-use-static-lambda --- .../clang-tidy/modernize/ModernizeTidyModule.cpp | 2 +- clang-tools-extra/docs/ReleaseNotes.rst | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp index dfb675f850d24..7471608b2172d 100644 --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -47,11 +47,11 @@ #include "UseRangesCheck.h" #include "UseScopedLockCheck.h" #include "UseStartsEndsWithCheck.h" +#include "UseStaticLambdaCheck.h" #include "UseStdBitCheck.h" #include "UseStdFormatCheck.h" #include "UseStdNumbersCheck.h" #include "UseStdPrintCheck.h" -#include "UseStaticLambdaCheck.h" #include "UseStringViewCheck.h" #include "UseStructuredBindingCheck.h" #include "UseTrailingReturnTypeCheck.h" diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index acf000908acb2..0c3370c0ba3cb 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -145,6 +145,12 @@ New checks ``llvm::to_vector(llvm::make_filter_range(...))`` that can be replaced with ``llvm::map_to_vector`` and ``llvm::filter_to_vector``. +- New :doc:`modernize-use-static-lambda + <clang-tidy/checks/modernize/use-static-lambda>` check. + + Finds lambdas with an empty capture list (``[]``) that are not marked + ``static`` and suggests adding the specifier (C++23). + - New :doc:`modernize-use-std-bit <clang-tidy/checks/modernize/use-std-bit>` check. >From ba74e6b0056a7e594439d860615f4a058880775b Mon Sep 17 00:00:00 2001 From: Aayush Shrivastava <[email protected]> Date: Mon, 11 May 2026 14:45:29 +0530 Subject: [PATCH 4/5] Fix inconsistent diagnostic string in modernize-use-static-lambda --- clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp index 24665882005b1..fdcdee06464ee 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp @@ -67,7 +67,7 @@ void UseStaticLambdaCheck::check(const MatchFinder::MatchResult &Result) { return; const SourceLocation InsertLoc = Lexer::getLocForEndOfToken(RParenLoc, 0, SM, LangOpts); - diag(LambdaLoc, "lambda with empty capture list can be marked static") + diag(LambdaLoc, "lambda with empty capture list can be marked 'static'") << FixItHint::CreateInsertion(InsertLoc, " static"); } else { // Lambda has no explicit parameter list: [...] { ... }. >From ede2b63a58aa44cc07031173cc82d4bc0e7e8d0e Mon Sep 17 00:00:00 2001 From: Aayush Shrivastava <[email protected]> Date: Tue, 12 May 2026 15:27:04 +0530 Subject: [PATCH 5/5] [clang-tidy] Address review feedback for modernize-use-static-lambda --- .../modernize/UseStaticLambdaCheck.cpp | 46 ++++++------ .../modernize/UseStaticLambdaCheck.h | 11 ++- .../checks/modernize/use-static-lambda.rst | 21 ++---- .../checkers/modernize/use-static-lambda.cpp | 75 +++++++++++++------ 4 files changed, 89 insertions(+), 64 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp index fdcdee06464ee..13ef0992f4f19 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp @@ -19,43 +19,41 @@ using namespace clang::ast_matchers; namespace clang::tidy::modernize { +namespace { +AST_MATCHER(LambdaExpr, hasNoCaptureDefault) { + return Node.getCaptureDefault() == LCD_None; +} +AST_MATCHER(LambdaExpr, callOperatorIsStatic) { + return Node.getCallOperator()->isStatic(); +} +AST_MATCHER(LambdaExpr, callOperatorIsConst) { + return Node.getCallOperator()->isConst(); +} +} // namespace + void UseStaticLambdaCheck::registerMatchers(MatchFinder *Finder) { - // Match lambdas that have no captures and no capture-default. Finder->addMatcher( - lambdaExpr(unless(hasAnyCapture(lambdaCapture()))).bind("lambda"), this); + lambdaExpr(hasNoCaptureDefault(), unless(hasAnyCapture(lambdaCapture())), + unless(callOperatorIsStatic()), callOperatorIsConst()) + .bind("lambda"), + this); } void UseStaticLambdaCheck::check(const MatchFinder::MatchResult &Result) { const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda"); - if (!Lambda) - return; - - // Reject lambdas with a capture-default (e.g. [=] or [&] with no actual - // captures): they are not truly capture-free. - if (Lambda->getCaptureDefault() != LCD_None) - return; - - const CXXMethodDecl *CallOp = Lambda->getCallOperator(); - - // Already static - if (CallOp->isStatic()) - return; - - // `static` and `mutable` are mutually exclusive lambda-specifiers (C++23). - // A mutable lambda has a non-const call operator that is not already static. - if (!CallOp->isConst()) - return; + assert(Lambda && "lambda should be bound by the matcher"); const SourceLocation LambdaLoc = Lambda->getBeginLoc(); if (LambdaLoc.isInvalid() || LambdaLoc.isMacroID()) return; + const CXXMethodDecl *CallOp = Lambda->getCallOperator(); const SourceManager &SM = *Result.SourceManager; const LangOptions &LangOpts = getLangOpts(); if (Lambda->hasExplicitParameters()) { // Lambda has an explicit parameter list: [...](params) { ... }. - // Insert static right after the closing ). + // Insert 'static' right after the closing ')'. const TypeSourceInfo *TSI = CallOp->getTypeSourceInfo(); if (!TSI) return; @@ -71,15 +69,15 @@ void UseStaticLambdaCheck::check(const MatchFinder::MatchResult &Result) { << FixItHint::CreateInsertion(InsertLoc, " static"); } else { // Lambda has no explicit parameter list: [...] { ... }. - // We must add () as well since specifiers require a parameter list. + // In C++23 (form 4), specs such as 'static' may appear without '()'. const SourceLocation IntroEnd = Lambda->getIntroducerRange().getEnd(); if (IntroEnd.isInvalid()) return; const SourceLocation InsertLoc = Lexer::getLocForEndOfToken(IntroEnd, 0, SM, LangOpts); diag(LambdaLoc, "lambda with empty capture list can be marked 'static'") - << FixItHint::CreateInsertion(InsertLoc, "() static"); + << FixItHint::CreateInsertion(InsertLoc, " static"); } } -} // namespace clang::tidy::modernize \ No newline at end of file +} // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.h index abb5d6360668b..dc90507d3c322 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.h @@ -17,8 +17,8 @@ namespace clang::tidy::modernize { /// /// A lambda with an empty capture list (``[]``) has no dependency on any /// enclosing state. Marking it ``static`` communicates this clearly and -/// may allow the compiler to skip generating an implicit conversion-to- -/// function-pointer operator. +/// enables the implicit conversion-to-function-pointer to return a direct +/// pointer to ``operator()`` rather than a trampoline. /// /// For the user-facing documentation see: /// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-static-lambda.html @@ -26,11 +26,14 @@ class UseStaticLambdaCheck : public ClangTidyCheck { public: UseStaticLambdaCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} - void registerMatchers(ast_matchers::MatchFinder *Finder) override; - void check(const ast_matchers::MatchFinder::MatchResult &Result) override; bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { return LangOpts.CPlusPlus23; } + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; }; } // namespace clang::tidy::modernize diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-static-lambda.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-static-lambda.rst index 5ff2721aa735d..424cc31fb67c4 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-static-lambda.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-static-lambda.rst @@ -7,15 +7,9 @@ Finds lambdas with an empty capture list (``[]``) that are not already marked ``static`` and suggests adding the ``static`` specifier (introduced in C++23). A non-capturing lambda does not depend on any enclosing state. Marking it -``static`` makes that property explicit in the source and allows the compiler -to omit the implicit conversion-to-function-pointer member. - -This check requires C++23 or later because ``static`` as a lambda-specifier -was introduced in C++23 (P2564R3). - -.. note:: - The ``static`` and ``mutable`` lambda-specifiers are mutually exclusive. - Mutable lambdas are not diagnosed even when their capture list is empty. +``static`` makes that property explicit in the source and enables the implicit +conversion-to-function-pointer to return a direct pointer to ``operator()`` +rather than a trampoline wrapping a default-constructed closure object. Example ------- @@ -32,8 +26,9 @@ transforms to: auto square = [](int x) static { return x * x; }; - auto answer = []() static { return 42; }; + auto answer = [] static { return 42; }; -Note that when the original lambda has no explicit parameter list, ``()`` is -inserted along with ``static``, because a parameter list is required whenever -lambda-specifiers are present. +When the original lambda has no explicit parameter list, ``static`` is inserted +directly after the capture list (``[]``) without adding ``()``. This is valid +in C++23, where lambda-specifiers may appear without an explicit parameter list +(grammar form 4: ``[captures] specs { body }``). diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda.cpp index 11dad0629b630..ebc6d603f65da 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda.cpp @@ -1,68 +1,49 @@ // RUN: %check_clang_tidy -std=c++23-or-later %s modernize-use-static-lambda %t -// Basic cases - void basicCases() { - // Lambda with explicit params, no capture. auto f1 = [](int x) { return x * x; }; // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] // CHECK-FIXES: auto f1 = [](int x) static { return x * x; }; - // Lambda with no params, no capture. auto f2 = [] { return 42; }; // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] - // CHECK-FIXES: auto f2 = []() static { return 42; }; + // CHECK-FIXES: auto f2 = [] static { return 42; }; - // Lambda with trailing return type. auto f3 = [](int x) -> int { return x; }; // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] // CHECK-FIXES: auto f3 = [](int x) static -> int { return x; }; - // Lambda with noexcept. auto f4 = []() noexcept { return 1; }; // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] // CHECK-FIXES: auto f4 = []() static noexcept { return 1; }; } -// Already static: no warning - void alreadyStatic() { auto f = [](int x) static { return x * x; }; (void)f; } -// Capturing lambdas: no warning - void capturingLambdas() { int x = 5; - - // Explicit capture by value. auto f1 = [x]() { return x; }; (void)f1; - // Explicit capture by reference. auto f2 = [&x]() { return x; }; (void)f2; - // Capture-default by value (even if nothing actually captured). + // Capture-default counts as a capture even if nothing is actually captured. auto f3 = [=]() { return 0; }; (void)f3; - // Capture-default by reference (even if nothing actually captured). auto f4 = [&]() { return 0; }; (void)f4; } -// Mutable lambda: no warning (static and mutable are mutually exclusive) - void mutableLambda() { - // Mutable with no capture is still excluded. auto g = []() mutable { return 0; }; (void)g; } -// Macro: no warning - #define MAKE_LAMBDA [](int x) { return x; } void macroLambda() { @@ -70,10 +51,58 @@ void macroLambda() { (void)f; } -// constexpr lambda: warning (static + constexpr is valid in C++23) - void constexprLambda() { auto f = []() constexpr { return 42; }; // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] // CHECK-FIXES: auto f = []() static constexpr { return 42; }; } + +void constevalLambda() { + auto L = []() consteval { return 1; }; + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] + // CHECK-FIXES: auto L = []() static consteval { return 1; }; + (void)L; +} + +void combinedSpecifiers() { + auto L = []() noexcept -> int { return 1; }; + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] + // CHECK-FIXES: auto L = []() static noexcept -> int { return 1; }; + (void)L; +} + +void genericLambda() { + auto L = [](auto X) { return X; }; + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] + // CHECK-FIXES: auto L = [](auto X) static { return X; }; + (void)L; +} + +void explicitTemplateParameterList() { + auto L = []<typename T>(T X) { return X; }; + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] + // CHECK-FIXES: auto L = []<typename T>(T X) static { return X; }; + (void)L; +} + +// The check must not produce duplicate diagnostics for template instantiations. +template <typename T> +void templated(T Value) { + auto L = [](T X) { return X; }; + // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: lambda with empty capture list can be marked 'static' [modernize-use-static-lambda] + // CHECK-FIXES: auto L = [](T X) static { return X; }; + (void)L; +} + +void instantiateTemplates() { + templated(1); + templated(2); +} + +// A lambda that is already static via a macro expansion must not be diagnosed. +#define STATIC static + +void staticFromMacro() { + auto L = []() STATIC { return 1; }; + (void)L; +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
