[clang-tools-extra] [clang-tidy] New checker: modernize-use-va-opt to replace the `, ##__VAR_ARG__` GNU extension (PR #188474)

via cfe-commits cfe-commits at lists.llvm.org
Wed Mar 25 05:31:01 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tools-extra

@llvm/pr-subscribers-clang-tidy

Author: serge-sans-paille

<details>
<summary>Changes</summary>



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


8 Files Affected:

- (modified) clang-tools-extra/clang-tidy/modernize/CMakeLists.txt (+1) 
- (modified) clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp (+2) 
- (added) clang-tools-extra/clang-tidy/modernize/UseVaOptCheck.cpp (+71) 
- (added) clang-tools-extra/clang-tidy/modernize/UseVaOptCheck.h (+33) 
- (modified) clang-tools-extra/docs/ReleaseNotes.rst (+5) 
- (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+2-1) 
- (added) clang-tools-extra/docs/clang-tidy/checks/modernize/use-va-opt.rst (+19) 
- (added) clang-tools-extra/test/clang-tidy/checkers/modernize/use-va-opt.cpp (+37) 


``````````diff
diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index 2c5c44db587fe..71aebcaf1feca 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -57,6 +57,7 @@ add_clang_library(clangTidyModernizeModule STATIC
   UseTransparentFunctorsCheck.cpp
   UseUncaughtExceptionsCheck.cpp
   UseUsingCheck.cpp
+  UseVaOptCheck.cpp
 
   LINK_LIBS
   clangTidy
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index cc13da7535bcb..e45538213c6e7 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -57,6 +57,7 @@
 #include "UseTransparentFunctorsCheck.h"
 #include "UseUncaughtExceptionsCheck.h"
 #include "UseUsingCheck.h"
+#include "UseVaOptCheck.h"
 
 using namespace clang::ast_matchers;
 
@@ -145,6 +146,7 @@ class ModernizeModule : public ClangTidyModule {
     CheckFactories.registerCheck<UseUncaughtExceptionsCheck>(
         "modernize-use-uncaught-exceptions");
     CheckFactories.registerCheck<UseUsingCheck>("modernize-use-using");
+    CheckFactories.registerCheck<UseVaOptCheck>("modernize-use-va-opt");
   }
 };
 
diff --git a/clang-tools-extra/clang-tidy/modernize/UseVaOptCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseVaOptCheck.cpp
new file mode 100644
index 0000000000000..2ea4acf20f740
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseVaOptCheck.cpp
@@ -0,0 +1,71 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "UseVaOptCheck.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Lex/PPCallbacks.h"
+#include "clang/Lex/Preprocessor.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+namespace {
+class VaOptPPCallbacks : public PPCallbacks {
+public:
+  VaOptPPCallbacks(Preprocessor *PP, UseVaOptCheck *Check)
+      : PP(PP), Check(Check) {}
+
+  void MacroDefined(const Token &MacroNameTok,
+                    const MacroDirective *MD) override {
+    const MacroInfo *MI = MD->getMacroInfo();
+    if (!MI->isVariadic())
+      return;
+
+    std::optional<Token> PrevComma;
+    bool PrevHashHash = false;
+    ;
+    for (Token Tok : MI->tokens()) {
+      if (PrevHashHash) {
+        if (const auto *II = Tok.getIdentifierInfo();
+            II && II->getName() == "__VA_ARGS__") {
+          // FIXME: The replacement really should be " __VA_OPT__(,)
+          // __VA_ARGS__", but this breaks the fixit which removes the , in that
+          // case o_O.
+          Check->diag(Tok.getLocation(),
+                      "Use __VA_OPT__ instead of GNU extension to __VA_ARGS__")
+              << FixItHint::CreateReplacement(
+                     SourceRange(PrevComma->getLocation(), Tok.getLocation()),
+                     " __VA_OPT__(',') __VA_ARGS__");
+        }
+        PrevComma = std::nullopt;
+        PrevHashHash = false;
+      } else if (PrevComma) {
+        if (Tok.is(tok::hashhash))
+          PrevHashHash = true;
+        else
+          PrevComma = std::nullopt;
+      } else if (Tok.is(tok::comma)) {
+        PrevComma = Tok;
+      }
+    }
+  }
+
+private:
+  Preprocessor *PP;
+  UseVaOptCheck *Check;
+};
+} // namespace
+
+void UseVaOptCheck::registerPPCallbacks(const SourceManager &SM,
+                                        Preprocessor *PP,
+                                        Preprocessor *ModuleExpanderPP) {
+  PP->addPPCallbacks(std::make_unique<VaOptPPCallbacks>(PP, this));
+}
+
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UseVaOptCheck.h b/clang-tools-extra/clang-tidy/modernize/UseVaOptCheck.h
new file mode 100644
index 0000000000000..11493f298d842
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseVaOptCheck.h
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_USEVAOPTCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEVAOPTCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::modernize {
+
+/// Use __VA_OPT__ instead of ##__VA_ARGS__ GNU extension.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-va-opt.html
+class UseVaOptCheck : public ClangTidyCheck {
+public:
+  UseVaOptCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+                           Preprocessor *ModuleExpanderPP) override;
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus20 || LangOpts.C23;
+  }
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEVAOPTCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 49901f8a706c6..c646d5281c0b5 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -145,6 +145,11 @@ New checks
   Finds places where structured bindings could be used to decompose pairs and
   suggests replacing them.
 
+- New :doc:`modernize-use-va-opt
+  <clang-tidy/checks/modernize/use-va-opt>` check.
+
+  Use __VA_OPT__ instead of ##__VA_ARGS__ GNU extension.
+
 - New :doc:`performance-string-view-conversions
   <clang-tidy/checks/performance/string-view-conversions>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index ceab1e9414951..c0f7f81c2b83a 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -241,7 +241,6 @@ Clang-Tidy Checks
    :doc:`google-runtime-int <google/runtime-int>`,
    :doc:`google-runtime-operator <google/runtime-operator>`,
    :doc:`google-upgrade-googletest-case <google/upgrade-googletest-case>`, "Yes"
-   :doc:`hicpp-exception-baseclass <hicpp/exception-baseclass>`,
    :doc:`hicpp-multiway-paths-covered <hicpp/multiway-paths-covered>`,
    :doc:`hicpp-signed-bitwise <hicpp/signed-bitwise>`,
    :doc:`linuxkernel-must-check-errs <linuxkernel/must-check-errs>`,
@@ -328,6 +327,7 @@ Clang-Tidy Checks
    :doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, "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-midway <modernize/use-std-midway>`,
    :doc:`modernize-use-std-numbers <modernize/use-std-numbers>`, "Yes"
    :doc:`modernize-use-std-print <modernize/use-std-print>`, "Yes"
    :doc:`modernize-use-string-view <modernize/use-string-view>`, "Yes"
@@ -336,6 +336,7 @@ Clang-Tidy Checks
    :doc:`modernize-use-transparent-functors <modernize/use-transparent-functors>`, "Yes"
    :doc:`modernize-use-uncaught-exceptions <modernize/use-uncaught-exceptions>`, "Yes"
    :doc:`modernize-use-using <modernize/use-using>`, "Yes"
+   :doc:`modernize-use-va-opt <modernize/use-va-opt>`, "Yes"
    :doc:`mpi-buffer-deref <mpi/buffer-deref>`, "Yes"
    :doc:`mpi-type-mismatch <mpi/type-mismatch>`, "Yes"
    :doc:`objc-assert-equals <objc/assert-equals>`, "Yes"
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-va-opt.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-va-opt.rst
new file mode 100644
index 0000000000000..af631ff18419a
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-va-opt.rst
@@ -0,0 +1,19 @@
+.. title:: clang-tidy - modernize-use-va-opt
+
+modernize-use-va-opt
+====================
+
+Suggest using ``__VA_OPT__(,)`` instead of ``, ##__VA_ARGS__`` when implementing
+variadic macro. ``, ##__VA_ARGS__`` is a GNU extension.
+
+.. code:: c++
+
+    extern int bar(...);
+    #define FOO(a, ...) bar(a, ##__VA_ARGS__)
+
+becomes:
+
+.. code:: c++
+
+    extern int bar(...);
+    #define FOO(a, ...) bar(a __VA_OPT__(,) __VA_ARGS__)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-va-opt.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-va-opt.cpp
new file mode 100644
index 0000000000000..b0fde0000de23
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-va-opt.cpp
@@ -0,0 +1,37 @@
+// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-use-va-opt %t
+
+extern void foo(...);
+
+
+// CHECK-MESSAGES: :[[@LINE+2]]:26: warning: Use __VA_OPT__ instead of GNU extension to __VA_ARGS__ [modernize-use-va-opt]
+// CHECK-FIXES: #define M0(...) foo(1 __VA_OPT__(',') __VA_ARGS__)
+#define M0(...) foo(1, ##__VA_ARGS__)
+
+// CHECK-MESSAGES: :[[@LINE+2]]:27: warning: Use __VA_OPT__ instead of GNU extension to __VA_ARGS__ [modernize-use-va-opt]
+// CHECK-FIXES: #define M1(...) foo(1 __VA_OPT__(',') __VA_ARGS__)
+#define M1(...) foo(1, ## __VA_ARGS__)
+
+// CHECK-MESSAGES: :[[@LINE+2]]:27: warning: Use __VA_OPT__ instead of GNU extension to __VA_ARGS__ [modernize-use-va-opt]
+// CHECK-FIXES: #define M2(...) foo(1 __VA_OPT__(',') __VA_ARGS__)
+#define M2(...) foo(1 ,## __VA_ARGS__)
+
+// CHECK-MESSAGES: :[[@LINE+2]]:28: warning: Use __VA_OPT__ instead of GNU extension to __VA_ARGS__ [modernize-use-va-opt]
+// CHECK-FIXES: #define M3(...) foo(1 __VA_OPT__(',') __VA_ARGS__)
+#define M3(...) foo(1 , ## __VA_ARGS__)
+
+// CHECK-MESSAGES: :[[@LINE+3]]:28: warning: Use __VA_OPT__ instead of GNU extension to __VA_ARGS__ [modernize-use-va-opt]
+// CHECK-MESSAGES: :[[@LINE+2]]:44: warning: Use __VA_OPT__ instead of GNU extension to __VA_ARGS__ [modernize-use-va-opt]
+// CHECK-FIXES: #define M4(...) foo(1 __VA_OPT__(',') __VA_ARGS__ __VA_OPT__(',') __VA_ARGS__)
+#define M4(...) foo(1 , ## __VA_ARGS__, ## __VA_ARGS__)
+
+// No message, this will never add a comma before __VA_ARGS__
+#define P0(...) foo(1 ##__VA_ARGS__)
+
+// No message, this will never add a comma before __VA_ARGS__
+#define P1(...) foo(__VA_ARGS__)
+
+// No message, this will never add a comma before __VA_ARGS__
+#define P2(...) foo(##__VA_ARGS__)
+
+// No message, this will never add a comma before __VA_ARGS__
+#define P3(...) foo(, __VA_ARGS__)

``````````

</details>


https://github.com/llvm/llvm-project/pull/188474


More information about the cfe-commits mailing list