llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang-tools-extra 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
