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
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits