diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -18,6 +18,7 @@ MakeUniqueCheck.cpp ModernizeTidyModule.cpp PassByValueCheck.cpp + PrintfToStdPrintCheck.cpp RawStringLiteralCheck.cpp RedundantVoidArgCheck.cpp ReplaceAutoPtrCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -19,6 +19,7 @@ #include "MakeSharedCheck.h" #include "MakeUniqueCheck.h" #include "PassByValueCheck.h" +#include "PrintfToStdPrintCheck.h" #include "RawStringLiteralCheck.h" #include "RedundantVoidArgCheck.h" #include "ReplaceAutoPtrCheck.h" @@ -64,6 +65,8 @@ CheckFactories.registerCheck("modernize-make-shared"); CheckFactories.registerCheck("modernize-make-unique"); CheckFactories.registerCheck("modernize-pass-by-value"); + CheckFactories.registerCheck( + "modernize-printf-to-std-print"); CheckFactories.registerCheck( "modernize-raw-string-literal"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/modernize/PrintfToStdPrintCheck.h b/clang-tools-extra/clang-tidy/modernize/PrintfToStdPrintCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/PrintfToStdPrintCheck.h @@ -0,0 +1,37 @@ +//===--- PrintfConvertCheck.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_FMT_PRINTFTOSTDPRINTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FMT_PRINTFTOSTDPRINTCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::modernize { +/// Documentation goes here. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/fmt-to-std-print-check.html +class PrintfToStdPrintCheck : public ClangTidyCheck { + std::vector PrintfLikeFunctions; + std::vector FprintfLikeFunctions; + StringRef ReplacementPrintFunction; + +public: + PrintfToStdPrintCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + if (ReplacementPrintFunction == "std::print") + return LangOpts.CPlusPlus2b; + return LangOpts.CPlusPlus; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FMT_PRINTFTOSTDPRINTCHECK_H diff --git a/clang-tools-extra/clang-tidy/modernize/PrintfToStdPrintCheck.cpp b/clang-tools-extra/clang-tidy/modernize/PrintfToStdPrintCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/PrintfToStdPrintCheck.cpp @@ -0,0 +1,79 @@ +//===--- PrintfToStdPrintCheck.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 "PrintfToStdPrintCheck.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 { + +PrintfToStdPrintCheck::PrintfToStdPrintCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + PrintfLikeFunctions(utils::options::parseStringList( + Options.get("PrintfLikeFunctions", ""))), + FprintfLikeFunctions(utils::options::parseStringList( + Options.get("FprintfLikeFunctions", ""))), + ReplacementPrintFunction(Options.get("PrintFunction", "std::print")) { + PrintfLikeFunctions.push_back("printf"); + PrintfLikeFunctions.push_back("absl::PrintF"); + FprintfLikeFunctions.push_back("fprintf"); + FprintfLikeFunctions.push_back("absl::FPrintF"); +} + +void PrintfToStdPrintCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + traverse(TK_AsIs, + callExpr(callee(functionDecl(matchers::matchesAnyListedName( + PrintfLikeFunctions)) + .bind("func_decl")), + hasArgument(0, stringLiteral())) + .bind("printf")), + this); + + Finder->addMatcher( + traverse(TK_AsIs, + callExpr(callee(functionDecl(matchers::matchesAnyListedName( + FprintfLikeFunctions)) + .bind("func_decl")), + hasArgument(1, stringLiteral())) + .bind("fprintf")), + this); +} + +void PrintfToStdPrintCheck::check(const MatchFinder::MatchResult &Result) { + unsigned FormatArgOffset = 0; + const auto *OldFunction = Result.Nodes.getNodeAs("func_decl"); + const auto *Printf = Result.Nodes.getNodeAs("printf"); + if (!Printf) { + Printf = Result.Nodes.getNodeAs("fprintf"); + FormatArgOffset = 1; + } + + utils::FormatStringConverter Converter(Result.Context, Printf, + FormatArgOffset, getLangOpts()); + if (Converter.canApply()) { + const auto *PrintfCall = Printf->getCallee(); + DiagnosticBuilder Diag = + diag(PrintfCall->getBeginLoc(), "Replace %0 with '%1'") + << OldFunction->getIdentifier() << ReplacementPrintFunction; + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(PrintfCall->getBeginLoc(), + PrintfCall->getEndLoc()), + ReplacementPrintFunction); + Converter.applyFixes(Diag, *Result.SourceManager); + } +} + +} // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt +++ b/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 diff --git a/clang-tools-extra/clang-tidy/utils/FormatStringConverter.h b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.h @@ -0,0 +1,66 @@ +//===--- 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 + +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 { + const ASTContext *Context; + bool ConversionPossible = true; + bool FormatStringNeededRewriting = false; + size_t PrintfFormatStringPos = 0U; + StringRef PrintfFormatString; + + const Expr *const *Args; + const unsigned NumArgs; + unsigned ArgsOffset; + const LangOptions &LangOpts; + + const StringLiteral *FormatExpr; + std::string StandardFormatString; + + /// Casts to be used to wrap arguments to retain printf compatibility. + std::vector> ArgFixes; + + virtual bool HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier &FS, + const char *startSpecifier, + unsigned specifierLen, + const TargetInfo &Target) override; + void appendFormatText(StringRef Text); + void finalizeFormatText(); + +public: + FormatStringConverter(const ASTContext *Context, const CallExpr *Call, + unsigned FormatArgOffset, const LangOptions &LO); + + bool canApply() const { return ConversionPossible; } + void applyFixes(DiagnosticBuilder &Diag, SourceManager &SM); +}; + +} // namespace clang::tidy::utils + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FORMATSTRINGCONVERTER_H diff --git a/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp @@ -0,0 +1,535 @@ +//===--- 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(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 +getCorrespondingSignedTypeName(const clang::QualType &QT) { + using namespace clang; + if (const auto *BT = llvm::dyn_cast(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 +getCorrespondingUnsignedTypeName(const clang::QualType &QT) { + using namespace clang; + if (const auto *BT = llvm::dyn_cast(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, + const LangOptions &LO) + : Context(ContextIn), Args(Call->getArgs()), NumArgs(Call->getNumArgs()), + ArgsOffset(FormatArgOffset + 1), LangOpts(LO) { + assert(ArgsOffset <= NumArgs); + FormatExpr = llvm::dyn_cast( + 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 auto Pointee = Arg->getType()->getPointeeType(); + // If the type is not char then std::format can't handle it without + // needing a cast + if (!isRealCharType(Pointee)) + ArgFixes.emplace_back(Arg, "reinterpret_cast("); + } + // 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()) { + if (const auto MaybeSignedTypeName = getCorrespondingSignedTypeName( + ET->getDecl()->getIntegerType())) + ArgFixes.emplace_back( + Arg, + (Twine("static_cast<") + *MaybeSignedTypeName + ">(").str()); + else + ConversionPossible = false; + } + } else if (Arg->getType()->isUnsignedIntegerType()) { + // 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 auto 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()) { + if (const auto MaybeUnsignedTypeName = + getCorrespondingUnsignedTypeName( + ET->getDecl()->getIntegerType())) + ArgFixes.emplace_back( + Arg, (Twine("static_cast<") + *MaybeUnsignedTypeName + ">(") + .str()); + else + ConversionPossible = false; + } + } else if (Arg->getType()->isSignedIntegerType()) { + // 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 auto 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: + if (Arg->getType()->isNullPtrType()) + ; // std::format knows how to format nullptr + else if (Arg->getType()->isVoidPointerType()) + ; // std::format knows how to format void pointers + else + ArgFixes.emplace_back(Arg, "reinterpret_cast("); + 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 diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -168,6 +168,14 @@ Enforces consistent token representation for invoked binary, unary and overloaded operators in C++ code. +- New :doc: `modernize-printf-to-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 check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -278,6 +278,7 @@ `modernize-make-shared `_, "Yes" `modernize-make-unique `_, "Yes" `modernize-pass-by-value `_, "Yes" + `modernize-printf-to-std-print `_, "Yes" `modernize-raw-string-literal `_, "Yes" `modernize-redundant-void-arg `_, "Yes" `modernize-replace-auto-ptr `_, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/printf-to-std-print.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/printf-to-std-print.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/printf-to-std-print.rst @@ -0,0 +1,94 @@ +.. title:: clang-tidy - modernize-printf-to-std-print + +modernize-printf-to-std-print +============================= + +This check is capable of converting calls to printf to calls to std::print +whilst also modifying the format string appropriately. + +In other words, it turns lines like:: + + fprintf(stderr, "The %s is %3d\n", answer, value); + +into:: + + 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 + ```` are removed correctly. + +- It supports field widths, precision, positional arguments, leading zeros, + leading +, alignment and alternative forms. + +- It is assumed that the ```` (or other appropriate) header has + already been included. No attempt is made to include it. + +- 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 ``reinterpret_cast`` to ``const void *``. +- 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(i))``. + +Options +------- + +.. option:: PrintfLikeFunctions + + A semicolon-separated list of (fully qualified) function/method/operator + names, with the requirement that the first parameter contains the + printf-style format string and the arguments to be formatted follow + immediately afterwards. + +.. option:: FprintfLikeFunctions + + A semicolon-separated list of (fully qualified) function/method/operator + names, 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. + +.. 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``. + +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()``. diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio copy from clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h copy to clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cstdio --- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h +++ b/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 -int printf(const char *, ...); +namespace std { + using ::FILE; + using ::printf; + using ::fprintf; +} -#endif // _STDIO_H_ +#endif // _STDIO_ diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h --- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdio.h @@ -12,7 +12,13 @@ // A header intended to contain C standard input and output library // declarations. +typedef struct structFILE {} FILE; +extern FILE *stderr; + int printf(const char *, ...); +int fprintf(FILE *, const char *, ...); + +#define NULL (0) #endif // _STDIO_H_ diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-fmt-print-convert.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-fmt-print-convert.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-fmt-print-convert.cpp @@ -0,0 +1,21 @@ +// RUN: %check_clang_tidy %s modernize-printf-to-std-print %t -- \ +// RUN: -config="{CheckOptions: \ +// RUN: [{key: modernize-printf-to-std-print.PrintFunction, \ +// RUN: value: 'fmt::print'}] \ +// RUN: }" \ +// RUN: -- -isystem %clang_tidy_headers + +#include +#include + +void printf_simple() { + printf("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'fmt::print' [modernize-printf-to-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: Replace 'fprintf' with 'fmt::print' [modernize-printf-to-std-print] + // CHECK-FIXES: fmt::print(fp, "Hello {} {}\n", "world", 42); +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert-absl.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert-absl.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert-absl.cpp @@ -0,0 +1,36 @@ +// RUN: %check_clang_tidy -std=c++2b %s modernize-printf-to-std-print %t -- -- -isystem %clang_tidy_headers + +#include +#include + +namespace absl +{ +// Use const char * for the format since the real type is hard to mock up. +template +int PrintF(const char *format, const Args&... args); + +template +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: Replace 'PrintF' with 'std::print' [modernize-printf-to-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: Replace 'PrintF' with 'std::print' [modernize-printf-to-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: Replace 'FPrintF' with 'std::print' [modernize-printf-to-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: Replace 'FPrintF' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(fp, "Hello {} {}\n", "world", 42); +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert-custom.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert-custom.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert-custom.cpp @@ -0,0 +1,56 @@ +// RUN: %check_clang_tidy -std=c++2b %s modernize-printf-to-std-print %t -- \ +// RUN: -config="{CheckOptions: \ +// RUN: [ \ +// RUN: { \ +// RUN: key: modernize-printf-to-std-print.PrintfLikeFunctions, \ +// RUN: value: '::myprintf; mynamespace::myprintf2' \ +// RUN: }, \ +// RUN: { \ +// RUN: key: modernize-printf-to-std-print.FprintfLikeFunctions, \ +// RUN: value: '::myfprintf; mynamespace::myfprintf2' \ +// RUN: } \ +// RUN: ] \ +// RUN: }" \ +// RUN: -- -isystem %clang_tidy_headers + +#include +#include + +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: Replace 'myprintf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {} {}\n", "world", 42); + + mynamespace::myprintf2("Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'myprintf2' with 'std::print' [modernize-printf-to-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: Replace 'myprintf2' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {} {}\n", "world", 42); +} + +void fprintf_simple(FILE *fp) +{ + myfprintf(stderr, "Hello %s %d\n", "world", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'myfprintf' with 'std::print' [modernize-printf-to-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: Replace 'myfprintf2' with 'std::print' [modernize-printf-to-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: Replace 'myfprintf2' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(stderr, "Hello {} {}\n", "world", 42); +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/printf-to-std-print-convert.cpp @@ -0,0 +1,1021 @@ +// RUN: %check_clang_tidy -std=c++2b %s modernize-printf-to-std-print %t -- -- -isystem %clang_tidy_headers + +#include +#include +#include + +void printf_simple() { + printf("Hello"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello"); +} + +void fprintf_simple() { + fprintf(stderr, "Hello"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'fprintf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print(stderr, "Hello"); +} + +void std_printf_simple() { + std::printf("std::Hello"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("std::Hello"); +} + +void printf_escape() { + printf("before \t"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("before \t"); + + printf("\n after"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("\n after"); + + printf("before \a after"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("not special \x1b\x01\x7f"); +} + +void printf_percent() { + printf("before %%"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("before %"); + + printf("%% after"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("% after"); + + printf("before %% after"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("before % after"); + + printf("Hello %% and another %%"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello % and another %"); + + printf("Not a string %%s"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Not a string %s"); +} + +void printf_curlies() { + printf("%d {}", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{} {{[{][{]}}}}", 42); + + printf("{}"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned char\n", static_cast(uc)); + + printf("Integer %hhi from signed char\n", sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from short\n", s); + + printf("Integer %hi from short\n", s); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned short\n", static_cast(us)); + + printf("Integer %hi from unsigned short\n", us); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned short\n", static_cast(us)); + + const int i = 42; + printf("Integer %d from integer\n", i); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from integer\n", i); + + printf("Integer %i from integer\n", i); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned integer\n", static_cast(ui)); + + printf("Integer %i from unsigned integer\n", ui); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned integer\n", static_cast(ui)); + + const long l = 42L; + printf("Integer %ld from long\n", l); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from long\n", l); + + printf("Integer %li from long\n", l); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned long\n", static_cast(ul)); + + printf("Integer %li from unsigned long\n", ul); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned long\n", static_cast(ul)); + + const long long ll = 42LL; + printf("Integer %lld from long long\n", ll); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned long long\n", static_cast(ull)); + + printf("Integer %lli from unsigned long long\n", ull); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned long long\n", static_cast(ull)); + + const intmax_t im = 42; + printf("Integer %jd from intmax_t\n", im); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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-NOTYET: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES-NOTYET: std::print("Integer {} from uintmax_t\n", static_cast(im)); + + printf("Integer %ji from intmax_t\n", uim); + // CHECK-MESSAGES-NOTYET: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES-NOTYET: std::print("Integer {} from uintmax_t\n", static_cast(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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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-NOTYET: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES-NOTYET: std::print("Integer {} from size_t\n", 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("Integer %u from bool\n", b); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {:d} from bool\n", b); + + const char c = 'A'; + printf("Unsigned integer %hhu from char\n", c); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Unsigned integer {} from char\n", static_cast(c)); + + const unsigned char sc = 'A'; + printf("Integer %hhu from signed char\n", sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from signed char\n", sc); + + printf("Integer %u from signed char\n", sc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from signed char\n", sc); + + const unsigned char uc = 'A'; + printf("Integer %hhu from unsigned char\n", uc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned char\n", uc); + + printf("Integer %u from unsigned char\n", uc); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned char\n", uc); + + const short s = 42; + printf("Integer %hu from short\n", s); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from short\n", static_cast(s)); + + const unsigned short us = 42U; + printf("Integer %hu from unsigned short\n", us); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned short\n", us); + + const int i = 42; + printf("Integer %u from signed integer\n", i); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from signed integer\n", static_cast(i)); + + const unsigned int ui = 42U; + printf("Integer %u from unsigned integer\n", ui); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned integer\n", ui); + + const long l = 42L; + printf("Integer %u from signed long\n", l); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from signed long\n", static_cast(l)); + + const unsigned long ul = 42UL; + printf("Integer %lu from unsigned long\n", ul); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from unsigned long\n", ul); + + const long long ll = 42LL; + printf("Integer %llu from long long\n", ll); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from long long\n", static_cast(ll)); + + const unsigned long long ull = 42ULL; + printf("Integer %llu from unsigned long long\n", ull); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("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("Integer %ju from intmax_t\n", im); + // CHECK-MESSAGES-NOTYET: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES-NOTYET: std::print("Integer {} from uintmax_t\n", uim); + + const uintmax_t uim = 42U; + printf("Integer %ju from uintmax_t\n", uim); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Integer {} from uintmax_t\n", uim); + + // This can't currently be converted since getCorrespodningUnsignedTypeName + // doesn't understand ptrdiff_t. + const int ai[] = { 0, 1, 2, 3}; + const auto pd = &ai[3] - &ai[0]; + printf("Integer %tu from ptrdiff_t\n", pd); + // CHECK-MESSAGES-NOTYET: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES-NOTYET: std::print("Integer {} from ptrdiff_t\n", pd); + + const size_t z = 42U; + printf("%zu\n", z); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\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: Replace 'fprintf' with 'std::print' [modernize-printf-to-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: Replace 'fprintf' with 'std::print' [modernize-printf-to-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: Replace 'fprintf' with 'std::print' [modernize-printf-to-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: Replace 'fprintf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hex {:x}\n", 42L); + + printf("HEX %X\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("HEX {:X}\n", 42); + + printf("Oct %lo\n", 42L); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hex {:#x}\n", 42L); + + printf("HEX %#X\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("HEX {:#X}\n", 42); + + printf("Oct %#lo\n", 42L); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Char {}\n", 'A'); +} + +void printf_string() { + printf("Hello %s after\n", "Goodbye"); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {} after\n", "Goodbye"); + + // std::print can't print signed char strings. + const signed char *sstring = reinterpret_cast("ustring"); + printf("signed char string %s\n", sstring); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("signed char string {}\n", reinterpret_cast(sstring)); + + // std::print can't print unsigned char strings. + const unsigned char *ustring = reinterpret_cast("ustring"); + printf("unsigned char string %s\n", ustring); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("unsigned char string {}\n", reinterpret_cast(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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:f} after\n", f); + + printf("Hello %g after\n", f); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:g} after\n", f); + + printf("Hello %e after\n", f); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:f} after\n", d); + + printf("Hello %g after\n", d); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:g} after\n", d); + + printf("Hello %e after\n", d); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:f} after\n", ld); + + printf("Hello %g after\n", ld); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:g} after\n", ld); + + printf("Hello %e after\n", ld); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Int* {} {} {}\n", reinterpret_cast(&i), "Double*", reinterpret_cast(&j)); + + printf("%p\n", nullptr); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", nullptr); + + const auto np = nullptr; + printf("%p\n", np); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", reinterpret_cast(NULL)); + + printf("%p\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", reinterpret_cast(42)); + + // If we already have a void pointer then no cast is required. + printf("%p\n", reinterpret_cast(44)); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", reinterpret_cast(44)); + + const void *p; + printf("%p\n", p); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", reinterpret_cast(pp)); + + printf("%p\n", printf_pointer); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", reinterpret_cast(printf_pointer)); +} + +class AClass +{ + void printf_this_pointer() + { + printf("%p\n", this); + // CHECK-MESSAGES: [[@LINE-1]]:5: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}\n", reinterpret_cast(this)); + } +}; + +void printf_positional_arg() { + printf("%1$d", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{0}", 42); + + printf("before %1$d", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("before {0}", 42); + + printf("%1$d after", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{0} after", 42); + + printf("before %1$d after", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:.3f}\n", 3.14159); + + printf("Hello %10.3f\n", 3.14159); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Hello {:.5}\n", "Goodbye"); + + printf("Hello %.5c\n", 'G'); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Wibble {:#x}\n", 42); + + printf("Wibble %#20x\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Wibble {:#20x}\n", 42); + + printf("Wibble %#020x\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Wibble {:#020x}\n", 42); + + printf("Wibble %#-20x\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Positive integer {:+}\n", 42); + + printf("Positive double %+f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Spaced integer {: }\n", 42); + + printf("Spaced integer %- d\n", 42); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Spaced integer {: }\n", 42); + + printf("Spaced double % f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("Spaced integer {:+}\n", 42); + + printf("Spaced double %+ f\n", 42.2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-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: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(plain_enum)); + + SignedPlainEnum splain_enum; + printf("%d", splain_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(splain_enum)); + + BoolEnum bool_enum; + printf("%d", bool_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(bool_enum)); + + CharEnum char_enum; + printf("%d", char_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(char_enum)); + + SCharEnum schar_enum; + printf("%d", schar_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(schar_enum)); + + UCharEnum uchar_enum; + printf("%d", uchar_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(uchar_enum)); + + ShortEnum short_enum; + printf("%d", short_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(short_enum)); + + UShortEnum ushort_enum; + printf("%d", ushort_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(ushort_enum)); + + IntEnum int_enum; + printf("%d", int_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(int_enum)); + + UIntEnum uint_enum; + printf("%d", uint_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(uint_enum)); + + LongEnum long_enum; + printf("%d", long_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(long_enum)); + + ULongEnum ulong_enum; + printf("%d", ulong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(ulong_enum)); + + LongLongEnum longlong_enum; + printf("%d", longlong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(longlong_enum)); + + ULongLongEnum ulonglong_enum; + printf("%d", ulonglong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(ulonglong_enum)); +} + +void printf_enum_u() { + PlainEnum plain_enum; + printf("%u", plain_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(plain_enum)); + + SignedPlainEnum splain_enum; + printf("%u", splain_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(splain_enum)); + + BoolEnum bool_enum; + printf("%u", bool_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(bool_enum)); + + CharEnum char_enum; + printf("%u", char_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(char_enum)); + + SCharEnum schar_enum; + printf("%u", schar_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(schar_enum)); + + UCharEnum uchar_enum; + printf("%u", uchar_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(uchar_enum)); + + ShortEnum short_enum; + printf("%u", short_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(short_enum)); + + UShortEnum ushort_enum; + printf("%u", ushort_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(ushort_enum)); + + IntEnum int_enum; + printf("%u", int_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(int_enum)); + + UIntEnum uint_enum; + printf("%u", uint_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(uint_enum)); + + LongEnum long_enum; + printf("%u", long_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(long_enum)); + + ULongEnum ulong_enum; + printf("%u", ulong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(ulong_enum)); + + LongLongEnum longlong_enum; + printf("%u", longlong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(longlong_enum)); + + ULongLongEnum ulonglong_enum; + printf("%u", ulonglong_enum); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: Replace 'printf' with 'std::print' [modernize-printf-to-std-print] + // CHECK-FIXES: std::print("{}", static_cast(ulonglong_enum)); +}