[TargetVersion] Only enable on RISC-V and AArch64 (#115991)
[llvm-project.git] / clang-tools-extra / clang-tidy / readability / OperatorsRepresentationCheck.cpp
blobccaa686f853234de7d9d655ba11dff953fb318ed
1 //===--- OperatorsRepresentationCheck.cpp - clang-tidy
2 //--------------------------===//
3 //
4 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 // See https://llvm.org/LICENSE.txt for license information.
6 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //
8 //===----------------------------------------------------------------------===//
10 #include "OperatorsRepresentationCheck.h"
11 #include "../utils/OptionsUtils.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 #include "clang/Lex/Lexer.h"
15 #include "llvm/ADT/STLExtras.h"
16 #include <array>
17 #include <utility>
19 using namespace clang::ast_matchers;
21 namespace clang::tidy::readability {
23 static StringRef getOperatorSpelling(SourceLocation Loc, ASTContext &Context) {
24 if (Loc.isInvalid())
25 return {};
27 SourceManager &SM = Context.getSourceManager();
29 Loc = SM.getSpellingLoc(Loc);
30 if (Loc.isInvalid())
31 return {};
33 const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);
34 return Lexer::getSourceText(TokenRange, SM, Context.getLangOpts());
37 namespace {
39 AST_MATCHER_P2(BinaryOperator, hasInvalidBinaryOperatorRepresentation,
40 BinaryOperatorKind, Kind, llvm::StringRef,
41 ExpectedRepresentation) {
42 if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())
43 return false;
45 StringRef Spelling =
46 getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
47 return !Spelling.empty() && Spelling != ExpectedRepresentation;
50 AST_MATCHER_P2(UnaryOperator, hasInvalidUnaryOperatorRepresentation,
51 UnaryOperatorKind, Kind, llvm::StringRef,
52 ExpectedRepresentation) {
53 if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())
54 return false;
56 StringRef Spelling =
57 getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
58 return !Spelling.empty() && Spelling != ExpectedRepresentation;
61 AST_MATCHER_P2(CXXOperatorCallExpr, hasInvalidOverloadedOperatorRepresentation,
62 OverloadedOperatorKind, Kind, llvm::StringRef,
63 ExpectedRepresentation) {
64 if (Node.getOperator() != Kind || ExpectedRepresentation.empty())
65 return false;
67 StringRef Spelling =
68 getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
69 return !Spelling.empty() && Spelling != ExpectedRepresentation;
72 } // namespace
74 constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 2U>
75 UnaryRepresentation{{{"!", "not"}, {"~", "compl"}}};
77 constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 9U>
78 OperatorsRepresentation{{{"&&", "and"},
79 {"||", "or"},
80 {"^", "xor"},
81 {"&", "bitand"},
82 {"|", "bitor"},
83 {"&=", "and_eq"},
84 {"|=", "or_eq"},
85 {"!=", "not_eq"},
86 {"^=", "xor_eq"}}};
88 static llvm::StringRef translate(llvm::StringRef Value) {
89 for (const auto &[Traditional, Alternative] : UnaryRepresentation) {
90 if (Value == Traditional)
91 return Alternative;
92 if (Value == Alternative)
93 return Traditional;
96 for (const auto &[Traditional, Alternative] : OperatorsRepresentation) {
97 if (Value == Traditional)
98 return Alternative;
99 if (Value == Alternative)
100 return Traditional;
102 return {};
105 static bool isNotOperatorStr(llvm::StringRef Value) {
106 return translate(Value).empty();
109 static bool isSeparator(char C) noexcept {
110 constexpr llvm::StringRef Separators(" \t\r\n\0()<>{};,");
111 return Separators.contains(C);
114 static bool needEscaping(llvm::StringRef Operator) {
115 switch (Operator[0]) {
116 case '&':
117 case '|':
118 case '!':
119 case '^':
120 case '~':
121 return false;
122 default:
123 return true;
127 static llvm::StringRef
128 getRepresentation(const std::vector<llvm::StringRef> &Config,
129 llvm::StringRef Traditional, llvm::StringRef Alternative) {
130 if (llvm::is_contained(Config, Traditional))
131 return Traditional;
132 if (llvm::is_contained(Config, Alternative))
133 return Alternative;
134 return {};
137 template <typename T>
138 static bool isAnyOperatorEnabled(const std::vector<llvm::StringRef> &Config,
139 const T &Operators) {
140 for (const auto &[traditional, alternative] : Operators) {
141 if (!getRepresentation(Config, traditional, alternative).empty())
142 return true;
144 return false;
147 OperatorsRepresentationCheck::OperatorsRepresentationCheck(
148 StringRef Name, ClangTidyContext *Context)
149 : ClangTidyCheck(Name, Context),
150 BinaryOperators(
151 utils::options::parseStringList(Options.get("BinaryOperators", ""))),
152 OverloadedOperators(utils::options::parseStringList(
153 Options.get("OverloadedOperators", ""))) {
154 llvm::erase_if(BinaryOperators, isNotOperatorStr);
155 llvm::erase_if(OverloadedOperators, isNotOperatorStr);
158 void OperatorsRepresentationCheck::storeOptions(
159 ClangTidyOptions::OptionMap &Opts) {
160 Options.store(Opts, "BinaryOperators",
161 utils::options::serializeStringList(BinaryOperators));
162 Options.store(Opts, "OverloadedOperators",
163 utils::options::serializeStringList(OverloadedOperators));
166 std::optional<TraversalKind>
167 OperatorsRepresentationCheck::getCheckTraversalKind() const {
168 return TK_IgnoreUnlessSpelledInSource;
171 bool OperatorsRepresentationCheck::isLanguageVersionSupported(
172 const LangOptions &LangOpts) const {
173 return LangOpts.CPlusPlus;
176 void OperatorsRepresentationCheck::registerBinaryOperatorMatcher(
177 MatchFinder *Finder) {
178 if (!isAnyOperatorEnabled(BinaryOperators, OperatorsRepresentation))
179 return;
181 Finder->addMatcher(
182 binaryOperator(
183 unless(isExpansionInSystemHeader()),
184 anyOf(hasInvalidBinaryOperatorRepresentation(
185 BO_LAnd, getRepresentation(BinaryOperators, "&&", "and")),
186 hasInvalidBinaryOperatorRepresentation(
187 BO_LOr, getRepresentation(BinaryOperators, "||", "or")),
188 hasInvalidBinaryOperatorRepresentation(
189 BO_NE, getRepresentation(BinaryOperators, "!=", "not_eq")),
190 hasInvalidBinaryOperatorRepresentation(
191 BO_Xor, getRepresentation(BinaryOperators, "^", "xor")),
192 hasInvalidBinaryOperatorRepresentation(
193 BO_And, getRepresentation(BinaryOperators, "&", "bitand")),
194 hasInvalidBinaryOperatorRepresentation(
195 BO_Or, getRepresentation(BinaryOperators, "|", "bitor")),
196 hasInvalidBinaryOperatorRepresentation(
197 BO_AndAssign,
198 getRepresentation(BinaryOperators, "&=", "and_eq")),
199 hasInvalidBinaryOperatorRepresentation(
200 BO_OrAssign,
201 getRepresentation(BinaryOperators, "|=", "or_eq")),
202 hasInvalidBinaryOperatorRepresentation(
203 BO_XorAssign,
204 getRepresentation(BinaryOperators, "^=", "xor_eq"))))
205 .bind("binary_op"),
206 this);
209 void OperatorsRepresentationCheck::registerUnaryOperatorMatcher(
210 MatchFinder *Finder) {
211 if (!isAnyOperatorEnabled(BinaryOperators, UnaryRepresentation))
212 return;
214 Finder->addMatcher(
215 unaryOperator(
216 unless(isExpansionInSystemHeader()),
217 anyOf(hasInvalidUnaryOperatorRepresentation(
218 UO_LNot, getRepresentation(BinaryOperators, "!", "not")),
219 hasInvalidUnaryOperatorRepresentation(
220 UO_Not, getRepresentation(BinaryOperators, "~", "compl"))))
221 .bind("unary_op"),
222 this);
225 void OperatorsRepresentationCheck::registerOverloadedOperatorMatcher(
226 MatchFinder *Finder) {
227 if (!isAnyOperatorEnabled(OverloadedOperators, OperatorsRepresentation) &&
228 !isAnyOperatorEnabled(OverloadedOperators, UnaryRepresentation))
229 return;
231 Finder->addMatcher(
232 cxxOperatorCallExpr(
233 unless(isExpansionInSystemHeader()),
234 anyOf(
235 hasInvalidOverloadedOperatorRepresentation(
236 OO_AmpAmp,
237 getRepresentation(OverloadedOperators, "&&", "and")),
238 hasInvalidOverloadedOperatorRepresentation(
239 OO_PipePipe,
240 getRepresentation(OverloadedOperators, "||", "or")),
241 hasInvalidOverloadedOperatorRepresentation(
242 OO_Exclaim,
243 getRepresentation(OverloadedOperators, "!", "not")),
244 hasInvalidOverloadedOperatorRepresentation(
245 OO_ExclaimEqual,
246 getRepresentation(OverloadedOperators, "!=", "not_eq")),
247 hasInvalidOverloadedOperatorRepresentation(
248 OO_Caret, getRepresentation(OverloadedOperators, "^", "xor")),
249 hasInvalidOverloadedOperatorRepresentation(
250 OO_Amp,
251 getRepresentation(OverloadedOperators, "&", "bitand")),
252 hasInvalidOverloadedOperatorRepresentation(
253 OO_Pipe,
254 getRepresentation(OverloadedOperators, "|", "bitor")),
255 hasInvalidOverloadedOperatorRepresentation(
256 OO_AmpEqual,
257 getRepresentation(OverloadedOperators, "&=", "and_eq")),
258 hasInvalidOverloadedOperatorRepresentation(
259 OO_PipeEqual,
260 getRepresentation(OverloadedOperators, "|=", "or_eq")),
261 hasInvalidOverloadedOperatorRepresentation(
262 OO_CaretEqual,
263 getRepresentation(OverloadedOperators, "^=", "xor_eq")),
264 hasInvalidOverloadedOperatorRepresentation(
265 OO_Tilde,
266 getRepresentation(OverloadedOperators, "~", "compl"))))
267 .bind("overloaded_op"),
268 this);
271 void OperatorsRepresentationCheck::registerMatchers(MatchFinder *Finder) {
272 registerBinaryOperatorMatcher(Finder);
273 registerUnaryOperatorMatcher(Finder);
274 registerOverloadedOperatorMatcher(Finder);
277 void OperatorsRepresentationCheck::check(
278 const MatchFinder::MatchResult &Result) {
280 SourceLocation Loc;
282 if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>("binary_op"))
283 Loc = Op->getOperatorLoc();
284 else if (const auto *Op = Result.Nodes.getNodeAs<UnaryOperator>("unary_op"))
285 Loc = Op->getOperatorLoc();
286 else if (const auto *Op =
287 Result.Nodes.getNodeAs<CXXOperatorCallExpr>("overloaded_op"))
288 Loc = Op->getOperatorLoc();
290 if (Loc.isInvalid())
291 return;
293 Loc = Result.SourceManager->getSpellingLoc(Loc);
294 if (Loc.isInvalid() || Loc.isMacroID())
295 return;
297 const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);
298 if (TokenRange.isInvalid())
299 return;
301 StringRef Spelling = Lexer::getSourceText(TokenRange, *Result.SourceManager,
302 Result.Context->getLangOpts());
303 StringRef TranslatedSpelling = translate(Spelling);
305 if (TranslatedSpelling.empty())
306 return;
308 std::string FixSpelling = TranslatedSpelling.str();
310 StringRef SourceRepresentation = "an alternative";
311 StringRef TargetRepresentation = "a traditional";
312 if (needEscaping(TranslatedSpelling)) {
313 SourceRepresentation = "a traditional";
314 TargetRepresentation = "an alternative";
316 StringRef SpellingEx = Lexer::getSourceText(
317 CharSourceRange::getCharRange(
318 TokenRange.getBegin().getLocWithOffset(-1),
319 TokenRange.getBegin().getLocWithOffset(Spelling.size() + 1U)),
320 *Result.SourceManager, Result.Context->getLangOpts());
321 if (SpellingEx.empty() || !isSeparator(SpellingEx.front()))
322 FixSpelling.insert(FixSpelling.begin(), ' ');
323 if (SpellingEx.empty() || !isSeparator(SpellingEx.back()))
324 FixSpelling.push_back(' ');
327 diag(
328 Loc,
329 "'%0' is %1 token spelling, consider using %2 token '%3' for consistency")
330 << Spelling << SourceRepresentation << TargetRepresentation
331 << TranslatedSpelling
332 << FixItHint::CreateReplacement(TokenRange, FixSpelling);
335 } // namespace clang::tidy::readability