https://github.com/gaul updated https://github.com/llvm/llvm-project/pull/209657

>From d763d7c597574b39731aeff859e4d9c632908aa6 Mon Sep 17 00:00:00 2001
From: Andrew Gaul <[email protected]>
Date: Wed, 25 Mar 2026 23:50:04 -0400
Subject: [PATCH] [clang-tidy] Add performance-substr-self-assignment check

Finds `s = s.substr(...)` self-assignments that materialize an
unnecessary temporary string and rewrites the prefix-stripping forms
(`substr(pos)`, `substr(pos, npos)`) to an in-place `s.erase(0, pos)`.
The truncation form `s = s.substr(0, count)` is diagnosed without a
fix-it because `s.erase(count)` throws std::out_of_range where
`substr` clamps.

Co-Authored-By: Claude Fable 5 <[email protected]>
---
 .../clang-tidy/performance/CMakeLists.txt     |   1 +
 .../performance/PerformanceTidyModule.cpp     |   3 +
 .../performance/SubstrSelfAssignmentCheck.cpp | 127 ++++++++++++
 .../performance/SubstrSelfAssignmentCheck.h   |  44 +++++
 clang-tools-extra/docs/ReleaseNotes.md        |   6 +
 .../docs/clang-tidy/checks/list.md            |   1 +
 .../performance/substr-self-assignment.md     |  54 ++++++
 .../checkers/Inputs/Headers/std/string        |   1 +
 .../performance/substr-self-assignment.cpp    | 181 ++++++++++++++++++
 9 files changed, 418 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/performance/substr-self-assignment.md
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/performance/substr-self-assignment.cpp

