Skip to content

Commit a084854

Browse files
committed
[clang-tidy] Add readability-operators-representation check
Check helps enforce consistent token representation for binary, unary and overloaded operators in C++ code. The check supports both traditional and alternative representations of operators. Reviewed By: carlosgalvezp Differential Revision: https://reviews.llvm.org/D144522
1 parent f1c5c84 commit a084854

File tree

9 files changed

+828
-0
lines changed

9 files changed

+828
-0
lines changed

clang-tools-extra/clang-tidy/readability/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ add_clang_library(clangTidyReadabilityModule
2929
NamedParameterCheck.cpp
3030
NamespaceCommentCheck.cpp
3131
NonConstParameterCheck.cpp
32+
OperatorsRepresentationCheck.cpp
3233
QualifiedAutoCheck.cpp
3334
ReadabilityTidyModule.cpp
3435
RedundantAccessSpecifiersCheck.cpp
Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
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+
//===----------------------------------------------------------------------===//
9+
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>
18+
19+
using namespace clang::ast_matchers;
20+
21+
namespace clang::tidy::readability {
22+
23+
static StringRef getOperatorSpelling(SourceLocation Loc, ASTContext &Context) {
24+
if (Loc.isInvalid())
25+
return {};
26+
27+
SourceManager &SM = Context.getSourceManager();
28+
29+
Loc = SM.getSpellingLoc(Loc);
30+
if (Loc.isInvalid())
31+
return {};
32+
33+
const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);
34+
return Lexer::getSourceText(TokenRange, SM, Context.getLangOpts());
35+
}
36+
37+
namespace {
38+
39+
AST_MATCHER_P2(BinaryOperator, hasInvalidBinaryOperatorRepresentation,
40+
BinaryOperatorKind, Kind, llvm::StringRef,
41+
ExpectedRepresentation) {
42+
if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())
43+
return false;
44+
45+
StringRef Spelling =
46+
getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
47+
return !Spelling.empty() && Spelling != ExpectedRepresentation;
48+
}
49+
50+
AST_MATCHER_P2(UnaryOperator, hasInvalidUnaryOperatorRepresentation,
51+
UnaryOperatorKind, Kind, llvm::StringRef,
52+
ExpectedRepresentation) {
53+
if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())
54+
return false;
55+
56+
StringRef Spelling =
57+
getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
58+
return !Spelling.empty() && Spelling != ExpectedRepresentation;
59+
}
60+
61+
AST_MATCHER_P2(CXXOperatorCallExpr, hasInvalidOverloadedOperatorRepresentation,
62+
OverloadedOperatorKind, Kind, llvm::StringRef,
63+
ExpectedRepresentation) {
64+
if (Node.getOperator() != Kind || ExpectedRepresentation.empty())
65+
return false;
66+
67+
StringRef Spelling =
68+
getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
69+
return !Spelling.empty() && Spelling != ExpectedRepresentation;
70+
}
71+
72+
} // namespace
73+
74+
constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 2U>
75+
UnaryRepresentation{{{"!", "not"}, {"~", "compl"}}};
76+
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"}}};
87+
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;
94+
}
95+
96+
for (const auto &[Traditional, Alternative] : OperatorsRepresentation) {
97+
if (Value == Traditional)
98+
return Alternative;
99+
if (Value == Alternative)
100+
return Traditional;
101+
}
102+
return {};
103+
}
104+
105+
static bool isNotOperatorStr(llvm::StringRef Value) {
106+
return translate(Value).empty();
107+
}
108+
109+
static bool isSeparator(char C) noexcept {
110+
constexpr llvm::StringRef Separators(" \t\r\n\0()<>{};,");
111+
return llvm::is_contained(Separators, C);
112+
}
113+
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;
124+
}
125+
}
126+
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 {};
135+
}
136+
137+
template <typename T>
138+
static bool isAnyOperatorEnabled(const std::vector<llvm::StringRef> &Config,
139+
T &&Operators) {
140+
for (const auto &[traditional, alternative] : Operators) {
141+
if (!getRepresentation(Config, traditional, alternative).empty())
142+
return true;
143+
}
144+
return false;
145+
}
146+
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);
156+
}
157+
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));
164+
}
165+
166+
std::optional<TraversalKind>
167+
OperatorsRepresentationCheck::getCheckTraversalKind() const {
168+
return TK_IgnoreUnlessSpelledInSource;
169+
}
170+
171+
bool OperatorsRepresentationCheck::isLanguageVersionSupported(
172+
const LangOptions &LangOpts) const {
173+
return LangOpts.CPlusPlus;
174+
}
175+
176+
void OperatorsRepresentationCheck::registerBinaryOperatorMatcher(
177+
MatchFinder *Finder) {
178+
if (!isAnyOperatorEnabled(BinaryOperators, OperatorsRepresentation))
179+
return;
180+
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);
207+
}
208+
209+
void OperatorsRepresentationCheck::registerUnaryOperatorMatcher(
210+
MatchFinder *Finder) {
211+
if (!isAnyOperatorEnabled(BinaryOperators, UnaryRepresentation))
212+
return;
213+
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);
223+
}
224+
225+
void OperatorsRepresentationCheck::registerOverloadedOperatorMatcher(
226+
MatchFinder *Finder) {
227+
if (!isAnyOperatorEnabled(OverloadedOperators, OperatorsRepresentation) &&
228+
!isAnyOperatorEnabled(OverloadedOperators, UnaryRepresentation))
229+
return;
230+
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);
269+
}
270+
271+
void OperatorsRepresentationCheck::registerMatchers(MatchFinder *Finder) {
272+
registerBinaryOperatorMatcher(Finder);
273+
registerUnaryOperatorMatcher(Finder);
274+
registerOverloadedOperatorMatcher(Finder);
275+
}
276+
277+
void OperatorsRepresentationCheck::check(
278+
const MatchFinder::MatchResult &Result) {
279+
280+
SourceLocation Loc;
281+
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();
289+
290+
if (Loc.isInvalid())
291+
return;
292+
293+
Loc = Result.SourceManager->getSpellingLoc(Loc);
294+
if (Loc.isInvalid() || Loc.isMacroID())
295+
return;
296+
297+
const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);
298+
if (TokenRange.isInvalid())
299+
return;
300+
301+
StringRef Spelling = Lexer::getSourceText(TokenRange, *Result.SourceManager,
302+
Result.Context->getLangOpts());
303+
StringRef TranslatedSpelling = translate(Spelling);
304+
305+
if (TranslatedSpelling.empty())
306+
return;
307+
308+
std::string FixSpelling = TranslatedSpelling.str();
309+
310+
StringRef SourceRepresentation = "an alternative";
311+
StringRef TargetRepresentation = "a traditional";
312+
if (needEscaping(TranslatedSpelling)) {
313+
SourceRepresentation = "a traditional";
314+
TargetRepresentation = "an alternative";
315+
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(' ');
325+
}
326+
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);
333+
}
334+
335+
} // namespace clang::tidy::readability
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===--- OperatorsRepresentationCheck.h - clang-tidy ------------*- C++ -*-===//
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+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_OPERATORSREPRESENTATIONCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_OPERATORSREPRESENTATIONCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
#include <vector>
14+
15+
namespace clang::tidy::readability {
16+
17+
/// Enforces consistent token representation for invoked binary, unary
18+
/// and overloaded operators in C++ code.
19+
///
20+
/// For the user-facing documentation see:
21+
/// http://clang.llvm.org/extra/clang-tidy/checks/readability/operators-representation.html
22+
class OperatorsRepresentationCheck : public ClangTidyCheck {
23+
public:
24+
OperatorsRepresentationCheck(StringRef Name, ClangTidyContext *Context);
25+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
26+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
27+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
28+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
29+
std::optional<TraversalKind> getCheckTraversalKind() const override;
30+
31+
private:
32+
void registerBinaryOperatorMatcher(ast_matchers::MatchFinder *Finder);
33+
void registerUnaryOperatorMatcher(ast_matchers::MatchFinder *Finder);
34+
void registerOverloadedOperatorMatcher(ast_matchers::MatchFinder *Finder);
35+
36+
std::vector<llvm::StringRef> BinaryOperators;
37+
std::vector<llvm::StringRef> OverloadedOperators;
38+
};
39+
40+
} // namespace clang::tidy::readability
41+
42+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_OPERATORSREPRESENTATIONCHECK_H

0 commit comments

Comments
 (0)