[clang-tools-extra] [clang-tidy] Add a new check 'modernize-use-string-view' (PR #172170)
Zinovy Nis via cfe-commits
cfe-commits at lists.llvm.org
Wed Dec 24 22:59:29 PST 2025
https://github.com/irishrover updated https://github.com/llvm/llvm-project/pull/172170
>From b543de70933fed3e763966097d5bc636835cb7a4 Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
Date: Sat, 13 Dec 2025 13:47:01 +0300
Subject: [PATCH 1/8] [clang-tidy] Add a new check 'modernize-use-string-view'
Looks for functions returning `std::[w|u8|u16|u32]string` and suggests to
change it to `std::[...]string_view` if possible and profitable.
Example:
```cpp
std::string foo(int i) { // <---- can be replaced to `std::string_view foo(...) {`
switch(i) {
case 1:
return "case1";
case 2:
return "case2";
default:
return {};
}
}
```
---
.../clang-tidy/modernize/CMakeLists.txt | 1 +
.../modernize/ModernizeTidyModule.cpp | 3 +
.../modernize/UseStringViewCheck.cpp | 84 +++++
.../clang-tidy/modernize/UseStringViewCheck.h | 34 ++
clang-tools-extra/docs/ReleaseNotes.rst | 16 +-
.../docs/clang-tidy/checks/list.rst | 1 +
.../checks/modernize/use-string-view.rst | 68 ++++
.../clang-tidy/checkers/Inputs/Headers/string | 13 +
.../checkers/modernize/use-string-view.cpp | 339 ++++++++++++++++++
9 files changed, 554 insertions(+), 5 deletions(-)
create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index 488c359661018..858cf921f9d34 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -50,6 +50,7 @@ add_clang_library(clangTidyModernizeModule STATIC
UseStdFormatCheck.cpp
UseStdNumbersCheck.cpp
UseStdPrintCheck.cpp
+ UseStringViewCheck.cpp
UseTrailingReturnTypeCheck.cpp
UseTransparentFunctorsCheck.cpp
UseUncaughtExceptionsCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index 7224b2f32fd73..db8851159c5e8 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -51,6 +51,7 @@
#include "UseStdFormatCheck.h"
#include "UseStdNumbersCheck.h"
#include "UseStdPrintCheck.h"
+#include "UseStringViewCheck.h"
#include "UseTrailingReturnTypeCheck.h"
#include "UseTransparentFunctorsCheck.h"
#include "UseUncaughtExceptionsCheck.h"
@@ -131,6 +132,8 @@ class ModernizeModule : public ClangTidyModule {
CheckFactories.registerCheck<UseNoexceptCheck>("modernize-use-noexcept");
CheckFactories.registerCheck<UseNullptrCheck>("modernize-use-nullptr");
CheckFactories.registerCheck<UseOverrideCheck>("modernize-use-override");
+ CheckFactories.registerCheck<UseStringViewCheck>(
+ "modernize-use-string-view");
CheckFactories.registerCheck<UseTrailingReturnTypeCheck>(
"modernize-use-trailing-return-type");
CheckFactories.registerCheck<UseTransparentFunctorsCheck>(
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp
new file mode 100644
index 0000000000000..1ccf21cad91d6
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp
@@ -0,0 +1,84 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "UseStringViewCheck.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+namespace {
+AST_MATCHER(NamedDecl, isOperatorDecl) {
+ const DeclarationName::NameKind NK = Node.getDeclName().getNameKind();
+ return NK != DeclarationName::Identifier &&
+ NK != DeclarationName::CXXConstructorName &&
+ NK != DeclarationName::CXXDestructorName;
+}
+} // namespace
+
+static llvm::StringRef toStringViewTypeStr(StringRef Type) {
+ if (Type.contains("wchar_t"))
+ return "std::wstring_view";
+ if (Type.contains("char8_t"))
+ return "std::u8string_view";
+ if (Type.contains("char16_t"))
+ return "std::u16string_view";
+ if (Type.contains("char32_t"))
+ return "std::u32string_view";
+ return "std::string_view";
+}
+
+static auto getStringTypeMatcher(StringRef CharType) {
+ return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasName(CharType))));
+}
+
+void UseStringViewCheck::registerMatchers(MatchFinder *Finder) {
+ const auto IsStdString = getStringTypeMatcher("::std::basic_string");
+ const auto IsStdStringView = getStringTypeMatcher("::std::basic_string_view");
+
+ Finder->addMatcher(
+ functionDecl(
+ isDefinition(),
+ unless(cxxMethodDecl(anyOf(isOperatorDecl(), isVirtual()))),
+ returns(IsStdString), hasDescendant(returnStmt()),
+ unless(anyOf(
+ ast_matchers::isTemplateInstantiation(),
+ isExplicitTemplateSpecialization(),
+ hasDescendant(returnStmt(hasReturnValue(unless(ignoringImplicit(
+ anyOf(stringLiteral(), hasType(IsStdStringView),
+ cxxConstructExpr(
+ hasType(IsStdString),
+ anyOf(argumentCountIs(0),
+ hasArgument(
+ 0,
+ ignoringParenImpCasts(anyOf(
+ stringLiteral(),
+ hasType(IsStdStringView)))))))))))))))
+ .bind("func"),
+ this);
+}
+
+void UseStringViewCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("func");
+ assert(MatchedDecl);
+ const llvm::StringRef DestReturnTypeStr = toStringViewTypeStr(
+ MatchedDecl->getReturnType().getCanonicalType().getAsString());
+
+ auto Diag = diag(MatchedDecl->getTypeSpecStartLoc(),
+ "consider using '%0' to avoid unnecessary "
+ "copying and allocations")
+ << DestReturnTypeStr;
+
+ for (const auto *FuncDecl : MatchedDecl->redecls())
+ Diag << FixItHint::CreateReplacement(FuncDecl->getReturnTypeSourceRange(),
+ DestReturnTypeStr);
+}
+
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.h
new file mode 100644
index 0000000000000..056bcd823a1ac
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.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_MODERNIZE_USESTRINGVIEWCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTRINGVIEWCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::modernize {
+
+/// Looks for functions returning `std::[w|u8|u16|u32]string` and suggests to
+/// change it to `std::[...]string_view` if possible and profitable.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-string-view.html
+class UseStringViewCheck : public ClangTidyCheck {
+public:
+ UseStringViewCheck(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::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTRINGVIEWCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 8229810cbb429..5329ecc801d0b 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -107,10 +107,10 @@ Hover
Code completion
^^^^^^^^^^^^^^^
-- Added a new ``MacroFilter`` configuration option to ``Completion`` to
- allow fuzzy-matching with the ``FuzzyMatch`` option when suggesting
- macros. ``ExactPrefix`` is the default, which retains previous
- behavior of suggesting macros which match the prefix exactly.
+- Added a new ``MacroFilter`` configuration option to ``Completion`` to
+ allow fuzzy-matching with the ``FuzzyMatch`` option when suggesting
+ macros. ``ExactPrefix`` is the default, which retains previous
+ behavior of suggesting macros which match the prefix exactly.
Code actions
^^^^^^^^^^^^
@@ -201,7 +201,7 @@ Improvements to clang-tidy
- Improved :program:`clang-tidy` by adding the `--removed-arg` option to remove
arguments sent to the compiler when invoking Clang-Tidy. This option was also
- added to :program:`run-clang-tidy.py` and :program:`clang-tidy-diff.py` and
+ added to :program:`run-clang-tidy.py` and :program:`clang-tidy-diff.py` and
can be configured in the config file through the `RemovedArgs` option.
- Deprecated the :program:`clang-tidy` ``zircon`` module. All checks have been
@@ -261,6 +261,12 @@ New checks
Finds virtual function overrides with different visibility than the function
in the base class.
+- New :doc:`modernize-use-string-view
+ <clang-tidy/checks/modernize/use-string-view>` check.
+
+ Looks for functions returning ``std::[w|u8|u16|u32]string`` and suggests to
+ change it to ``std::[...]string_view`` for performance reasons if possible.
+
- New :doc:`readability-redundant-parentheses
<clang-tidy/checks/readability/redundant-parentheses>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index e5e77b5cc418b..d61316f022a8c 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -330,6 +330,7 @@ Clang-Tidy Checks
:doc:`modernize-use-std-format <modernize/use-std-format>`, "Yes"
:doc:`modernize-use-std-numbers <modernize/use-std-numbers>`, "Yes"
:doc:`modernize-use-std-print <modernize/use-std-print>`, "Yes"
+ :doc:`modernize-use-string-view <modernize/use-string-view>`, "Yes"
:doc:`modernize-use-trailing-return-type <modernize/use-trailing-return-type>`, "Yes"
:doc:`modernize-use-transparent-functors <modernize/use-transparent-functors>`, "Yes"
:doc:`modernize-use-uncaught-exceptions <modernize/use-uncaught-exceptions>`, "Yes"
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
new file mode 100644
index 0000000000000..1b3bdc58db5ab
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
@@ -0,0 +1,68 @@
+.. title:: clang-tidy - modernize-use-string-view
+
+modernize-use-string-view
+====================================
+
+Looks for functions returning ``std::[w|u8|u16|u32]string`` and suggests to
+change it to ``std::[...]string_view`` for performance reasons if possible.
+
+Rationale:
+
+Each time a new ``std::string`` is created from a literal, a copy of that
+literal is allocated either in ``std::string``'s internal buffer
+(for short literals) or in a heap.
+
+For the cases where ``std::string`` is returned from a function,
+such allocations can sometimes be eliminated by using ``std::string_view``
+as a return type.
+
+This check looks for such functions returning ``std::string`` baked from the
+literals and suggests replacing their return type to ``std::string_view``.
+
+It handles ``std::string``, ``std::wstring``, ``std::u8string``,
+``std::u16string`` and ``std::u32string`` along with their aliases and selects
+the proper kind of ``std::string_view`` to return.
+
+Example:
+
+Consider the following code:
+
+.. code-block:: c++
+
+ std::string foo(int i) {
+ switch(i)
+ {
+ case 1:
+ return "case 1";
+ case 2:
+ return "case 2";
+ case 3:
+ return "case 3";
+ default:
+ return "default";
+ }
+ }
+
+In the code above a new ``std::string`` object is created on each function
+invocation, making a copy of a string literal and possibly allocating a memory
+in a heap.
+
+The check gets this code transformed into:
+
+.. code-block:: c++
+
+ std::string_view foo(int i) {
+ switch(i)
+ {
+ case 1:
+ return "case 1";
+ case 2:
+ return "case 2";
+ case 3:
+ return "case 3";
+ default:
+ return "default";
+ }
+ }
+
+New version re-uses statically allocated literals without additional overhead.
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..c792b6e43836a 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
@@ -22,6 +22,7 @@ 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);
@@ -90,8 +91,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 {
@@ -136,8 +143,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*);
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view.cpp
new file mode 100644
index 0000000000000..b90f36eec26cc
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view.cpp
@@ -0,0 +1,339 @@
+// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-use-string-view %t -- -- -isystem %clang_tidy_headers
+
+#include <string>
+
+namespace std {
+namespace literals {
+namespace string_literals {
+ string operator""s(const char *, size_t);
+}
+namespace string_view_literals {
+ string_view operator""sv(const char *, size_t);
+}
+}
+template <class T, class U> struct is_same { static constexpr bool value = false; };
+template <class T> struct is_same<T, T> { static constexpr bool value = true; };
+template <class T, class U> constexpr bool is_same_v = is_same<T, U>::value;
+} // namespace std
+
+
+// ==========================================================
+// Positive tests
+// ==========================================================
+
+std::string simpleLiteral() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view simpleLiteral() {
+ return "simpleLiteral";
+}
+
+std::wstring simpleLiteralW() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::wstring_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::wstring_view simpleLiteralW() {
+ return L"wide literal";
+}
+
+std::u8string simpleLiteral8() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::u8string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::u8string_view simpleLiteral8() {
+ return u8"simpleLiteral";
+}
+
+std::u16string simpleLiteral16() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::u16string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::u16string_view simpleLiteral16() {
+ return u"simpleLiteral";
+}
+
+std::u32string simpleLiteral32() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::u32string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::u32string_view simpleLiteral32() {
+ return U"simpleLiteral";
+}
+
+std::string simpleRLiteral() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view simpleRLiteral() {
+ return R"(simpleLiteral)";
+}
+
+[[nodiscard]] std::string Attributed() {
+// CHECK-MESSAGES:[[@LINE-1]]:15: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: {{\[\[nodiscard\]\]}} std::string_view Attributed() {
+ return "attributed";
+}
+
+const std::string Const() {
+// CHECK-MESSAGES:[[@LINE-1]]:7: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: const std::string_view Const() {
+ return "Const";
+}
+
+auto Trailing() -> std::string {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// TODO: support fixes for deduced types
+ return "Trailing";
+}
+
+std::string emptyReturn() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view emptyReturn() {
+ return {};
+}
+
+std::string ctorReturn() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view ctorReturn() {
+ return std::string();
+}
+
+std::string initList() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view initList() {
+ return {"list"};
+}
+
+std::string switchCaseTest(int i) {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view switchCaseTest(int i) {
+ switch (i) {
+ case 1:
+ return "case1";
+ case 2:
+ return "case2";
+ case 3:
+ return {};
+ default:
+ return "default";
+ }
+}
+
+std::string ifElseTest(bool flag) {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view ifElseTest(bool flag) {
+ if (flag)
+ return "true";
+ return "false";
+}
+
+class A {
+ std::string classMethodInt() { return "internal"; }
+// CHECK-MESSAGES:[[@LINE-1]]:3: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view classMethodInt() { return "internal"; }
+
+ std::string classMethodExt();
+// CHECK-FIXES: std::string_view classMethodExt();
+};
+
+std::string A::classMethodExt() { return "external"; }
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view A::classMethodExt() { return "external"; }
+
+#define MACRO "MACRO LITERAL"
+std::string macro() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view macro() {
+ return MACRO;
+}
+
+#define my_string std::string
+my_string macro_type() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view macro_type() {
+ return "MACTO LITERAL";
+}
+
+// ==========================================================
+// Negative tests
+// ==========================================================
+
+std::string localVariable() {
+ std::string s = "local variable";
+ // TODO: extract and return literal
+ return s;
+}
+
+std::string dynamicCalculation() {
+ std::string s1 = "hello ";
+ return s1 + "world";
+}
+
+std::string mixedReturns(bool flag) {
+ if (flag) {
+ return "safe static literal";
+ }
+ std::string s = "unsafe dynamic";
+ return s;
+}
+
+std::string ternary(bool flag) {
+ return flag ? "true" : "false";
+}
+
+std::string_view alreadyGood() {
+ return "alreadyGood";
+}
+
+std::wstring_view alreadyGoodW() {
+ return L"alreadyGood";
+}
+
+std::u8string_view alreadyGoodU8() {
+ return u8"alreadyGood";
+}
+
+std::u16string_view alreadyGoodU16() {
+ return u"alreadyGood";
+}
+
+std::u32string_view alreadyGoodU32() {
+ return U"alreadyGood";
+}
+
+std::string simpleLiteralS() {
+ // TODO: replace ""s to literal and return string_view
+ using namespace std::literals::string_literals;
+ return "simpleLiteral"s;
+}
+
+std::string_view alreadyGoodSV() {
+ using namespace std::literals::string_view_literals;
+ return "alreadyGood"sv;
+}
+
+std::string returnArgCopy(std::string s) {
+ // Must not be converted to string_view because of use-after-free on stack
+ return s;
+}
+
+std::string returnIndirection(const char* ptr) {
+ // Can be unsafe or intentional, like converting string_view into string
+ return ptr;
+}
+
+std::string localBuffer() {
+ char buf[] = "local buffer";
+ // Must not be converted to string_view because of use-after-free on stack
+ return buf;
+}
+
+std::string returnConstVar() {
+ // TODO: seems safe
+ constexpr auto kVar = "const string";
+ return kVar;
+}
+
+std::string passStringView(std::string_view sv) {
+ // Can be unsafe or intentional, like converting string_view into string
+ return std::string(sv);
+}
+
+std::string explicitConstruction() {
+ // Cannot be std::string_view: returning address of local temporary object
+ // TODO: extract and return literal
+ return std::string("explicitConstruction");
+}
+
+struct B {
+ virtual ~B() = default;
+ virtual std::string virtMethod1() { return "B::virtual1"; }
+ virtual std::string virtMethod2();
+};
+
+ std::string B::virtMethod2() { return "B::virtual2"; }
+
+struct C: public B {
+ std::string virtMethod1() override { return "C::virtual"; }
+ std::string virtMethod2() override;
+};
+
+std::string C::virtMethod2() { return "C::virtual"; }
+
+std::string lambda() {
+ // TODO: extract and return literal from lambda
+ return []() {
+ return "lambda";
+ }();
+}
+
+struct TemplateString {
+ static constexpr char* val = "TEMPLATE";
+ template<typename T>
+ // TODO: extract and return literal
+ std::string templateFunc() { return T::val; }
+ std::string templateFuncCall() {
+ return templateFunc<TemplateString>();
+ }
+};
+
+template <class T>
+std::basic_string<T> templateStringConditional() {
+ if constexpr(std::is_same_v<T, wchar_t>) {
+ return L"TEMPLATE";
+ } else {
+ return "TEMPLATE";
+ }
+}
+
+template <class T>
+std::basic_string<T> templateStringMixedConditional() {
+ if constexpr(std::is_same_v<T, wchar_t>) {
+ return L"TEMPLATE";
+ } else {
+ std::string s = "haha";
+ return s;
+ }
+}
+
+void UseTemplateStringConditional() {
+ templateStringConditional<char>();
+ templateStringConditional<wchar_t>();
+
+ templateStringMixedConditional<char>();
+ templateStringMixedConditional<wchar_t>();
+}
+
+std::string& Ref() {
+ static std::string s = "Ref";
+ return s;
+}
+
+const std::string& ConstRef() {
+ static std::string s = "ConstRef";
+ return s;
+}
+
+auto autoReturn() {
+ // Deduced to const char*
+ return "autoReturn";
+}
+
+template <class T>
+std::basic_string<T> templateString() {
+// Intentionally skip templates
+ return L"TEMPLATE";
+}
+std::wstring returnTemplateString() {
+ return templateString<wchar_t>();
+}
+
+template <typename R> R f() {
+// Intentionally skip templates
+ return "str";
+}
+template std::string f();
+
+struct stringOperator {
+ operator std::string() const {
+ return "conversion";
+ }
+};
+
+std::string safeFunctionWithLambda() {
+ auto lambda = []() -> std::string {
+ std::string local = "unsafe";
+ return local;
+ };
+ // TODO: fix hasDescendant(returnStmt(hasReturnValue... which is too strict
+ return "safe literal";
+}
\ No newline at end of file
>From 901a35998119adeb85a719a8eced4f1f265f5eb5 Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
Date: Sun, 21 Dec 2025 10:24:51 +0300
Subject: [PATCH 2/8] Auto-fix remarks
Co-authored-by: Baranov Victor <bar.victor.2002 at gmail.com>
---
.../clang-tidy/modernize/UseStringViewCheck.cpp | 4 ++--
.../clang-tidy/checks/modernize/use-string-view.rst | 12 ++----------
2 files changed, 4 insertions(+), 12 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp
index 1ccf21cad91d6..0bfc56d2fbd0c 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp
@@ -23,7 +23,7 @@ AST_MATCHER(NamedDecl, isOperatorDecl) {
}
} // namespace
-static llvm::StringRef toStringViewTypeStr(StringRef Type) {
+static StringRef toStringViewTypeStr(StringRef Type) {
if (Type.contains("wchar_t"))
return "std::wstring_view";
if (Type.contains("char8_t"))
@@ -68,7 +68,7 @@ void UseStringViewCheck::registerMatchers(MatchFinder *Finder) {
void UseStringViewCheck::check(const MatchFinder::MatchResult &Result) {
const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("func");
assert(MatchedDecl);
- const llvm::StringRef DestReturnTypeStr = toStringViewTypeStr(
+ const StringRef DestReturnTypeStr = toStringViewTypeStr(
MatchedDecl->getReturnType().getCanonicalType().getAsString());
auto Diag = diag(MatchedDecl->getTypeSpecStartLoc(),
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
index 1b3bdc58db5ab..70dd2726e5f0a 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
@@ -1,13 +1,11 @@
.. title:: clang-tidy - modernize-use-string-view
modernize-use-string-view
-====================================
+=========================
Looks for functions returning ``std::[w|u8|u16|u32]string`` and suggests to
change it to ``std::[...]string_view`` for performance reasons if possible.
-Rationale:
-
Each time a new ``std::string`` is created from a literal, a copy of that
literal is allocated either in ``std::string``'s internal buffer
(for short literals) or in a heap.
@@ -23,9 +21,7 @@ It handles ``std::string``, ``std::wstring``, ``std::u8string``,
``std::u16string`` and ``std::u32string`` along with their aliases and selects
the proper kind of ``std::string_view`` to return.
-Example:
-
-Consider the following code:
+Consider the following example:
.. code-block:: c++
@@ -34,10 +30,6 @@ Consider the following code:
{
case 1:
return "case 1";
- case 2:
- return "case 2";
- case 3:
- return "case 3";
default:
return "default";
}
>From 2be47ab99444febb66caa64d0e4794dc0d66759c Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
Date: Sun, 21 Dec 2025 11:07:21 +0300
Subject: [PATCH 3/8] Fix remarks
---
.../modernize/UseStringViewCheck.cpp | 51 ++++----
.../clang-tidy/modernize/UseStringViewCheck.h | 15 ++-
.../checks/modernize/use-string-view.rst | 25 ++--
.../modernize/use-string-view-cxx20.cpp | 42 +++++++
.../modernize/use-string-view-ignored.cpp | 56 +++++++++
.../checkers/modernize/use-string-view.cpp | 116 +++++++++++-------
6 files changed, 227 insertions(+), 78 deletions(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view-cxx20.cpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view-ignored.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp
index 0bfc56d2fbd0c..d1b6223f971e0 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "UseStringViewCheck.h"
+#include "../utils/Matchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
@@ -14,15 +15,6 @@ using namespace clang::ast_matchers;
namespace clang::tidy::modernize {
-namespace {
-AST_MATCHER(NamedDecl, isOperatorDecl) {
- const DeclarationName::NameKind NK = Node.getDeclName().getNameKind();
- return NK != DeclarationName::Identifier &&
- NK != DeclarationName::CXXConstructorName &&
- NK != DeclarationName::CXXDestructorName;
-}
-} // namespace
-
static StringRef toStringViewTypeStr(StringRef Type) {
if (Type.contains("wchar_t"))
return "std::wstring_view";
@@ -42,25 +34,26 @@ static auto getStringTypeMatcher(StringRef CharType) {
void UseStringViewCheck::registerMatchers(MatchFinder *Finder) {
const auto IsStdString = getStringTypeMatcher("::std::basic_string");
const auto IsStdStringView = getStringTypeMatcher("::std::basic_string_view");
-
+ const auto IgnoredFunctionsMatcher =
+ matchers::matchesAnyListedName(IgnoredFunctions);
+ const auto TernaryOperator = conditionalOperator(
+ hasTrueExpression(ignoringParenImpCasts(stringLiteral())),
+ hasFalseExpression(ignoringParenImpCasts(stringLiteral())));
+ const auto VirtualOrOperator =
+ cxxMethodDecl(anyOf(cxxConversionDecl(), isVirtual()));
Finder->addMatcher(
functionDecl(
- isDefinition(),
- unless(cxxMethodDecl(anyOf(isOperatorDecl(), isVirtual()))),
- returns(IsStdString), hasDescendant(returnStmt()),
- unless(anyOf(
- ast_matchers::isTemplateInstantiation(),
- isExplicitTemplateSpecialization(),
- hasDescendant(returnStmt(hasReturnValue(unless(ignoringImplicit(
- anyOf(stringLiteral(), hasType(IsStdStringView),
- cxxConstructExpr(
- hasType(IsStdString),
- anyOf(argumentCountIs(0),
- hasArgument(
- 0,
- ignoringParenImpCasts(anyOf(
- stringLiteral(),
- hasType(IsStdStringView)))))))))))))))
+ isDefinition(), unless(VirtualOrOperator),
+ unless(IgnoredFunctionsMatcher), returns(IsStdString),
+ hasDescendant(returnStmt()),
+ unless(hasDescendant(returnStmt(hasReturnValue(unless(
+ anyOf(stringLiteral(), hasType(IsStdStringView), TernaryOperator,
+ cxxConstructExpr(anyOf(
+ allOf(hasType(IsStdString), argumentCountIs(0)),
+ allOf(isListInitialization(),
+ unless(cxxTemporaryObjectExpr()),
+ hasArgument(0, ignoringParenImpCasts(
+ stringLiteral()))))))))))))
.bind("func"),
this);
}
@@ -77,8 +70,10 @@ void UseStringViewCheck::check(const MatchFinder::MatchResult &Result) {
<< DestReturnTypeStr;
for (const auto *FuncDecl : MatchedDecl->redecls())
- Diag << FixItHint::CreateReplacement(FuncDecl->getReturnTypeSourceRange(),
- DestReturnTypeStr);
+ if (const SourceRange ReturnTypeRange =
+ FuncDecl->getReturnTypeSourceRange();
+ ReturnTypeRange.isValid())
+ Diag << FixItHint::CreateReplacement(ReturnTypeRange, DestReturnTypeStr);
}
} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.h
index 056bcd823a1ac..0658ba081c7bf 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.h
+++ b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.h
@@ -10,6 +10,7 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTRINGVIEWCHECK_H
#include "../ClangTidyCheck.h"
+#include "../utils/OptionsUtils.h"
namespace clang::tidy::modernize {
@@ -21,12 +22,24 @@ namespace clang::tidy::modernize {
class UseStringViewCheck : public ClangTidyCheck {
public:
UseStringViewCheck(StringRef Name, ClangTidyContext *Context)
- : ClangTidyCheck(Name, Context) {}
+ : ClangTidyCheck(Name, Context),
+ IgnoredFunctions(utils::options::parseStringList(Options.get(
+ "IgnoredFunctions", "toString$;ToString$;to_string$;to_s$"))) {}
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;
}
+ std::optional<TraversalKind> getCheckTraversalKind() const override {
+ return TK_IgnoreUnlessSpelledInSource;
+ }
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override {
+ Options.store(Opts, "IgnoredFunctions",
+ utils::options::serializeStringList(IgnoredFunctions));
+ }
+
+private:
+ const std::vector<StringRef> IgnoredFunctions;
};
} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
index 70dd2726e5f0a..1eac38be8af2a 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
@@ -26,10 +26,10 @@ Consider the following example:
.. code-block:: c++
std::string foo(int i) {
- switch(i)
- {
+ switch(i) {
case 1:
return "case 1";
+ ...
default:
return "default";
}
@@ -44,17 +44,26 @@ The check gets this code transformed into:
.. code-block:: c++
std::string_view foo(int i) {
- switch(i)
- {
+ switch(i) {
case 1:
return "case 1";
- case 2:
- return "case 2";
- case 3:
- return "case 3";
+ ...
default:
return "default";
}
}
New version re-uses statically allocated literals without additional overhead.
+
+Options
+-------
+
+.. option:: IgnoredFunctions
+
+ A semicolon-separated list of the names of functions or methods to be
+ ignored. Regular expressions are accepted, e.g. ``[Rr]ef(erence)?$`` matches
+ every type with suffix ``Ref``, ``ref``, ``Reference`` and ``reference``.
+ The default is {``toString$;ToString$;to_string$;to_s$``}. If a
+ name in the list contains the sequence `::` it is matched against the
+ qualified type name (i.e. ``namespace::Type``), otherwise it is matched
+ against only the type name (i.e. ``Type``).
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view-cxx20.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view-cxx20.cpp
new file mode 100644
index 0000000000000..65f94c5354f38
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view-cxx20.cpp
@@ -0,0 +1,42 @@
+// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-use-string-view %t -- -- -isystem %clang_tidy_headers
+
+#include <string>
+
+// ==========================================================
+// Positive tests
+// ==========================================================
+
+std::string simpleLiteral() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view simpleLiteral() {
+ return "simpleLiteral";
+}
+
+std::wstring simpleLiteralW() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::wstring_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::wstring_view simpleLiteralW() {
+ return L"wide literal";
+}
+
+std::u8string simpleLiteral8() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::u8string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::u8string_view simpleLiteral8() {
+ return u8"simpleLiteral";
+}
+
+std::u16string simpleLiteral16() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::u16string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::u16string_view simpleLiteral16() {
+ return u"simpleLiteral";
+}
+
+std::u32string simpleLiteral32() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::u32string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::u32string_view simpleLiteral32() {
+ return U"simpleLiteral";
+}
+
+// ==========================================================
+// Negative tests
+// ==========================================================
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view-ignored.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view-ignored.cpp
new file mode 100644
index 0000000000000..642d804a745a1
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view-ignored.cpp
@@ -0,0 +1,56 @@
+// RUN: %check_clang_tidy \
+// RUN: -std=c++17-or-later %s modernize-use-string-view %t -- \
+// RUN: --config="{CheckOptions: {modernize-use-string-view.IgnoredFunctions: 'GoodButIgnored;GoodTooButAlsoIgnored'}}" \
+// RUN: -- -isystem %clang_tidy_headers
+
+#include <string>
+
+namespace std {
+namespace literals {
+namespace string_literals {
+ string operator""s(const char *, size_t);
+}
+namespace string_view_literals {
+ string_view operator""sv(const char *, size_t);
+}
+}
+template <class T, class U> struct is_same { static constexpr bool value = false; };
+template <class T> struct is_same<T, T> { static constexpr bool value = true; };
+template <class T, class U> constexpr bool is_same_v = is_same<T, U>::value;
+} // namespace std
+
+
+// ==========================================================
+// Positive tests
+// ==========================================================
+
+std::string toString() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view toString() {
+ return "not ignored by custom options";
+}
+
+std::string ToString() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view ToString() {
+ return "not ignored by custom options";
+}
+
+std::string to_string() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view to_string() {
+ return "not ignored by custom options";
+}
+
+// ==========================================================
+// Negative tests
+// ==========================================================
+
+std::string GoodButIgnored() {
+ return "ignored by explicit options";
+}
+
+std::string GoodTooButAlsoIgnored() {
+ return "also ignored by explicit options";
+}
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view.cpp
index b90f36eec26cc..3e66ecfdaf614 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-string-view.cpp
@@ -1,4 +1,6 @@
-// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-use-string-view %t -- -- -isystem %clang_tidy_headers
+// RUN: %check_clang_tidy \
+// RUN: -std=c++17-or-later %s modernize-use-string-view %t -- \
+// RUN: -- -isystem %clang_tidy_headers
#include <string>
@@ -33,24 +35,6 @@ std::wstring simpleLiteralW() {
return L"wide literal";
}
-std::u8string simpleLiteral8() {
-// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::u8string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
-// CHECK-FIXES: std::u8string_view simpleLiteral8() {
- return u8"simpleLiteral";
-}
-
-std::u16string simpleLiteral16() {
-// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::u16string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
-// CHECK-FIXES: std::u16string_view simpleLiteral16() {
- return u"simpleLiteral";
-}
-
-std::u32string simpleLiteral32() {
-// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::u32string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
-// CHECK-FIXES: std::u32string_view simpleLiteral32() {
- return U"simpleLiteral";
-}
-
std::string simpleRLiteral() {
// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
// CHECK-FIXES: std::string_view simpleRLiteral() {
@@ -75,10 +59,10 @@ auto Trailing() -> std::string {
return "Trailing";
}
-std::string emptyReturn() {
+std::string initList() {
// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
-// CHECK-FIXES: std::string_view emptyReturn() {
- return {};
+// CHECK-FIXES: std::string_view initList() {
+ return {"list"};
}
std::string ctorReturn() {
@@ -87,10 +71,16 @@ std::string ctorReturn() {
return std::string();
}
-std::string initList() {
+std::string ctorWithInitListReturn() {
// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
-// CHECK-FIXES: std::string_view initList() {
- return {"list"};
+// CHECK-FIXES: std::string_view ctorWithInitListReturn() {
+ return std::string{};
+}
+
+std::string emptyReturn() {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view emptyReturn() {
+ return {};
}
std::string switchCaseTest(int i) {
@@ -116,6 +106,12 @@ std::string ifElseTest(bool flag) {
return "false";
}
+std::string ternary(bool flag) {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+// CHECK-FIXES: std::string_view ternary(bool flag) {
+ return flag ? "true" : "false";
+}
+
class A {
std::string classMethodInt() { return "internal"; }
// CHECK-MESSAGES:[[@LINE-1]]:3: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
@@ -143,10 +139,28 @@ my_string macro_type() {
return "MACTO LITERAL";
}
+#define my_definition std::string function_inside_macro()
+my_definition {
+// CHECK-MESSAGES:[[@LINE-1]]:1: warning: consider using 'std::string_view' to avoid unnecessary copying and allocations [modernize-use-string-view]
+ return "literal";
+}
+
// ==========================================================
// Negative tests
// ==========================================================
+std::string toString() {
+ return "ignored by default options";
+}
+
+std::string ToString() {
+ return "ignored by default options";
+}
+
+std::string to_string() {
+ return "ignored by default options";
+}
+
std::string localVariable() {
std::string s = "local variable";
// TODO: extract and return literal
@@ -166,8 +180,9 @@ std::string mixedReturns(bool flag) {
return s;
}
-std::string ternary(bool flag) {
- return flag ? "true" : "false";
+std::string stringTernary(bool flag) {
+ // TODO: extract literals
+ return flag ? std::string("true") : std::string("false");
}
std::string_view alreadyGood() {
@@ -178,18 +193,6 @@ std::wstring_view alreadyGoodW() {
return L"alreadyGood";
}
-std::u8string_view alreadyGoodU8() {
- return u8"alreadyGood";
-}
-
-std::u16string_view alreadyGoodU16() {
- return u"alreadyGood";
-}
-
-std::u32string_view alreadyGoodU32() {
- return U"alreadyGood";
-}
-
std::string simpleLiteralS() {
// TODO: replace ""s to literal and return string_view
using namespace std::literals::string_literals;
@@ -224,7 +227,7 @@ std::string returnConstVar() {
}
std::string passStringView(std::string_view sv) {
- // Can be unsafe or intentional, like converting string_view into string
+ // TODO: Can be unsafe or intentional, like converting string_view into string
return std::string(sv);
}
@@ -234,6 +237,20 @@ std::string explicitConstruction() {
return std::string("explicitConstruction");
}
+std::string explicitConstructionWithInitList() {
+ // Cannot be std::string_view: returning address of local temporary object
+ // TODO: extract and return literal
+ return std::string{"explicitConstruction"};
+}
+
+std::string explicitConstructionEmpty() {
+ return std::string("");
+}
+
+std::string explicitConstructionWithInitListEmpty() {
+ return std::string{""};
+}
+
struct B {
virtual ~B() = default;
virtual std::string virtMethod1() { return "B::virtual1"; }
@@ -252,7 +269,7 @@ std::string C::virtMethod2() { return "C::virtual"; }
std::string lambda() {
// TODO: extract and return literal from lambda
return []() {
- return "lambda";
+ return "lambda";
}();
}
@@ -336,4 +353,21 @@ std::string safeFunctionWithLambda() {
};
// TODO: fix hasDescendant(returnStmt(hasReturnValue... which is too strict
return "safe literal";
+}
+
+using Handle = std::wstring;
+
+#ifdef HANDLE_SUPPORTED
+Handle handle_or_string();
+#else
+std::string handle_or_string();
+#endif
+
+#ifdef HANDLE_SUPPORTED
+Handle
+#else
+std::string
+#endif
+my_function() {
+ return handle_or_string();
}
\ No newline at end of file
>From 997c461c1c7fbdb12898afee2c92b7a8307ec4ae Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
Date: Mon, 22 Dec 2025 07:55:28 +0300
Subject: [PATCH 4/8] Update
clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
Co-authored-by: EugeneZelenko <eugene.zelenko at gmail.com>
---
.../docs/clang-tidy/checks/modernize/use-string-view.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
index 1eac38be8af2a..c435c68852769 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
@@ -61,7 +61,7 @@ Options
.. option:: IgnoredFunctions
A semicolon-separated list of the names of functions or methods to be
- ignored. Regular expressions are accepted, e.g. ``[Rr]ef(erence)?$`` matches
+ ignored. Regular expressions are accepted, e.g. `[Rr]ef(erence)?$` matches
every type with suffix ``Ref``, ``ref``, ``Reference`` and ``reference``.
The default is {``toString$;ToString$;to_string$;to_s$``}. If a
name in the list contains the sequence `::` it is matched against the
>From b6b02cd1f765a7465f64fc9fb4d1172ad2eafc6f Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
Date: Mon, 22 Dec 2025 07:55:54 +0300
Subject: [PATCH 5/8] Update
clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
Co-authored-by: EugeneZelenko <eugene.zelenko at gmail.com>
---
.../docs/clang-tidy/checks/modernize/use-string-view.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
index c435c68852769..f1455660bcf54 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
@@ -63,7 +63,7 @@ Options
A semicolon-separated list of the names of functions or methods to be
ignored. Regular expressions are accepted, e.g. `[Rr]ef(erence)?$` matches
every type with suffix ``Ref``, ``ref``, ``Reference`` and ``reference``.
- The default is {``toString$;ToString$;to_string$;to_s$``}. If a
+ The default is {`toString$;ToString$;to_string$;to_s$`}. If a
name in the list contains the sequence `::` it is matched against the
qualified type name (i.e. ``namespace::Type``), otherwise it is matched
against only the type name (i.e. ``Type``).
>From 7d10e190f54d032b1f73d96a1671f0af690c7b34 Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
Date: Mon, 22 Dec 2025 13:33:25 +0300
Subject: [PATCH 6/8] Update
clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
Co-authored-by: Baranov Victor <bar.victor.2002 at gmail.com>
---
.../docs/clang-tidy/checks/modernize/use-string-view.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
index f1455660bcf54..f025bfff3a7d8 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
@@ -67,3 +67,4 @@ Options
name in the list contains the sequence `::` it is matched against the
qualified type name (i.e. ``namespace::Type``), otherwise it is matched
against only the type name (i.e. ``Type``).
+ The default is `toString$;ToString$;to_string$`.
>From 3ba36be5858798dcec0022839491f86cb0267755 Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
Date: Mon, 22 Dec 2025 20:50:34 +0300
Subject: [PATCH 7/8] Extend type recognition
---
.../modernize/UseStringViewCheck.cpp | 20 ++++++++--
.../clang-tidy/modernize/UseStringViewCheck.h | 11 +-----
.../checks/modernize/use-string-view.rst | 37 +++++++++++++++++--
3 files changed, 51 insertions(+), 17 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp
index d1b6223f971e0..3f23ec7b05a55 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.cpp
@@ -8,6 +8,7 @@
#include "UseStringViewCheck.h"
#include "../utils/Matchers.h"
+#include "../utils/OptionsUtils.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
@@ -16,13 +17,13 @@ using namespace clang::ast_matchers;
namespace clang::tidy::modernize {
static StringRef toStringViewTypeStr(StringRef Type) {
- if (Type.contains("wchar_t"))
+ if (Type.contains("wchar_t") || Type.ends_with("wstring"))
return "std::wstring_view";
- if (Type.contains("char8_t"))
+ if (Type.contains("char8_t") || Type.ends_with("u8string"))
return "std::u8string_view";
- if (Type.contains("char16_t"))
+ if (Type.contains("char16_t") || Type.ends_with("u16string"))
return "std::u16string_view";
- if (Type.contains("char32_t"))
+ if (Type.contains("char32_t") || Type.ends_with("u32string"))
return "std::u32string_view";
return "std::string_view";
}
@@ -31,6 +32,17 @@ static auto getStringTypeMatcher(StringRef CharType) {
return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasName(CharType))));
}
+UseStringViewCheck::UseStringViewCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ IgnoredFunctions(utils::options::parseStringList(
+ Options.get("IgnoredFunctions", "toString$;ToString$;to_string$"))) {}
+
+void UseStringViewCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "IgnoredFunctions",
+ utils::options::serializeStringList(IgnoredFunctions));
+}
+
void UseStringViewCheck::registerMatchers(MatchFinder *Finder) {
const auto IsStdString = getStringTypeMatcher("::std::basic_string");
const auto IsStdStringView = getStringTypeMatcher("::std::basic_string_view");
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.h
index 0658ba081c7bf..d4373bc47e552 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.h
+++ b/clang-tools-extra/clang-tidy/modernize/UseStringViewCheck.h
@@ -10,7 +10,6 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTRINGVIEWCHECK_H
#include "../ClangTidyCheck.h"
-#include "../utils/OptionsUtils.h"
namespace clang::tidy::modernize {
@@ -21,10 +20,8 @@ namespace clang::tidy::modernize {
/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-string-view.html
class UseStringViewCheck : public ClangTidyCheck {
public:
- UseStringViewCheck(StringRef Name, ClangTidyContext *Context)
- : ClangTidyCheck(Name, Context),
- IgnoredFunctions(utils::options::parseStringList(Options.get(
- "IgnoredFunctions", "toString$;ToString$;to_string$;to_s$"))) {}
+ UseStringViewCheck(StringRef Name, ClangTidyContext *Context);
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
@@ -33,10 +30,6 @@ class UseStringViewCheck : public ClangTidyCheck {
std::optional<TraversalKind> getCheckTraversalKind() const override {
return TK_IgnoreUnlessSpelledInSource;
}
- void storeOptions(ClangTidyOptions::OptionMap &Opts) override {
- Options.store(Opts, "IgnoredFunctions",
- utils::options::serializeStringList(IgnoredFunctions));
- }
private:
const std::vector<StringRef> IgnoredFunctions;
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
index f025bfff3a7d8..d820d1658351f 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
@@ -55,6 +55,35 @@ The check gets this code transformed into:
New version re-uses statically allocated literals without additional overhead.
+The check handles at least the following cases (``[w|u8|u16|u32]string`` are
+handled the same way but omitted for brevity):
+
+* ``return "simpleLiteral";``
+* ``return MACRO_LITERAL;``
+* ``return R"(simpleLiteral)";``
+* ``auto foo() -> std::string { return "literal"; }``
+* ``return std::string();``
+* ``return std::string{};``
+* ``return {};``
+* ``return flag ? "foo" : "bar";``
+* switch statements as in the sample above
+* simple macro typedefs for ``std::string``
+
+Limitations
+-----------
+
+* In some cases the fixed code can be incorrect. But it's usually easy to fix.
+* No warning and/or fix are generated as for now for these code patterns:
+
+ * ``return std::string("literal");`` - hint: to prevent a warning and a fix
+ you may wrap your string with ``std::string(...)``
+ * ``return std::string{"literal"};``
+ * ``return "simpleLiteral"s;``
+ * ``auto foo() { return "autoReturn"; }``
+ * ``auto Trailing() -> std::string { return "Trailing"; }`` warns, doesn't fix
+ * returnings from lambda
+ * complicated macro and templated code
+
Options
-------
@@ -63,8 +92,8 @@ Options
A semicolon-separated list of the names of functions or methods to be
ignored. Regular expressions are accepted, e.g. `[Rr]ef(erence)?$` matches
every type with suffix ``Ref``, ``ref``, ``Reference`` and ``reference``.
- The default is {`toString$;ToString$;to_string$;to_s$`}. If a
- name in the list contains the sequence `::` it is matched against the
- qualified type name (i.e. ``namespace::Type``), otherwise it is matched
- against only the type name (i.e. ``Type``).
+ The default is {`toString$;ToString$;to_string$`}. If a name in the list
+ contains the sequence `::` it is matched against the qualified type name
+ (i.e. ``namespace::Type``), otherwise it is matched against only the type
+ name (i.e. ``Type``).
The default is `toString$;ToString$;to_string$`.
>From 7e2dbad1997284df8ddcfd9baed9324b0c17cbf2 Mon Sep 17 00:00:00 2001
From: Zinovy Nis <zinovy.nis at gmail.com>
Date: Tue, 23 Dec 2025 20:53:26 +0300
Subject: [PATCH 8/8] Fix docs
---
.../checks/modernize/use-string-view.rst | 42 ++++++++++++-------
1 file changed, 28 insertions(+), 14 deletions(-)
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
index d820d1658351f..7cc24a90bd763 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-string-view.rst
@@ -55,24 +55,24 @@ The check gets this code transformed into:
New version re-uses statically allocated literals without additional overhead.
-The check handles at least the following cases (``[w|u8|u16|u32]string`` are
-handled the same way but omitted for brevity):
-
-* ``return "simpleLiteral";``
-* ``return MACRO_LITERAL;``
-* ``return R"(simpleLiteral)";``
-* ``auto foo() -> std::string { return "literal"; }``
-* ``return std::string();``
-* ``return std::string{};``
-* ``return {};``
-* ``return flag ? "foo" : "bar";``
-* switch statements as in the sample above
-* simple macro typedefs for ``std::string``
+Hint
+----
+
+To prevent a warning and a fix wrap your string with ``std::string(...)``:
+
+.. code-block:: c++
+
+ std::string foo() {
+ return "default"; //warning and fix are generated
+ }
+
+ std::string bar() {
+ return std::string("default"); //warning and fix are NOT generated
+ }
Limitations
-----------
-* In some cases the fixed code can be incorrect. But it's usually easy to fix.
* No warning and/or fix are generated as for now for these code patterns:
* ``return std::string("literal");`` - hint: to prevent a warning and a fix
@@ -84,6 +84,20 @@ Limitations
* returnings from lambda
* complicated macro and templated code
+* In some cases the fixed code can be incorrect. But it's usually easy to fix:
+
+.. code-block:: c++
+
+ string foo() { // <--- will be replaced with string_view
+ return "foo";
+ }
+
+ void bar() {
+ string f = foo(); // <----- error: no viable conversion from 'std::string_view'
+ // (aka 'basic_string_view<char>') to 'std::string' (aka 'basic_string<char>')
+ }
+
+
Options
-------
More information about the cfe-commits
mailing list