https://github.com/BenBlaise created https://github.com/llvm/llvm-project/pull/76065
Finds literals explicitly casted to a type that could be expressed using builtin prefixes or suffixes. In elementary cases, provides automated fix-it hints. ```cpp (char)'a'; // -> 'a' (char16_t)U'a'; // -> u'a' static_cast<unsigned int>(0x1ul); // -> 0x1u reinterpret_cast<long long int>(3ll); // -> 3LL float(2.); // -> 2.f double{2.}; // -> 2. ``` Defined for character, integer and floating literals. >From b4d7dbcf67117471a2d7025013fb1fcd188b33b6 Mon Sep 17 00:00:00 2001 From: Benedict Blaise <benrt...@gmail.com> Date: Wed, 20 Dec 2023 16:07:10 +0100 Subject: [PATCH] [clang-tidy] Add readability-use-builtin-literals check Finds literals explicitly casted to a type that could be expressed using builtin prefixes or suffixes. In elementary cases, provides automated fix-it hints. Defined for character, integer and floating literals. --- .../clang-tidy/readability/CMakeLists.txt | 1 + .../readability/ReadabilityTidyModule.cpp | 3 + .../readability/UseBuiltinLiteralsCheck.cpp | 161 ++++++++++++++++++ .../readability/UseBuiltinLiteralsCheck.h | 37 ++++ clang-tools-extra/docs/ReleaseNotes.rst | 6 + .../docs/clang-tidy/checks/list.rst | 1 + .../readability/use-builtin-literals.rst | 38 +++++ .../use-builtin-literals-cpp17.cpp | 11 ++ .../use-builtin-literals-cpp23.cpp | 19 +++ .../readability/use-builtin-literals.cpp | 105 ++++++++++++ 10 files changed, 382 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/readability/UseBuiltinLiteralsCheck.cpp create mode 100644 clang-tools-extra/clang-tidy/readability/UseBuiltinLiteralsCheck.h create mode 100644 clang-tools-extra/docs/clang-tidy/checks/readability/use-builtin-literals.rst create mode 100644 clang-tools-extra/test/clang-tidy/checkers/readability/use-builtin-literals-cpp17.cpp create mode 100644 clang-tools-extra/test/clang-tidy/checkers/readability/use-builtin-literals-cpp23.cpp create mode 100644 clang-tools-extra/test/clang-tidy/checkers/readability/use-builtin-literals.cpp diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt index 5452c2d48a4617..3c5e12aaeb3bd4 100644 --- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt @@ -51,6 +51,7 @@ add_clang_library(clangTidyReadabilityModule UniqueptrDeleteReleaseCheck.cpp UppercaseLiteralSuffixCheck.cpp UseAnyOfAllOfCheck.cpp + UseBuiltinLiteralsCheck.cpp LINK_LIBS clangTidy diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp index b8e6e641432060..b0ec5a906cac55 100644 --- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -54,6 +54,7 @@ #include "UniqueptrDeleteReleaseCheck.h" #include "UppercaseLiteralSuffixCheck.h" #include "UseAnyOfAllOfCheck.h" +#include "UseBuiltinLiteralsCheck.h" namespace clang::tidy { namespace readability { @@ -151,6 +152,8 @@ class ReadabilityModule : public ClangTidyModule { "readability-uppercase-literal-suffix"); CheckFactories.registerCheck<UseAnyOfAllOfCheck>( "readability-use-anyofallof"); + CheckFactories.registerCheck<UseBuiltinLiteralsCheck>( + "readability-use-builtin-literals"); } }; diff --git a/clang-tools-extra/clang-tidy/readability/UseBuiltinLiteralsCheck.cpp b/clang-tools-extra/clang-tidy/readability/UseBuiltinLiteralsCheck.cpp new file mode 100644 index 00000000000000..ede46517898d87 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/UseBuiltinLiteralsCheck.cpp @@ -0,0 +1,161 @@ +//===--- UseBuiltinLiteralsCheck.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 +// +//===----------------------------------------------------------------------===// + +#include "UseBuiltinLiteralsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include <optional> + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { + +using RuleOnStd = bool (*)(const LangStandard &LS); + +struct Replacement { + Replacement(StringRef Seq, const RuleOnStd Std = nullptr) + : Seq(Seq), Std(Std) {} + bool operator()(const LangOptions &LO) const { + return Std ? Std(LangStandard::getLangStandardForKind(LO.LangStd)) : true; + } + StringRef Seq; + RuleOnStd Std; +}; + +} // namespace + +static const llvm::Regex CharRegex("^(u8|u|U|L)?"); +static const llvm::StringMap<Replacement> CharPrefix({ + {"char", {""}}, + {"char8_t", {"u8"}}, + {"char16_t", {"u"}}, + {"char32_t", {"U"}}, + {"wchar_t", {"L"}}, +}); + +static const llvm::Regex + IntRegex("(([uU]?[lL]{0,2})|([lL]{0,2}[uU]?)|([uU]?[zZ]?)|([zZ]?[uU]?))?$"); +static const llvm::StringMap<Replacement> IntSuffix({ + {"int", {""}}, + {"unsigned int", {"u"}}, + {"long", {"L"}}, + {"unsigned long", {"uL"}}, + {"long long", {"LL"}}, + {"unsigned long long", {"uLL"}}, + {"size_t", {"uz", [](const auto &LS) { return LS.isCPlusPlus23(); }}}, + {"std::size_t", {"uz", [](const auto &LS) { return LS.isCPlusPlus23(); }}}, +}); + +static const llvm::Regex FloatRegex( + "([fF]|[lL]|([fF]16)|([fF]32)|([fF]64)|([fF]128)|((bf|BF)16))?$"); +static const llvm::StringMap<Replacement> FloatSuffix({ + {"double", {""}}, + {"float", {"f"}}, + {"long double", {"L"}}, + {"std::float16_t", {"f16"}}, + {"std::float32_t", {"f32"}}, + {"std::float64_t", {"f64"}}, + {"std::float128_t", {"f128"}}, + {"std::bfloat16_t", {"bf16"}}, + {"float16_t", {"f16"}}, + {"float32_t", {"f32"}}, + {"float64_t", {"f64"}}, + {"float128_t", {"f128"}}, + {"bfloat16_t", {"bf16"}}, +}); + +void UseBuiltinLiteralsCheck::registerMatchers(MatchFinder *Finder) { + static const auto Literal = has(ignoringParenImpCasts( + expr(anyOf(characterLiteral().bind("char"), integerLiteral().bind("int"), + floatLiteral().bind("float"))) + .bind("lit"))); + Finder->addMatcher( + traverse(TK_IgnoreUnlessSpelledInSource, + explicitCastExpr(anyOf(Literal, has(initListExpr(Literal)))) + .bind("expr")), + this); +} + +static StringRef getRawStringRef(const SourceRange &Range, + const SourceManager &Sources, + const LangOptions &LangOpts) { + CharSourceRange TextRange = Lexer::getAsCharRange(Range, Sources, LangOpts); + return Lexer::getSourceText(TextRange, Sources, LangOpts); +} + +void UseBuiltinLiteralsCheck::check(const MatchFinder::MatchResult &Result) { + + const auto &SM = *Result.SourceManager; + const auto &Nodes = Result.Nodes; + + const auto *MatchedCast = Nodes.getNodeAs<ExplicitCastExpr>("expr"); + const auto *Lit = Nodes.getNodeAs<Expr>("lit"); + assert(MatchedCast && Lit); + + StringRef LitText = getRawStringRef(Lit->getExprLoc(), SM, getLangOpts()); + std::string CastType = MatchedCast->getTypeAsWritten().getAsString(); + std::string Fix; // Replacement string for the fix-it hint. + std::optional<StringRef> Seq; // Literal sequence, prefix or suffix. + + if (const auto *CharLit = Nodes.getNodeAs<CharacterLiteral>("char"); + CharLit && CharPrefix.contains(CastType)) { + if (const Replacement &Rep = CharPrefix.at(CastType); Rep(getLangOpts())) { + + Seq = Rep.Seq; + if (!CharLit->getLocation().isMacroID()) { + Fix.append(Rep.Seq); + Fix.append(CharRegex.sub("", LitText.str())); + } + } + } else if (const auto *IntLit = Nodes.getNodeAs<IntegerLiteral>("int"); + IntLit && IntSuffix.contains(CastType)) { + if (const Replacement &Rep = IntSuffix.at(CastType); Rep(getLangOpts())) { + + Seq = Rep.Seq; + if (!IntLit->getLocation().isMacroID()) { + Fix.append(IntRegex.sub("", LitText.str())); + Fix.append(Rep.Seq); + } + } + } else if (const auto *FloatLit = Nodes.getNodeAs<FloatingLiteral>("float"); + FloatLit && FloatSuffix.contains(CastType)) { + if (const Replacement &Rep = FloatSuffix.at(CastType); Rep(getLangOpts())) { + + Seq = Rep.Seq; + if (!FloatLit->getLocation().isMacroID()) { + Fix.append(FloatRegex.sub("", LitText.str())); + Fix.append(Rep.Seq); + } + } + } + + const TypeLoc CastTypeLoc = MatchedCast->getTypeInfoAsWritten()->getTypeLoc(); + + if (!Fix.empty() && !CastTypeLoc.getBeginLoc().isMacroID()) { + + // Recommend fix-it when no part of the explicit cast comes from a macro. + diag(MatchedCast->getExprLoc(), + "use built-in literal instead of explicit cast") + << FixItHint::CreateReplacement(MatchedCast->getSourceRange(), Fix); + } else if (Seq && MatchedCast->getExprLoc().isMacroID()) { + + // Recommend manual fix when the entire explicit cast is within a macro. + diag(MatchedCast->getExprLoc(), + "use built-in '%0' %1 instead of explicit cast to '%2'") + << *Seq + << (Nodes.getNodeAs<CharacterLiteral>("char") ? "prefix" : "suffix") + << CastType; + } +} + +} // namespace clang::tidy::readability diff --git a/clang-tools-extra/clang-tidy/readability/UseBuiltinLiteralsCheck.h b/clang-tools-extra/clang-tidy/readability/UseBuiltinLiteralsCheck.h new file mode 100644 index 00000000000000..7b90786408d9ba --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/UseBuiltinLiteralsCheck.h @@ -0,0 +1,37 @@ +//===--- UseBuiltinLiteralsCheck.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_READABILITY_USEBUILTINLITERALSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USEBUILTINLITERALSCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Finds literals explicitly casted to a type that could be expressed using +/// builtin prefixes or suffixes. Defined for character, integer, and floating +/// literals. Removes any suffixes or prefixes before applying the one that +/// corresponds to the type of the cast. +/// +/// An explicit cast within a macro will be matched, but will only yield a +/// suggestion for a manual fix. Otherwise, if either the destination type or +/// the literal was substituted from a macro, no warning will be produced. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/use-builtin-literals.html +class UseBuiltinLiteralsCheck : public ClangTidyCheck { +public: + UseBuiltinLiteralsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USEBUILTINLITERALSCHECK_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 6d91748e4cef18..5e144d94ccc297 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -218,6 +218,12 @@ New checks Detects C++ code where a reference variable is used to extend the lifetime of a temporary object that has just been constructed. +- New :doc:`readability-use-builtin-literals + <clang-tidy/checks/readability/use-builtin-literals>` check. + + Finds literals explicitly casted to a type that could be expressed using + builtin prefixes or suffixes. + 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 index 31f0e090db1d7d..e50562a22bc59e 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -380,6 +380,7 @@ Clang-Tidy Checks :doc:`readability-uniqueptr-delete-release <readability/uniqueptr-delete-release>`, "Yes" :doc:`readability-uppercase-literal-suffix <readability/uppercase-literal-suffix>`, "Yes" :doc:`readability-use-anyofallof <readability/use-anyofallof>`, + :doc:`readability-use-builtin-literals <readability/use-builtin-literals>`, "Yes" :doc:`zircon-temporary-objects <zircon/temporary-objects>`, diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/use-builtin-literals.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/use-builtin-literals.rst new file mode 100644 index 00000000000000..044b14442eb5ee --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/use-builtin-literals.rst @@ -0,0 +1,38 @@ +.. title:: clang-tidy - readability-use-builtin-literals + +readability-use-builtin-literals +================================ + +Finds literals explicitly casted to a type that could be expressed using builtin prefixes or suffixes. + +In elementary cases, provides automated fix-it hints. + +.. code-block:: c++ + + (char)'a'; // -> 'a' + (char16_t)U'a'; // -> u'a' + static_cast<unsigned int>(0x1ul); // -> 0x1u + reinterpret_cast<long long int>(3ll); // -> 3LL + float(2.); // -> 2.f + double{2.}; // -> 2. + +Defined for character, integer and floating literals. Removes any suffixes or prefixes before applying the one that corresponds to the type of the cast. Long values will always have uppercase ``L`` recommended, conforming to :doc:`readability-uppercase-literal-suffix <../readability/uppercase-literal-suffix>`. + +An explicit cast within a macro is matched, but yields only a suggestion for a manual fix. + +.. code-block:: c++ + + #define OPSHIFT ((unsigned)27) + OPSHIFT; // warning: use built-in 'u' instead of explicit cast to 'unsigned int' + +Otherwise, if either the destination type or the literal was substituted from a macro, no warnings are produced. + +.. code-block:: c++ + + #define INT_MAX 2147483647 + static_cast<unsigned>(INT_MAX); // no warning + + #define INTTYPE unsigned + (INTTYPE)31; // no warning + +Explicit casts within templates are also ignored. diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/use-builtin-literals-cpp17.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/use-builtin-literals-cpp17.cpp new file mode 100644 index 00000000000000..94f7bfbf3ccfba --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/use-builtin-literals-cpp17.cpp @@ -0,0 +1,11 @@ +// RUN: %check_clang_tidy -std=c++17-or-later %s readability-use-builtin-literals %t + +void warn_and_fix() { + + (char)u8'a'; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 'a'; + (wchar_t)u8'a'; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: L'a'; +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/use-builtin-literals-cpp23.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/use-builtin-literals-cpp23.cpp new file mode 100644 index 00000000000000..e9a790e624d8f2 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/use-builtin-literals-cpp23.cpp @@ -0,0 +1,19 @@ +// RUN: %check_clang_tidy -std=c++23-or-later %s readability-use-builtin-literals %t + +using size_t = decltype(sizeof(void*)); +namespace std { + using size_t = size_t; +} + +void warn_and_fix() { + + (size_t)6; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 6uz; + (std::size_t)6; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 6uz; + (size_t)6zu; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 6uz; +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/use-builtin-literals.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/use-builtin-literals.cpp new file mode 100644 index 00000000000000..df78ce3f1b9e81 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/use-builtin-literals.cpp @@ -0,0 +1,105 @@ +// RUN: %check_clang_tidy %s readability-use-builtin-literals %t + +void warn_and_fix() { + + (char16_t)U'a'; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: u'a'; + (char32_t)u'a'; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: U'a'; + + (int)1; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 1; + (unsigned int)0x1ul; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 0x1u; + (long int)2l; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 2L; + (unsigned long int)0x2lu; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 0x2uL; + (long long int)3ll; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 3LL; + (unsigned long long int)0x3llu; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 0x3uLL; + + (double)1.f; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 1.; + (float)2.; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 2.f; + (long double)3e0f; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 3e0L; + + float(2.); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 2.f; + double{2.}; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 2.; + + static_cast<double>(2.f); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 2.; + + reinterpret_cast<int>(1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in literal instead of explicit cast [readability-use-builtin-literals] + // CHECK-FIXES: 1; +} + +#define OPSHIFT ((unsigned)27) +#define OCHAR (2LU<<OPSHIFT) +#define OPSHIFT2 (27) +#define OCHAR2 (2LU<<(unsigned)OPSHIFT2) +#define SUBT unsigned +#define OCHAR3 (2LU<<(SUBT)27) +#define SUBT2 char +#define OCHAR4 (2LU<<(SUBT2)'a') + +void warn_and_recommend_fix() { + + OPSHIFT; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in 'u' suffix instead of explicit cast to 'unsigned int' [readability-use-builtin-literals] + OCHAR; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in 'u' suffix instead of explicit cast to 'unsigned int' [readability-use-builtin-literals] + OCHAR2; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in 'u' suffix instead of explicit cast to 'unsigned int' [readability-use-builtin-literals] + OCHAR3; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in 'u' suffix instead of explicit cast to 'unsigned int' [readability-use-builtin-literals] + OCHAR4; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use built-in '' prefix instead of explicit cast to 'char' [readability-use-builtin-literals] +} + +#define INT_MAX 2147483647 +#define MAXCOL 2 + +#ifdef CHECKED +#define SUINT int +#else +#define SUINT unsigned +#endif + +template <typename T> +T f() { + return T(1); +} + +int no_warn() { + +(void)0; +(unsigned*)0; + +static_cast<unsigned>(INT_MAX); +(unsigned)MAXCOL; + +(SUINT)31; + +return f<int>(); +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits