[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-12 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/30] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-11 Thread Zeyi Xu via cfe-commits


@@ -0,0 +1,175 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include 

zeyi2 wrote:

```suggestion
```

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-11 Thread Victor Chernyakin via cfe-commits

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-11 Thread Zeyi Xu via cfe-commits

https://github.com/zeyi2 approved this pull request.

LGTM with a nit, thank you!

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-11 Thread Victor Chernyakin via cfe-commits


@@ -0,0 +1,175 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include 
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+// Strip the format part first, since it may contain commas (e.g. 
{0:$[,]}).

localspook wrote:

Ah, sorry, one more thing: I believe this comment is now stale:
```suggestion
```

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-11 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/33] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-11 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/32] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-11 Thread Victor Chernyakin via cfe-commits

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-11 Thread Zeyi Xu via cfe-commits

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-11 Thread Yanzuo Liu via cfe-commits


@@ -0,0 +1,42 @@
+//===--===//
+//
+// 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_LLVM_FORMATVSTRINGCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_FORMATVSTRINGCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "llvm/ADT/StringMap.h"
+
+namespace clang::tidy::llvm_check {
+
+/// Validates llvm::formatv format strings against the provided arguments.
+///
+/// Checks that:
+/// - The number of format indices matches the number of arguments.
+/// - Every argument is used by the format string.
+/// - Automatic and explicit indices are not mixed.
+class FormatvStringCheck : public ClangTidyCheck {
+public:

zwuis wrote:

Sorry that I just realized this prevent matching nodes in template 
instantiations:

```cpp
template 
void f(Ts... Args) {
  formatv("{}", Args...);
}

void g() {
  f(1, 2); // no warning
}
```

, but I don't know how rare it is. It's up to you to revert it.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/29] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/28] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits


@@ -0,0 +1,183 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include 
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+// Strip the format part first, since it may contain commas (e.g. 
{0:$[,]}).
+StringRef IndexStr = Content;
+
+const size_t ColonPos = Content.find(':');
+if (ColonPos != StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+const size_t CommaPos = IndexStr.find(',');
+if (CommaPos != StringRef::npos)
+  IndexStr = IndexStr.substr(0, CommaPos);

kastiglione wrote:

looks good

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits


@@ -0,0 +1,189 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include 
+#include 
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions = {"llvm::formatv", "llvm::createStringErrorV"};
+
+  std::vector CustomFunctions =
+  utils::options::parseStringList(AdditionalFunctions);
+  copy(CustomFunctions, std::back_inserter(Functions));

kastiglione wrote:

`hasAnyName` uses unqualified matching if any of the names are unqualified. If 
my understanding is correct `::llvm::formatv` would match against 
`my_custom_namespace::llvm::formatv` – but only if the `AdditionalFunctions` 
contains unqualified names.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Victor Chernyakin via cfe-commits


@@ -0,0 +1,183 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include 
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+// Strip the format part first, since it may contain commas (e.g. 
{0:$[,]}).
+StringRef IndexStr = Content;
+
+const size_t ColonPos = Content.find(':');
+if (ColonPos != StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+const size_t CommaPos = IndexStr.find(',');
+if (CommaPos != StringRef::npos)
+  IndexStr = IndexStr.substr(0, CommaPos);

localspook wrote:

Up to you if you prefer it, but this can be written as:
```suggestion
const StringRef IndexStr = Content.substr(0, Content.find_first_of(",:");
```
(If `find_first_of` returns `npos`, that's okay, `substr` handles it)

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Victor Chernyakin via cfe-commits


@@ -0,0 +1,183 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include 
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+// Strip the format part first, since it may contain commas (e.g. 
{0:$[,]}).
+StringRef IndexStr = Content;
+
+const size_t ColonPos = Content.find(':');
+if (ColonPos != StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+const size_t CommaPos = IndexStr.find(',');
+if (CommaPos != StringRef::npos)
+  IndexStr = IndexStr.substr(0, CommaPos);
+
+IndexStr = IndexStr.trim();

localspook wrote:

If I'm understanding right, this line allows us to successfully parse something 
like `{  0   :x}` as a valid index? But does `formatv` actually allow that? If 
it does, can we add tests for it?

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Victor Chernyakin via cfe-commits


@@ -0,0 +1,189 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include 
+#include 
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions = {"llvm::formatv", "llvm::createStringErrorV"};
+
+  std::vector CustomFunctions =
+  utils::options::parseStringList(AdditionalFunctions);
+  copy(CustomFunctions, std::back_inserter(Functions));

localspook wrote:

> Now I wonder, after parsing the AdditionalFunctions, should a :: namespace 
> qualifier be added to its functions (if not present)?

(For context, the motivation for fully qualifying is to not match on things 
like `my_custom_namespace::llvm::formatv`)

While I agree that a user probably *intended* to fully qualify their own 
`AdditionalFunctions`, adding the `::` wouold be an invisible behavior-changing 
transformation, and we don't do it any other check, so I lean towards not 
touching what the user wrote.

> there could be a mix of qualified and unqualified names given to `hasAnyName`.

Having a mix is okay AFAIK, `hasAnyName` considers each name independently of 
the others.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Victor Chernyakin via cfe-commits


@@ -0,0 +1,189 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include 
+#include 
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions = {"llvm::formatv", "llvm::createStringErrorV"};
+
+  std::vector CustomFunctions =
+  utils::options::parseStringList(AdditionalFunctions);
+  copy(CustomFunctions, std::back_inserter(Functions));
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+  // Build a matcher for all configured function names.
+  Finder->addMatcher(
+  callExpr(callee(functionDecl(hasAnyName(Functions),
+   ast_matchers::isTemplateInstantiation())),
+   argumentCountAtLeast(1))
+  .bind("call"),
+  this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Call = Result.Nodes.getNodeAs("call");
+  assert(Call && Call->getNumArgs() > 0);
+
+  const auto *FD = Call->getDirectCallee();
+  assert(FD);
+
+  // Find the format string index from the template signature: it's the
+  // parameter immediately before the trailing parameter pack.
+  const FunctionDecl *TemplateDecl = FD;
+  if (const FunctionTemplateDecl *Primary = FD->getPrimaryTemplate())
+TemplateDecl = Primary->getTemplatedDecl();
+
+  const unsigned NumDeclParams = TemplateDecl->getNumParams();
+  if (NumDeclParams < 2)
+return;
+
+  const unsigned PackParamIndex = NumDeclParams - 1;
+  if (!TemplateDecl->getParamDecl(PackParamIndex)->isParameterPack())
+return;
+
+  const unsigned FmtStringIndex = PackParamIndex - 1;
+
+  if (Call->getNumArgs() <= FmtStringIndex)
+return;
+
+  // Extract the format string literal.
+  const Expr *FmtArg = Call->getArg(FmtStringIndex)->IgnoreParenImpCasts();
+  const auto *FmtLiteral = dyn_cast(FmtArg);
+  if (!FmtLiteral)
+return;
+
+  const StringRef FmtString = FmtLiteral->getString();
+  const int NumFmtArgs = Call->getNumArgs() - PackParamIndex;
+

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/22] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/27] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits


@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions.insert("llvm::formatv");
+  Functions.insert("llvm::createStringErrorV");
+
+  // Parse semicolon-separated function names from AdditionalFunctions.
+  const llvm::StringRef Input(AdditionalFunctions);
+  llvm::SmallVector Entries;
+  Input.split(Entries, ';', -1, false);
+  for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+  Functions.insert(Entry);
+  }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+  // Build a matcher for all configured function names.
+  std::vector Names;
+  Names.reserve(Functions.size());
+  llvm::copy(Functions.keys(), std::back_inserter(Names));

kastiglione wrote:

updated here 
https://github.com/llvm/llvm-project/pull/195974/commits/404b03fe4ccd3ec5f503e9164c65d809555a84b6

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits

kastiglione wrote:

I'm running this against llvm, and it surfaced a bug:

```
/Users/dlee43/src/llvm-project/llvm/lib/Support/BalancedPartitioning.cpp:25:17: 
warning: invalid replacement index in format string [llvm-formatv-string]
   25 |   OS << formatv("{{ID={0} Utilities={{{1:$[,]}} Bucket={2}}", Id,
  | ^
```

The index parsing breaks on `{1:$[,]}`. I'll have to make updates to the PR.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/26] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits


@@ -0,0 +1,189 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include 
+#include 
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions = {"llvm::formatv", "llvm::createStringErrorV"};
+
+  std::vector CustomFunctions =
+  utils::options::parseStringList(AdditionalFunctions);
+  copy(CustomFunctions, std::back_inserter(Functions));

kastiglione wrote:

Thanks, I applied your patch.

Now I wonder, after  parsing the `AdditionalFunctions`, should a `::` namespace 
qualifier be added to its functions (if not present)? If not, there could be a 
mix of qualified and unqualified names given to `hasAnyName`.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/25] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Victor Chernyakin via cfe-commits


@@ -0,0 +1,189 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include 
+#include 
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions = {"llvm::formatv", "llvm::createStringErrorV"};
+
+  std::vector CustomFunctions =
+  utils::options::parseStringList(AdditionalFunctions);
+  copy(CustomFunctions, std::back_inserter(Functions));

localspook wrote:

I would reorder this like so, to avoid allocating the extra `CustomFunctions` 
vector:
```suggestion
  Functions = utils::options::parseStringList(AdditionalFunctions);
  Functions.emplace_back("::llvm::formatv");
  Functions.emplace_back("::llvm::createStringErrorV");
```
(also note that the functions should be fully qualified)

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread via cfe-commits

github-actions[bot] wrote:


# :penguin: Linux x64 Test Results

The build failed before running any tests. Click on a failure below to see the 
details.


tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.o

```
FAILED: 
tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.o
sccache /opt/llvm/bin/clang++ -D_DEBUG -D_GLIBCXX_ASSERTIONS 
-D_GLIBCXX_USE_CXX11_ABI=1 -D_GNU_SOURCE 
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE 
-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/tools/extra/clang-tidy/llvm
 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/llvm
 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/tools/extra/clang-tidy
 -I/home/gha/actions-runner/_work/llvm-project/llvm-project/clang/include 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/include
 -I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/include 
-I/home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/include -gmlt 
-fPIC -fno-semantic-interposition -fvisibility-inlines-hidden -Werror 
-Werror=date-time -Werror=unguarded-availability-new -Wall -Wextra 
-Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers 
-pedantic -Wno-long-long -Wc++98-compat-extra-semi -Wimplicit-fallthrough 
-Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor 
-Wdelete-non-virtual-dtor -Wsuggest-override -Wstring-conversion 
-Wno-pass-failed -Wmisleading-indentation -Wctad-maybe-unsupported 
-fdiagnostics-color -ffunction-sections -fdata-sections -fno-common 
-Woverloaded-virtual -Wno-nested-anon-types -O3 -DNDEBUG -std=c++17 -UNDEBUG 
-fno-exceptions -funwind-tables -fno-rtti -MD -MT 
tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.o
 -MF 
tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.o.d
 -o 
tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.o
 -c 
/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp:176:7:
 error: use of undeclared identifier 'UsedIndices'; did you mean 
'UnusedIndices'?
176 |   UsedIndices.reset(Index);
|   ^~~
|   UnusedIndices
/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp:174:26:
 note: 'UnusedIndices' declared here
174 | llvm::SmallBitVector UnusedIndices(NumRequiredArgs, true);
|  ^
1 error generated.
```


