================
@@ -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 "UseStringViewCheck.h"
+#include "../utils/Matchers.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTDiagnostic.h"
+#include "clang/AST/Stmt.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/Diagnostic.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+static constexpr StringRef StringViewClassKey = "string";
+static constexpr StringRef WStringViewClassKey = "wstring";
+static constexpr StringRef U8StringViewClassKey = "u8string";
+static constexpr StringRef U16StringViewClassKey = "u16string";
+static constexpr StringRef U32StringViewClassKey = "u32string";
+
+static auto getStringTypeMatcher(StringRef CharType) {
+ return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasName(CharType))));
+}
+
+static void fixReturns(const FunctionDecl *FuncDecl, DiagnosticBuilder &Diag,
+ ASTContext &Context) {
+ auto Matches = match(
+ findAll(returnStmt(hasReturnValue(cxxConstructExpr(anyOf(
+ argumentCountIs(0), cxxTemporaryObjectExpr()))))
+ .bind("return")),
+ *FuncDecl->getBody(), Context);
+ for (const auto &Match : Matches) {
+ if (const auto *RetValue =
+ Match.getNodeAs<ReturnStmt>("return")->getRetValue())
+ if (const auto ReturnSourceRange = RetValue->getSourceRange();
+ ReturnSourceRange.isValid())
+ Diag << FixItHint::CreateReplacement(ReturnSourceRange, "{}");
+ }
+}
+
+UseStringViewCheck::UseStringViewCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ IgnoredFunctions(utils::options::parseStringList(
+ Options.get("IgnoredFunctions", "toString$;ToString$;to_string$"))) {
+ parseReplacementStringViewClass(
+ Options.get("ReplacementStringViewClass", ""));
+}
+
+void UseStringViewCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "IgnoredFunctions",
+ utils::options::serializeStringList(IgnoredFunctions));
+ Options.store(Opts, "ReplacementStringViewClass",
+ (Twine("") + StringViewClassKey + "=" + StringViewClass + ";" +
+ WStringViewClassKey + "=" + WStringViewClass + ";" +
+ U8StringViewClassKey + "=" + U8StringViewClass + ";" +
+ U16StringViewClassKey + "=" + U16StringViewClass + ";" +
+ U32StringViewClassKey + "=" + U32StringViewClass)
+ .str());
+}
+
+void UseStringViewCheck::registerMatchers(MatchFinder *Finder) {
+ const auto IsStdString = getStringTypeMatcher("::std::basic_string");
+ // TODO: also consider *StringViewClass types
+ const auto IsStdStringView =
getStringTypeMatcher("::std::basic_string_view");
+ const auto IgnoredFunctionsMatcher =
+ matchers::matchesAnyListedRegexName(IgnoredFunctions);
+ const auto TernaryOperator = conditionalOperator(
+ hasTrueExpression(ignoringParenImpCasts(stringLiteral())),
+ hasFalseExpression(ignoringParenImpCasts(stringLiteral())));
+ const auto VirtualOrOperator =
+ cxxMethodDecl(anyOf(cxxConversionDecl(), isVirtual()));
+ Finder->addMatcher(
+ functionDecl(
+ isDefinition(),
+ unless(anyOf(VirtualOrOperator, IgnoredFunctionsMatcher,
+ ast_matchers::isExplicitTemplateSpecialization())),
+ 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);
+}
+
+void UseStringViewCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("func");
+ assert(MatchedDecl);
+ bool ShouldAKA = false;
+ const std::string DesugaredTypeStr =
+ clang::desugarForDiagnostic(
+ *Result.Context, QualType(MatchedDecl->getReturnType()), ShouldAKA)
+ .getAsString();
+ const StringRef DestReturnTypeStr = toStringViewTypeStr(DesugaredTypeStr);
+
+ auto Diag =
+ diag(MatchedDecl->getTypeSpecStartLoc(),
+ "consider using '%0' to avoid unnecessary copying and allocations")
+ << DestReturnTypeStr;
+
+ fixReturns(MatchedDecl, Diag, *Result.Context);
+
+ for (const auto *FuncDecl : MatchedDecl->redecls())
+ if (const SourceRange ReturnTypeRange =
+ FuncDecl->getReturnTypeSourceRange();
+ ReturnTypeRange.isValid())
+ Diag << FixItHint::CreateReplacement(ReturnTypeRange, DestReturnTypeStr);
+}
+
+StringRef UseStringViewCheck::toStringViewTypeStr(StringRef Type) const {
+ if (Type.contains("wchar_t"))
+ return WStringViewClass;
+ if (Type.contains("char8_t"))
+ return U8StringViewClass;
+ if (Type.contains("char16_t"))
+ return U16StringViewClass;
+ if (Type.contains("char32_t"))
+ return U32StringViewClass;
+ return StringViewClass;
+}
+
+void UseStringViewCheck::parseReplacementStringViewClass(StringRef Options) {
----------------
irishrover wrote:
> Can we use llvm::StringSwitch here?
It seems no:
```
?? = llvm::StringSwitch<StringRef>(Split.first)
.Case(StringViewClassKey, ?)
.Case(WStringViewClassKey, ?)
.Case(U8StringViewClassKey, ?)
.Case(U16StringViewClassKey, ?)
.Case(U32StringViewClassKey, ?)
.Default(StringRef());
```
https://github.com/llvm/llvm-project/pull/172170
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits