[clang-tools-extra] 83f875d - [clang-tidy] Add modernize-printf-to-std-print check

Piotr Zegar via cfe-commits cfe-commits at lists.llvm.org
Mon Jun 26 12:05:21 PDT 2023


Author: Mike Crowe
Date: 2023-06-26T19:05:02Z
New Revision: 83f875dc94d7a5cc9b2da32e2a7b1502e3b0640c

URL: https://github.com/llvm/llvm-project/commit/83f875dc94d7a5cc9b2da32e2a7b1502e3b0640c
DIFF: https://github.com/llvm/llvm-project/commit/83f875dc94d7a5cc9b2da32e2a7b1502e3b0640c.diff

LOG: [clang-tidy] Add modernize-printf-to-std-print check

Add FormatStringConverter utility class that is capable of converting
printf-style format strings into std::print-style format strings along
with recording a set of casts to wrap the arguments as required and
removing now-unnecessary calls to std::string::c_str() and
std::string::data()

Use FormatStringConverter to implement a new clang-tidy check that is
capable of converting calls to printf, fprintf, absl::PrintF,
absl::FPrintF, or any functions configured by an option to calls to
std::print and std::println, or other functions configured by options.

In other words, the check turns:

 fprintf(stderr, "The %s is %3d\n", description.c_str(), value);

into:

 std::println(stderr, "The {} is {:3}", description, value);

if it can.

std::print and std::println can do almost anything that standard printf
can, but the conversion has some some limitations that are described in
the documentation. If conversion is not possible then the call remains
unchanged.

Depends on D153716

Reviewed By: PiotrZSL

Differential Revision: https://reviews.llvm.org/D149280

Added: 
    clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
    clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h
    clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp
    clang-tools-extra/clang-tidy/utils/FormatStringConverter.h
    clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst
    clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstddef
    clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdint
    clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio
    clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/inttypes.h
    clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stddef.h
    clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp
    clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp
    clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-fmt.cpp
    clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp

Modified: 
    clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
    clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
    clang-tools-extra/clang-tidy/utils/CMakeLists.txt
    clang-tools-extra/docs/ReleaseNotes.rst
    clang-tools-extra/docs/clang-tidy/checks/list.rst
    clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h
    clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index 91e2d8d5ee4e5..ef10cb0e38826 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -37,6 +37,7 @@ add_clang_library(clangTidyModernizeModule
   UseNoexceptCheck.cpp
   UseNullptrCheck.cpp
   UseOverrideCheck.cpp
+  UseStdPrintCheck.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 ee0fab18d2068..9bbc4dc50ec0e 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -38,6 +38,7 @@
 #include "UseNoexceptCheck.h"
 #include "UseNullptrCheck.h"
 #include "UseOverrideCheck.h"
+#include "UseStdPrintCheck.h"
 #include "UseTrailingReturnTypeCheck.h"
 #include "UseTransparentFunctorsCheck.h"
 #include "UseUncaughtExceptionsCheck.h"
@@ -64,6 +65,7 @@ class ModernizeModule : public ClangTidyModule {
     CheckFactories.registerCheck<MakeSharedCheck>("modernize-make-shared");
     CheckFactories.registerCheck<MakeUniqueCheck>("modernize-make-unique");
     CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value");
+    CheckFactories.registerCheck<UseStdPrintCheck>("modernize-use-std-print");
     CheckFactories.registerCheck<RawStringLiteralCheck>(
         "modernize-raw-string-literal");
     CheckFactories.registerCheck<RedundantVoidArgCheck>(

diff  --git a/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
new file mode 100644
index 0000000000000..9b368f9a3e6c9
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
@@ -0,0 +1,136 @@
+//===--- UseStdPrintCheck.cpp - clang-tidy-----------------------*- C++ -*-===//
+//
+// 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 "UseStdPrintCheck.h"
+#include "../utils/FormatStringConverter.h"
+#include "../utils/Matchers.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/FixIt.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+namespace {
+AST_MATCHER(StringLiteral, isOrdinary) { return Node.isOrdinary(); }
+} // namespace
+
+UseStdPrintCheck::UseStdPrintCheck(StringRef Name, ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context),
+      StrictMode(Options.getLocalOrGlobal("StrictMode", false)),
+      PrintfLikeFunctions(utils::options::parseStringList(
+          Options.get("PrintfLikeFunctions", ""))),
+      FprintfLikeFunctions(utils::options::parseStringList(
+          Options.get("FprintfLikeFunctions", ""))),
+      ReplacementPrintFunction(
+          Options.get("ReplacementPrintFunction", "std::print")),
+      ReplacementPrintlnFunction(
+          Options.get("ReplacementPrintlnFunction", "std::println")),
+      IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
+                                               utils::IncludeSorter::IS_LLVM),
+                      areDiagsSelfContained()),
+      MaybeHeaderToInclude(Options.get("PrintHeader")) {
+
+  if (PrintfLikeFunctions.empty() && FprintfLikeFunctions.empty()) {
+    PrintfLikeFunctions.push_back("::printf");
+    PrintfLikeFunctions.push_back("absl::PrintF");
+    FprintfLikeFunctions.push_back("::fprintf");
+    FprintfLikeFunctions.push_back("absl::FPrintF");
+  }
+
+  if (!MaybeHeaderToInclude && (ReplacementPrintFunction == "std::print" ||
+                                ReplacementPrintlnFunction == "std::println"))
+    MaybeHeaderToInclude = "<print>";
+}
+
+void UseStdPrintCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  using utils::options::serializeStringList;
+  Options.store(Opts, "StrictMode", StrictMode);
+  Options.store(Opts, "PrintfLikeFunctions",
+                serializeStringList(PrintfLikeFunctions));
+  Options.store(Opts, "FprintfLikeFunctions",
+                serializeStringList(FprintfLikeFunctions));
+  Options.store(Opts, "ReplacementPrintFunction", ReplacementPrintFunction);
+  Options.store(Opts, "ReplacementPrintlnFunction", ReplacementPrintlnFunction);
+  Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
+  if (MaybeHeaderToInclude)
+    Options.store(Opts, "PrintHeader", *MaybeHeaderToInclude);
+}
+
+void UseStdPrintCheck::registerPPCallbacks(const SourceManager &SM,
+                                           Preprocessor *PP,
+                                           Preprocessor *ModuleExpanderPP) {
+  IncludeInserter.registerPreprocessor(PP);
+}
+
+void UseStdPrintCheck::registerMatchers(MatchFinder *Finder) {
+  if (!PrintfLikeFunctions.empty())
+    Finder->addMatcher(
+        callExpr(argumentCountAtLeast(1),
+                 hasArgument(0, stringLiteral(isOrdinary())),
+                 callee(functionDecl(
+                            unless(cxxMethodDecl()),
+                            matchers::matchesAnyListedName(PrintfLikeFunctions))
+                            .bind("func_decl")))
+            .bind("printf"),
+        this);
+
+  if (!FprintfLikeFunctions.empty())
+    Finder->addMatcher(
+        callExpr(argumentCountAtLeast(2),
+                 hasArgument(1, stringLiteral(isOrdinary())),
+                 callee(functionDecl(unless(cxxMethodDecl()),
+                                     matchers::matchesAnyListedName(
+                                         FprintfLikeFunctions))
+                            .bind("func_decl")))
+            .bind("fprintf"),
+        this);
+}
+
+void UseStdPrintCheck::check(const MatchFinder::MatchResult &Result) {
+  unsigned FormatArgOffset = 0;
+  const auto *OldFunction = Result.Nodes.getNodeAs<FunctionDecl>("func_decl");
+  const auto *Printf = Result.Nodes.getNodeAs<CallExpr>("printf");
+  if (!Printf) {
+    Printf = Result.Nodes.getNodeAs<CallExpr>("fprintf");
+    FormatArgOffset = 1;
+  }
+
+  utils::FormatStringConverter Converter(
+      Result.Context, Printf, FormatArgOffset, StrictMode, getLangOpts());
+  const Expr *PrintfCall = Printf->getCallee();
+  const StringRef ReplacementFunction = Converter.usePrintNewlineFunction()
+                                            ? ReplacementPrintlnFunction
+                                            : ReplacementPrintFunction;
+  if (!Converter.canApply()) {
+    diag(PrintfCall->getBeginLoc(),
+         "unable to use '%0' instead of %1 because %2")
+        << ReplacementFunction << OldFunction->getIdentifier()
+        << Converter.conversionNotPossibleReason();
+    return;
+  }
+
+  DiagnosticBuilder Diag =
+      diag(PrintfCall->getBeginLoc(), "use '%0' instead of %1")
+      << ReplacementFunction << OldFunction->getIdentifier();
+
+  Diag << FixItHint::CreateReplacement(
+      CharSourceRange::getTokenRange(PrintfCall->getBeginLoc(),
+                                     PrintfCall->getEndLoc()),
+      ReplacementFunction);
+  Converter.applyFixes(Diag, *Result.SourceManager);
+
+  if (MaybeHeaderToInclude)
+    Diag << IncludeInserter.createIncludeInsertion(
+        Result.Context->getSourceManager().getFileID(PrintfCall->getBeginLoc()),
+        *MaybeHeaderToInclude);
+}
+
+} // namespace clang::tidy::modernize

diff  --git a/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h
new file mode 100644
index 0000000000000..7a06cf38b4264
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h
@@ -0,0 +1,50 @@
+//===--- UseStdPrintCheck.h - clang-tidy-------------------------*- C++ -*-===//
+//
+// 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_USESTDPRINTCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USESTDPRINTCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "../utils/IncludeInserter.h"
+
+namespace clang::tidy::modernize {
+/// Convert calls to printf-like functions to std::print and std::println
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html
+class UseStdPrintCheck : public ClangTidyCheck {
+public:
+  UseStdPrintCheck(StringRef Name, ClangTidyContext *Context);
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    if (ReplacementPrintFunction == "std::print" ||
+        ReplacementPrintlnFunction == "std::println")
+      return LangOpts.CPlusPlus23;
+    return LangOpts.CPlusPlus;
+  }
+  void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+                           Preprocessor *ModuleExpanderPP) override;
+  void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  std::optional<TraversalKind> getCheckTraversalKind() const override {
+    return TK_IgnoreUnlessSpelledInSource;
+  }
+
+private:
+  bool StrictMode;
+  std::vector<StringRef> PrintfLikeFunctions;
+  std::vector<StringRef> FprintfLikeFunctions;
+  StringRef ReplacementPrintFunction;
+  StringRef ReplacementPrintlnFunction;
+  utils::IncludeInserter IncludeInserter;
+  std::optional<StringRef> MaybeHeaderToInclude;
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USESTDPRINTCHECK_H

