mikecrowe updated this revision to Diff 520439.
mikecrowe added a comment.

Address many more review comments, including:

- Only add casts for signed/unsigned discrepancy if StrictMode option is set.
- Use IncludeInserter to add <print> or other required header.

Review comments still outstanding:

- Use println if format string ends in `\n`.
- Remove c_str() as part of the same check (I'm not really sure how to do this, 
are there any other checks that I should look at for inspiration?)
- Emit warnings to explain why if conversion is not possible.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D149280/new/

https://reviews.llvm.org/D149280

Files:
  clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
  clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
  clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
  clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h
  clang-tools-extra/clang-tidy/utils/CMakeLists.txt
  clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp
  clang-tools-extra/clang-tidy/utils/FormatStringConverter.h
  clang-tools-extra/docs/ReleaseNotes.rst
  clang-tools-extra/docs/clang-tidy/checks/list.rst
  clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst
  clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio
  clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.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

Index: clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp
@@ -0,0 +1,1049 @@
+// RUN: %check_clang_tidy -check-suffixes=,STRICT \
+// RUN:   -std=c++2b %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++2b %s modernize-use-std-print %t -- \
+// RUN:   -config="{CheckOptions: [{key: StrictMode, value: false}]}" \
+// RUN:   -- -isystem %clang_tidy_headers
+#include <cstdio>
+// CHECK-FIXES: #include <print>
+#include <inttypes.h>
+#include <string.h>
+
+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 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() {
+  int pos;
+  printf("%d %n %d\n", 42, &pos, 72);
+  // fmt doesn't do the equivalent of %n, so leave the call alone.
+
+  printf("Error %m\n");
+  // fmt 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.
+}
+
+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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("uint64:{} uintmax:{}\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {:d} from bool\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {:d} from bool\n", b);
+
+  // The 'd' is always necessary since otherwise the parameter will be formatted as a character
+  const char c = 'A';
+  printf("Integer %d from char\n", c);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {:d} from char\n", c);
+
+  printf("Integer %i from char\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {:d} from char\n", 'A');
+
+  const signed char sc = 'A';
+  printf("Integer %hhd from signed char\n", sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from signed char\n", sc);
+
+  printf("Integer %hhi from signed char\n", sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from signed char\n", sc);
+
+  const unsigned char uc = 'A';
+  printf("Integer %hhd from unsigned char\n", uc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Integer {} from unsigned char\n", uc);
+  // CHECK-FIXES-STRICT: std::print("Integer {} from unsigned char\n", static_cast<signed char>(uc));
+
+  printf("Integer %hhi from signed char\n", sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from signed char\n", sc);
+
+  const short s = 42;
+  printf("Integer %hd from short\n", s);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from short\n", s);
+
+  printf("Integer %hi from short\n", s);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from short\n", s);
+
+  const unsigned short us = 42U;
+  printf("Integer %hd from unsigned short\n", us);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Integer {} from unsigned short\n", us);
+  // CHECK-FIXES-STRICT: std::print("Integer {} from unsigned short\n", static_cast<short>(us));
+
+  printf("Integer %hi from unsigned short\n", us);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Integer {} from unsigned short\n", us);
+  // CHECK-FIXES-STRICT: std::print("Integer {} from unsigned short\n", static_cast<short>(us));
+
+  const int i = 42;
+  printf("Integer %d from integer\n", i);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from integer\n", i);
+
+  printf("Integer %i from integer\n", i);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from integer\n", i);
+
+  const unsigned int ui = 42U;
+  printf("Integer %d from unsigned integer\n", ui);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Integer {} from unsigned integer\n", ui);
+  // CHECK-FIXES-STRICT: std::print("Integer {} from unsigned integer\n", static_cast<int>(ui));
+
+  printf("Integer %i from unsigned integer\n", ui);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Integer {} from unsigned integer\n", ui);
+  // CHECK-FIXES-STRICT: std::print("Integer {} from unsigned integer\n", static_cast<int>(ui));
+
+  const long l = 42L;
+  printf("Integer %ld from long\n", l);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from long\n", l);
+
+  printf("Integer %li from long\n", l);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from long\n", l);
+
+  const unsigned long ul = 42UL;
+  printf("Integer %ld from unsigned long\n", ul);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Integer {} from unsigned long\n", ul);
+  // CHECK-FIXES-STRICT: std::print("Integer {} from unsigned long\n", static_cast<long>(ul));
+
+  printf("Integer %li from unsigned long\n", ul);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Integer {} from unsigned long\n", ul);
+  // CHECK-FIXES-STRICT: std::print("Integer {} from unsigned long\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from long long\n", ll);
+
+  printf("Integer %lli from long long\n", ll);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from long long\n", ll);
+
+  const unsigned long long ull = 42ULL;
+  printf("Integer %lld from unsigned long long\n", ull);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Integer {} from unsigned long long\n", ull);
+  // CHECK-FIXES-STRICT: std::print("Integer {} from unsigned long long\n", static_cast<long long>(ull));
+
+  printf("Integer %lli from unsigned long long\n", ull);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Integer {} from unsigned long long\n", ull);
+  // CHECK-FIXES-STRICT: std::print("Integer {} from unsigned long long\n", static_cast<long long>(ull));
+
+  const intmax_t im = 42;
+  printf("Integer %jd from intmax_t\n", im);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from intmax_t\n", im);
+
+  printf("Integer %ji from intmax_t\n", im);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from intmax_t\n", im);
+
+  // This doesn't currently work since getCorrespondingSignedTypeName() doesn't recognise uintmax_t.
+  const uintmax_t uim = 42;
+  printf("Integer %jd from uintmax_t\n", uim);
+  // CHECK-MESSAGES-NOTSTRICT: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Integer {} from uintmax_t\n", uim);
+  // CHECK-FIXES-NOTYET-STRICT: std::print("Integer {} from uintmax_t\n", static_cast<intmax_t>(uim));
+
+  printf("Integer %ji from intmax_t\n", uim);
+  // CHECK-MESSAGES-NOTSTRICT: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Integer {} from intmax_t\n", uim);
+  // CHECK-FIXES-NOTYET-STRICT: std::print("Integer {} from intmax_t\n", static_cast<intmax_t>(im));
+
+  // We don't have ptrdiff_t here.
+  const int ai[] = { 0, 1, 2, 3};
+  const auto pd = &ai[3] - &ai[0];
+  printf("Integer %td from ptrdiff_t\n", pd);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from ptrdiff_t\n", pd);
+
+  printf("Integer %ti from ptrdiff_t\n", pd);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Integer {} from ptrdiff_t\n", pd);
+
+  // This doesn't currently work since getCorrespondingSignedTypeName() doesn't
+  // recognize size_t.
+  const size_t z = 42UL;
+  printf("Integer %zd from size_t\n", z);
+  // CHECK-MESSAGES-NOTSTRICT: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Integer {} from size_t\n", z);
+  // CHECK-FIXES-NOTYET-STRICT: std::print("Integer {} from size_t\n", static_cast<ssize_t>(z));
+}
+
+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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Unsigned integer {:d} from bool\n", b);
+
+  const char c = 'A';
+  printf("Unsigned integer %hhu from char\n", c);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Unsigned integer {:d} from char\n", c);
+  // CHECK-FIXES-STRICT: std::print("Unsigned integer {} from char\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Unsigned integer {} from signed char\n", sc);
+  // CHECK-FIXES-STRICT: std::print("Unsigned integer {} from signed char\n", static_cast<unsigned char>(sc));
+
+  printf("Unsigned integer %u from signed char\n", sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Unsigned integer {} from signed char\n", sc);
+  // CHECK-FIXES-STRICT: std::print("Unsigned integer {} from signed char\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Unsigned integer {} from unsigned char\n", uc);
+
+  printf("Unsigned integer %u from unsigned char\n", uc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Unsigned integer {} from unsigned char\n", uc);
+
+  const short s = 42;
+  printf("Unsigned integer %hu from short\n", s);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Unsigned integer {} from short\n", s);
+  // CHECK-FIXES-STRICT: std::print("Unsigned integer {} from short\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Unsigned integer {} from unsigned short\n", us);
+
+  const int i = 42;
+  printf("Unsigned integer %u from signed integer\n", i);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Unsigned integer {} from signed integer\n", i);
+  // CHECK-FIXES-STRICT: std::print("Unsigned integer {} from signed integer\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Unsigned integer {} from unsigned integer\n", ui);
+
+  const long l = 42L;
+  printf("Unsigned integer %u from signed long\n", l);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Unsigned integer {} from signed long\n", l);
+  // CHECK-FIXES-STRICT: std::print("Unsigned integer {} from signed long\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Unsigned integer {} from unsigned long\n", ul);
+
+  const long long ll = 42LL;
+  printf("Unsigned integer %llu from long long\n", ll);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Unsigned integer {} from long long\n", ll);
+  // CHECK-FIXES-STRICT: std::print("Unsigned integer {} from long long\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Unsigned integer {} from unsigned long long\n", ull);
+
+  // This doesn't work yet since getCorrespondingUnsignedTypeName doesn't understand intmax_t.
+  const intmax_t im = 42;
+  printf("Unsigned integer %ju from intmax_t\n", im);
+  // CHECK-MESSAGES-NOTSTRICT: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Unsigned integer {} from intmax_t\n", im);
+  // CHECK-FIXES-NOTYET-STRICT: std::print("Unsigned integer {} from uintmax_t\n", static_cast<intmax_t>(im));
+
+  const uintmax_t uim = 42U;
+  printf("Unsigned integer %ju from uintmax_t\n", uim);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Unsigned integer {} from uintmax_t\n", uim);
+
+  // This can't currently be converted since getCorrespondingUnsignedTypeName
+  // doesn't understand ptrdiff_t.
+  const int ai[] = { 0, 1, 2, 3};
+  const auto pd = &ai[3] - &ai[0];
+  printf("Unsigned integer %tu from ptrdiff_t\n", pd);
+  // CHECK-MESSAGES-NOTSTRICT: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOTSTRICT: std::print("Unsigned integer {} from ptrdiff_t\n", pd);
+  // CHECK-FIXES-NOTYET-STRICT: std::print("Unsigned integer {} from ptrdiff_t\n", static_cast<size_t>(pd));
+
+  const size_t z = 42U;
+  printf("Unsigned integer %zu from size_t\n", z);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Unsigned integer {} from size_t\n", z);
+}
+
+// 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::print' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "Integer {} from integer\n", 42);
+
+  fprintf(stderr, "Integer %i from integer\n", 65);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "Integer {} from integer\n", 65);
+
+  fprintf(stderr, "Integer %i from char\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "Integer {:d} from char\n", 'A');
+
+  fprintf(stderr, "Integer %d from char\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "Integer {:d} from char\n", 'A');
+}
+
+void printf_char() {
+  const char c = 'A';
+  printf("Char %c from char\n", c);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Char {} from char\n", c);
+
+  const signed char sc = 'A';
+  printf("Char %c from signed char\n", sc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Char {:c} from signed char\n", sc);
+
+  const unsigned char uc = 'A';
+  printf("Char %c from unsigned char\n", uc);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Char {:c} from unsigned char\n", uc);
+
+  const int i = 65;
+  printf("Char %c from integer\n", i);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Char {:c} from integer\n", i);
+
+  const unsigned int ui = 65;
+  printf("Char %c from unsigned integer\n", ui);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Char {:c} from unsigned integer\n", ui);
+
+  const unsigned long long ull = 65;
+  printf("Char %c from unsigned long long\n", ull);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Char {:c} from unsigned long long\n", ull);
+}
+
+void printf_bases() {
+  printf("Hex %lx\n", 42L);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hex {:x}\n", 42L);
+
+  printf("HEX %X\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("HEX {:X}\n", 42);
+
+  printf("Oct %lo\n", 42L);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Oct {:o}\n", 42L);
+}
+
+void printf_alternative_forms() {
+  printf("Hex %#lx\n", 42L);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hex {:#x}\n", 42L);
+
+  printf("HEX %#X\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("HEX {:#X}\n", 42);
+
+  printf("Oct %#lo\n", 42L);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Oct {:#o}\n", 42L);
+
+  printf("Double %#f %#F\n", -42.0, -42.0);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Double {:#f} {:#F}\n", -42.0, -42.0);
+
+  printf("Double %#g %#G\n", -42.0, -42.0);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Double {:#g} {:#G}\n", -42.0, -42.0);
+
+  printf("Double %#e %#E\n", -42.0, -42.0);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Double {:#e} {:#E}\n", -42.0, -42.0);
+
+  printf("Double %#a %#A\n", -42.0, -42.0);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Double {:#a} {:#A}\n", -42.0, -42.0);
+
+  // Characters don't have an alternate form
+  printf("Char %#c\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Char {}\n", 'A');
+
+  // Strings don't have an alternate form
+  printf("Char %#c\n", 'A');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Char {}\n", 'A');
+}
+
+void printf_string() {
+  printf("Hello %s after\n", "Goodbye");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {} after\n", "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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("signed char string {}\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("unsigned char string {}\n", 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 different
+  // output?
+
+  const float f = 42.0F;
+  printf("Hello %f after\n", f);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:f} after\n", f);
+
+  printf("Hello %g after\n", f);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:g} after\n", f);
+
+  printf("Hello %e after\n", f);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:e} after\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:f} after\n", d);
+
+  printf("Hello %g after\n", d);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:g} after\n", d);
+
+  printf("Hello %e after\n", d);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:e} after\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:f} after\n", ld);
+
+  printf("Hello %g after\n", ld);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:g} after\n", ld);
+
+  printf("Hello %e after\n", ld);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:e} after\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Int* {} {} {}\n", static_cast<const void *>(&i), "Double*", static_cast<const void *>(&j));
+
+  printf("%p\n", nullptr);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}\n", nullptr);
+
+  const auto np = nullptr;
+  printf("%p\n", np);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}\n", np);
+
+  // NULL isn't a pointer, so std::print needs some help.
+  printf("%p\n", NULL);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}\n", static_cast<const void *>(NULL));
+
+  printf("%p\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}\n", reinterpret_cast<const void *>(44));
+
+  const void *p;
+  printf("%p\n", p);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}\n", static_cast<const void *>(pp));
+
+  printf("%p\n", printf_pointer);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("{}\n", static_cast<const void *>(printf_pointer));
+}
+
+class AClass
+{
+  void printf_this_pointer()
+  {
+    printf("%p\n", this);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::print("{}\n", static_cast<const void *>(this));
+  }
+};
+
+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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Right-justified integer {:4} after\n", 42);
+
+  printf("Right-justified double %4f\n", 227.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Right-justified double {:4f}\n", 227.2);
+
+  printf("Right-justified double %4g\n", 227.4);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Right-justified double {:4g}\n", 227.4);
+
+  printf("Right-justified integer with field width argument %*d after\n", 5, 424242);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Right-justified integer with field width argument {:{}} after\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Right-justified integer with field width argument {1:{0}} after\n", 5, 424242);
+
+  printf("Right-justified string %20s\n", "Hello");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Right-justified string {:>20}\n", "Hello");
+
+  printf("Right-justified string with field width argument %2$*1$s after\n", 20, "wibble");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Right-justified string with field width argument {1:>{0}} after\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Left-justified integer {:<4}\n", 42);
+
+  printf("Left-justified integer %--4d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Left-justified integer {:<4}\n", 42);
+
+  printf("Left-justified double %-4f\n", 227.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Left-justified double {:<4f}\n", 227.2);
+
+  printf("Left-justified double %-4g\n", 227.4);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Left-justified double {:<4g}\n", 227.4);
+
+  printf("Left-justified integer with field width argument %-*d after\n", 5, 424242);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Left-justified integer with field width argument {:<{}} after\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Left-justified integer with field width argument {1:<{0}} after\n", 5, 424242);
+
+  printf("Left-justified string %-20s\n", "Hello");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Left-justified string {:20}\n", "Hello");
+
+  printf("Left-justified string with field width argument %2$-*1$s after\n", 5, "wibble");
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Left-justified string with field width argument {1:{0}} after\n", 5, "wibble");
+}
+
+void printf_precision() {
+  printf("Hello %.3f\n", 3.14159);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:.3f}\n", 3.14159);
+
+  printf("Hello %10.3f\n", 3.14159);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:10.3f}\n", 3.14159);
+
+  printf("Hello %.*f after\n", 10, 3.14159265358979323846);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:.{}f} after\n", 10, 3.14159265358979323846);
+
+  printf("Hello %10.*f after\n", 3, 3.14159265358979323846);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:10.{}f} after\n", 3, 3.14159265358979323846);
+
+  printf("Hello %*.*f after\n", 10, 4, 3.14159265358979323846);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:{}.{}f} after\n", 10, 4, 3.14159265358979323846);
+
+  printf("Hello %1$.*2$f after\n", 3.14159265358979323846, 4);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {0:.{1}f} after\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:.5}\n", "Goodbye");
+
+  printf("Hello %.5c\n", 'G');
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {:.5}\n", '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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {0:{1}.{2}f} after\n", 3.14159265358979323846, 4, 2);
+}
+
+void printf_alternative_form() {
+  printf("Wibble %#x\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Wibble {:#x}\n", 42);
+
+  printf("Wibble %#20x\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Wibble {:#20x}\n", 42);
+
+  printf("Wibble %#020x\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Wibble {:#020x}\n", 42);
+
+  printf("Wibble %#-20x\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Wibble {:<#20x}\n", 42);
+}
+
+void printf_leading_plus() {
+  printf("Positive integer %+d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Positive integer {:+}\n", 42);
+
+  printf("Positive double %+f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Positive double {:+f}\n", 42.2);
+
+  printf("Positive double %+g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Positive double {:+g}\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Positive string {}\n", "string");
+}
+
+void printf_leading_space() {
+  printf("Spaced integer % d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Spaced integer {: }\n", 42);
+
+  printf("Spaced integer %- d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Spaced integer {: }\n", 42);
+
+  printf("Spaced double % f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Spaced double {: f}\n", 42.2);
+
+  printf("Spaced double % g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Spaced double {: g}\n", 42.2);
+}
+
+void printf_leading_zero() {
+  printf("Leading zero integer %03d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Leading zero integer {:03}\n", 42);
+
+  printf("Leading minus and zero integer %-03d minus ignored\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Leading minus and zero integer {:<03} minus ignored\n", 42);
+
+  printf("Leading zero unsigned integer %03u\n", 42U);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Leading zero unsigned integer {:03}\n", 42U);
+
+  printf("Leading zero double %03f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Leading zero double {:03f}\n", 42.2);
+
+  printf("Leading zero double %03g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Leading zero double {:03g}\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Spaced integer {:+}\n", 42);
+
+  printf("Spaced double %+ f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Spaced double {:+f}\n", 42.2);
+
+  printf("Spaced double % +g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Spaced double {:+g}\n", 42.2);
+}
+
+void printf_leading_zero_and_plus() {
+  printf("Leading zero integer %+03d\n", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Leading zero integer {:+03}\n", 42);
+
+  printf("Leading zero double %0+3f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Leading zero double {:+03f}\n", 42.2);
+
+  printf("Leading zero double %0+3g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Leading zero double {:+03g}\n", 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::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Leading zero and space integer {: 03}\n", 42);
+
+  printf("Leading zero and space double %0 3f\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Leading zero and space double {: 03f}\n", 42.2);
+
+  printf("Leading zero and space double %0 3g\n", 42.2);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Leading zero and space double {: 03g}\n", 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));
+}
Index: clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-fmt.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-fmt.cpp
@@ -0,0 +1,30 @@
+// RUN: %check_clang_tidy %s modernize-use-std-print %t -- \
+// RUN:   -config="{CheckOptions: \
+// RUN:             [ \
+// RUN:              { \
+// RUN:               key: modernize-use-std-print.PrintFunction, \
+// RUN:               value: 'fmt::print' \
+// 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\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::print' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: fmt::print("Hello {} {}\n", "world", 42);
+}
+
+void fprintf_simple(FILE *fp) {
+  fprintf(fp, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'fmt::print' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES: fmt::print(fp, "Hello {} {}\n", "world", 42);
+}
Index: clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp
@@ -0,0 +1,62 @@
+// RUN: %check_clang_tidy -std=c++2b %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\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'myprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {} {}\n", "world", 42);
+
+  mynamespace::myprintf2("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'myprintf2' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {} {}\n", "world", 42);
+
+  using mynamespace::myprintf2;
+  myprintf2("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'myprintf2' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {} {}\n", "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\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'myfprintf' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "Hello {} {}\n", "world", 42);
+
+  mynamespace::myfprintf2(stderr, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'myfprintf2' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "Hello {} {}\n", "world", 42);
+
+  using mynamespace::myfprintf2;
+  myfprintf2(stderr, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'myfprintf2' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(stderr, "Hello {} {}\n", "world", 42);
+
+  // When using custom options leave fprintf alone
+  fprintf(stderr, "Hello %s %d\n", "world", 42);
+}
Index: clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp
@@ -0,0 +1,36 @@
+// RUN: %check_clang_tidy -std=c++2b %s modernize-use-std-print %t -- -- -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\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'PrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {} {}\n", "world", 42);
+
+  using namespace absl;
+  PrintF("Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'PrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::print("Hello {} {}\n", "world", 42);
+}
+
+void fprintf_simple(FILE *fp) {
+  absl::FPrintF(fp, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'FPrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(fp, "Hello {} {}\n", "world", 42);
+
+  using namespace absl;
+  FPrintF(fp, "Hello %s %d\n", "world", 42);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'FPrintF' [modernize-use-std-print]
+  // CHECK-FIXES: std::print(fp, "Hello {} {}\n", "world", 42);
+}
Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h
===================================================================
--- clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h
+++ 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_
 
Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio
===================================================================
--- clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio
+++ clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio
@@ -1,4 +1,4 @@
-//===--- stdio.h - Stub header for tests ------------------------*- C++ -*-===//
+//===--- 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.
@@ -6,13 +6,16 @@
 //
 //===----------------------------------------------------------------------===//
 
-#ifndef _STDIO_H_
-#define _STDIO_H_
+#ifndef _STDIO_
+#define _STDIO_
 
-// A header intended to contain C standard input and output library
-// declarations.
+#include <stdio.h>
 
-int printf(const char *, ...);
+namespace std {
+    using ::FILE;
+    using ::printf;
+    using ::fprintf;
+}
 
-#endif // _STDIO_H_
+#endif // _STDIO_
 
Index: clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst
===================================================================
--- /dev/null
+++ clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst
@@ -0,0 +1,137 @@
+.. title:: clang-tidy - modernize-use-std-print
+
+modernize-use-std-print
+=======================
+
+Converts calls to ``printf``, ``fprintf``, ``absl::PrintF``,
+``absl::FPrintf`` or other functions via a configuration option, to
+equivalent calls to C++23's ``std::print`` or another function via a
+configuration option, modifying the format string appropriately.
+
+In other words, it turns lines like::
+
+.. code-block:: c++
+
+  fprintf(stderr, "The %s is %3d\n", answer, value);
+
+into::
+
+.. code-block:: c++
+
+  std::print(stderr, "The {} is {:3}\n", answer, value);
+
+and works for ``printf``, ``fprintf``, ``absl::PrintF``, ``absl::FPrintf``
+and any functions specified via the ``PrintfLikeFunctions`` and
+``FprintfLikeFunctions`` options.
+
+It doesn't do a bad job, but it's not perfect. In particular:
+
+- It assumes that the input is mostly sane. 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. 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
+  ``PrintFunction`` option.
+- the format string is rewritten to use the ``std::formatter`` language.
+- 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 differ in
+  signedness will be wrapped in an approprate ``static_cast``. For example,
+  given ``int i = 42``, then ``printf("%u\n", i)`` will become
+  ``std::printf("{}\n", static_cast<unsigned int>(i))``.
+
+Options
+-------
+
+.. option:: StrictMode
+
+   When true, the check will add casts when printing signed or unsigned
+   types as the opposite signedness to ensure that the output matches that
+   of ``printf``. For example:
+
+  .. code-block:: c++
+
+    int i = -42;
+    unsigned int u = 0xffffffff;
+    printf("%d %u\n", i, u);
+
+  would be turned into::
+
+  .. 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:: PrintFunction
+
+   The function that will be used to replace ``printf``, ``fprintf`` etc.
+   during conversion rather than the default ``std::print``. It is expected
+   that the function provides an interface that is compatible with
+   ``std::print``. A suitable candidate would be ``fmt::print``.
+
+.. option:: PrintHeader
+
+   The header that must be included for the declaration of
+   ``PrintFunction`` so that a ``#include`` directive can be added if
+   required. If ``PrintFunction`` is ``std::print`` then this option will
+   default to ``<print>``, otherwise this option will default to nothing
+   and no ``#include`` directive will be added.
+
+Companion checks
+----------------
+
+It is helpful to use the `readability-redundant-string-cstr
+<../readability/redundant-string-cstr.html>` check after this check to
+remove now-unnecessary calls to ``std::string::c_str()``.
Index: clang-tools-extra/docs/clang-tidy/checks/list.rst
===================================================================
--- clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -280,6 +280,7 @@
    `modernize-make-shared <modernize/make-shared.html>`_, "Yes"
    `modernize-make-unique <modernize/make-unique.html>`_, "Yes"
    `modernize-pass-by-value <modernize/pass-by-value.html>`_, "Yes"
+   `modernize-use-std-print <modernize/use-std-print>`_, "Yes"
    `modernize-raw-string-literal <modernize/raw-string-literal.html>`_, "Yes"
    `modernize-redundant-void-arg <modernize/redundant-void-arg.html>`_, "Yes"
    `modernize-replace-auto-ptr <modernize/replace-auto-ptr.html>`_, "Yes"
Index: clang-tools-extra/docs/ReleaseNotes.rst
===================================================================
--- clang-tools-extra/docs/ReleaseNotes.rst
+++ clang-tools-extra/docs/ReleaseNotes.rst
@@ -165,6 +165,14 @@
   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 a configuration option, to
+  equivalent calls to C++23's ``std::print`` or another function via a
+  configuration option, modifying the format string appropriately.
+
 - New :doc:`performance-avoid-endl
   <clang-tidy/checks/performance/avoid-endl>` check.
 
Index: clang-tools-extra/clang-tidy/utils/FormatStringConverter.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/utils/FormatStringConverter.h
@@ -0,0 +1,67 @@
+//===--- 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 "llvm/ADT/Optional.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:
+  FormatStringConverter(const ASTContext *Context, const CallExpr *Call,
+                        unsigned FormatArgOffset, bool StrictMode,
+                        const LangOptions &LO);
+
+  bool canApply() const { return ConversionPossible; }
+  void applyFixes(DiagnosticBuilder &Diag, SourceManager &SM);
+
+private:
+  const ASTContext *Context;
+  const bool StrictMode;
+  const Expr *const *Args;
+  const unsigned NumArgs;
+  unsigned ArgsOffset;
+  const LangOptions &LangOpts;
+  bool ConversionPossible = true;
+  bool FormatStringNeededRewriting = false;
+  size_t PrintfFormatStringPos = 0U;
+  StringRef PrintfFormatString;
+
+  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;
+
+  bool HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier &FS,
+                             const char *startSpecifier, unsigned specifierLen,
+                             const TargetInfo &Target) override;
+  void appendFormatText(StringRef Text);
+  void finalizeFormatText();
+};
+
+} // namespace clang::tidy::utils
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FORMATSTRINGCONVERTER_H
Index: clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp
@@ -0,0 +1,536 @@
+//===--- 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 "clang/AST/Expr.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Lex/Lexer.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/Debug.h"
+
+namespace {
+/// Is the passed type the actual "char" type, whether that be signed or
+/// unsigned, rather than explicit signed char or unsigned char types.
+bool isRealCharType(const clang::QualType &Ty) {
+  using namespace clang;
+  if (const auto *BT = llvm::dyn_cast<BuiltinType>(Ty)) {
+    const bool result = (BT->getKind() == BuiltinType::Char_U ||
+                         BT->getKind() == BuiltinType::Char_S);
+    return result;
+  } else {
+    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.
+std::optional<llvm::StringRef>
+getCorrespondingSignedTypeName(const clang::QualType &QT) {
+  using namespace clang;
+  if (const auto *BT = llvm::dyn_cast<BuiltinType>(QT)) {
+    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;
+    }
+  } else {
+    llvm::dbgs() << "Unknown corresponding signed type for non-BuiltinType '"
+                 << QT.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.
+std::optional<llvm::StringRef>
+getCorrespondingUnsignedTypeName(const clang::QualType &QT) {
+  using namespace clang;
+  if (const auto *BT = llvm::dyn_cast<BuiltinType>(QT)) {
+    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 '"
+                   << QT.getAsString() << "'\n";
+      return std::nullopt;
+    }
+  } else {
+    llvm::dbgs() << "Unknown corresponding unsigned type for non-BuiltinType '"
+                 << QT.getAsString() << "'\n";
+    return std::nullopt;
+  }
+}
+} // namespace
+
+namespace clang::tidy::utils {
+
+FormatStringConverter::FormatStringConverter(const ASTContext *ContextIn,
+                                             const CallExpr *Call,
+                                             unsigned FormatArgOffset,
+                                             bool StrictMode,
+                                             const LangOptions &LO)
+    : Context(ContextIn), StrictMode(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);
+  PrintfFormatString = FormatExpr->getString();
+
+  // Assume that the output will be approximately the same size as the input,
+  // but perhaps with a few escapes expanded.
+  StandardFormatString.reserve(PrintfFormatString.size() + 8);
+  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);
+}
+
+/// Called for each format specifier by ParsePrintfString.
+bool FormatStringConverter::HandlePrintfSpecifier(
+    const analyze_printf::PrintfSpecifier &FS, const char *StartSpecifier,
+    unsigned SpecifierLen, const TargetInfo &Target) {
+  using namespace analyze_printf;
+
+  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));
+
+  using analyze_format_string::ConversionSpecifier;
+  const ConversionSpecifier Spec = FS.getConversionSpecifier();
+
+  if (Spec.getKind() == ConversionSpecifier::PercentArg)
+    StandardFormatString.push_back('%');
+  else if (Spec.getKind() == ConversionSpecifier::Kind::nArg) {
+    // std::format doesn't do the equivalent of %n
+    ConversionPossible = false;
+    return false;
+  } else if (Spec.getKind() == ConversionSpecifier::Kind::PrintErrno) {
+    // std::format 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.
+    ConversionPossible = false;
+    return false;
+  } else {
+    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:
+    // [[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.
+
+    // Convert alignment
+    {
+      // We only care about alignment if a field width is specified
+      if (FS.getFieldWidth().getHowSpecified() !=
+          OptionalAmount::NotSpecified) {
+        const ConversionSpecifier Spec = FS.getConversionSpecifier();
+        if (Spec.getKind() == 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('<');
+        }
+      }
+    }
+
+    // Convert sign
+    {
+      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(' ');
+      }
+    }
+
+    // Convert alternative form
+    if (FS.hasAlternativeForm()) {
+      switch (Spec.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;
+      }
+    }
+
+    // Convert leading zero
+    if (FS.hasLeadingZeros())
+      FormatSpec.push_back('0');
+
+    // Convert field width
+    {
+      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;
+      }
+    }
+
+    // Convert precision
+    {
+      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;
+      }
+    }
+
+    // Convert type
+    {
+      if (FS.getArgIndex() + ArgsOffset >= NumArgs) {
+        // Argument index out of range. Give up.
+        ConversionPossible = false;
+        return false;
+      }
+
+      // If we've got this far, then the specifier must have an associated
+      // argument
+      assert(FS.consumesDataArgument());
+
+      const Expr *Arg =
+          Args[FS.getArgIndex() + ArgsOffset]->IgnoreImplicitAsWritten();
+      using analyze_format_string::ConversionSpecifier;
+      const ConversionSpecifier Spec = FS.getConversionSpecifier();
+      switch (Spec.getKind()) {
+      case ConversionSpecifier::Kind::sArg:
+        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 *>(");
+        }
+        // Strings never need to have their type specified.
+        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:
+        if (Arg->getType()->isBooleanType()) {
+          // std::format will print bool as either "true" or "false" by default,
+          // but printf prints them as "0" or "1". Ber compatible with printf by
+          // requesting decimal output.
+          FormatSpec.push_back('d');
+        } else if (Arg->getType()->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 = Arg->getType()->getAs<EnumType>()) {
+            if (const std::optional<llvm::StringRef> MaybeSignedTypeName =
+                    getCorrespondingSignedTypeName(
+                        ET->getDecl()->getIntegerType()))
+              ArgFixes.emplace_back(
+                  Arg,
+                  (Twine("static_cast<") + *MaybeSignedTypeName + ">(").str());
+            else
+              ConversionPossible = false;
+          }
+        } else if (Arg->getType()->isUnsignedIntegerType() && StrictMode) {
+          // 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<llvm::StringRef> MaybeSignedTypeName =
+                  getCorrespondingSignedTypeName(Arg->getType()))
+            ArgFixes.emplace_back(
+                Arg,
+                (Twine("static_cast<") + *MaybeSignedTypeName + ">(").str());
+          else
+            ConversionPossible = false;
+        } else if (isRealCharType(Arg->getType()) ||
+                   !Arg->getType()->isIntegerType()) {
+          // Only specify integer if the argument is of a different type
+          FormatSpec.push_back('d');
+        }
+        break;
+      case ConversionSpecifier::Kind::uArg:
+        if (Arg->getType()->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 = Arg->getType()->getAs<EnumType>()) {
+            if (const std::optional<llvm::StringRef> MaybeUnsignedTypeName =
+                    getCorrespondingUnsignedTypeName(
+                        ET->getDecl()->getIntegerType()))
+              ArgFixes.emplace_back(
+                  Arg, (Twine("static_cast<") + *MaybeUnsignedTypeName + ">(")
+                           .str());
+            else
+              ConversionPossible = false;
+          }
+        } else if (Arg->getType()->isSignedIntegerType() && StrictMode) {
+          // printf will happily print an signed type as unsigned if told to.
+          // Even -Wformat doesn't warn for this. std::format will format as
+          // signed unless we cast it.
+          if (const std::optional<llvm::StringRef> MaybeUnsignedTypeName =
+                  getCorrespondingUnsignedTypeName(Arg->getType()))
+            ArgFixes.emplace_back(
+                Arg,
+                (Twine("static_cast<") + *MaybeUnsignedTypeName + ">(").str());
+          else
+            ConversionPossible = false;
+        } else if (isRealCharType(Arg->getType()) ||
+                   Arg->getType()->isBooleanType() ||
+                   !Arg->getType()->isIntegerType()) {
+          // Only specify integer if the argument is of a different type
+          FormatSpec.push_back('d');
+        }
+        break;
+      case ConversionSpecifier::Kind::pArg:
+        // std::format knows how to format void pointers and nullptrs
+        if (!Arg->getType()->isNullPtrType() &&
+            !Arg->getType()->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
+        ConversionPossible = false;
+        return false;
+      }
+    }
+
+    if (!FormatSpec.empty()) {
+      StandardFormatString.push_back(':');
+      StandardFormatString.append(FormatSpec);
+    }
+
+    StandardFormatString.push_back('}');
+  }
+
+  // Skip over specifier
+  PrintfFormatStringPos = StartSpecifierPos + SpecifierLen;
+  assert(PrintfFormatStringPos <= PrintfFormatString.size());
+
+  FormatStringNeededRewriting = true;
+  return true;
+}
+
+/// 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();
+
+  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) {
+  finalizeFormatText();
+  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, ")");
+  }
+}
+} // namespace clang::tidy::utils
Index: clang-tools-extra/clang-tidy/utils/CMakeLists.txt
===================================================================
--- clang-tools-extra/clang-tidy/utils/CMakeLists.txt
+++ clang-tools-extra/clang-tidy/utils/CMakeLists.txt
@@ -11,6 +11,7 @@
   ExceptionSpecAnalyzer.cpp
   ExprSequence.cpp
   FileExtensionsUtils.cpp
