llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang-tidy

Author: Aayush Shrivastava (iamaayushrivastava)

<details>
<summary>Changes</summary>

Adds a new check that flags non-capturing lambdas (empty `[]` capture list) 
that are not yet marked `static`, and offers a fix-it to insert the specifier. 
Gated on C++23.

---
Full diff: https://github.com/llvm/llvm-project/pull/196893.diff


8 Files Affected:

- (modified) clang-tools-extra/clang-tidy/modernize/CMakeLists.txt (+1) 
- (modified) clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp 
(+3) 
- (added) clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.cpp (+77) 
- (added) clang-tools-extra/clang-tidy/modernize/UseStaticLambdaCheck.h (+38) 
- (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1) 
- (added) 
clang-tools-extra/docs/clang-tidy/checks/modernize/use-static-lambda.rst (+39) 
- (added) 
clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda-cxx20.cpp
 (+10) 
- (added) 
clang-tools-extra/test/clang-tidy/checkers/modernize/use-static-lambda.cpp 
(+79) 


``````````diff
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; };
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/196893
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to