clang-tools 24.0.0git
UseToUnderlyingCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "clang/Tooling/FixIt.h"
13
14using namespace clang::ast_matchers;
15
16namespace clang::tidy {
17
18template <>
19struct OptionEnumMapping<modernize::UseToUnderlyingCheck::ImpreciseCastsKind> {
20 static llvm::ArrayRef<
21 std::pair<modernize::UseToUnderlyingCheck::ImpreciseCastsKind, StringRef>>
23 using ImpreciseCastsKind =
25 static constexpr std::pair<ImpreciseCastsKind, StringRef> Mapping[] = {
26 {ImpreciseCastsKind::Ignore, "Ignore"},
27 {ImpreciseCastsKind::Warn, "Warn"},
28 {ImpreciseCastsKind::PreserveType, "PreserveType"},
29 {ImpreciseCastsKind::UseUnderlyingType, "UseUnderlyingType"},
30 };
31 return {Mapping};
32 }
33};
34
35} // namespace clang::tidy
36
37namespace clang::tidy::modernize {
38
40 ClangTidyContext *Context)
41 : ClangTidyCheck(Name, Context),
42 ImpreciseCasts(Options.get("ImpreciseCasts", ImpreciseCastsKind::Warn)),
43 ReplacementFunction(
44 Options.get("ReplacementFunction", "std::to_underlying")),
45 ReplacementFunctionHeader(Options.get("ReplacementFunctionHeader", "")),
46 IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
47 utils::IncludeSorter::IS_LLVM),
48 areDiagsSelfContained()) {
49 if (ReplacementFunction == "std::to_underlying") {
50 if (ReplacementFunctionHeader.empty())
51 ReplacementFunctionHeader = "<utility>";
52 else if (ReplacementFunctionHeader != "<utility>")
53 configurationDiag("'std::to_underlying' is declared in '<utility>', but "
54 "'ReplacementFunctionHeader' is set to '%0'")
55 << ReplacementFunctionHeader;
56 }
57}
58
60 const LangOptions &LangOpts) const {
61 // std::to_underlying is a C++23 library facility, but a user-provided
62 // replacement (e.g. llvm::to_underlying) only requires scoped enumerations,
63 // which are available since C++11.
64 if (ReplacementFunction == "std::to_underlying")
65 return LangOpts.CPlusPlus23;
66 return LangOpts.CPlusPlus11;
67}
68
69void UseToUnderlyingCheck::registerPPCallbacks(const SourceManager &SM,
70 Preprocessor *PP,
71 Preprocessor *ModuleExpanderPP) {
72 IncludeInserter.registerPreprocessor(PP);
73}
74
76 Options.store(Opts, "ImpreciseCasts", ImpreciseCasts);
77 Options.store(Opts, "ReplacementFunction", ReplacementFunction);
78 Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
79 Options.store(Opts, "ReplacementFunctionHeader", ReplacementFunctionHeader);
80}
81
82void UseToUnderlyingCheck::registerMatchers(MatchFinder *Finder) {
83 // Match an explicit cast (``static_cast``, C-style or functional) from a
84 // scoped enumeration to an integer type. Enum-to-enum casts and casts to a
85 // floating-point or pointer type are excluded here; whether the conversion is
86 // precise or imprecise is decided in check().
87 Finder->addMatcher(
88 explicitCastExpr(
89 hasDestinationType(
90 qualType(isInteger(), unless(hasCanonicalType(enumType())))),
91 hasSourceExpression(
92 expr(hasType(hasCanonicalType(enumType(
93 hasDeclaration(enumDecl(isScoped()).bind("enum"))))))
94 .bind("operand")))
95 .bind("cast"),
96 this);
97}
98
99void UseToUnderlyingCheck::check(const MatchFinder::MatchResult &Result) {
100 const auto *Cast = Result.Nodes.getNodeAs<ExplicitCastExpr>("cast");
101 const auto *Operand = Result.Nodes.getNodeAs<Expr>("operand");
102 const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("enum");
103
104 const QualType DestType = Cast->getType().getCanonicalType();
105 const QualType UnderlyingType =
106 Enum->getIntegerType().getCanonicalType().getUnqualifiedType();
107 const QualType DestUnqualified = DestType.getUnqualifiedType();
108 const bool IsPrecise = UnderlyingType == DestUnqualified;
109
110 // A cast to ``bool`` that is not precise (i.e. the underlying type is not
111 // ``bool``) expresses a truthiness test rather than a request for the
112 // underlying value, so it is left untouched.
113 if (DestType->isBooleanType() && !IsPrecise)
114 return;
115
116 if (!IsPrecise && ImpreciseCasts == ImpreciseCastsKind::Ignore)
117 return;
118
119 auto Diag = diag(Cast->getBeginLoc(),
120 "use '%0' to convert a scoped enumeration to its "
121 "underlying type")
122 << ReplacementFunction;
123
124 if (!IsPrecise && ImpreciseCasts == ImpreciseCastsKind::Warn)
125 return;
126
127 const StringRef OperandText =
128 tooling::fixit::getText(*Operand, *Result.Context);
129 if (OperandText.empty())
130 return;
131
132 const std::string Call =
133 (ReplacementFunction + "(" + OperandText + ")").str();
134
135 const bool ReplaceWholeCast =
136 IsPrecise || ImpreciseCasts == ImpreciseCastsKind::UseUnderlyingType;
137 if (ReplaceWholeCast) {
138 // `static_cast<long>(e)` -> `to_underlying(e)`
139 Diag << tooling::fixit::createReplacement(*Cast, Call);
140 } else {
141 // `static_cast<long>(e)` -> `static_cast<long>(to_underlying(e))`
142 Diag << tooling::fixit::createReplacement(*Operand, Call);
143 }
144
145 if (!ReplacementFunctionHeader.empty())
146 Diag << IncludeInserter.createIncludeInsertion(
147 Result.SourceManager->getFileID(
148 Result.SourceManager->getExpansionLoc(Cast->getBeginLoc())),
149 ReplacementFunctionHeader);
150}
151
152} // namespace clang::tidy::modernize
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
UseToUnderlyingCheck(StringRef Name, ClangTidyContext *Context)
ImpreciseCastsKind
How to treat an imprecise cast, i.e.
@ Warn
Diagnose imprecise casts but do not offer a fix-it.
@ UseUnderlyingType
Replace the whole cast with a call to the replacement function, changing the resulting type to the un...
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override
llvm::StringMap< ClangTidyValue > OptionMap
static llvm::ArrayRef< std::pair< modernize::UseToUnderlyingCheck::ImpreciseCastsKind, StringRef > > getEnumMapping()
This class should be specialized by any enum type that needs to be converted to and from an llvm::Str...