================
@@ -0,0 +1,153 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 and
+ // 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*, const char*, wchar_t*, etc.)
+ // or array of any character type (char[], char[N], const char[N], etc.).
+ const auto IsCharPointerOrArray =
+ anyOf(hasType(pointerType(pointee(isAnyCharacter()))),
+ hasType(arrayType(hasElementType(isAnyCharacter()))));
+
+ // Matches expressions that can be implicitly converted to string_view
+ // without going through std::string:
+ // - string_view itself (no conversion needed)
+ // - string literals ("hello", L"wide", u8"utf8", etc.)
+ // - character pointers (const char*, char*)
+ // - character arrays (char arr[], char arr[N])
+ // These are the expressions we want to preserve after removing
+ // the redundant std::string conversion.
+ const auto ImplicitlyConvertibleToStringView =
+ expr(anyOf(hasType(IsStdStringView), stringLiteral(),
+ IsCharPointerOrArray))
+ .bind("originalStringView");
+
+ // Matches direct std::string construction from a string_view-convertible
+ // expression. This handles cases like:
+ // - Implicit construction in certain contexts
+ // - Brace initialization: std::string{sv}
+ // Excludes copy/move constructors to avoid false positives when
+ // an existing std::string is being copied or moved.
+ const auto RedundantStringConstruction = cxxConstructExpr(
+ hasType(IsStdString),
+ hasArgument(0, ignoringImplicit(ImplicitlyConvertibleToStringView)),
+ unless(hasDeclaration(cxxConstructorDecl(isCopyConstructor()))),
+ unless(hasDeclaration(cxxConstructorDecl(isMoveConstructor()))));
+
+ // Matches functional cast syntax: std::string(expr)
+ // In the AST, this appears as CXXFunctionalCastExpr containing
+ // a CXXConstructExpr. Example: 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.
+ // When these are present, the std::string construction is not redundant.
+ 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.)
+ //
+ // Detected patterns (will be flagged):
+ // void foo(std::string_view sv);
+ // foo(std::string(sv)); // sv -> string -> string_view
+ // foo(std::string{"literal"}); // literal -> string -> string_view
+ // foo(std::string(ptr)); // const char* -> string -> string_view
+ //
+ // Excluded patterns (will NOT be flagged):
+ // foo(std::string(sv) + "suffix"); // operator+ requires std::string
+ // foo("prefix" + std::string(sv)); // operator+ requires std::string
+ // foo(std::string("x").append("y")); // append() requires std::string
+ // foo(std::string(sv).substr(0, 5)); // substr() requires
+ 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))))
+ .bind("call"),
+ this);
+}
+
+void StringViewConversionsCheck::check(const MatchFinder::MatchResult &Result)
{
+ // Get the full argument expression passed to the function.
+ // This has type string_view after implicit conversions.
+ const auto *ParamExpr = Result.Nodes.getNodeAs<Expr>("expr");
+ if (!ParamExpr)
+ return;
+
+ // Get the redundant std::string construction expression.
+ // This is either CXXFunctionalCastExpr for std::string(x) syntax
+ // or CXXTemporaryObjectExpr for std::string{x} syntax.
+ const auto *RedundantExpr = Result.Nodes.getNodeAs<Expr>("redundantExpr");
+ if (!RedundantExpr)
+ return;
+
+ // Get the original expression that was passed to std::string constructor.
+ // This is what we want to use as the replacement.
+ const auto *OriginalExpr =
Result.Nodes.getNodeAs<Expr>("originalStringView");
+ if (!OriginalExpr)
+ return;
+
+ // 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"). If source ranges don't match, there's something between the string
+ // construction and the function argument, so we shouldn't transform.
+ assert(ParamExpr->getSourceRange() == RedundantExpr->getSourceRange());
+
+ // Extract the source text of the original expression to use as replacement.
+ // For example, if the code is std::string(sv), we extract "sv".
+ const StringRef OriginalText = Lexer::getSourceText(
+ CharSourceRange::getTokenRange(OriginalExpr->getSourceRange()),
+ *Result.SourceManager, getLangOpts());
+
+ // Skip if we couldn't extract the source text (e.g., macro expansion
issues).
+ if (OriginalText.empty())
+ return;
+
+ diag(RedundantExpr->getBeginLoc(),
+ "redundant conversion to %0 and then back to %1")
+ << RedundantExpr->getType().getAsString()
+ << ParamExpr->getType().getAsString()
+ << FixItHint::CreateReplacement(
+ CharSourceRange::getTokenRange(RedundantExpr->getSourceRange()),
----------------
localspook wrote:
I believe the explicit conversion to a token range is redundant:
```suggestion
RedundantExpr->getSourceRange(),
```
https://github.com/llvm/llvm-project/pull/174288
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits