[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