If these failures are unrelated to your changes (for example tests are broken 
or flaky at HEAD), please open an issue at 
https://github.com/llvm/llvm-project/issues and add the `infrastructure` label.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread via cfe-commits

github-actions[bot] wrote:


# :window: Windows x64 Test Results

The build failed before running any tests. Click on a failure below to see the 
details.


[code=1] 
tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.obj

```
FAILED: [code=1] 
tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.obj
sccache C:\clang\clang-msvc\bin\clang-cl.exe  /nologo -TP -DCLANG_BUILD_STATIC 
-DUNICODE -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_NONSTDC_NO_WARNINGS 
-D_CRT_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS -D_GLIBCXX_ASSERTIONS 
-D_HAS_EXCEPTIONS=0 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE 
-D_SCL_SECURE_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS -D_UNICODE 
-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS 
-IC:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\clang-tidy\llvm
 -IC:\_work\llvm-project\llvm-project\clang-tools-extra\clang-tidy\llvm 
-IC:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\clang-tidy 
-IC:\_work\llvm-project\llvm-project\clang\include 
-IC:\_work\llvm-project\llvm-project\build\tools\clang\include 
-IC:\_work\llvm-project\llvm-project\build\include 
-IC:\_work\llvm-project\llvm-project\llvm\include /DWIN32 /D_WINDOWS   
/Zc:inline /Zc:__cplusplus /Oi /Brepro /bigobj /permissive- 
-Werror=unguarded-availability-new /W4  -Wextra -Wno-unused-parameter 
-Wwrite-strings -Wcast-qual -Wmissing-field-initializers -Wimplicit-fallthrough 
-Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor 
-Wdelete-non-virtual-dtor -Wsuggest-override -Wstring-conversion 
-Wno-pass-failed -Wmisleading-indentation -Wctad-maybe-unsupported /Gw /O2 /Ob2 
 -std:c++17 -MD -UNDEBUG /EHs-c- /GR- /showIncludes 
/Fotools\clang\tools\extra\clang-tidy\llvm\CMakeFiles\obj.clangTidyLLVMModule.dir\FormatvStringCheck.cpp.obj
 
/Fdtools\clang\tools\extra\clang-tidy\llvm\CMakeFiles\obj.clangTidyLLVMModule.dir\
 -c -- 
C:\_work\llvm-project\llvm-project\clang-tools-extra\clang-tidy\llvm\FormatvStringCheck.cpp
C:\_work\llvm-project\llvm-project\clang-tools-extra\clang-tidy\llvm\FormatvStringCheck.cpp(176,7):
 error: use of undeclared identifier 'UsedIndices'; did you mean 
'UnusedIndices'?
176 |   UsedIndices.reset(Index);
|   ^~~
|   UnusedIndices
C:\_work\llvm-project\llvm-project\clang-tools-extra\clang-tidy\llvm\FormatvStringCheck.cpp(174,26):
 note: 'UnusedIndices' declared here
174 | llvm::SmallBitVector UnusedIndices(NumRequiredArgs, true);
|  ^
1 error generated.
```


If these failures are unrelated to your changes (for example tests are broken 
or flaky at HEAD), please open an issue at 
https://github.com/llvm/llvm-project/issues and add the `infrastructure` label.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/24] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/23] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits


@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions.insert("llvm::formatv");
+  Functions.insert("llvm::createStringErrorV");
+
+  // Parse semicolon-separated function names from AdditionalFunctions.
+  const llvm::StringRef Input(AdditionalFunctions);

kastiglione wrote:

here are all such drops of explicit `llvm::` namespace use 
https://github.com/llvm/llvm-project/pull/195974/commits/bd2f88264e9cb1b3791d7f85b47bbc2a5d91fe51

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits


@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions.insert("llvm::formatv");
+  Functions.insert("llvm::createStringErrorV");
+
+  // Parse semicolon-separated function names from AdditionalFunctions.
+  const llvm::StringRef Input(AdditionalFunctions);
+  llvm::SmallVector Entries;
+  Input.split(Entries, ';', -1, false);
+  for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+  Functions.insert(Entry);
+  }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+  // Build a matcher for all configured function names.
+  std::vector Names;
+  Names.reserve(Functions.size());
+  llvm::copy(Functions.keys(), std::back_inserter(Names));
+
+  Finder->addMatcher(
+  callExpr(callee(functionDecl(hasAnyName(Names),
+   ast_matchers::isTemplateInstantiation())),
+   argumentCountAtLeast(1))
+  .bind("call"),
+  this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Call = Result.Nodes.getNodeAs("call");
+  assert(Call && Call->getNumArgs() > 0);
+
+  const auto *FD = Call->getDirectCallee();
+  assert(FD);
+
+  // Find the format string index from the template signature: it's the
+  // parameter immediately before the trailing parameter pack.
+  const FunctionDecl *TemplateDecl = FD;
+  if (const FunctionTemplateDecl *Primary = FD->getPrimaryTemplate())
+TemplateDecl = Primary->getTemplatedDecl();
+
+  const unsigned NumDeclParams = TemplateDecl->getNumParams();
+  if (NumDeclParams < 2)
+return;
+
+  const unsigned PackParamIndex = NumDeclParams - 1;
+  if (!TemplateDecl->getParamDecl(PackParamIndex)->isParameterPack())
+return;
+
+  const unsigned FmtStringIndex = PackParamIndex - 1;
+
+  if (Call->getNumArgs() <= FmtStringIndex)
+return;
+
+  /

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits


@@ -0,0 +1,69 @@
+// RUN: %check_clang_tidy %s llvm-formatv-string %t
+
+namespace llvm {
+
+template 
+void formatv(const char *Fmt, Ts &&...Vals) {}
+
+template 
+void formatv(bool Validate, const char *Fmt, Ts &&...Vals) {}
+
+} // namespace llvm
+
+void correct() {
+  llvm::formatv("{0}", 1);
+  llvm::formatv("{0} {1}", 1, 2);
+  llvm::formatv("{0} {0}", 1);
+  llvm::formatv("{1} {0}", 1, 2);
+  llvm::formatv("{0,10}", 1);
+  llvm::formatv("{0,-10}", 1);
+  llvm::formatv("{0:x}", 1);
+  llvm::formatv("{0,10:x}", 1);
+  llvm::formatv("no replacements");
+  llvm::formatv("escaped {{ braces }}");
+  llvm::formatv("{}", 1);
+  llvm::formatv("{} {}", 1, 2);
+  llvm::formatv(false, "{0}", 1);
+}
+
+void too_few_args() {
+  llvm::formatv("{0} {1}", 1);

kastiglione wrote:

done 
https://github.com/llvm/llvm-project/pull/195974/commits/1a28bca0f0c6b645acf066ff3cc59a1443753a08

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Victor Chernyakin via cfe-commits

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Victor Chernyakin via cfe-commits


@@ -0,0 +1,189 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include 
+#include 
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions = {"llvm::formatv", "llvm::createStringErrorV"};
+
+  std::vector CustomFunctions =
+  utils::options::parseStringList(AdditionalFunctions);
+  copy(CustomFunctions, std::back_inserter(Functions));
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+  // Build a matcher for all configured function names.
+  Finder->addMatcher(
+  callExpr(callee(functionDecl(hasAnyName(Functions),
+   ast_matchers::isTemplateInstantiation())),
+   argumentCountAtLeast(1))
+  .bind("call"),
+  this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Call = Result.Nodes.getNodeAs("call");
+  assert(Call && Call->getNumArgs() > 0);
+
+  const auto *FD = Call->getDirectCallee();
+  assert(FD);
+
+  // Find the format string index from the template signature: it's the
+  // parameter immediately before the trailing parameter pack.
+  const FunctionDecl *TemplateDecl = FD;
+  if (const FunctionTemplateDecl *Primary = FD->getPrimaryTemplate())
+TemplateDecl = Primary->getTemplatedDecl();
+
+  const unsigned NumDeclParams = TemplateDecl->getNumParams();
+  if (NumDeclParams < 2)
+return;
+
+  const unsigned PackParamIndex = NumDeclParams - 1;
+  if (!TemplateDecl->getParamDecl(PackParamIndex)->isParameterPack())
+return;
+
+  const unsigned FmtStringIndex = PackParamIndex - 1;
+
+  if (Call->getNumArgs() <= FmtStringIndex)
+return;
+
+  // Extract the format string literal.
+  const Expr *FmtArg = Call->getArg(FmtStringIndex)->IgnoreParenImpCasts();
+  const auto *FmtLiteral = dyn_cast(FmtArg);
+  if (!FmtLiteral)
+return;
+
+  const StringRef FmtString = FmtLiteral->getString();
+  const int NumFmtArgs = Call->getNumArgs() - PackParamIndex;
+

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Zeyi Xu via cfe-commits

zeyi2 wrote:

> Have you tried running the check over the LLVM codebase? It would be 
> interesting to see if it finds anything.

I'm testing it now, will post the comment here after the run finishes :)

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits

kastiglione wrote:

I ran it on the lldb codebase and found over 20 bugs.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Victor Chernyakin via cfe-commits

localspook wrote:

Have you tried running the check over the LLVM codebase? It would be 
interesting to see if it finds anything.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-10 Thread Dave Lee via cfe-commits


@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions.insert("llvm::formatv");
+  Functions.insert("llvm::createStringErrorV");
+
+  // Parse semicolon-separated function names from AdditionalFunctions.
+  const llvm::StringRef Input(AdditionalFunctions);
+  llvm::SmallVector Entries;
+  Input.split(Entries, ';', -1, false);
+  for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+  Functions.insert(Entry);
+  }

kastiglione wrote:

updated here 
https://github.com/llvm/llvm-project/pull/195974/commits/404b03fe4ccd3ec5f503e9164c65d809555a84b6

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-09 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/18] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-09 Thread Yanzuo Liu via cfe-commits


@@ -0,0 +1,24 @@
+// RUN: %check_clang_tidy %s llvm-formatv-string %t
+
+namespace llvm {
+
+template 
+void createStringErrorV(int EC, const char *Fmt, Ts &&...Vals) {}
+
+template 
+void createStringErrorV(const char *Fmt, Ts &&...Vals) {}
+
+} // namespace llvm
+
+void correct() {
+  llvm::createStringErrorV(0, "{0} {1}", 1, 2);
+  llvm::createStringErrorV("{0}", 42);
+}
+
+void wrong_count() {
+  llvm::createStringErrorV(0, "{0} {1}", 1);
+  // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: formatv() format string 
requires 2 arguments, but 1 argument was provided

zwuis wrote:

```txt
warning: formatv() format
 ^^^
```


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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-09 Thread Victor Chernyakin via cfe-commits


@@ -0,0 +1,47 @@
+//===--===//
+//
+// 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_LLVM_FORMATVSTRINGCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_FORMATVSTRINGCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "llvm/ADT/StringSet.h"
+
+namespace clang::tidy::llvm_check {
+
+/// Validates llvm::formatv format strings against the provided arguments.
+///
+/// Checks that:
+/// - The number of format indices matches the number of arguments.
+/// - Every argument is used by the format string.
+/// - Automatic and explicit indices are not mixed.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/llvm/formatv-string.html
+class FormatvStringCheck : public ClangTidyCheck {
+public:
+  FormatvStringCheck(StringRef Name, ClangTidyContext *Context);
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+return LangOpts.CPlusPlus;
+  }
+  void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+  std::optional getCheckTraversalKind() const override {
+return TK_IgnoreUnlessSpelledInSource;
+  }
+
+private:
+  llvm::StringSet<> Functions;
+  const std::string AdditionalFunctions;

localspook wrote:

```suggestion
  const StringRef AdditionalFunctions;
```

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-09 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/17] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-09 Thread Victor Chernyakin via cfe-commits


@@ -0,0 +1,69 @@
+// RUN: %check_clang_tidy %s llvm-formatv-string %t
+
+namespace llvm {
+
+template 
+void formatv(const char *Fmt, Ts &&...Vals) {}
+
+template 
+void formatv(bool Validate, const char *Fmt, Ts &&...Vals) {}
+
+} // namespace llvm
+
+void correct() {
+  llvm::formatv("{0}", 1);
+  llvm::formatv("{0} {1}", 1, 2);
+  llvm::formatv("{0} {0}", 1);
+  llvm::formatv("{1} {0}", 1, 2);
+  llvm::formatv("{0,10}", 1);
+  llvm::formatv("{0,-10}", 1);
+  llvm::formatv("{0:x}", 1);
+  llvm::formatv("{0,10:x}", 1);
+  llvm::formatv("no replacements");
+  llvm::formatv("escaped {{ braces }}");
+  llvm::formatv("{}", 1);
+  llvm::formatv("{} {}", 1, 2);
+  llvm::formatv(false, "{0}", 1);
+}
+
+void too_few_args() {
+  llvm::formatv("{0} {1}", 1);

localspook wrote:

Can we add a test with replacement fields but no format args passed? i.e.:
```cpp
llvm::formatv("{0} {1}");
```


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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-09 Thread Victor Chernyakin via cfe-commits

https://github.com/localspook commented:

I have some comments, but overall the functionality, docs, etc. all look solid!

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-09 Thread Victor Chernyakin via cfe-commits


@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions.insert("llvm::formatv");
+  Functions.insert("llvm::createStringErrorV");
+
+  // Parse semicolon-separated function names from AdditionalFunctions.
+  const llvm::StringRef Input(AdditionalFunctions);
+  llvm::SmallVector Entries;
+  Input.split(Entries, ';', -1, false);
+  for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+  Functions.insert(Entry);
+  }

localspook wrote:

There's a function for this, `utils::options::parseStringList` (it's in 
`"../utils/OptionsUtils.h"`).

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-09 Thread Victor Chernyakin via cfe-commits


@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions.insert("llvm::formatv");
+  Functions.insert("llvm::createStringErrorV");
+
+  // Parse semicolon-separated function names from AdditionalFunctions.
+  const llvm::StringRef Input(AdditionalFunctions);
+  llvm::SmallVector Entries;
+  Input.split(Entries, ';', -1, false);
+  for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+  Functions.insert(Entry);
+  }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+  // Build a matcher for all configured function names.
+  std::vector Names;
+  Names.reserve(Functions.size());
+  llvm::copy(Functions.keys(), std::back_inserter(Names));

localspook wrote:

`Functions` can be a `std::vector` directly, instead of being a `StringSet` and 
requiring this extra translation code. Yes, it means it'll have duplicate 
entries if a user specified duplicate names, but that's okay, it doesn't cause 
any problems.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-09 Thread Victor Chernyakin via cfe-commits


@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions.insert("llvm::formatv");
+  Functions.insert("llvm::createStringErrorV");
+
+  // Parse semicolon-separated function names from AdditionalFunctions.
+  const llvm::StringRef Input(AdditionalFunctions);
+  llvm::SmallVector Entries;
+  Input.split(Entries, ';', -1, false);
+  for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+  Functions.insert(Entry);
+  }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+  // Build a matcher for all configured function names.
+  std::vector Names;
+  Names.reserve(Functions.size());
+  llvm::copy(Functions.keys(), std::back_inserter(Names));
+
+  Finder->addMatcher(
+  callExpr(callee(functionDecl(hasAnyName(Names),
+   ast_matchers::isTemplateInstantiation())),
+   argumentCountAtLeast(1))
+  .bind("call"),
+  this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Call = Result.Nodes.getNodeAs("call");
+  assert(Call && Call->getNumArgs() > 0);
+
+  const auto *FD = Call->getDirectCallee();
+  assert(FD);
+
+  // Find the format string index from the template signature: it's the
+  // parameter immediately before the trailing parameter pack.
+  const FunctionDecl *TemplateDecl = FD;
+  if (const FunctionTemplateDecl *Primary = FD->getPrimaryTemplate())
+TemplateDecl = Primary->getTemplatedDecl();
+
+  const unsigned NumDeclParams = TemplateDecl->getNumParams();
+  if (NumDeclParams < 2)
+return;
+
+  const unsigned PackParamIndex = NumDeclParams - 1;
+  if (!TemplateDecl->getParamDecl(PackParamIndex)->isParameterPack())
+return;
+
+  const unsigned FmtStringIndex = PackParamIndex - 1;
+
+  if (Call->getNumArgs() <= FmtStringIndex)
+return;
+
+  /

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-09 Thread Victor Chernyakin via cfe-commits

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-09 Thread Victor Chernyakin via cfe-commits


@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions.insert("llvm::formatv");
+  Functions.insert("llvm::createStringErrorV");
+
+  // Parse semicolon-separated function names from AdditionalFunctions.
+  const llvm::StringRef Input(AdditionalFunctions);

localspook wrote:

clang-tidy code avoids `llvm::` qualifiers where possible:
```suggestion
  const StringRef Input(AdditionalFunctions);
```

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-08 Thread Yanzuo Liu via cfe-commits


@@ -0,0 +1,199 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.

zwuis wrote:

I'm surprised that `}}` is not a escape sequence, which is different from 
`std::format`.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-08 Thread Yanzuo Liu via cfe-commits

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-08 Thread Yanzuo Liu via cfe-commits


@@ -0,0 +1,199 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  Functions.insert("llvm::formatv");
+  Functions.insert("llvm::createStringErrorV");
+
+  // Parse semicolon-separated function names from AdditionalFunctions.
+  const llvm::StringRef Input(AdditionalFunctions);
+  llvm::SmallVector Entries;
+  Input.split(Entries, ';', -1, false);
+  for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+  Functions.insert(Entry);
+  }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+  // Build a matcher for all configured function names.
+  std::vector Names;
+  Names.reserve(Functions.size());
+  llvm::copy(Functions.keys(), std::back_inserter(Names));
+
+  Finder->addMatcher(
+  callExpr(callee(functionDecl(hasAnyName(Names),
+   ast_matchers::isTemplateInstantiation())),

zwuis wrote:

Redundant `ast_matchers::`.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-08 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/13] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-08 Thread via cfe-commits


@@ -0,0 +1,49 @@
+.. title:: clang-tidy - llvm-formatv-string
+
+llvm-formatv-string
+===
+
+Validates ``llvm::formatv`` format strings against the arguments provided,
+similar to how the compiler validates ``printf`` format strings.
+
+This check diagnoses the following issues:
+
+- The number of replacement indices in the format string does not match the
+  number of arguments provided.
+- A format string does not use one of the given arguments.
+- Mixing of automatic and explicit indices (e.g. ``{} {1}``).
+
+.. code-block:: c++
+
+  // warning: formatv() format string requires 2 arguments, but 1 argument was 
provided
+  llvm::formatv("{0} {1}", x);
+
+  // warning: formatv() format string mixes automatic and explicit indices
+  llvm::formatv("{} {1}", x, y);
+
+  // warning: formatv() argument unused in format string
+  llvm::formatv("{0} {2}", x, y, z);
+
+  // OK.
+  llvm::formatv("{0} {1}", x, y);
+  llvm::formatv("{} {}", x, y);
+  llvm::formatv("{0} {0}", x);
+
+The check only operates on calls where the format string is a string literal.
+Dynamic format strings are not diagnosed.
+
+Options
+---
+
+.. option:: AdditionalFunctions
+
+  A semicolon-separated list of additional fully qualified function names to
+  check, beyond ``llvm::formatv`` and ``llvm::createStringErrorV``. Each
+  function must be a variadic template whose last parameter is a parameter
+  pack. The format string is assumed to be the parameter immediately preceding
+  the pack.
+
+  For example, to check ``mylib::log(Level, const char *Fmt, Ts&&...)`` set
+  this option to ``mylib::log``.

EugeneZelenko wrote:

```suggestion
  this option to `mylib::log`.
```

Option values should in in single back-ticks.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-08 Thread Yanzuo Liu via cfe-commits

https://github.com/zwuis commented:

> question for reviewers, should any of these diagnostics be an error? right 
> now they're all warnings

AFAIK clang-tidy checks always emit warnings and notes.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-08 Thread via cfe-commits


@@ -133,6 +125,12 @@ New checks
   Finds functions where throwing exceptions is unsafe but the function is still
   marked as potentially throwing.
 
+- New :doc:`llvm-formatv-string
+  ` check.
+
+  Validates ``llvm::formatv`` format strings against the provided arguments,

EugeneZelenko wrote:

Please synchronize with first statement in documentation.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-08 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/12] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-07 Thread Dave Lee via cfe-commits


@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {

kastiglione wrote:

I misunderstood, yes some of this validation could be reused for `std::format`

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-06 Thread Dave Lee via cfe-commits

kastiglione wrote:

question for reviewers, should any of these diagnostics be an error? right now 
they're all warnings

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-06 Thread Dave Lee via cfe-commits


@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {

kastiglione wrote:

I am not particularly familiar with `std::format`. According to my searching, 
the format string validation is done at compile time by the standard library. 
In which case it seems unlikely to be able to reuse that validation?

Quoting https://en.cppreference.com/cpp/utility/format/format

> Since [P2216R3](https://wg21.link/P2216R3), `std::format` does a compile-time 
> check on the format string (via the helper type `std::format_string` or 
> `std::wformat_string`). If it is found to be invalid for the types of the 
> arguments to be formatted, a compilation error will be emitted. If the format 
> string cannot be a compile-time constant, or the compile-time check needs to 
> be avoided, use `std::vformat` or 
> [`std::dynamic_format`](https://en.cppreference.com/cpp/utility/format/dynamic_format)
>  on `fmt`(since C++26) instead.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-06 Thread Dave Lee via cfe-commits

kastiglione wrote:

thank you for the helpful reviews 🙏 

I believe I have addressed all the reviews.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-06 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/10] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-06 Thread Zeyi Xu via cfe-commits


@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"

zeyi2 wrote:

Nit: no need to include this file, `clang-tidy` already heavily relies on 
transitive rule for header inclusion :)

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread Yanzuo Liu via cfe-commits

https://github.com/zwuis commented:

> Please add entry to Release Notes.

FYI you can run clang-tools-extra/clang-tidy/add_new_check.py to create a new 
check.

https://clang.llvm.org/extra/clang-tidy/Contributing.html

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread Yanzuo Liu via cfe-commits


@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 0;
+
+  // Parse "name1:idx1;name2:idx2;..." from AdditionalFunctions.
+  llvm::StringRef Input(AdditionalFunctions);
+  while (!Input.empty()) {
+const auto [Entry, Rest] = Input.split(';');
+Input = Rest;
+if (Entry.empty())
+  continue;
+const auto [Name, IdxStr] = Entry.rsplit(':');
+unsigned Idx = 0;
+if (Name.empty() || IdxStr.empty() || IdxStr.getAsInteger(10, Idx)) {
+  configurationDiag("invalid entry '%0' in option AdditionalFunctions, "
+"expected 'fully::qualified::name:fmt_arg_index'")
+  << Entry;
+  continue;
+}
+Functions[Name] = Idx;
+  }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+  // Build a matcher for all configured function names.
+  std::vector Names;
+  Names.reserve(Functions.size());
+  llvm::copy(Functions.keys(), std::back_inserter(Names));
+
+  Finder->addMatcher(
+  callExpr(callee(functionDecl(hasAnyName(Names.bind("call"), this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Call = Result.Nodes.getNodeAs("call");
+  if (!Call || Call->getNumArgs() == 0)

zwuis wrote:

Use `assert(Call)`. `check` will be called only if the matcher matches.

---

We can use `argumentCountAtLeast` matcher.

https://clang.llvm.org/docs/LibASTMatchersReference.html

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread Yanzuo Liu via cfe-commits


@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 0;
+
+  // Parse "name1:idx1;name2:idx2;..." from AdditionalFunctions.
+  llvm::StringRef Input(AdditionalFunctions);
+  while (!Input.empty()) {
+const auto [Entry, Rest] = Input.split(';');
+Input = Rest;
+if (Entry.empty())
+  continue;
+const auto [Name, IdxStr] = Entry.rsplit(':');
+unsigned Idx = 0;
+if (Name.empty() || IdxStr.empty() || IdxStr.getAsInteger(10, Idx)) {
+  configurationDiag("invalid entry '%0' in option AdditionalFunctions, "
+"expected 'fully::qualified::name:fmt_arg_index'")
+  << Entry;
+  continue;
+}
+Functions[Name] = Idx;
+  }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+  // Build a matcher for all configured function names.
+  std::vector Names;
+  Names.reserve(Functions.size());
+  llvm::copy(Functions.keys(), std::back_inserter(Names));
+
+  Finder->addMatcher(
+  callExpr(callee(functionDecl(hasAnyName(Names.bind("call"), this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Call = Result.Nodes.getNodeAs("call");
+  if (!Call || Call->getNumArgs() == 0)
+return;
+
+  const auto *FD = Call->getDirectCallee();
+  if (!FD)

zwuis wrote:

Use `assert`. We expect the matcher works.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread Yanzuo Liu via cfe-commits


@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 0;
+
+  // Parse "name1:idx1;name2:idx2;..." from AdditionalFunctions.
+  llvm::StringRef Input(AdditionalFunctions);
+  while (!Input.empty()) {
+const auto [Entry, Rest] = Input.split(';');
+Input = Rest;
+if (Entry.empty())
+  continue;
+const auto [Name, IdxStr] = Entry.rsplit(':');
+unsigned Idx = 0;
+if (Name.empty() || IdxStr.empty() || IdxStr.getAsInteger(10, Idx)) {
+  configurationDiag("invalid entry '%0' in option AdditionalFunctions, "
+"expected 'fully::qualified::name:fmt_arg_index'")
+  << Entry;
+  continue;
+}
+Functions[Name] = Idx;
+  }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+  // Build a matcher for all configured function names.
+  std::vector Names;
+  Names.reserve(Functions.size());
+  llvm::copy(Functions.keys(), std::back_inserter(Names));
+
+  Finder->addMatcher(
+  callExpr(callee(functionDecl(hasAnyName(Names.bind("call"), this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Call = Result.Nodes.getNodeAs("call");
+  if (!Call || Call->getNumArgs() == 0)
+return;
+
+  const auto *FD = Call->getDirectCallee();
+  if (!FD)
+return;
+
+  // Look up the format string parameter index for this function.
+  const std::string QualName = FD->getQualifiedNameAsString();
+  assert(Functions.contains(QualName) &&
+ "matched function not in Functions map");
+  unsigned FmtArgIdx = Functions.lookup(QualName);
+
+  // For llvm::formatv, also handle the (bool, const char*, ...) overload.
+  if (QualName == "llvm::formatv" && FD->getNumParams() > 0 &&
+  FD->getParamDecl(0)->getType()->isBooleanType())
+FmtArgIdx = 1;
+
+  if (Call->getNu

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread Yanzuo Liu via cfe-commits

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread Dave Lee via cfe-commits

kastiglione wrote:

This is my first clang-tidy check. Feedback and direction welcome.

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread Dave Lee via cfe-commits

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread Dave Lee via cfe-commits

https://github.com/kastiglione updated 
https://github.com/llvm/llvm-project/pull/195974

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 1/2] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 0;

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread Dave Lee via cfe-commits

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread Dave Lee via cfe-commits

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread via cfe-commits

github-actions[bot] wrote:




:warning: C/C++ code linter, clang-tidy found issues in your code. :warning:



You can test this locally with the following command:


```bash

git diff -U0 origin/main...HEAD -- 
clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h 
clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp |
python3 clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py   -path build -p1 
-quiet
```





View the output from clang-tidy here.


```


clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp:56:5: warning: 
variable 'Content' of type 'llvm::StringRef' can be declared 'const' 
[misc-const-correctness]
   56 | llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
  | ^
  | const 
clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp:162:3: warning: 
variable 'FmtStr' of type 'llvm::StringRef' can be declared 'const' 
[misc-const-correctness]
  162 |   llvm::StringRef FmtStr = FmtLiteral->getString();
  |   ^
  |   const 
clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp:187:10: warning: 
variable 'Index' of type 'unsigned int' can be declared 'const' 
[misc-const-correctness]
  187 | for (unsigned Index : Parsed.Indices)
  |  ^
  |   const
```




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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread Dave Lee via cfe-commits

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread Dave Lee via cfe-commits

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


[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread via cfe-commits

llvmorg-github-actions[bot] wrote:




@llvm/pr-subscribers-clang-tidy

Author: Dave Lee (kastiglione)


Changes



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


8 Files Affected:

- (modified) clang-tools-extra/clang-tidy/llvm/CMakeLists.txt (+1) 
- (added) clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp (+202) 
- (added) clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h (+42) 
- (modified) clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp (+2) 
- (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1) 
- (added) clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst 
(+60) 
- (added) 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp 
(+28) 
- (added) clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp 
(+58) 


``diff
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] = 0;
+
+  // Parse "name1:idx1;name2:idx2;..." from AdditionalFunctions.
+  llvm::StringRef Input(AdditionalFunctions);
+  while (!Input.empty()) {
+auto [Entry, Rest] = Input.split(';');
+Input = Rest;
+if (Entry.empty())
+  continue;
+auto [Name, IdxStr] = Entry.rsplit(':');
+unsigned Idx = 0;
+if (Name.empty() || IdxStr.empty() || IdxStr.ge

[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)

2026-05-05 Thread Dave Lee via cfe-commits

https://github.com/kastiglione created 
https://github.com/llvm/llvm-project/pull/195974

None

>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee 
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH] [clang-tidy] Add `llvm-formatv-string`

---
 .../clang-tidy/llvm/CMakeLists.txt|   1 +
 .../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
 .../clang-tidy/llvm/FormatvStringCheck.h  |  42 
 .../clang-tidy/llvm/LLVMTidyModule.cpp|   2 +
 .../docs/clang-tidy/checks/list.rst   |   1 +
 .../clang-tidy/checks/llvm/formatv-string.rst |  60 ++
 .../llvm/formatv-string-additional.cpp|  28 +++
 .../checkers/llvm/formatv-string.cpp  |  58 +
 8 files changed, 394 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_clang_library(clangTidyLLVMModule STATIC
+  FormatvStringCheck.cpp
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
   LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+  llvm::SmallVector Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+  ParseResult Result;
+  unsigned NextAutoIndex = 0;
+  bool HasAutomatic = false;
+  bool HasExplicit = false;
+
+  while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+  break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+  Fmt = Fmt.drop_front(2);
+  continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+  return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+  IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+  Index = NextAutoIndex++;
+  HasAutomatic = true;
+} else {
+  if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+  HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+  }
+
+  if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+  return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+   ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+  AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+  // Always check llvm::formatv (both overloads).
+  Functions["llvm::formatv"] =