[clang-tools-extra] [clang-tidy] Add a new check 'performance-string-view-conversions' (PR #174288)
Zinovy Nis via cfe-commits
cfe-commits at lists.llvm.org
Wed Jan 21 02:15:04 PST 2026
https://github.com/irishrover updated https://github.com/llvm/llvm-project/pull/174288
>From 99c9e02a2cb5fb1f608d5518efd9b41e50c0fc21 Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
Date: Sat, 3 Jan 2026 17:48:40 +0300
Subject: [PATCH 1/4] [clang-tidy] Add a new check
'performance-string-view-conversions'
Finds and removes redundant conversions from ``std::[w|u8|u16|u32]string_view``
to ``std::[...]string`` in call expressions expecting ``std::[...]string_view``.
---
.../clang-tidy/performance/CMakeLists.txt | 1 +
.../performance/PerformanceTidyModule.cpp | 3 +
.../StringViewConversionsCheck.cpp | 107 +++++++++++++++
.../performance/StringViewConversionsCheck.h | 34 +++++
clang-tools-extra/docs/ReleaseNotes.rst | 6 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../performance/string-view-conversions.rst | 32 +++++
.../clang-tidy/checkers/Inputs/Headers/string | 24 ++++
.../string-view-conversions-cxx20.cpp | 27 ++++
.../performance/string-view-conversions.cpp | 127 ++++++++++++++++++
10 files changed, 362 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/performance/string-view-conversions.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions-cxx20.cpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions.cpp
diff --git a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt
index 9a2f90069edbf..4dba117e1ee54 100644
--- a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt
@@ -21,6 +21,7 @@ add_clang_library(clangTidyPerformanceModule STATIC
NoexceptMoveConstructorCheck.cpp
NoexceptSwapCheck.cpp
PerformanceTidyModule.cpp
+ StringViewConversionsCheck.cpp
TriviallyDestructibleCheck.cpp
TypePromotionInMathFnCheck.cpp
UnnecessaryCopyInitializationCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp
index 6bab1a46d18db..294a209e4c602 100644
--- a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp
@@ -23,6 +23,7 @@
#include "NoexceptDestructorCheck.h"
#include "NoexceptMoveConstructorCheck.h"
#include "NoexceptSwapCheck.h"
+#include "StringViewConversionsCheck.h"
#include "TriviallyDestructibleCheck.h"
#include "TypePromotionInMathFnCheck.h"
#include "UnnecessaryCopyInitializationCheck.h"
@@ -62,6 +63,8 @@ class PerformanceModule : public ClangTidyModule {
"performance-noexcept-move-constructor");
CheckFactories.registerCheck<NoexceptSwapCheck>(
"performance-noexcept-swap");
+ CheckFactories.registerCheck<StringViewConversionsCheck>(
+ "performance-string-view-conversions");
CheckFactories.registerCheck<TriviallyDestructibleCheck>(
"performance-trivially-destructible");
CheckFactories.registerCheck<TypePromotionInMathFnCheck>(
diff --git a/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp b/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp
new file mode 100644
index 0000000000000..006cb1591b07e
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp
@@ -0,0 +1,107 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "StringViewConversionsCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::performance {
+
+static auto getStringTypeMatcher(StringRef CharType) {
+ return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasName(CharType))));
+}
+
+void StringViewConversionsCheck::registerMatchers(MatchFinder *Finder) {
+ // Matchers for std::basic_[w|u8|u16|u32]string[_view] families.
+ const auto IsStdString = getStringTypeMatcher("::std::basic_string");
+ const auto IsStdStringView = getStringTypeMatcher("::std::basic_string_view");
+
+ // Matches pointer to any character type (char*, etc.) or array of any
+ // character type (char[], etc.).
+ const auto IsCharPointerOrArray =
+ anyOf(hasType(pointerType(pointee(isAnyCharacter()))),
+ hasType(arrayType(hasElementType(isAnyCharacter()))));
+
+ const auto ImplicitlyConvertibleToStringView =
+ expr(anyOf(hasType(IsStdStringView), IsCharPointerOrArray,
+ hasType(IsStdString)))
+ .bind("originalStringView");
+
+ // Matches std::string construction from a string_view-convertible expression:
+ // - Direct construction: std::string{sv}, std::string{s}
+ // - Copy from existing string: std::string(s) where s is std::string
+ const auto RedundantStringConstruction = cxxConstructExpr(
+ hasType(IsStdString),
+ hasArgument(0, ignoringImplicit(ImplicitlyConvertibleToStringView)));
+
+ // Matches functional cast syntax: std::string(expr):
+ // std::string(sv), std::string("literal")
+ const auto RedundantFunctionalCast = cxxFunctionalCastExpr(
+ hasType(IsStdString), hasDescendant(RedundantStringConstruction));
+
+ // Match method calls on std::string that modify or use the string,
+ // such as operator+, append(), substr(), c_str(), etc.
+ const auto HasStringOperatorCall = hasDescendant(cxxOperatorCallExpr(
+ hasOverloadedOperatorName("+"), hasType(IsStdString)));
+ const auto HasStringMethodCall =
+ hasDescendant(cxxMemberCallExpr(on(hasType(IsStdString))));
+
+ // Main matcher: finds function calls where:
+ // 1. A parameter has type string_view
+ // 2. The corresponding argument contains a redundant std::string construction
+ // (either functional cast syntax or direct construction/brace init)
+ // 3. The argument does NOT involve:
+ // - String concatenation with operator+ (string_view doesn't support it)
+ // - Method calls on the std::string (like append(), substr(), etc.)
+ Finder->addMatcher(
+ callExpr(forEachArgumentWithParam(
+ expr(hasType(IsStdStringView),
+ // Match either syntax for std::string construction
+ hasDescendant(expr(anyOf(RedundantFunctionalCast,
+ RedundantStringConstruction))
+ .bind("redundantExpr")),
+ // Exclude cases of std::string methods or operator+ calls
+ unless(anyOf(HasStringOperatorCall, HasStringMethodCall)))
+ .bind("expr"),
+ parmVarDecl(hasType(IsStdStringView)))),
+ this);
+}
+
+void StringViewConversionsCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *ParamExpr = Result.Nodes.getNodeAs<Expr>("expr");
+ assert(ParamExpr);
+
+ const auto *RedundantExpr = Result.Nodes.getNodeAs<Expr>("redundantExpr");
+ assert(RedundantExpr);
+
+ const auto *OriginalExpr = Result.Nodes.getNodeAs<Expr>("originalStringView");
+ assert(OriginalExpr);
+
+ // Sanity check. Verify that the redundant expression is the direct source of
+ // the argument, not part of a larger expression (e.g., std::string(sv) +
+ // "bar").
+ assert(ParamExpr->getSourceRange() == RedundantExpr->getSourceRange());
+
+ const StringRef OriginalText = Lexer::getSourceText(
+ CharSourceRange::getTokenRange(OriginalExpr->getSourceRange()),
+ *Result.SourceManager, getLangOpts());
+
+ if (OriginalText.empty())
+ return;
+
+ diag(RedundantExpr->getBeginLoc(),
+ "redundant conversion to %0 and then back to %1")
+ << RedundantExpr->getType() << ParamExpr->getType()
+ << FixItHint::CreateReplacement(RedundantExpr->getSourceRange(),
+ OriginalText);
+}
+
+} // namespace clang::tidy::performance
diff --git a/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.h b/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.h
new file mode 100644
index 0000000000000..d1d82a55fc9f0
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.h
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_PERFORMANCE_STRINGVIEWCONVERSIONSCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_STRINGVIEWCONVERSIONSCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::performance {
+
+/// Finds and removes redundant conversions from std::string_view to std::string
+/// in call expressions expecting std::string_view.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/performance/string-view-conversions.html
+class StringViewConversionsCheck : public ClangTidyCheck {
+public:
+ StringViewConversionsCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus17;
+ }
+};
+
+} // namespace clang::tidy::performance
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_STRINGVIEWCONVERSIONSCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 4a44f3db9ac03..668c762324d75 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -97,6 +97,12 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^
+- New :doc:`performance-string-view-conversions
+ <clang-tidy/checks/performance/string-view-conversions>` check.
+
+ Finds and removes redundant conversions from ``std::[w|u8|u16|u32]string_view`` to
+ ``std::[...]string`` in call expressions expecting ``std::[...]string_view``.
+
New check aliases
^^^^^^^^^^^^^^^^^
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 3dabf887dc2e1..8fbf35bd18880 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -363,6 +363,7 @@ Clang-Tidy Checks
:doc:`performance-noexcept-destructor <performance/noexcept-destructor>`, "Yes"
:doc:`performance-noexcept-move-constructor <performance/noexcept-move-constructor>`, "Yes"
:doc:`performance-noexcept-swap <performance/noexcept-swap>`, "Yes"
+ :doc:`performance-string-view-conversions <performance/string-view-conversions>`, "Yes"
:doc:`performance-trivially-destructible <performance/trivially-destructible>`, "Yes"
:doc:`performance-type-promotion-in-math-fn <performance/type-promotion-in-math-fn>`, "Yes"
:doc:`performance-unnecessary-copy-initialization <performance/unnecessary-copy-initialization>`, "Yes"
diff --git a/clang-tools-extra/docs/clang-tidy/checks/performance/string-view-conversions.rst b/clang-tools-extra/docs/clang-tidy/checks/performance/string-view-conversions.rst
new file mode 100644
index 0000000000000..347c3a7597689
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/performance/string-view-conversions.rst
@@ -0,0 +1,32 @@
+.. title:: clang-tidy - performance-string-view-conversions
+
+performance-string-view-conversions
+===================================
+
+Finds and removes redundant conversions from ``std::[w|u8|u16|u32]string_view``
+to ``std::[...]string`` in call expressions expecting ``std::[...]string_view``.
+
+
+Before:
+
+.. code-block:: c++
+
+ void foo(int p1, std::string_view p2, double p3);
+ void bar(std::string_view sv) {
+ foo(42, std::string(sv), 3.14); // conversion to std::string is
+ // redundant as std::string_view
+ // is expected
+ foo(42, std::string("foo"), 3.14); // conversion to std::string is
+ // redundant as std::string_view
+ // is expected
+ }
+
+After:
+
+.. code-block:: c++
+
+ void foo(int p1, std::string_view p2, double p3);
+ void bar(std::string_view sv) {
+ foo(42, sv, 3.14);
+ foo(42, "foo", 3.14);
+ }
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
index 6cedda4202f14..c3fd2cf6c1ff7 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
@@ -22,9 +22,11 @@ struct basic_string {
typedef size_t size_type;
typedef basic_string<C, T, A> _Type;
basic_string();
+ basic_string(basic_string_view<C, T>);
basic_string(const C *p, const A &a = A());
basic_string(const C *p, size_type count);
basic_string(const C *b, const C *e);
+ basic_string(size_t, C);
~basic_string();
@@ -90,8 +92,14 @@ struct basic_string {
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
+#if __cplusplus >= 202002L
+typedef basic_string<char8_t> u8string;
+typedef basic_string<char16_t> u16string;
+typedef basic_string<char32_t> u32string;
+#else
typedef basic_string<char16> u16string;
typedef basic_string<char32> u32string;
+#endif
template <typename C, typename T>
struct basic_string_view {
@@ -100,6 +108,7 @@ struct basic_string_view {
const C *str;
constexpr basic_string_view(const C* s) : str(s) {}
+ basic_string_view(const basic_string<C, T>&) {}
const C *data() const;
@@ -136,8 +145,14 @@ struct basic_string_view {
typedef basic_string_view<char> string_view;
typedef basic_string_view<wchar_t> wstring_view;
+#if __cplusplus >= 202002L
+typedef basic_string_view<char8_t> u8string_view;
+typedef basic_string_view<char16_t> u16string_view;
+typedef basic_string_view<char32_t> u32string_view;
+#else
typedef basic_string_view<char16> u16string_view;
typedef basic_string_view<char32> u32string_view;
+#endif
std::string operator+(const std::string&, const std::string&);
std::string operator+(const std::string&, const char*);
@@ -166,6 +181,15 @@ bool operator!=(const char*, const std::string_view&);
#endif
size_t strlen(const char* str);
+
+namespace literals {
+namespace string_literals {
+ string operator""s(const char *, size_t);
+}
+namespace string_view_literals {
+ string_view operator""sv(const char *, size_t);
+}
+}
}
#endif // _STRING_
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions-cxx20.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions-cxx20.cpp
new file mode 100644
index 0000000000000..1a49883dad100
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions-cxx20.cpp
@@ -0,0 +1,27 @@
+// RUN: %check_clang_tidy -std=c++20-or-later %s performance-string-view-conversions %t -- \
+// RUN: -- -isystem %clang_tidy_headers
+
+#include <string>
+
+using namespace std::literals::string_literals;
+using namespace std::literals::string_view_literals;
+
+void foo_u8sv(int p1, std::u8string_view p2, double p3);
+void foo_u16sv(int p1, std::u16string_view p2, double p3);
+void foo_u32sv(int p1, std::u32string_view p2, double p3);
+
+void positive(std::string_view sv, std::wstring_view wsv) {
+ // [u8|u16|32]string([u8|u16|32]string_view)
+ //
+ foo_u8sv(42, std::u8string(u8"Hello, world"), 3.14);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: redundant conversion to 'std::u8string' (aka 'basic_string<char8_t>') and then back to 'std::u8string_view' (aka 'basic_string_view<char8_t>') [performance-string-view-conversions]
+ // CHECK-FIXES: foo_u8sv(42, u8"Hello, world", 3.14);
+
+ foo_u16sv(42, std::u16string(u"Hello, world"), 3.14);
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: redundant conversion to 'std::u16string' (aka 'basic_string<char16_t>') and then back to 'std::u16string_view' (aka 'basic_string_view<char16_t>') [performance-string-view-conversions]
+ // CHECK-FIXES: foo_u16sv(42, u"Hello, world", 3.14);
+
+ foo_u32sv(42, std::u32string(U"Hello, world"), 3.14);
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: redundant conversion to 'std::u32string' (aka 'basic_string<char32_t>') and then back to 'std::u32string_view' (aka 'basic_string_view<char32_t>') [performance-string-view-conversions]
+ // CHECK-FIXES: foo_u32sv(42, U"Hello, world", 3.14);
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions.cpp
new file mode 100644
index 0000000000000..e27abae1bccff
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions.cpp
@@ -0,0 +1,127 @@
+// RUN: %check_clang_tidy -std=c++17-or-later %s performance-string-view-conversions %t -- \
+// RUN: -- -isystem %clang_tidy_headers
+
+#include <string>
+
+using namespace std::literals::string_literals;
+using namespace std::literals::string_view_literals;
+
+// Support for std::move
+namespace std {
+template <typename>
+struct remove_reference;
+
+template <typename _Tp>
+struct remove_reference {
+ typedef _Tp type;
+};
+
+template <typename _Tp>
+struct remove_reference<_Tp &> {
+ typedef _Tp type;
+};
+
+template <typename _Tp>
+struct remove_reference<_Tp &&> {
+ typedef _Tp type;
+};
+
+template <typename _Tp>
+constexpr typename std::remove_reference<_Tp>::type &&move(_Tp &&__t) {
+ return static_cast<typename std::remove_reference<_Tp>::type &&>(__t);
+}
+} // namespace std
+
+void foo_sv(int p1, std::string_view p2, double p3);
+void foo_wsv(int p1, std::wstring_view p2, double p3);
+void foo_str(int p1, const std::string& p2, double p3);
+void foo_wstr(int p1, const std::wstring& p2, double p3);
+std::string foo_str(int p1);
+std::string_view foo_sv(int p1);
+
+void positive(std::string_view sv, std::wstring_view wsv) {
+ // string(string_view)
+ //
+ foo_sv(42, std::string(sv), 3.14);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: redundant conversion to 'std::string' (aka 'basic_string<char>') and then back to 'std::string_view' (aka 'basic_string_view<char>') [performance-string-view-conversions]
+ // CHECK-FIXES: foo_sv(42, sv, 3.14);
+
+ foo_sv(42, std::string("Hello, world"), 3.14);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: redundant conversion to 'std::string' (aka 'basic_string<char>') and then back to 'std::string_view' (aka 'basic_string_view<char>') [performance-string-view-conversions]
+ // CHECK-FIXES: foo_sv(42, "Hello, world", 3.14);
+
+ // TODO: support for ""sv literals
+ foo_sv(42, "Hello, world"s, 3.14);
+
+ foo_sv(42, std::string{"Hello, world"}, 3.14);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: redundant conversion to 'std::string' (aka 'basic_string<char>') and then back to 'std::string_view' (aka 'basic_string_view<char>') [performance-string-view-conversions]
+ // CHECK-FIXES: foo_sv(42, "Hello, world", 3.14);
+
+ const char *ptr = "Hello, world";
+ foo_sv(42, std::string(ptr), 3.14);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: redundant conversion to 'std::string' (aka 'basic_string<char>') and then back to 'std::string_view' (aka 'basic_string_view<char>') [performance-string-view-conversions]
+ // CHECK-FIXES: foo_sv(42, ptr, 3.14);
+
+ char arr[] = "Hello, world";
+ foo_sv(42, std::string(arr), 3.14);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: redundant conversion to 'std::string' (aka 'basic_string<char>') and then back to 'std::string_view' (aka 'basic_string_view<char>') [performance-string-view-conversions]
+ // CHECK-FIXES: foo_sv(42, arr, 3.14);
+
+ foo_sv(42, std::string(foo_sv(42)), 3.14);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: redundant conversion to 'std::string' (aka 'basic_string<char>') and then back to 'std::string_view' (aka 'basic_string_view<char>') [performance-string-view-conversions]
+ // CHECK-FIXES: foo_sv(42, foo_sv(42), 3.14);
+
+ std::string s = "hello";
+ foo_sv(42, std::string(s), 3.14);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: redundant conversion to 'std::string' (aka 'basic_string<char>') and then back to 'std::string_view' (aka 'basic_string_view<char>') [performance-string-view-conversions]
+ // CHECK-FIXES: foo_sv(42, s, 3.14);
+
+ foo_sv(42, std::string{s}, 3.14);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: redundant conversion to 'std::string' (aka 'basic_string<char>') and then back to 'std::string_view' (aka 'basic_string_view<char>') [performance-string-view-conversions]
+ // CHECK-FIXES: foo_sv(42, s, 3.14);
+
+ // wstring(wstring_view)
+ //
+ foo_wsv(42, std::wstring(wsv), 3.14);
+ // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: redundant conversion to 'std::wstring' (aka 'basic_string<wchar_t>') and then back to 'std::wstring_view' (aka 'basic_string_view<wchar_t>') [performance-string-view-conversions]
+ // CHECK-FIXES: foo_wsv(42, wsv, 3.14);
+
+ const wchar_t *wptr = L"Hello, world";
+ foo_wsv(42, std::wstring(wptr), 3.14);
+ // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: redundant conversion to 'std::wstring' (aka 'basic_string<wchar_t>') and then back to 'std::wstring_view' (aka 'basic_string_view<wchar_t>') [performance-string-view-conversions]
+ // CHECK-FIXES: foo_wsv(42, wptr, 3.14);
+}
+
+void negative(std::string_view sv, std::wstring_view wsv) {
+ // No warnings expected: already string_view
+ foo_sv(42, sv, 3.14);
+ foo_sv(42, "Hello, world", 3.14);
+ foo_sv(42, foo_sv(42), 3.14);
+ // No warnings expected: complex expression
+ foo_sv(42, std::string(sv) + "bar", 3.14);
+ foo_sv(42,
+ std::string( sv ) +
+ ("foo" "bar") ,
+ 3.14);
+ foo_sv(42, "foo" + std::string(sv), 3.14);
+ foo_sv(42, "foo" + std::string(sv) + "bar", 3.14);
+ foo_sv(42, std::string(sv) + std::string(sv), 3.14);
+ foo_sv(42, std::string("foo") + std::string("bar"), 3.14);
+ foo_sv(42, std::string(5, 'a'), 3.14);
+ foo_sv(42, std::string("foo").append("bar"), 3.14);
+ foo_sv(42, std::string(sv).substr(0, 5), 3.14);
+ foo_sv(42, std::string(sv).c_str(), 3.14);
+
+ // No warnings expected: string parameter, not string-view
+ foo_str(42, std::string(sv), 3.14);
+ foo_str(42, std::string("Hello, world"), 3.14);
+ foo_wstr(42, std::wstring(wsv), 3.14);
+ foo_wstr(42, std::wstring(L"Hello, world"), 3.14);
+
+ foo_sv(42, foo_str(42), 3.14);
+ foo_sv(42, foo_sv(42), 3.14);
+
+ // Move semantics ignored
+ std::string s;
+ foo_sv(42, std::move(s), 3.14);
+}
>From d66bcfa640d2e55d1143076f8f673ae8c0712b8f Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
Date: Sun, 18 Jan 2026 14:35:13 +0300
Subject: [PATCH 2/4] One more case and a test for it
foo_wsv(foo_wstr(142, "Hello, world")) should not be converted
---
.../clang-tidy/performance/StringViewConversionsCheck.cpp | 7 +++++++
.../checkers/performance/string-view-conversions.cpp | 7 +++++++
2 files changed, 14 insertions(+)
diff --git a/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp b/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp
index 006cb1591b07e..000791ef9a3bf 100644
--- a/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp
@@ -54,6 +54,11 @@ void StringViewConversionsCheck::registerMatchers(MatchFinder *Finder) {
const auto HasStringMethodCall =
hasDescendant(cxxMemberCallExpr(on(hasType(IsStdString))));
+ const auto IsCallReturningString = callExpr(hasType(IsStdString));
+ const auto IsImplicitStringViewFromCall =
+ cxxConstructExpr(hasType(IsStdStringView),
+ hasArgument(0, ignoringImplicit(IsCallReturningString)));
+
// Main matcher: finds function calls where:
// 1. A parameter has type string_view
// 2. The corresponding argument contains a redundant std::string construction
@@ -64,6 +69,8 @@ void StringViewConversionsCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
callExpr(forEachArgumentWithParam(
expr(hasType(IsStdStringView),
+ // Ignore cases where the argument is a function call
+ unless(ignoringParenImpCasts(IsImplicitStringViewFromCall)),
// Match either syntax for std::string construction
hasDescendant(expr(anyOf(RedundantFunctionalCast,
RedundantStringConstruction))
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions.cpp
index e27abae1bccff..1c573f1b9ff17 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions.cpp
@@ -34,9 +34,12 @@ constexpr typename std::remove_reference<_Tp>::type &&move(_Tp &&__t) {
void foo_sv(int p1, std::string_view p2, double p3);
void foo_wsv(int p1, std::wstring_view p2, double p3);
+void foo_wsv(std::wstring_view p2);
void foo_str(int p1, const std::string& p2, double p3);
void foo_wstr(int p1, const std::wstring& p2, double p3);
+
std::string foo_str(int p1);
+std::wstring foo_wstr(int, const std::string&);
std::string_view foo_sv(int p1);
void positive(std::string_view sv, std::wstring_view wsv) {
@@ -124,4 +127,8 @@ void negative(std::string_view sv, std::wstring_view wsv) {
// Move semantics ignored
std::string s;
foo_sv(42, std::move(s), 3.14);
+
+ // Inner calls are ignored
+ foo_wsv(foo_wstr(142, "Hello, world"));
+ foo_wsv(foo_wstr(1, std::string("Hello, world")));
}
>From ed7a32da6c7e0b3eaac8d649b8a9c971c49e29c7 Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
Date: Tue, 20 Jan 2026 22:05:10 +0300
Subject: [PATCH 3/4] Fix docs & fix a new case ...std::string("Hello, world",
5)...
---
.../clang-tidy/performance/StringViewConversionsCheck.cpp | 3 ++-
.../checks/performance/string-view-conversions.rst | 1 -
.../checkers/performance/string-view-conversions.cpp | 7 +++++--
3 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp b/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp
index 000791ef9a3bf..c2602074c5682 100644
--- a/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp
@@ -40,7 +40,8 @@ void StringViewConversionsCheck::registerMatchers(MatchFinder *Finder) {
// - Copy from existing string: std::string(s) where s is std::string
const auto RedundantStringConstruction = cxxConstructExpr(
hasType(IsStdString),
- hasArgument(0, ignoringImplicit(ImplicitlyConvertibleToStringView)));
+ hasArgument(0, ignoringImplicit(ImplicitlyConvertibleToStringView)),
+ unless(hasArgument(1, unless(cxxDefaultArgExpr()))));
// Matches functional cast syntax: std::string(expr):
// std::string(sv), std::string("literal")
diff --git a/clang-tools-extra/docs/clang-tidy/checks/performance/string-view-conversions.rst b/clang-tools-extra/docs/clang-tidy/checks/performance/string-view-conversions.rst
index 347c3a7597689..8d673ea1c6e1b 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/performance/string-view-conversions.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/performance/string-view-conversions.rst
@@ -6,7 +6,6 @@ performance-string-view-conversions
Finds and removes redundant conversions from ``std::[w|u8|u16|u32]string_view``
to ``std::[...]string`` in call expressions expecting ``std::[...]string_view``.
-
Before:
.. code-block:: c++
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions.cpp
index 1c573f1b9ff17..bfd5521b39d72 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/string-view-conversions.cpp
@@ -129,6 +129,9 @@ void negative(std::string_view sv, std::wstring_view wsv) {
foo_sv(42, std::move(s), 3.14);
// Inner calls are ignored
- foo_wsv(foo_wstr(142, "Hello, world"));
- foo_wsv(foo_wstr(1, std::string("Hello, world")));
+ foo_wsv(foo_wstr(42, "Hello, world"));
+ foo_wsv(foo_wstr(42, std::string("Hello, world")));
+
+ // No warnings expected: string parameter of a limited length, not string-view
+ foo_sv(142, std::string("Hello, world", 5), 3.14);
}
>From c5acf6fc19af5168b12b187793484bd74d13f406 Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
Date: Wed, 21 Jan 2026 13:14:54 +0300
Subject: [PATCH 4/4] Update
clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp
Co-authored-by: Baranov Victor <bar.victor.2002 at gmail.com>
---
.../clang-tidy/performance/StringViewConversionsCheck.cpp | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp b/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp
index c2602074c5682..f09f6f203bf3a 100644
--- a/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/performance/StringViewConversionsCheck.cpp
@@ -85,13 +85,9 @@ void StringViewConversionsCheck::registerMatchers(MatchFinder *Finder) {
void StringViewConversionsCheck::check(const MatchFinder::MatchResult &Result) {
const auto *ParamExpr = Result.Nodes.getNodeAs<Expr>("expr");
- assert(ParamExpr);
-
const auto *RedundantExpr = Result.Nodes.getNodeAs<Expr>("redundantExpr");
- assert(RedundantExpr);
-
const auto *OriginalExpr = Result.Nodes.getNodeAs<Expr>("originalStringView");
- assert(OriginalExpr);
+ assert(RedundantExpr && ParamExpr && OriginalExpr);
// Sanity check. Verify that the redundant expression is the direct source of
// the argument, not part of a larger expression (e.g., std::string(sv) +
More information about the cfe-commits
mailing list