+  FormatStringConverter.cpp
   FixItHintUtils.cpp
   HeaderGuard.cpp
   IncludeInserter.cpp
Index: clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h
@@ -0,0 +1,40 @@
+//===--- 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 {
+class UseStdPrintCheck : public ClangTidyCheck {
+public:
+  UseStdPrintCheck(StringRef Name, ClangTidyContext *Context);
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    if (ReplacementPrintFunction == "std::print")
+      return LangOpts.CPlusPlus23;
+    return LangOpts.CPlusPlus;
+  }
+  void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+                           Preprocessor *ModuleExpanderPP) override;
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+  bool StrictMode;
+  std::vector<StringRef> PrintfLikeFunctions;
+  std::vector<StringRef> FprintfLikeFunctions;
+  StringRef ReplacementPrintFunction;
+  utils::IncludeInserter IncludeInserter;
+  std::optional<StringRef> MaybeHeaderToInclude;
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USESTDPRINTCHECK_H
Index: clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
@@ -0,0 +1,101 @@
+//===--- 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"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+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("PrintFunction", "std::print")),
+      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")
+    MaybeHeaderToInclude = "<print>";
+}
+
+void UseStdPrintCheck::registerPPCallbacks(const SourceManager &SM,
+                                           Preprocessor *PP,
+                                           Preprocessor *ModuleExpanderPP) {
+  IncludeInserter.registerPreprocessor(PP);
+}
+
+void UseStdPrintCheck::registerMatchers(MatchFinder *Finder) {
+  Finder->addMatcher(
+      traverse(TK_IgnoreUnlessSpelledInSource,
+               callExpr(callee(functionDecl(matchers::matchesAnyListedName(
+                                                PrintfLikeFunctions))
+                                   .bind("func_decl")),
+                        hasArgument(0, stringLiteral()))
+                   .bind("printf")),
+      this);
+
+  Finder->addMatcher(
+      traverse(TK_IgnoreUnlessSpelledInSource,
+               callExpr(callee(functionDecl(matchers::matchesAnyListedName(
+                                                FprintfLikeFunctions))
+                                   .bind("func_decl")),
+                        hasArgument(1, stringLiteral()))
+                   .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());
+  if (Converter.canApply()) {
+    const Expr *PrintfCall = Printf->getCallee();
+    DiagnosticBuilder Diag =
+        diag(PrintfCall->getBeginLoc(), "use '%0' instead of %1")
+        << ReplacementPrintFunction << OldFunction->getIdentifier();
+
+    Diag << FixItHint::CreateReplacement(
+        CharSourceRange::getTokenRange(PrintfCall->getBeginLoc(),
+                                       PrintfCall->getEndLoc()),
+        ReplacementPrintFunction);
+    Converter.applyFixes(Diag, *Result.SourceManager);
+
+    if (MaybeHeaderToInclude)
+      Diag << IncludeInserter.createIncludeInsertion(
+          Result.Context->getSourceManager().getFileID(
+              PrintfCall->getBeginLoc()),
+          *MaybeHeaderToInclude);
+  }
+}
+
+} // namespace clang::tidy::modernize
Index: clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
===================================================================
--- clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ 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 @@
     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>(
Index: clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
===================================================================
--- clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -37,6 +37,7 @@
   UseNoexceptCheck.cpp
   UseNullptrCheck.cpp
   UseOverrideCheck.cpp
+  UseStdPrintCheck.cpp
   UseTrailingReturnTypeCheck.cpp
   UseTransparentFunctorsCheck.cpp
   UseUncaughtExceptionsCheck.cpp
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to