diff  --git a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt
index 50d1cf812b690..6c7d52a4b1873 100644
--- a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt
@@ -11,6 +11,7 @@ add_clang_library(clangTidyUtils
   ExceptionSpecAnalyzer.cpp
   ExprSequence.cpp
   FileExtensionsUtils.cpp
+  FormatStringConverter.cpp
   FixItHintUtils.cpp
   HeaderGuard.cpp
   IncludeInserter.cpp

diff  --git a/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp
new file mode 100644
index 0000000000000..f12fa84ec6b14
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp
@@ -0,0 +1,686 @@
+//===--- FormatStringConverter.cpp - clang-tidy----------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of the FormatStringConverter class which is used to convert
+/// printf format strings to C++ std::formatter format strings.
+///
+//===----------------------------------------------------------------------===//
+
+#include "FormatStringConverter.h"
+#include "../utils/FixItHintUtils.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/FixIt.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/Debug.h"
+
+using namespace clang::ast_matchers;
+using namespace clang::analyze_printf;
+
+namespace clang::tidy::utils {
+using clang::analyze_format_string::ConversionSpecifier;
+
+/// Is the passed type the actual "char" type, whether that be signed or
+/// unsigned, rather than explicit signed char or unsigned char types.
+static bool isRealCharType(const clang::QualType &Ty) {
+  using namespace clang;
+  const Type *DesugaredType = Ty->getUnqualifiedDesugaredType();
+  if (const auto *BT = llvm::dyn_cast<BuiltinType>(DesugaredType))
+    return (BT->getKind() == BuiltinType::Char_U ||
+            BT->getKind() == BuiltinType::Char_S);
+  return false;
+}
+
+/// If possible, return the text name of the signed type that corresponds to the
+/// passed integer type. If the passed type is already signed then its name is
+/// just returned. Only supports BuiltinTypes.
+static std::optional<std::string>
+getCorrespondingSignedTypeName(const clang::QualType &QT) {
+  using namespace clang;
+  const auto UQT = QT.getUnqualifiedType();
+  if (const auto *BT = llvm::dyn_cast<BuiltinType>(UQT)) {
+    switch (BT->getKind()) {
+    case BuiltinType::UChar:
+    case BuiltinType::Char_U:
+    case BuiltinType::SChar:
+    case BuiltinType::Char_S:
+      return "signed char";
+    case BuiltinType::UShort:
+    case BuiltinType::Short:
+      return "short";
+    case BuiltinType::UInt:
+    case BuiltinType::Int:
+      return "int";
+    case BuiltinType::ULong:
+    case BuiltinType::Long:
+      return "long";
+    case BuiltinType::ULongLong:
+    case BuiltinType::LongLong:
+      return "long long";
+    default:
+      llvm::dbgs() << "Unknown corresponding signed type for BuiltinType '"
+                   << QT.getAsString() << "'\n";
+      return std::nullopt;
+    }
+  }
+
+  // Deal with fixed-width integer types from <cstdint>. Use std:: prefix only
+  // if the argument type does.
+  const std::string TypeName = UQT.getAsString();
+  StringRef SimplifiedTypeName{TypeName};
+  const bool InStd = SimplifiedTypeName.consume_front("std::");
+  const StringRef Prefix = InStd ? "std::" : "";
+
+  if (SimplifiedTypeName.starts_with("uint") &&
+      SimplifiedTypeName.ends_with("_t"))
+    return (Twine(Prefix) + SimplifiedTypeName.drop_front()).str();
+
+  if (SimplifiedTypeName == "size_t")
+    return (Twine(Prefix) + "ssize_t").str();
+
+  llvm::dbgs() << "Unknown corresponding signed type for non-BuiltinType '"
+               << UQT.getAsString() << "'\n";
+  return std::nullopt;
+}
+
+/// If possible, return the text name of the unsigned type that corresponds to
+/// the passed integer type. If the passed type is already unsigned then its
+/// name is just returned. Only supports BuiltinTypes.
+static std::optional<std::string>
+getCorrespondingUnsignedTypeName(const clang::QualType &QT) {
+  using namespace clang;
+  const auto UQT = QT.getUnqualifiedType();
+  if (const auto *BT = llvm::dyn_cast<BuiltinType>(UQT)) {
+    switch (BT->getKind()) {
+    case BuiltinType::SChar:
+    case BuiltinType::Char_S:
+    case BuiltinType::UChar:
+    case BuiltinType::Char_U:
+      return "unsigned char";
+    case BuiltinType::Short:
+    case BuiltinType::UShort:
+      return "unsigned short";
+    case BuiltinType::Int:
+    case BuiltinType::UInt:
+      return "unsigned int";
+    case BuiltinType::Long:
+    case BuiltinType::ULong:
+      return "unsigned long";
+    case BuiltinType::LongLong:
+    case BuiltinType::ULongLong:
+      return "unsigned long long";
+    default:
+      llvm::dbgs() << "Unknown corresponding unsigned type for BuiltinType '"
+                   << UQT.getAsString() << "'\n";
+      return std::nullopt;
+    }
+  }
+
+  // Deal with fixed-width integer types from <cstdint>. Use std:: prefix only
+  // if the argument type does.
+  const std::string TypeName = UQT.getAsString();
+  StringRef SimplifiedTypeName{TypeName};
+  const bool InStd = SimplifiedTypeName.consume_front("std::");
+  const StringRef Prefix = InStd ? "std::" : "";
+
+  if (SimplifiedTypeName.starts_with("int") &&
+      SimplifiedTypeName.ends_with("_t"))
+    return (Twine(Prefix) + "u" + SimplifiedTypeName).str();
+
+  if (SimplifiedTypeName == "ssize_t")
+    return (Twine(Prefix) + "size_t").str();
+  if (SimplifiedTypeName == "ptr
diff _t")
+    return (Twine(Prefix) + "size_t").str();
+
+  llvm::dbgs() << "Unknown corresponding unsigned type for non-BuiltinType '"
+               << UQT.getAsString() << "'\n";
+  return std::nullopt;
+}
+
+static std::optional<std::string>
+castTypeForArgument(ConversionSpecifier::Kind ArgKind,
+                    const clang::QualType &QT) {
+  if (ArgKind == ConversionSpecifier::Kind::uArg)
+    return getCorrespondingUnsignedTypeName(QT);
+  return getCorrespondingSignedTypeName(QT);
+}
+
+static bool isMatchingSignedness(ConversionSpecifier::Kind ArgKind,
+                                 const clang::QualType &ArgType) {
+  if (const auto *BT = llvm::dyn_cast<BuiltinType>(ArgType)) {
+    // Unadorned char never matches any expected signedness since it
+    // could be signed or unsigned.
+    const auto ArgTypeKind = BT->getKind();
+    if (ArgTypeKind == BuiltinType::Char_U ||
+        ArgTypeKind == BuiltinType::Char_S)
+      return false;
+  }
+
+  if (ArgKind == ConversionSpecifier::Kind::uArg)
+    return ArgType->isUnsignedIntegerType();
+  return ArgType->isSignedIntegerType();
+}
+
+namespace {
+AST_MATCHER(clang::QualType, isRealChar) {
+  return clang::tidy::utils::isRealCharType(Node);
+}
+} // namespace
+
+static bool castMismatchedIntegerTypes(const CallExpr *Call, bool StrictMode) {
+  /// For printf-style functions, the signedness of the type printed is
+  /// indicated by the corresponding type in the format string.
+  /// std::print will determine the signedness from the type of the
+  /// argument. This means that it is necessary to generate a cast in
+  /// StrictMode to ensure that the exact behaviour is maintained.
+  /// However, for templated functions like absl::PrintF and
+  /// fmt::printf, the signedness of the type printed is also taken from
+  /// the actual argument like std::print, so such casts are never
+  /// necessary. printf-style functions are variadic, whereas templated
+  /// ones aren't, so we can use that to distinguish between the two
+  /// cases.
+  if (StrictMode) {
+    const FunctionDecl *FuncDecl = Call->getDirectCallee();
+    assert(FuncDecl);
+    return FuncDecl->isVariadic();
+  }
+  return false;
+}
+
+FormatStringConverter::FormatStringConverter(ASTContext *ContextIn,
+                                             const CallExpr *Call,
+                                             unsigned FormatArgOffset,
+                                             bool StrictMode,
+                                             const LangOptions &LO)
+    : Context(ContextIn),
+      CastMismatchedIntegerTypes(castMismatchedIntegerTypes(Call, StrictMode)),
+      Args(Call->getArgs()), NumArgs(Call->getNumArgs()),
+      ArgsOffset(FormatArgOffset + 1), LangOpts(LO) {
+  assert(ArgsOffset <= NumArgs);
+  FormatExpr = llvm::dyn_cast<StringLiteral>(
+      Args[FormatArgOffset]->IgnoreImplicitAsWritten());
+  assert(FormatExpr);
+  if (!FormatExpr->isOrdinary())
+    return; // No wide string support yet
+  PrintfFormatString = FormatExpr->getString();
+
+  // Assume that the output will be approximately the same size as the input,
+  // but perhaps with a few escapes expanded.
+  const size_t EstimatedGrowth = 8;
+  StandardFormatString.reserve(PrintfFormatString.size() + EstimatedGrowth);
+  StandardFormatString.push_back('\"');
+
+  const bool IsFreeBsdkPrintf = false;
+
+  using clang::analyze_format_string::ParsePrintfString;
+  ParsePrintfString(*this, PrintfFormatString.data(),
+                    PrintfFormatString.data() + PrintfFormatString.size(),
+                    LangOpts, Context->getTargetInfo(), IsFreeBsdkPrintf);
+  finalizeFormatText();
+}
+
+void FormatStringConverter::emitAlignment(const PrintfSpecifier &FS,
+                                          std::string &FormatSpec) {
+  ConversionSpecifier::Kind ArgKind = FS.getConversionSpecifier().getKind();
+
+  // We only care about alignment if a field width is specified
+  if (FS.getFieldWidth().getHowSpecified() != OptionalAmount::NotSpecified) {
+    if (ArgKind == ConversionSpecifier::sArg) {
+      // Strings are left-aligned by default with std::format, so we only
+      // need to emit an alignment if this one needs to be right aligned.
+      if (!FS.isLeftJustified())
+        FormatSpec.push_back('>');
+    } else {
+      // Numbers are right-aligned by default with std::format, so we only
+      // need to emit an alignment if this one needs to be left aligned.
+      if (FS.isLeftJustified())
+        FormatSpec.push_back('<');
+    }
+  }
+}
+
+void FormatStringConverter::emitSign(const PrintfSpecifier &FS,
+                                     std::string &FormatSpec) {
+  const ConversionSpecifier Spec = FS.getConversionSpecifier();
+
+  // Ignore on something that isn't numeric. For printf it's would be a
+  // compile-time warning but ignored at runtime, but for std::format it
+  // ought to be a compile-time error.
+  if (Spec.isAnyIntArg() || Spec.isDoubleArg()) {
+    // + is preferred to ' '
+    if (FS.hasPlusPrefix())
+      FormatSpec.push_back('+');
+    else if (FS.hasSpacePrefix())
+      FormatSpec.push_back(' ');
+  }
+}
+
+void FormatStringConverter::emitAlternativeForm(const PrintfSpecifier &FS,
+                                                std::string &FormatSpec) {
+  if (FS.hasAlternativeForm()) {
+    switch (FS.getConversionSpecifier().getKind()) {
+    case ConversionSpecifier::Kind::aArg:
+    case ConversionSpecifier::Kind::AArg:
+    case ConversionSpecifier::Kind::eArg:
+    case ConversionSpecifier::Kind::EArg:
+    case ConversionSpecifier::Kind::fArg:
+    case ConversionSpecifier::Kind::FArg:
+    case ConversionSpecifier::Kind::gArg:
+    case ConversionSpecifier::Kind::GArg:
+    case ConversionSpecifier::Kind::xArg:
+    case ConversionSpecifier::Kind::XArg:
+    case ConversionSpecifier::Kind::oArg:
+      FormatSpec.push_back('#');
+      break;
+    default:
+      // Alternative forms don't exist for other argument kinds
+      break;
+    }
+  }
+}
+
+void FormatStringConverter::emitFieldWidth(const PrintfSpecifier &FS,
+                                           std::string &FormatSpec) {
+  {
+    const OptionalAmount FieldWidth = FS.getFieldWidth();
+    switch (FieldWidth.getHowSpecified()) {
+    case OptionalAmount::NotSpecified:
+      break;
+    case OptionalAmount::Constant:
+      FormatSpec.append(llvm::utostr(FieldWidth.getConstantAmount()));
+      break;
+    case OptionalAmount::Arg:
+      FormatSpec.push_back('{');
+      if (FieldWidth.usesPositionalArg()) {
+        // std::format argument identifiers are zero-based, whereas printf
+        // ones are one based.
+        assert(FieldWidth.getPositionalArgIndex() > 0U);
+        FormatSpec.append(llvm::utostr(FieldWidth.getPositionalArgIndex() - 1));
+      }
+      FormatSpec.push_back('}');
+      break;
+    case OptionalAmount::Invalid:
+      break;
+    }
+  }
+}
+
+void FormatStringConverter::emitPrecision(const PrintfSpecifier &FS,
+                                          std::string &FormatSpec) {
+  const OptionalAmount FieldPrecision = FS.getPrecision();
+  switch (FieldPrecision.getHowSpecified()) {
+  case OptionalAmount::NotSpecified:
+    break;
+  case OptionalAmount::Constant:
+    FormatSpec.push_back('.');
+    FormatSpec.append(llvm::utostr(FieldPrecision.getConstantAmount()));
+    break;
+  case OptionalAmount::Arg:
+    FormatSpec.push_back('.');
+    FormatSpec.push_back('{');
+    if (FieldPrecision.usesPositionalArg()) {
+      // std::format argument identifiers are zero-based, whereas printf
+      // ones are one based.
+      assert(FieldPrecision.getPositionalArgIndex() > 0U);
+      FormatSpec.append(
+          llvm::utostr(FieldPrecision.getPositionalArgIndex() - 1));
+    }
+    FormatSpec.push_back('}');
+    break;
+  case OptionalAmount::Invalid:
+    break;
+  }
+}
+
+void FormatStringConverter::emitStringArgument(const Expr *Arg) {
+  // If the argument is the result of a call to std::string::c_str() or
+  // data() with a return type of char then we can remove that call and
+  // pass the std::string directly. We don't want to do so if the return
+  // type is not a char pointer (though it's unlikely that such code would
+  // compile without warnings anyway.) See RedundantStringCStrCheck.
+
+  if (!StringCStrCallExprMatcher) {
+    // Lazily create the matcher
+    const auto StringDecl = type(hasUnqualifiedDesugaredType(recordType(
+        hasDeclaration(cxxRecordDecl(hasName("::std::basic_string"))))));
+    const auto StringExpr = expr(
+        anyOf(hasType(StringDecl), hasType(qualType(pointsTo(StringDecl)))));
+
+    StringCStrCallExprMatcher =
+        cxxMemberCallExpr(
+            on(StringExpr.bind("arg")), callee(memberExpr().bind("member")),
+            callee(cxxMethodDecl(hasAnyName("c_str", "data"),
+                                 returns(pointerType(pointee(isRealChar()))))))
+            .bind("call");
+  }
+
+  auto CStrMatches = match(*StringCStrCallExprMatcher, *Arg, *Context);
+  if (CStrMatches.size() == 1)
+    ArgCStrRemovals.push_back(CStrMatches.front());
+  else if (Arg->getType()->isPointerType()) {
+    const QualType Pointee = Arg->getType()->getPointeeType();
+    // printf is happy to print signed char and unsigned char strings, but
+    // std::format only likes char strings.
+    if (Pointee->isCharType() && !isRealCharType(Pointee))
+      ArgFixes.emplace_back(Arg, "reinterpret_cast<const char *>(");
+  }
+}
+
+bool FormatStringConverter::emitIntegerArgument(
+    ConversionSpecifier::Kind ArgKind, const Expr *Arg, unsigned ArgIndex,
+    std::string &FormatSpec) {
+  const clang::QualType &ArgType = Arg->getType();
+  if (ArgType->isBooleanType()) {
+    // std::format will print bool as either "true" or "false" by default,
+    // but printf prints them as "0" or "1". Be compatible with printf by
+    // requesting decimal output.
+    FormatSpec.push_back('d');
+  } else if (ArgType->isEnumeralType()) {
+    // std::format will try to find a specialization to print the enum
+    // (and probably fail), whereas printf would have just expected it to
+    // be passed as its underlying type. However, printf will have forced
+    // the signedness based on the format string, so we need to do the
+    // same.
+    if (const auto *ET = ArgType->getAs<EnumType>()) {
+      if (const std::optional<std::string> MaybeCastType =
+              castTypeForArgument(ArgKind, ET->getDecl()->getIntegerType()))
+        ArgFixes.emplace_back(
+            Arg, (Twine("static_cast<") + *MaybeCastType + ">(").str());
+      else
+        return conversionNotPossible(
+            (Twine("argument ") + Twine(ArgIndex) + " has unexpected enum type")
+                .str());
+    }
+  } else if (CastMismatchedIntegerTypes &&
+             !isMatchingSignedness(ArgKind, ArgType)) {
+    // printf will happily print an unsigned type as signed if told to.
+    // Even -Wformat doesn't warn for this. std::format will format as
+    // unsigned unless we cast it.
+    if (const std::optional<std::string> MaybeCastType =
+            castTypeForArgument(ArgKind, ArgType))
+      ArgFixes.emplace_back(
+          Arg, (Twine("static_cast<") + *MaybeCastType + ">(").str());
+    else
+      return conversionNotPossible(
+          (Twine("argument ") + Twine(ArgIndex) + " cannot be cast to " +
+           Twine(ArgKind == ConversionSpecifier::Kind::uArg ? "unsigned"
+                                                            : "signed") +
+           " integer type to match format"
+           " specifier and StrictMode is enabled")
+              .str());
+  } else if (isRealCharType(ArgType) || !ArgType->isIntegerType()) {
+    // Only specify integer if the argument is of a 
diff erent type
+    FormatSpec.push_back('d');
+  }
+  return true;
+}
+
+/// Append the corresponding standard format string type fragment to FormatSpec,
+/// and store any argument fixes for later application.
+/// @returns true on success, false on failure
+bool FormatStringConverter::emitType(const PrintfSpecifier &FS, const Expr *Arg,
+                                     std::string &FormatSpec) {
+  ConversionSpecifier::Kind ArgKind = FS.getConversionSpecifier().getKind();
+  switch (ArgKind) {
+  case ConversionSpecifier::Kind::sArg:
+    emitStringArgument(Arg);
+    break;
+  case ConversionSpecifier::Kind::cArg:
+    // The type must be "c" to get a character unless the type is exactly
+    // char (whether that be signed or unsigned for the target.)
+    if (!isRealCharType(Arg->getType()))
+      FormatSpec.push_back('c');
+    break;
+  case ConversionSpecifier::Kind::dArg:
+  case ConversionSpecifier::Kind::iArg:
+  case ConversionSpecifier::Kind::uArg:
+    if (!emitIntegerArgument(ArgKind, Arg, FS.getArgIndex() + ArgsOffset,
+                             FormatSpec))
+      return false;
+    break;
+  case ConversionSpecifier::Kind::pArg: {
+    const clang::QualType &ArgType = Arg->getType();
+    // std::format knows how to format void pointers and nullptrs
+    if (!ArgType->isNullPtrType() && !ArgType->isVoidPointerType())
+      ArgFixes.emplace_back(Arg, "static_cast<const void *>(");
+    break;
+  }
+  case ConversionSpecifier::Kind::xArg:
+    FormatSpec.push_back('x');
+    break;
+  case ConversionSpecifier::Kind::XArg:
+    FormatSpec.push_back('X');
+    break;
+  case ConversionSpecifier::Kind::oArg:
+    FormatSpec.push_back('o');
+    break;
+  case ConversionSpecifier::Kind::aArg:
+    FormatSpec.push_back('a');
+    break;
+  case ConversionSpecifier::Kind::AArg:
+    FormatSpec.push_back('A');
+    break;
+  case ConversionSpecifier::Kind::eArg:
+    FormatSpec.push_back('e');
+    break;
+  case ConversionSpecifier::Kind::EArg:
+    FormatSpec.push_back('E');
+    break;
+  case ConversionSpecifier::Kind::fArg:
+    FormatSpec.push_back('f');
+    break;
+  case ConversionSpecifier::Kind::FArg:
+    FormatSpec.push_back('F');
+    break;
+  case ConversionSpecifier::Kind::gArg:
+    FormatSpec.push_back('g');
+    break;
+  case ConversionSpecifier::Kind::GArg:
+    FormatSpec.push_back('G');
+    break;
+  default:
+    // Something we don't understand
+    return conversionNotPossible((Twine("argument ") +
+                                  Twine(FS.getArgIndex() + ArgsOffset) +
+                                  " has an unsupported format specifier")
+                                     .str());
+  }
+
+  return true;
+}
+
+/// Append the standard format string equivalent of the passed PrintfSpecifier
+/// to StandardFormatString and store any argument fixes for later application.
+/// @returns true on success, false on failure
+bool FormatStringConverter::convertArgument(const PrintfSpecifier &FS,
+                                            const Expr *Arg,
+                                            std::string &StandardFormatString) {
+  // The specifier must have an associated argument
+  assert(FS.consumesDataArgument());
+
+  StandardFormatString.push_back('{');
+
+  if (FS.usesPositionalArg()) {
+    // std::format argument identifiers are zero-based, whereas printf ones
+    // are one based.
+    assert(FS.getPositionalArgIndex() > 0U);
+    StandardFormatString.append(llvm::utostr(FS.getPositionalArgIndex() - 1));
+  }
+
+  // std::format format argument parts to potentially emit:
+  // [[fill]align][sign]["#"]["0"][width]["."precision][type]
+  std::string FormatSpec;
+
+  // printf doesn't support specifying the fill character - it's always a
+  // space, so we never need to generate one.
+
+  emitAlignment(FS, FormatSpec);
+  emitSign(FS, FormatSpec);
+  emitAlternativeForm(FS, FormatSpec);
+
+  if (FS.hasLeadingZeros())
+    FormatSpec.push_back('0');
+
+  emitFieldWidth(FS, FormatSpec);
+  emitPrecision(FS, FormatSpec);
+
+  if (!emitType(FS, Arg, FormatSpec))
+    return false;
+
+  if (!FormatSpec.empty()) {
+    StandardFormatString.push_back(':');
+    StandardFormatString.append(FormatSpec);
+  }
+
+  StandardFormatString.push_back('}');
+  return true;
+}
+
+/// Called for each format specifier by ParsePrintfString.
+bool FormatStringConverter::HandlePrintfSpecifier(const PrintfSpecifier &FS,
+                                                  const char *StartSpecifier,
+                                                  unsigned SpecifierLen,
+                                                  const TargetInfo &Target) {
+
+  const size_t StartSpecifierPos = StartSpecifier - PrintfFormatString.data();
+  assert(StartSpecifierPos + SpecifierLen <= PrintfFormatString.size());
+
+  // Everything before the specifier needs copying verbatim
+  assert(StartSpecifierPos >= PrintfFormatStringPos);
+
+  appendFormatText(StringRef(PrintfFormatString.begin() + PrintfFormatStringPos,
+                             StartSpecifierPos - PrintfFormatStringPos));
+
+  const ConversionSpecifier::Kind ArgKind =
+      FS.getConversionSpecifier().getKind();
+
+  // Skip over specifier
+  PrintfFormatStringPos = StartSpecifierPos + SpecifierLen;
+  assert(PrintfFormatStringPos <= PrintfFormatString.size());
+
+  FormatStringNeededRewriting = true;
+
+  if (ArgKind == ConversionSpecifier::Kind::nArg) {
+    // std::print doesn't do the equivalent of %n
+    return conversionNotPossible("'%n' is not supported in format string");
+  }
+
+  if (ArgKind == ConversionSpecifier::Kind::PrintErrno) {
+    // std::print doesn't support %m. In theory we could insert a
+    // strerror(errno) parameter (assuming that libc has a thread-safe
+    // implementation, which glibc does), but that would require keeping track
+    // of the input and output parameter indices for position arguments too.
+    return conversionNotPossible("'%m' is not supported in format string");
+  }
+
+  if (ArgKind == ConversionSpecifier::PercentArg) {
+    StandardFormatString.push_back('%');
+    return true;
+  }
+
+  const unsigned ArgIndex = FS.getArgIndex() + ArgsOffset;
+  if (ArgIndex >= NumArgs) {
+    // Argument index out of range. Give up.
+    return conversionNotPossible(
+        (Twine("argument index ") + Twine(ArgIndex) + " is out of range")
+            .str());
+  }
+
+  return convertArgument(FS, Args[ArgIndex]->IgnoreImplicitAsWritten(),
+                         StandardFormatString);
+}
+
+/// Called at the very end just before applying fixes to capture the last part
+/// of the format string.
+void FormatStringConverter::finalizeFormatText() {
+  appendFormatText(
+      StringRef(PrintfFormatString.begin() + PrintfFormatStringPos,
+                PrintfFormatString.size() - PrintfFormatStringPos));
+  PrintfFormatStringPos = PrintfFormatString.size();
+
+  if (StringRef(StandardFormatString).ends_with("\\n") &&
+      !StringRef(StandardFormatString).ends_with("\\\\n")) {
+    UsePrintNewlineFunction = true;
+    FormatStringNeededRewriting = true;
+    StandardFormatString.erase(StandardFormatString.end() - 2,
+                               StandardFormatString.end());
+  }
+
+  StandardFormatString.push_back('\"');
+}
+
+/// Append literal parts of the format text, reinstating escapes as required.
+void FormatStringConverter::appendFormatText(const StringRef Text) {
+  for (const char Ch : Text) {
+    if (Ch == '\a')
+      StandardFormatString += "\\a";
+    else if (Ch == '\b')
+      StandardFormatString += "\\b";
+    else if (Ch == '\f')
+      StandardFormatString += "\\f";
+    else if (Ch == '\n')
+      StandardFormatString += "\\n";
+    else if (Ch == '\r')
+      StandardFormatString += "\\r";
+    else if (Ch == '\t')
+      StandardFormatString += "\\t";
+    else if (Ch == '\v')
+      StandardFormatString += "\\v";
+    else if (Ch == '\"')
+      StandardFormatString += "\\\"";
+    else if (Ch == '\\')
+      StandardFormatString += "\\\\";
+    else if (Ch == '{') {
+      StandardFormatString += "{{";
+      FormatStringNeededRewriting = true;
+    } else if (Ch == '}') {
+      StandardFormatString += "}}";
+      FormatStringNeededRewriting = true;
+    } else if (Ch < 32) {
+      StandardFormatString += "\\x";
+      StandardFormatString += llvm::hexdigit(Ch >> 4, true);
+      StandardFormatString += llvm::hexdigit(Ch & 0xf, true);
+    } else
+      StandardFormatString += Ch;
+  }
+}
+
+/// Called by the check when it is ready to apply the fixes.
+void FormatStringConverter::applyFixes(DiagnosticBuilder &Diag,
+                                       SourceManager &SM) {
+  if (FormatStringNeededRewriting) {
+    Diag << FixItHint::CreateReplacement(
+        CharSourceRange::getTokenRange(FormatExpr->getBeginLoc(),
+                                       FormatExpr->getEndLoc()),
+        StandardFormatString);
+  }
+
+  for (const auto &[Arg, Replacement] : ArgFixes) {
+    SourceLocation AfterOtherSide =
+        Lexer::findNextToken(Arg->getEndLoc(), SM, LangOpts)->getLocation();
+
+    Diag << FixItHint::CreateInsertion(Arg->getBeginLoc(), Replacement)
+         << FixItHint::CreateInsertion(AfterOtherSide, ")");
+  }
+
+  for (const auto &Match : ArgCStrRemovals) {
+    const auto *Call = Match.getNodeAs<CallExpr>("call");
+    const auto *Arg = Match.getNodeAs<Expr>("arg");
+    const auto *Member = Match.getNodeAs<MemberExpr>("member");
+    const bool Arrow = Member->isArrow();
+    const std::string ArgText =
+        Arrow ? utils::fixit::formatDereference(*Arg, *Context)
+              : tooling::fixit::getText(*Arg, *Context).str();
+    if (!ArgText.empty())
+      Diag << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText);
+  }
+}
+} // namespace clang::tidy::utils

diff  --git a/clang-tools-extra/clang-tidy/utils/FormatStringConverter.h b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.h
new file mode 100644
index 0000000000000..b246013c24c45
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.h
@@ -0,0 +1,98 @@
+//===--- FormatStringConverter.h - clang-tidy--------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Declaration of the FormatStringConverter class which is used to convert
+/// printf format strings to C++ std::formatter format strings.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FORMATSTRINGCONVERTER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FORMATSTRINGCONVERTER_H
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/FormatString.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include <string>
+
+namespace clang::tidy::utils {
+
+/// Convert a printf-style format string to a std::formatter-style one, and
+/// prepare any casts that are required to wrap the arguments to retain printf
+/// compatibility. This class is expecting to work on the already-cooked format
+/// string (i.e. all the escapes have been converted) so we have to convert them
+/// back. This means that we might not convert them back using the same form.
+class FormatStringConverter
+    : public clang::analyze_format_string::FormatStringHandler {
+public:
+  using ConversionSpecifier = clang::analyze_format_string::ConversionSpecifier;
+  using PrintfSpecifier = analyze_printf::PrintfSpecifier;
+  FormatStringConverter(ASTContext *Context, const CallExpr *Call,
+                        unsigned FormatArgOffset, bool StrictMode,
+                        const LangOptions &LO);
+
+  bool canApply() const { return ConversionNotPossibleReason.empty(); }
+  const std::string &conversionNotPossibleReason() const {
+    return ConversionNotPossibleReason;
+  }
+  void applyFixes(DiagnosticBuilder &Diag, SourceManager &SM);
+  bool usePrintNewlineFunction() const { return UsePrintNewlineFunction; }
+
+private:
+  ASTContext *Context;
+  const bool CastMismatchedIntegerTypes;
+  const Expr *const *Args;
+  const unsigned NumArgs;
+  unsigned ArgsOffset;
+  const LangOptions &LangOpts;
+  std::string ConversionNotPossibleReason;
+  bool FormatStringNeededRewriting = false;
+  bool UsePrintNewlineFunction = false;
+  size_t PrintfFormatStringPos = 0U;
+  StringRef PrintfFormatString;
+
+  /// Lazily-created c_str() call matcher
+  std::optional<clang::ast_matchers::StatementMatcher>
+      StringCStrCallExprMatcher;
+
+  const StringLiteral *FormatExpr;
+  std::string StandardFormatString;
+
+  /// Casts to be used to wrap arguments to retain printf compatibility.
+  std::vector<std::tuple<const Expr *, std::string>> ArgFixes;
+  std::vector<clang::ast_matchers::BoundNodes> ArgCStrRemovals;
+
+  void emitAlignment(const PrintfSpecifier &FS, std::string &FormatSpec);
+  void emitSign(const PrintfSpecifier &FS, std::string &FormatSpec);
+  void emitAlternativeForm(const PrintfSpecifier &FS, std::string &FormatSpec);
+  void emitFieldWidth(const PrintfSpecifier &FS, std::string &FormatSpec);
+  void emitPrecision(const PrintfSpecifier &FS, std::string &FormatSpec);
+  void emitStringArgument(const Expr *Arg);
+  bool emitIntegerArgument(ConversionSpecifier::Kind ArgKind, const Expr *Arg,
+                           unsigned ArgIndex, std::string &FormatSpec);
+
+  bool emitType(const PrintfSpecifier &FS, const Expr *Arg,
+                std::string &FormatSpec);
+  bool convertArgument(const PrintfSpecifier &FS, const Expr *Arg,
+                       std::string &StandardFormatString);
+
+  bool HandlePrintfSpecifier(const PrintfSpecifier &FS,
+                             const char *StartSpecifier, unsigned SpecifierLen,
+                             const TargetInfo &Target) override;
+
+  void appendFormatText(StringRef Text);
+  void finalizeFormatText();
+  bool conversionNotPossible(std::string Reason) {
+    ConversionNotPossibleReason = std::move(Reason);
+    return false;
+  }
+};
+
+} // namespace clang::tidy::utils
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FORMATSTRINGCONVERTER_H

diff  --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 03426772f6037..726ec41f6a736 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -181,6 +181,16 @@ New checks
   Converts standard library type traits of the form ``traits<...>::type`` and
   ``traits<...>::value`` into ``traits_t<...>`` and ``traits_v<...>`` respectively.
 
+- New :doc:`modernize-use-std-print
+  <clang-tidy/checks/modernize/use-std-print>` check.
+
+  Converts calls to ``printf``, ``fprintf``, ``absl::PrintF``,
+  ``absl::FPrintf`` or other functions via configuration options, to
+  equivalent calls to C++23's ``std::print`` and ``std::println``, or other
+  functions via a configuration option, modifying the format string
+  appropriately and removing now-unnecessary calls to
+  ``std::string::c_str()`` and ``std::string::data()``.
+
 - New :doc:`performance-avoid-endl
   <clang-tidy/checks/performance/avoid-endl>` check.
 

diff  --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 552d1c40c25b8..966e98672e0fb 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -302,6 +302,7 @@ Clang-Tidy Checks
    `modernize-use-noexcept <modernize/use-noexcept.html>`_, "Yes"
    `modernize-use-nullptr <modernize/use-nullptr.html>`_, "Yes"
    `modernize-use-override <modernize/use-override.html>`_, "Yes"
+   `modernize-use-std-print <modernize/use-std-print.html>`_, "Yes"
    `modernize-use-trailing-return-type <modernize/use-trailing-return-type.html>`_, "Yes"
    `modernize-use-transparent-functors <modernize/use-transparent-functors.html>`_, "Yes"
    `modernize-use-uncaught-exceptions <modernize/use-uncaught-exceptions.html>`_, "Yes"

diff  --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst
new file mode 100644
index 0000000000000..385ee35c4f8f8
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst
@@ -0,0 +1,147 @@
+.. title:: clang-tidy - modernize-use-std-print
+
+modernize-use-std-print
+=======================
+
+Converts calls to ``printf``, ``fprintf``, ``absl::PrintF`` and
+``absl::FPrintf`` to equivalent calls to C++23's ``std::print`` or
+``std::println`` as appropriate, modifying the format string appropriately.
+The replaced and replacement functions can be customised by configuration
+options. Each argument that is the result of a call to ``std::string::c_str()`` and
+``std::string::data()`` will have that now-unnecessary call removed in a
+similar manner to the readability-redundant-string-cstr check.
+
+In other words, it turns lines like::
+
+.. code-block:: c++
+
+  fprintf(stderr, "The %s is %3d\n", description.c_str(), value);
+
+into::
+
+.. code-block:: c++
+
+  std::println(stderr, "The {} is {:3}", description, value);
+
+It doesn't do a bad job, but it's not perfect. In particular:
+
+- It assumes that the format string is correct for the arguments. If you
+  get any warnings when compiling with `-Wformat` then misbehaviour is
+  possible.
+
+- At the point that the check runs, the AST contains a single
+  ``StringLiteral`` for the format string and any macro expansion, token
+  pasting, adjacent string literal concatenation and escaping has been
+  handled. Although it's possible for the check to automatically put the
+  escapes back, they may not be exactly as they were written (e.g.
+  ``"\x0a"`` will become ``"\n"`` and ``"ab" "cd"`` will become
+  ``"abcd"``.) This is helpful since it means that the PRIx macros from
+  ``<inttypes.h>`` are removed correctly.
+
+- It supports field widths, precision, positional arguments, leading zeros,
+  leading ``+``, alignment and alternative forms.
+
+- Use of any unsupported flags or specifiers will cause the entire
+  statement to be left alone and a warning to be emitted. Particular
+  unsupported features are:
+
+  - The ``%'`` flag for thousands separators.
+
+  - The glibc extension ``%m``.
+
+If conversion would be incomplete or unsafe then the entire invocation will
+be left unchanged.
+
+If the call is deemed suitable for conversion then:
+
+- ``printf``, ``fprintf``, ``absl::PrintF``, ``absl::FPrintF`` and any
+  functions specified by the `PrintfLikeFunctions` option or
+  `FprintfLikeFunctions` are replaced with the function specified by the
+  `ReplacementPrintlnFunction` option if the format string ends with ``\n``
+  or `ReplacementPrintFunction` otherwise.
+- the format string is rewritten to use the ``std::formatter`` language and
+  a ``\n`` is removed from the end.
+- any arguments that corresponded to ``%p`` specifiers that
+  ``std::formatter`` wouldn't accept are wrapped in a ``static_cast``
+  to ``const void *``.
+- any arguments that corresponded to ``%s`` specifiers where the argument
+  is of ``signed char`` or ``unsigned char`` type are wrapped in a
+  ``reinterpret_cast<const char *>``.
+- any arguments where the format string and the parameter 
diff er in
+  signedness will be wrapped in an approprate ``static_cast`` if `StrictMode`
+  is enabled.
+- any arguments that end in a call to ``std::string::c_str()`` or
+  ``std::string_data()`` will have that call removed.
+
+Options
+-------
+
+.. option:: StrictMode
+
+   When `true`, the check will add casts when converting from variadic
+   functions like ``printf`` and printing signed or unsigned integer types
+   (including fixed-width integer types from ``<cstdint>``, ``ptr
diff _t``,
+   ``size_t`` and ``ssize_t``) as the opposite signedness to ensure that
+   the output matches that of ``printf``. This does not apply when
+   converting from non-variadic functions such as ``absl::PrintF`` and
+   ``fmt::printf``. For example, with `StrictMode` enabled:
+
+  .. code-block:: c++
+
+    int i = -42;
+    unsigned int u = 0xffffffff;
+    printf("%d %u\n", i, u);
+
+  would be converted to::
+
+  .. code-block:: c++
+
+    std::print("{} {}\n", static_cast<unsigned int>(i), static_cast<int>(u));
+
+  to ensure that the output will continue to be the unsigned representation
+  of `-42` and the signed representation of `0xffffffff` (often
+  `4294967254` and `-1` respectively.) When `false` (which is the default),
+  these casts will not be added which may cause a change in the output.
+
+.. option:: PrintfLikeFunctions
+
+   A semicolon-separated list of (fully qualified) extra function names to
+   replace, with the requirement that the first parameter contains the
+   printf-style format string and the arguments to be formatted follow
+   immediately afterwards. If neither this option nor
+   `FprintfLikeFunctions` are set then the default value for this option
+   is `printf; absl::PrintF`, otherwise it is empty.
+
+
+.. option:: FprintfLikeFunctions
+
+   A semicolon-separated list of (fully qualified) extra function names to
+   replace, with the requirement that the first parameter is retained, the
+   second parameter contains the printf-style format string and the
+   arguments to be formatted follow immediately afterwards. If neither this
+   option nor `PrintfLikeFunctions` are set then the default value for
+   this option is `fprintf; absl::FPrintF`, otherwise it is empty.
+
+.. option:: ReplacementPrintFunction
+
+   The function that will be used to replace ``printf``, ``fprintf`` etc.
+   during conversion rather than the default ``std::print`` when the
+   originalformat string does not end with ``\n``. It is expected that the
+   function provides an interface that is compatible with ``std::print``. A
+   suitable candidate would be ``fmt::print``.
+
+.. option:: ReplacementPrintlnFunction
+
+   The function that will be used to replace ``printf``, ``fprintf`` etc.
+   during conversion rather than the default ``std::println`` when the
+   original format string ends with ``\n``. It is expected that the
+   function provides an interface that is compatible with ``std::println``.
+   A suitable candidate would be ``fmt::println``.
+
+.. option:: PrintHeader
+
+   The header that must be included for the declaration of
+   `ReplacementPrintFunction` so that a ``#include`` directive can be
+   added if required. If `ReplacementPrintFunction` is ``std::print``
+   then this option will default to ``<print>``, otherwise this option will
+   default to nothing and no ``#include`` directive will be added.

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstddef b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstddef
new file mode 100644
index 0000000000000..800285e887cda
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstddef
@@ -0,0 +1,19 @@
+//===--- cstddef - Stub header for tests -------------------------*- C++ -*-===//
+//
+// 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 _CSTDDEF_
+#define _CSTDDEF_
+
+#include "stddef.h"
+
+namespace std {
+  using ::ptr
diff _t;
+  using ::size_t;
+}
+
+#endif // _CSTDDEF_

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdint b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdint
new file mode 100644
index 0000000000000..2029eb90dcb87
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdint
@@ -0,0 +1,40 @@
+//===--- cstdint - Stub header for tests ------------------------*- C++ -*-===//
+//
+// 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 _CSTDINT_
+#define _CSTDINT_
+
+typedef __INTMAX_TYPE__  intmax_t;
+typedef __UINTMAX_TYPE__ uintmax_t;
+
+namespace std {
+  using ::intmax_t;
+  using ::uintmax_t;
+}
+
+#define DECLARE_INTTYPE(N) \
+  typedef __INT ## N ## _TYPE__ int ## N ## _t; \
+  typedef int ## N ## _t int_least ## N ## _t; \
+  typedef int ## N ## _t int_fast ## N ## _t; \
+  typedef __UINT ## N ## _TYPE__ uint ## N ## _t; \
+  typedef uint ## N ## _t uint_least ## N ## _t; \
+  typedef uint ## N ## _t uint_fast ## N ## _t; \
+  namespace std { \
+    using ::int ## N ## _t; \
+    using ::int_least ## N ## _t; \
+    using ::int_fast ## N ## _t; \
+    using ::uint ## N ## _t; \
+    using ::uint_least ## N ## _t; \
+    using ::uint_fast ## N ## _t; \
+ } // std
+
+// For reasons unknown, these aren't coming in from <stdint.h>
+DECLARE_INTTYPE(8)
+DECLARE_INTTYPE(64)
+
+#endif // _CSTDINT__

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio
new file mode 100644
index 0000000000000..5024529f96da6
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio
@@ -0,0 +1,21 @@
+//===--- cstdio - Stub header for tests -------------------------*- C++ -*-===//
+//
+// 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 _STDIO_
+#define _STDIO_
+
+#include <stdio.h>
+
+namespace std {
+  using ::FILE;
+  using ::printf;
+  using ::fprintf;
+}
+
+#endif // _STDIO_
+

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/inttypes.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/inttypes.h
new file mode 100644
index 0000000000000..9dc7ae39b3a3f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/inttypes.h
@@ -0,0 +1,62 @@
+//===--- inttypes.h - Stub header for tests ---------------------*- C++ -*-===//
+//
+// 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 _INTTYPES_H_
+#define _INTTYPES_H_
+
+typedef __INTMAX_TYPE__  intmax_t;
+typedef __INT64_TYPE__ int64_t;
+typedef __INT32_TYPE__ int32_t;
+typedef __INT16_TYPE__ int16_t;
+typedef __INT8_TYPE__ int8_t;
+
+typedef __UINTMAX_TYPE__ uintmax_t;
+typedef __UINT64_TYPE__ uint64_t;
+typedef __UINT32_TYPE__ uint32_t;
+typedef __UINT16_TYPE__ uint16_t;
+typedef __UINT8_TYPE__ uint8_t;
+
+#define PRIdMAX "lld"
+#define PRId64 "lld"
+#define PRId32 "d"
+#define PRId16 "hd"
+#define PRId8  "hhd"
+
+#define PRIiMAX "lli"
+#define PRIi64 "lli"
+#define PRIi32 "i"
+#define PRIi16 "hi"
+#define PRIi8  "hhi"
+
+#define PRIiFAST64 "lli"
+#define PRIiFAST32 "i"
+#define PRIiFAST16 "hi"
+#define PRIiFAST8  "hhi"
+
+#define PRIiLEAST64 "lli"
+#define PRIiLEAST32 "i"
+#define PRIiLEAST16 "hi"
+#define PRIiLEAST8  "hhi"
+
+#define PRIuMAX "llu"
+#define PRIu64 "llu"
+#define PRIu32 "u"
+#define PRIu16 "hu"
+#define PRIu8  "hhu"
+
+#define PRIuFAST64 "llu"
+#define PRIuFAST32 "u"
+#define PRIuFAST16 "hu"
+#define PRIuFAST8  "hhu"
+
+#define PRIuLEAST64 "llu"
+#define PRIuLEAST32 "u"
+#define PRIuLEAST16 "hu"
+#define PRIuLEAST8  "hhu"
+
+#endif // _INTTYPES_H_

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stddef.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stddef.h
new file mode 100644
index 0000000000000..24b8ef012d4a0
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stddef.h
@@ -0,0 +1,15 @@
+//===--- stddef.h - Stub header for tests -------------------------*- C -*-===//
+//
+// 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 _STDDEF_H_
+#define _STDDEF_H_
+
+typedef __PTRDIFF_TYPE__ ptr
diff _t;
+typedef __SIZE_TYPE__ size_t;
+
+#endif _STDDEF_H_

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h
index eebe9fd914ad0..803abfdd590f7 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h
@@ -12,7 +12,13 @@
 // A header intended to contain C standard input and output library
 // declarations.
 
+typedef struct structFILE {} FILE;
+extern FILE *stderr;
+
 int printf(const char *, ...);
+int fprintf(FILE *, const char *, ...);
+
+#define NULL (0)
 
 #endif // _STDIO_H_
 

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h
index 931fcf36345a5..4ab7e930e4b50 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string.h
@@ -9,7 +9,7 @@
 #ifndef _STRING_H_
 #define _STRING_H_
 
-typedef __typeof__(sizeof(0)) size_t;
+#include "stddef.h"
 
 void *memcpy(void *dest, const void *src, size_t n);
 

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp
new file mode 100644
index 0000000000000..5345380028309
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp
@@ -0,0 +1,87 @@
+// RUN: %check_clang_tidy \
+// RUN:   -std=c++23 %s modernize-use-std-print %t -- \
+// RUN:   -config="{CheckOptions: [{key: StrictMode, value: true}]}" \
+// RUN:   -- -isystem %clang_tidy_headers
+// RUN: %check_clang_tidy \
+// RUN:   -std=c++23 %s modernize-use-std-print %t -- \
+// RUN:   -config="{CheckOptions: [{key: StrictMode, value: false}]}" \
+// RUN:   -- -isystem %clang_tidy_headers
+
+#include <cstdio>
+#include <string.h>
+
+namespace absl
+{
+// Use const char * for the format since the real type is hard to mock up.
+template <typename... Args>
+int PrintF(const char *format, const Args&... args);
+
+template <typename... Args>
+int FPrintF(FILE* output, const char *format, const Args&... args);
+}
+
+void printf_simple() {
+  absl::PrintF("Hello %s %d", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'PrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {} {}", "world", 42);
+}
+
+void printf_newline() {
+  absl::PrintF("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'PrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {} {}", "world", 42);
+
+  using namespace absl;
+  PrintF("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'PrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {} {}", "world", 42);
+}
+
+// absl uses the type of the argument rather than the format string, so unsigned
+// types will be printed as unsigned even if the format string indicates signed
+// and vice-versa. This is exactly what std::print will do too, so no casts are
+// required.
+void printf_no_casts_in_strict_mode() {
+  using namespace absl;
+
+  const unsigned short us = 42U;
+  PrintF("Integer %hd from unsigned short\n", us);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'PrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from unsigned short", us);
+
+  const short s = 42;
+  PrintF("Unsigned integer %hu from short\n", s);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'PrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from short", s);
+}
+
+void fprintf_simple(FILE *fp) {
+  absl::FPrintF(fp, "Hello %s %d", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'FPrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(fp, "Hello {} {}", "world", 42);
+}
+
+void fprintf_newline(FILE *fp) {
+  absl::FPrintF(fp, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'FPrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(fp, "Hello {} {}", "world", 42);
+
+  using namespace absl;
+  FPrintF(fp, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'FPrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(fp, "Hello {} {}", "world", 42);
+}
+
+void fprintf_no_casts_in_strict_mode(FILE *fp) {
+  using namespace absl;
+
+  const unsigned short us = 42U;
+  FPrintF(fp, "Integer %hd from unsigned short\n", us);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'FPrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(fp, "Integer {} from unsigned short", us);
+
+  const short s = 42;
+  FPrintF(fp, "Unsigned integer %hu from short\n", s);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'FPrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(fp, "Unsigned integer {} from short", s);
+}

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp
new file mode 100644
index 0000000000000..08779837c75e7
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp
@@ -0,0 +1,75 @@
+// RUN: %check_clang_tidy -std=c++23 %s modernize-use-std-print %t -- \
+// RUN:   -config="{CheckOptions: \
+// RUN:             [ \
+// RUN:              { \
+// RUN:               key: modernize-use-std-print.PrintfLikeFunctions, \
+// RUN:               value: '::myprintf; mynamespace::myprintf2' \
+// RUN:              }, \
+// RUN:              { \
+// RUN:               key: modernize-use-std-print.FprintfLikeFunctions, \
+// RUN:               value: '::myfprintf; mynamespace::myfprintf2' \
+// RUN:              } \
+// RUN:             ] \
+// RUN:            }" \
+// RUN:   -- -isystem %clang_tidy_headers
+
+#include <cstdio>
+#include <string.h>
+
+int myprintf(const char *, ...);
+int myfprintf(FILE *fp, const char *, ...);
+
+namespace mynamespace {
+int myprintf2(const char *, ...);
+int myfprintf2(FILE *fp, const char *, ...);
+}
+
+void printf_simple() {
+  myprintf("Hello %s %d", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'myprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {} {}", "world", 42);
+}
+
+void printf_newline() {
+  myprintf("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {} {}", "world", 42);
+
+  mynamespace::myprintf2("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myprintf2' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {} {}", "world", 42);
+
+  using mynamespace::myprintf2;
+  myprintf2("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myprintf2' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {} {}", "world", 42);
+
+  // When using custom options leave printf alone
+  printf("Hello %s %d\n", "world", 42);
+}
+
+void fprintf_simple(FILE *fp)
+{
+  myfprintf(stderr, "Hello %s %d", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'myfprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "Hello {} {}", "world", 42);
+}
+
+void fprintf_newline(FILE *fp)
+{
+  myfprintf(stderr, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myfprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(stderr, "Hello {} {}", "world", 42);
+
+  mynamespace::myfprintf2(stderr, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myfprintf2' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(stderr, "Hello {} {}", "world", 42);
+
+  using mynamespace::myfprintf2;
+  myfprintf2(stderr, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'myfprintf2' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(stderr, "Hello {} {}", "world", 42);
+
+  // When using custom options leave fprintf alone
+  fprintf(stderr, "Hello %s %d\n", "world", 42);
+}

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-fmt.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-fmt.cpp
new file mode 100644
index 0000000000000..81a5631fd9d84
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-fmt.cpp
@@ -0,0 +1,46 @@
+// RUN: %check_clang_tidy %s modernize-use-std-print %t -- \
+// RUN:   -config="{CheckOptions: \
+// RUN:             [ \
+// RUN:              { \
+// RUN:               key: modernize-use-std-print.ReplacementPrintFunction, \
+// RUN:               value: 'fmt::print' \
+// RUN:              }, \
+// RUN:              { \
+// RUN:               key: modernize-use-std-print.ReplacementPrintlnFunction, \
+// RUN:               value: 'fmt::println' \
+// RUN:              }, \
+// RUN:              { \
+// RUN:               key: modernize-use-std-print.PrintHeader, \
+// RUN:               value: '<fmt/core.h>' \
+// RUN:              } \
+// RUN:             ] \
+// RUN:            }" \
+// RUN:   -- -isystem %clang_tidy_headers
+
+#include <cstdio>
+// CHECK-FIXES: #include <fmt/core.h>
+#include <string.h>
+
+void printf_simple() {
+  printf("Hello %s %d", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: fmt::print("Hello {} {}", "world", 42);
+}
+
+void printf_newline() {
+  printf("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: fmt::println("Hello {} {}", "world", 42);
+}
+
+void fprintf_simple(FILE *fp) {
+  fprintf(fp, "Hello %s %d", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::print' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: fmt::print(fp, "Hello {} {}", "world", 42);
+}
+
+void fprintf_newline(FILE *fp) {
+  fprintf(fp, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::println' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: fmt::println(fp, "Hello {} {}", "world", 42);
+}

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp
new file mode 100644
index 0000000000000..a40eec922ab55
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp
@@ -0,0 +1,1263 @@
+// RUN: %check_clang_tidy -check-suffixes=,STRICT \
+// RUN:   -std=c++23 %s modernize-use-std-print %t -- \
+// RUN:   -config="{CheckOptions: [{key: StrictMode, value: true}]}" \
+// RUN:   -- -isystem %clang_tidy_headers
+// RUN: %check_clang_tidy -check-suffixes=,NOTSTRICT \
+// RUN:   -std=c++23 %s modernize-use-std-print %t -- \
+// RUN:   -config="{CheckOptions: [{key: StrictMode, value: false}]}" \
+// RUN:   -- -isystem %clang_tidy_headers
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+// CHECK-FIXES: #include <print>
+#include <inttypes.h>
+#include <string.h>
+#include <string>
+
+void printf_simple() {
+  printf("Hello");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello");
+}
+
+void printf_newline() {
+  printf("Hello\n");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello");
+
+  printf("Split" "\n");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Split");
+
+  printf("Double\n\n");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Double\n");
+}
+
+void printf_deceptive_newline() {
+  printf("Hello\\n");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello\\n");
+
+  printf("Hello\x0a");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello");
+}
+
+void fprintf_simple() {
+  fprintf(stderr, "Hello");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "Hello");
+}
+
+void std_printf_simple() {
+  std::printf("std::Hello");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("std::Hello");
+}
+
+void printf_escape() {
+  printf("before \t");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before \t");
+
+  printf("\n after");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("\n after");
+
+  printf("before \a after");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before \a after");
+
+  printf("Bell\a%dBackspace\bFF%s\fNewline\nCR\rTab\tVT\vEscape\x1b\x07%d", 42, "string", 99);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Bell\a{}Backspace\bFF{}\fNewline\nCR\rTab\tVT\vEscape\x1b\a{}", 42, "string", 99);
+
+  printf("not special \x1b\x01\x7f");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("not special \x1b\x01\x7f");
+}
+
+void printf_percent() {
+  printf("before %%");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before %");
+
+  printf("%% after");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("% after");
+
+  printf("before %% after");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before % after");
+
+  printf("Hello %% and another %%");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello % and another %");
+
+  printf("Not a string %%s");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Not a string %s");
+}
+
+void printf_curlies() {
+  printf("%d {}", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{} {{[{][{]}}}}", 42);
+
+  printf("{}");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{{[{][{]}}}}");
+}
+
+void printf_unsupported_format_specifiers() {
+  int pos;
+  printf("%d %n %d\n", 42, &pos, 72);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: unable to use 'std::println' instead of 'printf' because '%n' is not supported in format string [modernize-use-std-print]
+
+  printf("Error %m\n");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: unable to use 'std::println' instead of 'printf' because '%m' is not supported in format string [modernize-use-std-print]
+}
+
+void printf_not_string_literal(const char *fmt) {
+  // We can't convert the format string if it's not a literal
+  printf(fmt, 42);
+}
+
+void printf_inttypes_ugliness() {
+  // The one advantage of the checker seeing the token pasted version of the
+  // format string is that we automatically cope with the horrendously-ugly
+  // inttypes.h macros!
+  int64_t u64 = 42;
+  uintmax_t umax = 4242;
+  printf("uint64:%" PRId64 " uintmax:%" PRIuMAX "\n", u64, umax);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("uint64:{} uintmax:{}", u64, umax);
+}
+
+void printf_raw_string() {
+  // This one doesn't require the format string to be changed, so it stays intact
+  printf(R"(First\Second)");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(R"(First\Second)");
+
+  // This one does require the format string to be changed, so unfortunately it
+  // gets reformatted as a normal string.
+  printf(R"(First %d\Second)", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("First {}\\Second", 42);
+}
+
+void printf_integer_d() {
+  const bool b = true;
+  // The "d" type is necessary here for compatibility with printf since
+  // std::print will print booleans as "true" or "false".
+  printf("Integer %d from bool\n", b);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {:d} from bool", b);
+
+  // The "d" type is necessary here for compatibility with printf since
+  // std::print will print booleans as "true" or "false".
+  printf("Integer %i from bool\n", b);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {:d} from bool", b);
+
+  // The 'd' is always necessary if we pass a char since otherwise the
+  // parameter will be formatted as a character. In StrictMode, the
+  // cast is always necessary to maintain the printf behaviour since
+  // char may be unsigned, but this also means that the 'd' is not
+  // necessary.
+  const char c = 'A';
+  printf("Integers %d %hhd from char\n", c, c);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integers {:d} {:d} from char", c, c);
+  // CHECK-FIXES-STRICT: std::println("Integers {} {} from char", static_cast<signed char>(c), static_cast<signed char>(c));
+
+  printf("Integers %i %hhi from char\n", c, c);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integers {:d} {:d} from char", c, c);
+  // CHECK-FIXES-STRICT: std::println("Integers {} {} from char", static_cast<signed char>(c), static_cast<signed char>(c));
+
+  const signed char sc = 'A';
+  printf("Integers %d %hhd from signed char\n", sc, sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integers {} {} from signed char", sc, sc);
+
+  printf("Integers %i %hhi from signed char\n", sc, sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integers {} {} from signed char", sc, sc);
+
+  const unsigned char uc = 'A';
+  printf("Integers %d %hhd from unsigned char\n", uc, uc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integers {} {} from unsigned char", uc, uc);
+  // CHECK-FIXES-STRICT: std::println("Integers {} {} from unsigned char", static_cast<signed char>(uc), static_cast<signed char>(uc));
+
+  printf("Integers %i %hhi from unsigned char\n", uc, uc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integers {} {} from unsigned char", uc, uc);
+  // CHECK-FIXES-STRICT: std::println("Integers {} {} from unsigned char", static_cast<signed char>(uc), static_cast<signed char>(uc));
+
+  const int8_t i8 = 42;
+  printf("Integer %" PRIi8 " from int8_t\n", i8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from int8_t", i8);
+
+  const int_fast8_t if8 = 42;
+  printf("Integer %" PRIiFAST8 " from int_fast8_t\n", if8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from int_fast8_t", if8);
+
+  const int_least8_t il8 = 42;
+  printf("Integer %" PRIiFAST8 " from int_least8_t\n", il8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from int_least8_t", il8);
+
+  const uint8_t u8 = 42U;
+  const std::uint8_t su8 = u8;
+  printf("Integers %" PRIi8 " and %" PRId8 " from uint8_t\n", u8, su8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integers {} and {} from uint8_t", u8, su8);
+  // CHECK-FIXES-STRICT: std::println("Integers {} and {} from uint8_t", static_cast<int8_t>(u8), static_cast<std::int8_t>(su8));
+
+  const uint_fast8_t uf8 = 42U;
+  printf("Integer %" PRIiFAST8 " from uint_fast8_t\n", uf8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from uint_fast8_t", uf8);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from uint_fast8_t", static_cast<int_fast8_t>(uf8));
+
+  const uint_least8_t ul8 = 42U;
+  printf("Integer %" PRIiLEAST8 " from uint_least8_t\n", ul8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from uint_least8_t", ul8);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from uint_least8_t", static_cast<int_least8_t>(ul8));
+
+  const short s = 42;
+  printf("Integer %hd from short\n", s);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from short", s);
+
+  printf("Integer %hi from short\n", s);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from short", s);
+
+  const unsigned short us = 42U;
+  printf("Integer %hd from unsigned short\n", us);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned short", us);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned short", static_cast<short>(us));
+
+  printf("Integer %hi from unsigned short\n", us);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned short", us);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned short", static_cast<short>(us));
+
+  const int i = 42;
+  printf("Integer %d from integer\n", i);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from integer", i);
+
+  printf("Integer %i from integer\n", i);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from integer", i);
+
+  const unsigned int ui = 42U;
+  printf("Integer %d from unsigned integer\n", ui);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned integer", ui);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned integer", static_cast<int>(ui));
+
+  printf("Integer %i from unsigned integer\n", ui);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned integer", ui);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned integer", static_cast<int>(ui));
+
+  const long l = 42L;
+  printf("Integer %ld from long\n", l);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from long", l);
+
+  printf("Integer %li from long\n", l);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from long", l);
+
+  const unsigned long ul = 42UL;
+  printf("Integer %ld from unsigned long\n", ul);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned long", ul);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned long", static_cast<long>(ul));
+
+  printf("Integer %li from unsigned long\n", ul);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned long", ul);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned long", static_cast<long>(ul));
+
+  const long long ll = 42LL;
+  printf("Integer %lld from long long\n", ll);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from long long", ll);
+
+  printf("Integer %lli from long long\n", ll);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integer {} from long long", ll);
+
+  const unsigned long long ull = 42ULL;
+  printf("Integer %lld from unsigned long long\n", ull);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned long long", ull);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned long long", static_cast<long long>(ull));
+
+  printf("Integer %lli from unsigned long long\n", ull);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from unsigned long long", ull);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from unsigned long long", static_cast<long long>(ull));
+
+  const intmax_t im = 42;
+  const std::intmax_t sim = im;
+  printf("Integers %jd and %jd from intmax_t\n", im, sim);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integers {} and {} from intmax_t", im, sim);
+
+  printf("Integers %ji and %ji from intmax_t\n", im, sim);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integers {} and {} from intmax_t", im, sim);
+
+  const uintmax_t uim = 42;
+  const std::uintmax_t suim = uim;
+  printf("Integers %jd and %jd from uintmax_t\n", uim, suim);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integers {} and {} from uintmax_t", uim, suim);
+  // CHECK-FIXES-STRICT: std::println("Integers {} and {} from uintmax_t", static_cast<intmax_t>(uim), static_cast<std::intmax_t>(suim));
+
+  printf("Integer %ji from intmax_t\n", uim);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integer {} from intmax_t", uim);
+  // CHECK-FIXES-STRICT: std::println("Integer {} from intmax_t", static_cast<intmax_t>(uim));
+
+  const int ai[] = { 0, 1, 2, 3};
+  const ptr
diff _t pd = &ai[3] - &ai[0];
+  const std::ptr
diff _t spd = pd;
+  printf("Integers %td and %td from ptr
diff _t\n", pd, spd);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integers {} and {} from ptr
diff _t", pd, spd);
+
+  printf("Integers %ti and %ti from ptr
diff _t\n", pd, spd);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Integers {} and {} from ptr
diff _t", pd, spd);
+
+  const size_t z = 42UL;
+  const std::size_t sz = z;
+  printf("Integers %zd and %zd from size_t\n", z, sz);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Integers {} and {} from size_t", z, sz);
+  // CHECK-FIXES-STRICT: std::println("Integers {} and {} from size_t", static_cast<ssize_t>(z), static_cast<std::ssize_t>(sz));
+}
+
+void printf_integer_u()
+{
+  const bool b = true;
+  // The "d" type is necessary here for compatibility with printf since
+  // std::print will print booleans as "true" or "false".
+  printf("Unsigned integer %u from bool\n", b);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {:d} from bool", b);
+
+  const char c = 'A';
+  printf("Unsigned integer %hhu from char\n", c);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {:d} from char", c);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from char", static_cast<unsigned char>(c));
+
+  const signed char sc = 'A';
+  printf("Unsigned integer %hhu from signed char\n", sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from signed char", sc);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from signed char", static_cast<unsigned char>(sc));
+
+  printf("Unsigned integer %u from signed char\n", sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from signed char", sc);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from signed char", static_cast<unsigned char>(sc));
+
+  const unsigned char uc = 'A';
+  printf("Unsigned integer %hhu from unsigned char\n", uc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from unsigned char", uc);
+
+  printf("Unsigned integer %u from unsigned char\n", uc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from unsigned char", uc);
+
+  const int8_t i8 = 42;
+  printf("Unsigned integer %" PRIu8 " from int8_t\n", i8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from int8_t", i8);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from int8_t", static_cast<uint8_t>(i8));
+
+  const int_fast8_t if8 = 42;
+  printf("Unsigned integer %" PRIuFAST8 " from int_fast8_t\n", if8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from int_fast8_t", if8);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from int_fast8_t", static_cast<uint_fast8_t>(if8));
+
+  const int_least8_t il8 = 42;
+  printf("Unsigned integer %" PRIuFAST8 " from int_least8_t\n", il8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from int_least8_t", il8);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from int_least8_t", static_cast<uint_least8_t>(il8));
+
+  const uint8_t u8 = 42U;
+  printf("Unsigned integer %" PRIu8 " from uint8_t\n", u8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from uint8_t", u8);
+
+  const uint_fast8_t uf8 = 42U;
+  printf("Unsigned integer %" PRIuFAST8 " from uint_fast8_t\n", uf8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from uint_fast8_t", uf8);
+
+  const uint_least8_t ul8 = 42U;
+  printf("Unsigned integer %" PRIuLEAST8 " from uint_least8_t\n", ul8);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from uint_least8_t", ul8);
+
+  const short s = 42;
+  printf("Unsigned integer %hu from short\n", s);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from short", s);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from short", static_cast<unsigned short>(s));
+
+  const unsigned short us = 42U;
+  printf("Unsigned integer %hu from unsigned short\n", us);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from unsigned short", us);
+
+  const int i = 42;
+  printf("Unsigned integer %u from signed integer\n", i);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from signed integer", i);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from signed integer", static_cast<unsigned int>(i));
+
+  const unsigned int ui = 42U;
+  printf("Unsigned integer %u from unsigned integer\n", ui);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from unsigned integer", ui);
+
+  const long l = 42L;
+  printf("Unsigned integer %u from signed long\n", l);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from signed long", l);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from signed long", static_cast<unsigned long>(l));
+
+  const unsigned long ul = 42UL;
+  printf("Unsigned integer %lu from unsigned long\n", ul);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from unsigned long", ul);
+
+  const long long ll = 42LL;
+  printf("Unsigned integer %llu from long long\n", ll);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integer {} from long long", ll);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integer {} from long long", static_cast<unsigned long long>(ll));
+
+  const unsigned long long ull = 42ULL;
+  printf("Unsigned integer %llu from unsigned long long\n", ull);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integer {} from unsigned long long", ull);
+
+  const intmax_t im = 42;
+  const std::intmax_t sim = im;
+  printf("Unsigned integers %ju and %ju from intmax_t\n", im, sim);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integers {} and {} from intmax_t", im, sim);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integers {} and {} from intmax_t", static_cast<uintmax_t>(im), static_cast<std::uintmax_t>(sim));
+
+  const uintmax_t uim = 42U;
+  const std::uintmax_t suim = uim;
+  printf("Unsigned integers %ju and %ju from uintmax_t\n", uim, suim);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integers {} and {} from uintmax_t", uim, suim);
+
+  const int ai[] = { 0, 1, 2, 3};
+  const ptr
diff _t pd = &ai[3] - &ai[0];
+  const std::ptr
diff _t spd = pd;
+  printf("Unsigned integers %tu and %tu from ptr
diff _t\n", pd, spd);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println("Unsigned integers {} and {} from ptr
diff _t", pd, spd);
+  // CHECK-FIXES-STRICT: std::println("Unsigned integers {} and {} from ptr
diff _t", static_cast<size_t>(pd), static_cast<std::size_t>(spd));
+
+  const size_t z = 42U;
+  const std::size_t sz = z;
+  printf("Unsigned integers %zu and %zu from size_t\n", z, sz);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Unsigned integers {} and {} from size_t", z, sz);
+}
+
+// This checks that we get the argument offset right with the extra FILE * argument
+void fprintf_integer() {
+  fprintf(stderr, "Integer %d from integer\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(stderr, "Integer {} from integer", 42);
+
+  fprintf(stderr, "Integer %i from integer\n", 65);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println(stderr, "Integer {} from integer", 65);
+
+  fprintf(stderr, "Integer %i from char\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println(stderr, "Integer {:d} from char", 'A');
+  // CHECK-FIXES-STRICT: std::println(stderr, "Integer {} from char", static_cast<signed char>('A'));
+
+  fprintf(stderr, "Integer %d from char\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::println(stderr, "Integer {:d} from char", 'A');
+  // CHECK-FIXES-STRICT: std::println(stderr, "Integer {} from char", static_cast<signed char>('A'));
+}
+
+void printf_char() {
+  const char c = 'A';
+  printf("Char %c from char\n", c);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {} from char", c);
+
+  const signed char sc = 'A';
+  printf("Char %c from signed char\n", sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {:c} from signed char", sc);
+
+  const unsigned char uc = 'A';
+  printf("Char %c from unsigned char\n", uc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {:c} from unsigned char", uc);
+
+  const int i = 65;
+  printf("Char %c from integer\n", i);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {:c} from integer", i);
+
+  const unsigned int ui = 65;
+  printf("Char %c from unsigned integer\n", ui);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {:c} from unsigned integer", ui);
+
+  const unsigned long long ull = 65;
+  printf("Char %c from unsigned long long\n", ull);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {:c} from unsigned long long", ull);
+}
+
+void printf_bases() {
+  printf("Hex %lx\n", 42L);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hex {:x}", 42L);
+
+  printf("HEX %X\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("HEX {:X}", 42);
+
+  printf("Oct %lo\n", 42L);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Oct {:o}", 42L);
+}
+
+void printf_alternative_forms() {
+  printf("Hex %#lx\n", 42L);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hex {:#x}", 42L);
+
+  printf("HEX %#X\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("HEX {:#X}", 42);
+
+  printf("Oct %#lo\n", 42L);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Oct {:#o}", 42L);
+
+  printf("Double %#f %#F\n", -42.0, -42.0);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Double {:#f} {:#F}", -42.0, -42.0);
+
+  printf("Double %#g %#G\n", -42.0, -42.0);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Double {:#g} {:#G}", -42.0, -42.0);
+
+  printf("Double %#e %#E\n", -42.0, -42.0);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Double {:#e} {:#E}", -42.0, -42.0);
+
+  printf("Double %#a %#A\n", -42.0, -42.0);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Double {:#a} {:#A}", -42.0, -42.0);
+
+  // Characters don't have an alternate form
+  printf("Char %#c\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {}", 'A');
+
+  // Strings don't have an alternate form
+  printf("Char %#c\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Char {}", 'A');
+}
+
+void printf_string() {
+  printf("Hello %s after\n", "Goodbye");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {} after", "Goodbye");
+
+  // std::print can't print signed char strings.
+  const signed char *sstring = reinterpret_cast<const signed char *>("ustring");
+  printf("signed char string %s\n", sstring);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("signed char string {}", reinterpret_cast<const char *>(sstring));
+
+  // std::print can't print unsigned char strings.
+  const unsigned char *ustring = reinterpret_cast<const unsigned char *>("ustring");
+  printf("unsigned char string %s\n", ustring);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("unsigned char string {}", reinterpret_cast<const char *>(ustring));
+}
+
+void printf_float() {
+  // If the type is not specified then either f or e will be used depending on
+  // whichever is shorter. This means that it is necessary to be specific to
+  // maintain compatibility with printf.
+
+  // TODO: Should we force a cast here, since printf will promote to double
+  // automatically, but std::format will not, which could result in 
diff erent
+  // output?
+
+  const float f = 42.0F;
+  printf("Hello %f after\n", f);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:f} after", f);
+
+  printf("Hello %g after\n", f);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:g} after", f);
+
+  printf("Hello %e after\n", f);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:e} after", f);
+}
+
+void printf_double() {
+  // If the type is not specified then either f or e will be used depending on
+  // whichever is shorter. This means that it is necessary to be specific to
+  // maintain compatibility with printf.
+
+  const double d = 42.0;
+  printf("Hello %f after\n", d);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:f} after", d);
+
+  printf("Hello %g after\n", d);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:g} after", d);
+
+  printf("Hello %e after\n", d);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:e} after", d);
+}
+
+void printf_long_double() {
+  // If the type is not specified then either f or e will be used depending on
+  // whichever is shorter. This means that it is necessary to be specific to
+  // maintain compatibility with printf.
+
+  const long double ld = 42.0L;
+  printf("Hello %Lf after\n", ld);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:f} after", ld);
+
+  printf("Hello %g after\n", ld);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:g} after", ld);
+
+  printf("Hello %e after\n", ld);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:e} after", ld);
+}
+
+void printf_pointer() {
+  int i;
+  double j;
+  printf("Int* %p %s %p\n", &i, "Double*", &j);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Int* {} {} {}", static_cast<const void *>(&i), "Double*", static_cast<const void *>(&j));
+
+  printf("%p\n", nullptr);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", nullptr);
+
+  const auto np = nullptr;
+  printf("%p\n", np);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", np);
+
+  // NULL isn't a pointer, so std::print needs some help.
+  printf("%p\n", NULL);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", static_cast<const void *>(NULL));
+
+  printf("%p\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", static_cast<const void *>(42));
+
+  // If we already have a void pointer then no cast is required.
+  printf("%p\n", reinterpret_cast<const void *>(44));
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", reinterpret_cast<const void *>(44));
+
+  const void *p;
+  printf("%p\n", p);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", p);
+
+  // But a pointer to a pointer to void does need a cast
+  const void **pp;
+  printf("%p\n", pp);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", static_cast<const void *>(pp));
+
+  printf("%p\n", printf_pointer);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("{}", static_cast<const void *>(printf_pointer));
+}
+
+class AClass
+{
+  int member;
+
+  void printf_this_pointer()
+  {
+    printf("%p\n", this);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("{}", static_cast<const void *>(this));
+  }
+
+  void printf_pointer_to_member_function()
+  {
+    printf("%p\n", &AClass::printf_pointer_to_member_function);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("{}", static_cast<const void *>(&AClass::printf_pointer_to_member_function));
+  }
+
+  void printf_pointer_to_member_variable()
+  {
+    printf("%p\n", &AClass::member);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("{}", static_cast<const void *>(&AClass::member));
+  }
+};
+
+void printf_positional_arg() {
+  printf("%1$d", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{0}", 42);
+
+  printf("before %1$d", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before {0}", 42);
+
+  printf("%1$d after", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{0} after", 42);
+
+  printf("before %1$d after", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before {0} after", 42);
+
+  printf("before %2$d between %1$s after", "string", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("before {1} between {0} after", "string", 42);
+}
+
+// printf always defaults to right justification,, no matter what the type is of
+// the argument. std::format uses left justification by default for strings, and
+// right justification for numbers.
+void printf_right_justified() {
+  printf("Right-justified integer %4d after\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified integer {:4} after", 42);
+
+  printf("Right-justified double %4f\n", 227.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified double {:4f}", 227.2);
+
+  printf("Right-justified double %4g\n", 227.4);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified double {:4g}", 227.4);
+
+  printf("Right-justified integer with field width argument %*d after\n", 5, 424242);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified integer with field width argument {:{}} after", 5, 424242);
+
+  printf("Right-justified integer with field width argument %2$*1$d after\n", 5, 424242);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified integer with field width argument {1:{0}} after", 5, 424242);
+
+  printf("Right-justified string %20s\n", "Hello");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified string {:>20}", "Hello");
+
+  printf("Right-justified string with field width argument %2$*1$s after\n", 20, "wibble");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Right-justified string with field width argument {1:>{0}} after", 20, "wibble");
+}
+
+// printf always requires - for left justification, no matter what the type is
+// of the argument. std::format uses left justification by default for strings,
+// and right justification for numbers.
+void printf_left_justified() {
+  printf("Left-justified integer %-4d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified integer {:<4}", 42);
+
+  printf("Left-justified integer %--4d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified integer {:<4}", 42);
+
+  printf("Left-justified double %-4f\n", 227.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified double {:<4f}", 227.2);
+
+  printf("Left-justified double %-4g\n", 227.4);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified double {:<4g}", 227.4);
+
+  printf("Left-justified integer with field width argument %-*d after\n", 5, 424242);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified integer with field width argument {:<{}} after", 5, 424242);
+
+  printf("Left-justified integer with field width argument %2$-*1$d after\n", 5, 424242);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified integer with field width argument {1:<{0}} after", 5, 424242);
+
+  printf("Left-justified string %-20s\n", "Hello");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified string {:20}", "Hello");
+
+  printf("Left-justified string with field width argument %2$-*1$s after\n", 5, "wibble");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Left-justified string with field width argument {1:{0}} after", 5, "wibble");
+}
+
+void printf_precision() {
+  printf("Hello %.3f\n", 3.14159);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:.3f}", 3.14159);
+
+  printf("Hello %10.3f\n", 3.14159);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:10.3f}", 3.14159);
+
+  printf("Hello %.*f after\n", 10, 3.14159265358979323846);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:.{}f} after", 10, 3.14159265358979323846);
+
+  printf("Hello %10.*f after\n", 3, 3.14159265358979323846);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:10.{}f} after", 3, 3.14159265358979323846);
+
+  printf("Hello %*.*f after\n", 10, 4, 3.14159265358979323846);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:{}.{}f} after", 10, 4, 3.14159265358979323846);
+
+  printf("Hello %1$.*2$f after\n", 3.14159265358979323846, 4);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {0:.{1}f} after", 3.14159265358979323846, 4);
+
+  // Precision is ignored, but maintained on non-numeric arguments
+  printf("Hello %.5s\n", "Goodbye");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:.5}", "Goodbye");
+
+  printf("Hello %.5c\n", 'G');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {:.5}", 'G');
+}
+
+void printf_field_width_and_precision() {
+  printf("Hello %1$*2$.*3$f after\n", 3.14159265358979323846, 4, 2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Hello {0:{1}.{2}f} after", 3.14159265358979323846, 4, 2);
+}
+
+void printf_alternative_form() {
+  printf("Wibble %#x\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Wibble {:#x}", 42);
+
+  printf("Wibble %#20x\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Wibble {:#20x}", 42);
+
+  printf("Wibble %#020x\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Wibble {:#020x}", 42);
+
+  printf("Wibble %#-20x\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Wibble {:<#20x}", 42);
+}
+
+void printf_leading_plus() {
+  printf("Positive integer %+d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Positive integer {:+}", 42);
+
+  printf("Positive double %+f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Positive double {:+f}", 42.2);
+
+  printf("Positive double %+g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Positive double {:+g}", 42.2);
+
+  // Ignore leading plus on strings to avoid potential runtime exception where
+  // printf would have just ignored it.
+  printf("Positive string %+s\n", "string");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Positive string {}", "string");
+}
+
+void printf_leading_space() {
+  printf("Spaced integer % d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced integer {: }", 42);
+
+  printf("Spaced integer %- d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced integer {: }", 42);
+
+  printf("Spaced double % f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced double {: f}", 42.2);
+
+  printf("Spaced double % g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced double {: g}", 42.2);
+}
+
+void printf_leading_zero() {
+  printf("Leading zero integer %03d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero integer {:03}", 42);
+
+  printf("Leading minus and zero integer %-03d minus ignored\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading minus and zero integer {:<03} minus ignored", 42);
+
+  printf("Leading zero unsigned integer %03u\n", 42U);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero unsigned integer {:03}", 42U);
+
+  printf("Leading zero double %03f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero double {:03f}", 42.2);
+
+  printf("Leading zero double %03g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero double {:03g}", 42.2);
+}
+
+void printf_leading_plus_and_space() {
+  // printf prefers plus to space. {fmt} will throw if both are present.
+  printf("Spaced integer % +d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced integer {:+}", 42);
+
+  printf("Spaced double %+ f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced double {:+f}", 42.2);
+
+  printf("Spaced double % +g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Spaced double {:+g}", 42.2);
+}
+
+void printf_leading_zero_and_plus() {
+  printf("Leading zero integer %+03d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero integer {:+03}", 42);
+
+  printf("Leading zero double %0+3f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero double {:+03f}", 42.2);
+
+  printf("Leading zero double %0+3g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero double {:+03g}", 42.2);
+}
+
+void printf_leading_zero_and_space() {
+  printf("Leading zero and space integer %0 3d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero and space integer {: 03}", 42);
+
+  printf("Leading zero and space double %0 3f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero and space double {: 03f}", 42.2);
+
+  printf("Leading zero and space double %0 3g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Leading zero and space double {: 03g}", 42.2);
+}
+
+// add signed plained enum too
+enum PlainEnum { red };
+enum SignedPlainEnum { black = -42 };
+enum BoolEnum : unsigned int { yellow };
+enum CharEnum : char { purple };
+enum SCharEnum : signed char  { aquamarine };
+enum UCharEnum : unsigned char  { pink };
+enum ShortEnum : short { beige };
+enum UShortEnum : unsigned short { grey };
+enum IntEnum : int { green };
+enum UIntEnum : unsigned int { blue };
+enum LongEnum : long { magenta };
+enum ULongEnum : unsigned long { cyan };
+enum LongLongEnum : long long { taupe };
+enum ULongLongEnum : unsigned long long { brown };
+
+void printf_enum_d() {
+  PlainEnum plain_enum;
+  printf("%d", plain_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<int>(plain_enum));
+
+  SignedPlainEnum splain_enum;
+  printf("%d", splain_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<int>(splain_enum));
+
+  BoolEnum bool_enum;
+  printf("%d", bool_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<int>(bool_enum));
+
+  CharEnum char_enum;
+  printf("%d", char_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<signed char>(char_enum));
+
+  SCharEnum schar_enum;
+  printf("%d", schar_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<signed char>(schar_enum));
+
+  UCharEnum uchar_enum;
+  printf("%d", uchar_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<signed char>(uchar_enum));
+
+  ShortEnum short_enum;
+  printf("%d", short_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<short>(short_enum));
+
+  UShortEnum ushort_enum;
+  printf("%d", ushort_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<short>(ushort_enum));
+
+  IntEnum int_enum;
+  printf("%d", int_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<int>(int_enum));
+
+  UIntEnum uint_enum;
+  printf("%d", uint_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<int>(uint_enum));
+
+  LongEnum long_enum;
+  printf("%d", long_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<long>(long_enum));
+
+  ULongEnum ulong_enum;
+  printf("%d", ulong_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<long>(ulong_enum));
+
+  LongLongEnum longlong_enum;
+  printf("%d", longlong_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<long long>(longlong_enum));
+
+  ULongLongEnum ulonglong_enum;
+  printf("%d", ulonglong_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<long long>(ulonglong_enum));
+}
+
+void printf_enum_u() {
+  PlainEnum plain_enum;
+  printf("%u", plain_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(plain_enum));
+
+  SignedPlainEnum splain_enum;
+  printf("%u", splain_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(splain_enum));
+
+  BoolEnum bool_enum;
+  printf("%u", bool_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(bool_enum));
+
+  CharEnum char_enum;
+  printf("%u", char_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned char>(char_enum));
+
+  SCharEnum schar_enum;
+  printf("%u", schar_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned char>(schar_enum));
+
+  UCharEnum uchar_enum;
+  printf("%u", uchar_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned char>(uchar_enum));
+
+  ShortEnum short_enum;
+  printf("%u", short_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned short>(short_enum));
+
+  UShortEnum ushort_enum;
+  printf("%u", ushort_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned short>(ushort_enum));
+
+  IntEnum int_enum;
+  printf("%u", int_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(int_enum));
+
+  UIntEnum uint_enum;
+  printf("%u", uint_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned int>(uint_enum));
+
+  LongEnum long_enum;
+  printf("%u", long_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned long>(long_enum));
+
+  ULongEnum ulong_enum;
+  printf("%u", ulong_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned long>(ulong_enum));
+
+  LongLongEnum longlong_enum;
+  printf("%u", longlong_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned long long>(longlong_enum));
+
+  ULongLongEnum ulonglong_enum;
+  printf("%u", ulonglong_enum);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}", static_cast<unsigned long long>(ulonglong_enum));
+}
+
+void printf_string_function(const char *(*callback)()) {
+  printf("printf string from callback %s", callback());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("printf string from callback {}", callback());
+}
+
+template <typename CharType>
+struct X
+{
+  const CharType *str() const;
+};
+
+void printf_string_member_function(const X<char> &x, const X<const char> &cx) {
+  printf("printf string from member function %s", x.str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("printf string from member function {}", x.str());
+
+  printf("printf string from member function on const %s", cx.str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("printf string from member function on const {}", cx.str());
+}
+
+void printf_string_cstr(const std::string &s1, const std::string &s2) {
+  printf("printf string one c_str %s", s1.c_str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("printf string one c_str {}", s1);
+
+  printf("printf string two c_str %s %s\n", s1.c_str(), s2.data());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("printf string two c_str {} {}", s1, s2);
+}
+
+void printf_not_char_string_cstr(const std::wstring &ws1) {
+  // This test is to check that we only remove
+  // std::basic_string<CharType>::c_str()/data() when CharType is char. I've
+  // been unable to come up with a genuine situation where someone would have
+  // actually successfully called those methods when this isn't the case without
+  // -Wformat warning, but it seems sensible to restrict removal regardless.
+  printf("printf bogus wstring c_str %s", ws1.c_str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("printf bogus wstring c_str {}", ws1.c_str());
+}
+
+void fprintf_string_cstr(const std::string &s1) {
+  fprintf(stderr, "fprintf string c_str %s", s1.c_str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "fprintf string c_str {}", s1);
+}
+
+void printf_string_pointer_cstr(const std::string *s1, const std::string *s2) {
+  printf("printf string pointer one c_str %s", s1->c_str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("printf string pointer one c_str {}", *s1);
+
+  printf("printf string pointer two c_str %s %s\n", s1->c_str(), s2->data());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("printf string pointer two c_str {} {}", *s1, *s2);
+}
+
+void fprintf_string_pointer_cstr(const std::string *s1) {
+  fprintf(stderr, "fprintf string pointer c_str %s", s1->c_str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "fprintf string pointer c_str {}", *s1);
+}
+
+template <typename T>
+struct iterator {
+  T *operator->();
+  T &operator*();
+};
+
+void printf_iterator_cstr(iterator<std::string> i1, iterator<std::string> i2)
+{
+  printf("printf iterator c_str %s %s\n", i1->c_str(), i2->data());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("printf iterator c_str {} {}", *i1, *i2);
+}
+
+// Something that isn't std::string, so the calls to c_str() and data() must not
+// be removed even though the printf call will be replaced.
+struct S
+{
+  const char *c_str() const;
+  const char *data() const;
+};
+
+void p(S s1, S *s2)
+{
+  printf("Not std::string %s %s", s1.c_str(), s2->c_str());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Not std::string {} {}", s1.c_str(), s2->c_str());
+
+  printf("Not std::string %s %s", s1.data(), s2->data());
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Not std::string {} {}", s1.data(), s2->data());
+}


        


More information about the cfe-commits mailing list