diff --git a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt
index f55a6cf2800f3..7d70ab6ef1465 100644
--- a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt
@@ -23,6 +23,7 @@ add_clang_library(clangTidyPerformanceModule STATIC
   PerformanceTidyModule.cpp
   PreferSingleCharOverloadsCheck.cpp
   StringViewConversionsCheck.cpp
+  SubstrSelfAssignmentCheck.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 9eee02494be91..54d8e99a81ab1 100644
--- a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp
@@ -25,6 +25,7 @@
 #include "NoexceptSwapCheck.h"
 #include "PreferSingleCharOverloadsCheck.h"
 #include "StringViewConversionsCheck.h"
+#include "SubstrSelfAssignmentCheck.h"
 #include "TriviallyDestructibleCheck.h"
 #include "TypePromotionInMathFnCheck.h"
 #include "UnnecessaryCopyInitializationCheck.h"
@@ -71,6 +72,8 @@ class PerformanceModule : public ClangTidyModule {
         "performance-prefer-single-char-overloads");
     CheckFactories.registerCheck<StringViewConversionsCheck>(
         "performance-string-view-conversions");
+    CheckFactories.registerCheck<SubstrSelfAssignmentCheck>(
+        "performance-substr-self-assignment");
     CheckFactories.registerCheck<TriviallyDestructibleCheck>(
         "performance-trivially-destructible");
     CheckFactories.registerCheck<TypePromotionInMathFnCheck>(
diff --git 
a/clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.cpp 
b/clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.cpp
new file mode 100644
index 0000000000000..ebdd186c99771
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.cpp
@@ -0,0 +1,127 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "SubstrSelfAssignmentCheck.h"
+#include "../utils/Matchers.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Tooling/FixIt.h"
+#include "llvm/ADT/STLExtras.h"
+#include <optional>
+#include <string>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::performance {
+
+SubstrSelfAssignmentCheck::SubstrSelfAssignmentCheck(StringRef Name,
+                                                     ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context),
+      StringLikeClasses(utils::options::parseStringList(
+          Options.get("StringLikeClasses", "::std::basic_string"))) {}
+
+void SubstrSelfAssignmentCheck::storeOptions(
+    ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "StringLikeClasses",
+                utils::options::serializeStringList(StringLikeClasses));
+}
+
+void SubstrSelfAssignmentCheck::registerMatchers(MatchFinder *Finder) {
+  const auto VarRef = declRefExpr(to(varDecl().bind("var"))).bind("lhs");
+
+  const auto StringClass =
+      cxxRecordDecl(hasAnyName(StringLikeClasses)).bind("string-class");
+
+  // The static member 'npos' of the matched string class. A bare name
+  // comparison is not enough: a local variable or parameter that happens to
+  // be called 'npos' carries a real count, which has no single-'erase'
+  // rewrite.
+  const auto NposDecl =
+      varDecl(hasName("npos"),
+              hasDeclContext(cxxRecordDecl(equalsBoundNode("string-class"))));
+
+  const auto Npos = expr(ignoringParenImpCasts(
+      mapAnyOf(declRefExpr, memberExpr).with(hasDeclaration(NposDecl))));
+
+  const auto SubstrCall =
+      cxxMemberCallExpr(
+          callee(cxxMethodDecl(hasName("substr"), ofClass(StringClass))),
+          on(declRefExpr(to(varDecl(equalsBoundNode("var"))))),
+          optionally(hasArgument(1, Npos.bind("npos-count"))))
+          .bind("substr");
+
+  // Match: s = s.substr(...), except in unevaluated contexts such as
+  // decltype or sizeof, where no temporary is ever materialized.
+  Finder->addMatcher(
+      cxxOperatorCallExpr(
+          hasOperatorName("="), hasArgument(0, VarRef),
+          hasArgument(1, SubstrCall),
+          unless(anyOf(hasAncestor(typeLoc()),
+                       hasAncestor(expr(matchers::hasUnevaluatedContext())))))
+          .bind("assign"),
+      this);
+}
+
+void SubstrSelfAssignmentCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *AssignExpr =
+      Result.Nodes.getNodeAs<CXXOperatorCallExpr>("assign");
+  const auto *LHS = Result.Nodes.getNodeAs<DeclRefExpr>("lhs");
+  const auto *SubstrExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("substr");
+
+  // Count only explicitly-written arguments (exclude CXXDefaultArgExpr).
+  const unsigned NumExplicitArgs =
+      llvm::count_if(SubstrExpr->arguments(), [](const Expr *Arg) {
+        return !isa<CXXDefaultArgExpr>(Arg);
+      });
+
+  // s = s.substr(pos) and s = s.substr(pos, npos) strip a prefix and can be
+  // rewritten as s.erase(0, pos). The truncation form s = s.substr(0, count)
+  // is diagnosed without a fix-it: s.erase(count) throws std::out_of_range
+  // for count > s.size() where substr merely clamps. The general
+  // two-argument form has no single-'erase' equivalent and is ignored.
+  // TODO: Also emit a fix-it for the truncation form when
+  // `count <= size()` can be proven.
+  // TODO(C++23): P2438R2 makes 's = std::move(s).substr(pos, count)' an
+  // exact, allocation-free rewrite for all forms; emit it as the fix-it in
+  // C++23 mode.
+  const bool IsPrefixStrip =
+      NumExplicitArgs == 1 ||
+      (NumExplicitArgs == 2 &&
+       Result.Nodes.getNodeAs<Expr>("npos-count") != nullptr);
+  bool IsTruncation = false;
+  if (!IsPrefixStrip && NumExplicitArgs == 2) {
+    const auto *PosLiteral =
+        dyn_cast<IntegerLiteral>(SubstrExpr->getArg(0)->IgnoreParenImpCasts());
+    IsTruncation = PosLiteral && PosLiteral->getValue() == 0;
+  }
+  if (!IsPrefixStrip && !IsTruncation)
+    return;
+
+  // Rewriting a macro expansion is unsafe; emit the warning without a fix-it.
+  // TODO: Only emit this fix-it when `pos <= size()` can be proven.
+  std::optional<std::string> Replacement;
+  if (IsPrefixStrip && !AssignExpr->getBeginLoc().isMacroID() &&
+      !AssignExpr->getEndLoc().isMacroID()) {
+    StringRef VarName = tooling::fixit::getText(*LHS, *Result.Context);
+    StringRef PosText =
+        tooling::fixit::getText(*SubstrExpr->getArg(0), *Result.Context);
+    if (!VarName.empty() && !PosText.empty())
+      Replacement = (VarName + ".erase(0, " + PosText + ")").str();
+  }
+
+  auto Diag = diag(AssignExpr->getOperatorLoc(),
+                   "inefficient self-assignment via 'substr'; use 'erase' to "
+                   "modify the string in-place");
+  if (Replacement)
+    Diag << FixItHint::CreateReplacement(AssignExpr->getSourceRange(),
+                                         *Replacement);
+}
+
+} // namespace clang::tidy::performance
diff --git 
a/clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.h 
b/clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.h
new file mode 100644
index 0000000000000..cf9dc425c44d2
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.h
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_SUBSTRSELFASSIGNMENTCHECK_H
+#define 
LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_SUBSTRSELFASSIGNMENTCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+#include <vector>
+
+namespace clang::tidy::performance {
+
+/// Finds cases where a string variable is assigned the result of calling
+/// ``substr()`` on itself (e.g., ``s = s.substr(x, y)``). This pattern creates
+/// an unnecessary temporary string; the same effect can be achieved in-place
+/// using ``erase()``.
+///
+/// For the user-facing documentation see:
+/// 
https://clang.llvm.org/extra/clang-tidy/checks/performance/substr-self-assignment.html
+class SubstrSelfAssignmentCheck : public ClangTidyCheck {
+public:
+  SubstrSelfAssignmentCheck(StringRef Name, ClangTidyContext *Context);
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus;
+  }
+  std::optional<TraversalKind> getCheckTraversalKind() const override {
+    return TK_IgnoreUnlessSpelledInSource;
+  }
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+
+private:
+  const std::vector<StringRef> StringLikeClasses;
+};
+
+} // namespace clang::tidy::performance
+
+#endif // 
LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_SUBSTRSELFASSIGNMENTCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.md 
b/clang-tools-extra/docs/ReleaseNotes.md
index 633418a2abb98..056f6cb0f13f6 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -113,6 +113,12 @@ infrastructure are described first, followed by 
tool-specific sections.
   Finds calls to `value_or` (and alternative spellings `valueOr`,
   `ValueOr`) on optional types where the return type is expensive to copy.
 
+- New {doc}`performance-substr-self-assignment
+  <clang-tidy/checks/performance/substr-self-assignment>` check.
+
+  Finds cases where a string variable is assigned the result of calling
+  `substr()` on itself.
+
 - New {doc}`portability-avoid-pragma-comment
   <clang-tidy/checks/portability/avoid-pragma-comment>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.md 
b/clang-tools-extra/docs/clang-tidy/checks/list.md
index 5a220b13eb599..4fcf554b483e1 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.md
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.md
@@ -368,6 +368,7 @@ readability/*
 | {doc}`performance-noexcept-swap <performance/noexcept-swap>` | Yes |
 | {doc}`performance-prefer-single-char-overloads 
<performance/prefer-single-char-overloads>` | Yes |
 | {doc}`performance-string-view-conversions 
<performance/string-view-conversions>` | Yes |
+| {doc}`performance-substr-self-assignment 
<performance/substr-self-assignment>` | 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/substr-self-assignment.md
 
b/clang-tools-extra/docs/clang-tidy/checks/performance/substr-self-assignment.md
new file mode 100644
index 0000000000000..7a568ade43c2a
--- /dev/null
+++ 
b/clang-tools-extra/docs/clang-tidy/checks/performance/substr-self-assignment.md
@@ -0,0 +1,54 @@
+```{title} clang-tidy - performance-substr-self-assignment
+```
+
+# performance-substr-self-assignment
+
+Finds cases where a string variable is assigned the result of calling
+`substr()` on itself. This pattern materializes an unnecessary temporary
+string (an allocation, a copy of the surviving characters, and a
+deallocation) and discards the original capacity; the same effect can be
+achieved in-place with `erase()`.
+
+```cpp
+std::string s = "hello world";
+
+s = s.substr(5);                     // warning; fix-it: s.erase(0, 5)
+s = s.substr(5, std::string::npos);  // warning; fix-it: s.erase(0, 5)
+s = s.substr(0, 3);                  // warning; no fix-it (see below)
+```
+
+The fix-it for the prefix-stripping forms replaces the assignment with
+`s.erase(0, pos)`. The two expressions differ only when `pos > s.size()`:
+`substr` throws `std::out_of_range`, while `erase(0, pos)` clamps and
+erases the whole string. Code that relies on that exception changes
+behavior under the fix-it.
+
+The truncation form `s = s.substr(0, count)` is diagnosed without a
+fix-it: the tempting replacement `s.erase(count)` throws
+`std::out_of_range` whenever `count > s.size()`, whereas `substr` clamps
+`count` and leaves the string unchanged. A safe manual rewrite is
+`s.resize(std::min(count, s.size()))`.
+
+In C++23, `s = std::move(s).substr(pos, count)` is an exact,
+allocation-free rewrite for every form: the rvalue `substr` overload
+([P2438R2](https://wg21.link/p2438r2)) constructs the result by moving
+from the string, reusing its buffer, and preserves the exception behavior
+of the original code. A future version of this check may suggest it
+automatically in C++23 mode. Note that the same spelling compiles before
+C++23 but silently performs a full copy.
+
+No diagnostic is emitted for the general form `s = s.substr(pos, count)`,
+which has no single-call in-place equivalent, nor in unevaluated contexts
+such as `decltype` or `sizeof`, where no temporary is ever materialized.
+Inside macro expansions the warning is emitted without a fix-it. Only
+self-assignments to plain variables are diagnosed; assignments through
+class members or pointers are not.
+
+## Options
+
+```{option} StringLikeClasses
+
+Semicolon-separated list of names of string-like classes. By default only
+`::std::basic_string` is considered. Classes listed here must provide
+`substr`, `erase`, and `npos` with `std::basic_string` semantics.
+```
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/string 
b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/string
index 766f240c655fb..667d48ad65334 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/string
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/string
@@ -77,6 +77,7 @@ struct basic_string {
   _Type& insert(size_type pos, const C* s);
   _Type& insert(size_type pos, const C* s, size_type n);
 
+  _Type& erase(size_type pos = 0, size_type count = npos);
   _Type substr(size_type pos = 0, size_type count = npos) const;
 
   constexpr bool starts_with(std::basic_string_view<C, T> sv) const noexcept;
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/performance/substr-self-assignment.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/performance/substr-self-assignment.cpp
new file mode 100644
index 0000000000000..848d43fb6748c
--- /dev/null
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/performance/substr-self-assignment.cpp
@@ -0,0 +1,181 @@
+// RUN: %check_clang_tidy %s performance-substr-self-assignment %t
+#include <string>
+
+void OneArg() {
+  std::string s = "hello world";
+
+  // Basic case: s = s.substr(pos)
+  s = s.substr(5);
+  // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place 
[performance-substr-self-assignment]
+  // CHECK-FIXES: s.erase(0, 5);
+
+  // With a variable as the argument.
+  int pos = 3;
+  s = s.substr(pos);
+  // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: s.erase(0, pos);
+
+  // With a more complex expression.
+  s = s.substr(pos + 1);
+  // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: s.erase(0, pos + 1);
+}
+
+void WholeStringCopies() {
+  std::string s = "hello world";
+
+  // A full self-copy via substr() has no erase() rewrite; not diagnosed.
+  s = s.substr();
+
+  // substr(0) is still a pointless full copy; the fix-it degenerates to a
+  // no-op erase.
+  s = s.substr(0);
+  // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: s.erase(0, 0);
+}
+
+void TruncationNoFixIt() {
+  std::string s = "hello world";
+
+  // The truncation form s = s.substr(0, count) is diagnosed but gets no
+  // fix-it: 'erase(count)' throws std::out_of_range when count > size(),
+  // while 'substr(0, count)' clamps.
+  s = s.substr(0, 5);
+  // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: s = s.substr(0, 5);
+
+  size_t count = 3;
+  s = s.substr(0, count);
+  // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: s = s.substr(0, count);
+}
+
+void TwoArgWithNpos() {
+  std::string s = "hello world";
+
+  // s = s.substr(pos, npos) is equivalent to s = s.substr(pos).
+  s = s.substr(3, std::string::npos);
+  // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: s.erase(0, 3);
+
+  int pos = 2;
+  s = s.substr(pos, std::string::npos);
+  // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: s.erase(0, pos);
+
+  // Member-syntax spelling of npos.
+  s = s.substr(4, s.npos);
+  // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: s.erase(0, 4);
+}
+
+void LocalVariableNamedNpos() {
+  std::string s = "hello world";
+
+  // A local variable named 'npos' is not std::basic_string::npos; the count
+  // is meaningful, so there is no single-'erase' rewrite and no warning.
+  size_t npos = 3;
+  s = s.substr(2, npos);
+}
+
+void ParamNamedNpos(std::string s, std::string::size_type npos) {
+  s = s.substr(1, npos);
+}
+
+void TwoArgGeneral() {
+  std::string s = "hello world";
+
+  // General two-argument case: no diagnostic (no single erase() equivalent).
+  s = s.substr(2, 3);
+
+  int pos = 1;
+  size_t len = 4;
+  s = s.substr(pos, len);
+}
+
+void Negatives() {
+  std::string s = "hello";
+  std::string t = "world";
+
+  // Different variables -- not a self-assignment.
+  s = t.substr(1);
+  s = t.substr(0, 3);
+
+  // Not an assignment to the same variable.
+  std::string r = s.substr(1);
+}
+
+// Unevaluated operands never materialize the temporary; no diagnostic.
+std::string GlobalStr;
+using UnevaluatedT = decltype(GlobalStr = GlobalStr.substr(1));
+
+void UnevaluatedContexts() {
+  std::string s = "hello";
+  (void)sizeof(s = s.substr(1));
+}
+
+struct Holder {
+  std::string Str;
+  // Self-assignment through a class member is not matched; only plain
+  // variables are.
+  void trim() { Str = Str.substr(2); }
+};
+
+template <typename T>
+void dependentType(T t) {
+  // Type-dependent: no diagnostic, including in instantiations.
+  t = t.substr(1);
+}
+void instantiate() { dependentType(std::string("hello")); }
+
+void WideString() {
+  std::wstring ws = L"hello world";
+
+  ws = ws.substr(3);
+  // CHECK-MESSAGES: [[@LINE-1]]:6: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: ws.erase(0, 3);
+
+  // Truncation form: warning only, no fix-it.
+  ws = ws.substr(0, 5);
+  // CHECK-MESSAGES: [[@LINE-1]]:6: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: ws = ws.substr(0, 5);
+}
+
+void Parenthesized() {
+  std::string s = "hello";
+
+  // Parenthesized object on the RHS -- should still match.
+  s = (s).substr(2);
+  // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: s.erase(0, 2);
+
+  // Parenthesized substr call on the RHS.
+  s = (s.substr(3));
+  // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: s.erase(0, 3);
+
+  // Parenthesized left-hand side.
+  (s) = s.substr(1);
+  // CHECK-MESSAGES: [[@LINE-1]]:7: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: s.erase(0, 1);
+}
+
+#define STRIP_PREFIX(str, n) str = str.substr(n)
+void MacroExpansion() {
+  std::string s = "hello";
+
+  // Diagnosed, but no fix-it: rewriting a macro expansion is unsafe.
+  STRIP_PREFIX(s, 2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: STRIP_PREFIX(s, 2);
+}
+
+#define OFFSET 2
+void MacroArgument() {
+  std::string s = "hello";
+
+  // Only the argument comes from a macro; the fix-it preserves its spelling.
+  s = s.substr(OFFSET);
+  // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 
'substr'; use 'erase' to modify the string in-place
+  // CHECK-FIXES: s.erase(0, OFFSET);
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to