[clang-tools-extra] 5122738 - [clang-tidy] Support expressions of literals in modernize-macro-to-enum
via cfe-commits
cfe-commits at lists.llvm.org
Fri May 13 17:46:28 PDT 2022
Author: Richard
Date: 2022-05-13T18:45:54-06:00
New Revision: 5122738331362b574baf0a5a17311cddd52a253e
URL: https://github.com/llvm/llvm-project/commit/5122738331362b574baf0a5a17311cddd52a253e
DIFF: https://github.com/llvm/llvm-project/commit/5122738331362b574baf0a5a17311cddd52a253e.diff
LOG: [clang-tidy] Support expressions of literals in modernize-macro-to-enum
Add a recursive descent parser to match macro expansion tokens against
fully formed valid expressions of integral literals. Partial
expressions will not be matched -- they can't be valid initializing
expressions for an enum.
Differential Revision: https://reviews.llvm.org/D124500
Fixes #55055
Added:
clang-tools-extra/clang-tidy/modernize/IntegralLiteralExpressionMatcher.cpp
clang-tools-extra/clang-tidy/modernize/IntegralLiteralExpressionMatcher.h
clang-tools-extra/unittests/clang-tidy/ModernizeModuleTest.cpp
Modified:
clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp
clang-tools-extra/docs/clang-tidy/checks/modernize-macro-to-enum.rst
clang-tools-extra/test/clang-tidy/checkers/modernize-macro-to-enum.cpp
clang-tools-extra/unittests/clang-tidy/CMakeLists.txt
Removed:
################################################################################
diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index 6bf20552e0182..9d13001037b8e 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -9,6 +9,7 @@ add_clang_library(clangTidyModernizeModule
ConcatNestedNamespacesCheck.cpp
DeprecatedHeadersCheck.cpp
DeprecatedIosBaseAliasesCheck.cpp
+ IntegralLiteralExpressionMatcher.cpp
LoopConvertCheck.cpp
LoopConvertUtils.cpp
MacroToEnumCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/IntegralLiteralExpressionMatcher.cpp b/clang-tools-extra/clang-tidy/modernize/IntegralLiteralExpressionMatcher.cpp
new file mode 100644
index 0000000000000..91a10f8956e62
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/IntegralLiteralExpressionMatcher.cpp
@@ -0,0 +1,232 @@
+//===--- IntegralLiteralExpressionMatcher.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 "IntegralLiteralExpressionMatcher.h"
+
+#include <cctype>
+#include <stdexcept>
+
+namespace clang {
+namespace tidy {
+namespace modernize {
+
+// Validate that this literal token is a valid integer literal. A literal token
+// could be a floating-point token, which isn't acceptable as a value for an
+// enumeration. A floating-point token must either have a decimal point or an
+// exponent ('E' or 'P').
+static bool isIntegralConstant(const Token &Token) {
+ const char *Begin = Token.getLiteralData();
+ const char *End = Begin + Token.getLength();
+
+ // Not a hexadecimal floating-point literal.
+ if (Token.getLength() > 2 && Begin[0] == '0' && std::toupper(Begin[1]) == 'X')
+ return std::none_of(Begin + 2, End, [](char C) {
+ return C == '.' || std::toupper(C) == 'P';
+ });
+
+ // Not a decimal floating-point literal or complex literal.
+ return std::none_of(Begin, End, [](char C) {
+ return C == '.' || std::toupper(C) == 'E' || std::toupper(C) == 'I';
+ });
+}
+
+bool IntegralLiteralExpressionMatcher::advance() {
+ ++Current;
+ return Current != End;
+}
+
+bool IntegralLiteralExpressionMatcher::consume(tok::TokenKind Kind) {
+ if (Current->is(Kind)) {
+ ++Current;
+ return true;
+ }
+
+ return false;
+}
+
+bool IntegralLiteralExpressionMatcher::nonTerminalChainedExpr(
+ bool (IntegralLiteralExpressionMatcher::*NonTerminal)(),
+ const std::function<bool(Token)> &IsKind) {
+ if (!(this->*NonTerminal)())
+ return false;
+ if (Current == End)
+ return true;
+
+ while (Current != End) {
+ if (!IsKind(*Current))
+ break;
+
+ if (!advance())
+ return false;
+
+ if (!(this->*NonTerminal)())
+ return false;
+ }
+
+ return true;
+}
+
+// Advance over unary operators.
+bool IntegralLiteralExpressionMatcher::unaryOperator() {
+ if (Current->isOneOf(tok::TokenKind::minus, tok::TokenKind::plus,
+ tok::TokenKind::tilde, tok::TokenKind::exclaim)) {
+ return advance();
+ }
+
+ return true;
+}
+
+bool IntegralLiteralExpressionMatcher::unaryExpr() {
+ if (!unaryOperator())
+ return false;
+
+ if (consume(tok::TokenKind::l_paren)) {
+ if (Current == End)
+ return false;
+
+ if (!expr())
+ return false;
+
+ if (Current == End)
+ return false;
+
+ return consume(tok::TokenKind::r_paren);
+ }
+
+ if (!Current->isLiteral() || isStringLiteral(Current->getKind()) ||
+ !isIntegralConstant(*Current)) {
+ return false;
+ }
+ ++Current;
+ return true;
+}
+
+bool IntegralLiteralExpressionMatcher::multiplicativeExpr() {
+ return nonTerminalChainedExpr<tok::TokenKind::star, tok::TokenKind::slash,
+ tok::TokenKind::percent>(
+ &IntegralLiteralExpressionMatcher::unaryExpr);
+}
+
+bool IntegralLiteralExpressionMatcher::additiveExpr() {
+ return nonTerminalChainedExpr<tok::plus, tok::minus>(
+ &IntegralLiteralExpressionMatcher::multiplicativeExpr);
+}
+
+bool IntegralLiteralExpressionMatcher::shiftExpr() {
+ return nonTerminalChainedExpr<tok::TokenKind::lessless,
+ tok::TokenKind::greatergreater>(
+ &IntegralLiteralExpressionMatcher::additiveExpr);
+}
+
+bool IntegralLiteralExpressionMatcher::compareExpr() {
+ if (!shiftExpr())
+ return false;
+ if (Current == End)
+ return true;
+
+ if (Current->is(tok::TokenKind::spaceship)) {
+ if (!advance())
+ return false;
+
+ if (!shiftExpr())
+ return false;
+ }
+
+ return true;
+}
+
+bool IntegralLiteralExpressionMatcher::relationalExpr() {
+ return nonTerminalChainedExpr<tok::TokenKind::less, tok::TokenKind::greater,
+ tok::TokenKind::lessequal,
+ tok::TokenKind::greaterequal>(
+ &IntegralLiteralExpressionMatcher::compareExpr);
+}
+
+bool IntegralLiteralExpressionMatcher::equalityExpr() {
+ return nonTerminalChainedExpr<tok::TokenKind::equalequal,
+ tok::TokenKind::exclaimequal>(
+ &IntegralLiteralExpressionMatcher::relationalExpr);
+}
+
+bool IntegralLiteralExpressionMatcher::andExpr() {
+ return nonTerminalChainedExpr<tok::TokenKind::amp>(
+ &IntegralLiteralExpressionMatcher::equalityExpr);
+}
+
+bool IntegralLiteralExpressionMatcher::exclusiveOrExpr() {
+ return nonTerminalChainedExpr<tok::TokenKind::caret>(
+ &IntegralLiteralExpressionMatcher::andExpr);
+}
+
+bool IntegralLiteralExpressionMatcher::inclusiveOrExpr() {
+ return nonTerminalChainedExpr<tok::TokenKind::pipe>(
+ &IntegralLiteralExpressionMatcher::exclusiveOrExpr);
+}
+
+bool IntegralLiteralExpressionMatcher::logicalAndExpr() {
+ return nonTerminalChainedExpr<tok::TokenKind::ampamp>(
+ &IntegralLiteralExpressionMatcher::inclusiveOrExpr);
+}
+
+bool IntegralLiteralExpressionMatcher::logicalOrExpr() {
+ return nonTerminalChainedExpr<tok::TokenKind::pipepipe>(
+ &IntegralLiteralExpressionMatcher::logicalAndExpr);
+}
+
+bool IntegralLiteralExpressionMatcher::conditionalExpr() {
+ if (!logicalOrExpr())
+ return false;
+ if (Current == End)
+ return true;
+
+ if (Current->is(tok::TokenKind::question)) {
+ if (!advance())
+ return false;
+
+ // A gcc extension allows x ? : y as a synonym for x ? x : y.
+ if (Current->is(tok::TokenKind::colon)) {
+ if (!advance())
+ return false;
+
+ if (!expr())
+ return false;
+
+ return true;
+ }
+
+ if (!expr())
+ return false;
+ if (Current == End)
+ return false;
+
+ if (!Current->is(tok::TokenKind::colon))
+ return false;
+
+ if (!advance())
+ return false;
+
+ if (!expr())
+ return false;
+ }
+ return true;
+}
+
+bool IntegralLiteralExpressionMatcher::commaExpr() {
+ return nonTerminalChainedExpr<tok::TokenKind::comma>(
+ &IntegralLiteralExpressionMatcher::conditionalExpr);
+}
+
+bool IntegralLiteralExpressionMatcher::expr() { return commaExpr(); }
+
+bool IntegralLiteralExpressionMatcher::match() {
+ return expr() && Current == End;
+}
+
+} // namespace modernize
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tools-extra/clang-tidy/modernize/IntegralLiteralExpressionMatcher.h b/clang-tools-extra/clang-tidy/modernize/IntegralLiteralExpressionMatcher.h
new file mode 100644
index 0000000000000..9202cc1ed4574
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/IntegralLiteralExpressionMatcher.h
@@ -0,0 +1,73 @@
+//===--- IntegralLiteralExpressionMatcher.h - 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_INTEGRAL_LITERAL_EXPRESSION_MATCHER_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_INTEGRAL_LITERAL_EXPRESSION_MATCHER_H
+
+#include <clang/Lex/Token.h>
+#include <llvm/ADT/ArrayRef.h>
+
+namespace clang {
+namespace tidy {
+namespace modernize {
+
+// Parses an array of tokens and returns true if they conform to the rules of
+// C++ for whole expressions involving integral literals. Follows the operator
+// precedence rules of C++.
+class IntegralLiteralExpressionMatcher {
+public:
+ IntegralLiteralExpressionMatcher(ArrayRef<Token> Tokens)
+ : Current(Tokens.begin()), End(Tokens.end()) {}
+
+ bool match();
+
+private:
+ bool advance();
+ bool consume(tok::TokenKind Kind);
+ bool nonTerminalChainedExpr(
+ bool (IntegralLiteralExpressionMatcher::*NonTerminal)(),
+ const std::function<bool(Token)> &IsKind);
+ template <tok::TokenKind Kind>
+ bool nonTerminalChainedExpr(
+ bool (IntegralLiteralExpressionMatcher::*NonTerminal)()) {
+ return nonTerminalChainedExpr(NonTerminal,
+ [](Token Tok) { return Tok.is(Kind); });
+ }
+ template <tok::TokenKind K1, tok::TokenKind K2, tok::TokenKind... Ks>
+ bool nonTerminalChainedExpr(
+ bool (IntegralLiteralExpressionMatcher::*NonTerminal)()) {
+ return nonTerminalChainedExpr(
+ NonTerminal, [](Token Tok) { return Tok.isOneOf(K1, K2, Ks...); });
+ }
+
+ bool unaryOperator();
+ bool unaryExpr();
+ bool multiplicativeExpr();
+ bool additiveExpr();
+ bool shiftExpr();
+ bool compareExpr();
+ bool relationalExpr();
+ bool equalityExpr();
+ bool andExpr();
+ bool exclusiveOrExpr();
+ bool inclusiveOrExpr();
+ bool logicalAndExpr();
+ bool logicalOrExpr();
+ bool conditionalExpr();
+ bool commaExpr();
+ bool expr();
+
+ ArrayRef<Token>::iterator Current;
+ ArrayRef<Token>::iterator End;
+};
+
+} // namespace modernize
+} // namespace tidy
+} // namespace clang
+
+#endif
diff --git a/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp b/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp
index 0d813a14233ca..5d7daf837007f 100644
--- a/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp
@@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//
#include "MacroToEnumCheck.h"
+#include "IntegralLiteralExpressionMatcher.h"
+
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Preprocessor.h"
@@ -73,25 +75,6 @@ static bool hasOnlyComments(SourceLocation Loc, const LangOptions &Options,
return true;
}
-// Validate that this literal token is a valid integer literal. A literal token
-// could be a floating-point token, which isn't acceptable as a value for an
-// enumeration. A floating-point token must either have a decimal point or an
-// exponent ('E' or 'P').
-static bool isIntegralConstant(const Token &Token) {
- const char *Begin = Token.getLiteralData();
- const char *End = Begin + Token.getLength();
-
- // not a hexadecimal floating-point literal
- if (Token.getLength() > 2 && Begin[0] == '0' && std::toupper(Begin[1]) == 'X')
- return std::none_of(Begin + 2, End, [](char C) {
- return C == '.' || std::toupper(C) == 'P';
- });
-
- // not a decimal floating-point literal
- return std::none_of(
- Begin, End, [](char C) { return C == '.' || std::toupper(C) == 'E'; });
-}
-
static StringRef getTokenName(const Token &Tok) {
return Tok.is(tok::raw_identifier) ? Tok.getRawIdentifier()
: Tok.getIdentifierInfo()->getName();
@@ -232,6 +215,7 @@ class MacroToEnumCallbacks : public PPCallbacks {
void issueDiagnostics();
void warnMacroEnum(const EnumMacro &Macro) const;
void fixEnumMacro(const MacroList &MacroList) const;
+ bool isInitializer(ArrayRef<Token> MacroTokens);
MacroToEnumCheck *Check;
const LangOptions &LangOpts;
@@ -335,6 +319,13 @@ void MacroToEnumCallbacks::FileChanged(SourceLocation Loc,
CurrentFile = &Files.back();
}
+bool MacroToEnumCallbacks::isInitializer(ArrayRef<Token> MacroTokens)
+{
+ IntegralLiteralExpressionMatcher Matcher(MacroTokens);
+ return Matcher.match();
+}
+
+
// Any defined but rejected macro is scanned for identifiers that
// are to be excluded as enums.
void MacroToEnumCallbacks::MacroDefined(const Token &MacroNameTok,
@@ -360,55 +351,8 @@ void MacroToEnumCallbacks::MacroDefined(const Token &MacroNameTok,
return;
}
- // Return Lit when +Lit, -Lit or ~Lit; otherwise return Unknown.
- Token Unknown;
- Unknown.setKind(tok::TokenKind::unknown);
- auto GetUnopArg = [Unknown](Token First, Token Second) {
- return First.isOneOf(tok::TokenKind::minus, tok::TokenKind::plus,
- tok::TokenKind::tilde)
- ? Second
- : Unknown;
- };
-
- // It could just be a single token.
- Token Tok = MacroTokens.front();
-
- // It can be any arbitrary nesting of matched parentheses around
- // +Lit, -Lit, ~Lit or Lit.
- if (MacroTokens.size() > 2) {
- // Strip off matching '(', ..., ')' token pairs.
- size_t Begin = 0;
- size_t End = MacroTokens.size() - 1;
- assert(End >= 2U);
- for (; Begin < MacroTokens.size() / 2; ++Begin, --End) {
- if (!MacroTokens[Begin].is(tok::TokenKind::l_paren) ||
- !MacroTokens[End].is(tok::TokenKind::r_paren))
- break;
- }
- size_t Size = End >= Begin ? (End - Begin + 1U) : 0U;
-
- // It was a single token inside matching parens.
- if (Size == 1)
- Tok = MacroTokens[Begin];
- else if (Size == 2)
- // It can be +Lit, -Lit or ~Lit.
- Tok = GetUnopArg(MacroTokens[Begin], MacroTokens[End]);
- else {
- // Zero or too many tokens after we stripped matching parens.
- rememberExpressionTokens(MacroTokens);
- return;
- }
- } else if (MacroTokens.size() == 2) {
- // It can be +Lit, -Lit, or ~Lit.
- Tok = GetUnopArg(MacroTokens.front(), MacroTokens.back());
- }
-
- if (!Tok.isLiteral() || isStringLiteral(Tok.getKind()) ||
- !isIntegralConstant(Tok)) {
- if (Tok.isAnyIdentifier())
- rememberExpressionName(Tok);
+ if (!isInitializer(MacroTokens))
return;
- }
if (!isConsecutiveMacro(MD))
newEnum();
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize-macro-to-enum.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize-macro-to-enum.rst
index d7784419faa1f..d56fa7194dd54 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize-macro-to-enum.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize-macro-to-enum.rst
@@ -14,12 +14,16 @@ within the constraints outlined below.
Potential macros for replacement must meet the following constraints:
-- Macros must expand only to integral literal tokens or simple expressions
- of literal tokens. The unary operators plus, minus and tilde are
- recognized to allow for positive, negative and bitwise negated integers.
- The above expressions may also be surrounded by matching pairs of
- parentheses. More complicated integral constant expressions are not
- recognized by this check.
+- Macros must expand only to integral literal tokens or expressions
+ of literal tokens. The expression may contain any of the unary
+ operators ``-``, ``+``, ``~`` or ``!``, any of the binary operators
+ ``,``, ``-``, ``+``, ``*``, ``/``, ``%``, ``&``, ``|``, ``^``, ``<``,
+ ``>``, ``<=``, ``>=``, ``==``, ``!=``, ``||``, ``&&``, ``<<``, ``>>``
+ or ``<=>``, the ternary operator ``?:`` and its
+ `GNU extension <https://gcc.gnu.org/onlinedocs/gcc/Conditionals.html>`_.
+ Parenthesized expressions are also recognized. This recognizes
+ most valid expressions. In particular, expressions with the
+ ``sizeof`` operator are not recognized.
- Macros must be defined on sequential source file lines, or with
only comment lines in between macro definitions.
- Macros must all be defined in the same source file.
@@ -27,7 +31,10 @@ Potential macros for replacement must meet the following constraints:
(Conditional include guards are exempt from this constraint.)
- Macros must not be defined adjacent to other preprocessor directives.
- Macros must not be used in any conditional preprocessing directive.
+- Macros must not be used as arguments to other macros.
- Macros must not be undefined.
+- Macros must be defined at the top-level, not inside any declaration or
+ definition.
Each cluster of macros meeting the above constraints is presumed to
be a set of values suitable for replacement by an anonymous enum.
@@ -37,6 +44,14 @@ same line as a macro definition or between subsequent macro definitions
are preserved in the output. No formatting is assumed in the provided
replacements, although clang-tidy can optionally format all fixes.
+.. warning::
+
+ Initializing expressions are assumed to be valid initializers for
+ an enum. C requires that enum values fit into an ``int``, but
+ this may not be the case for some accepted constant expressions.
+ For instance ``1 << 40`` will not fit into an ``int`` when the size of
+ an ``int`` is 32 bits.
+
Examples:
.. code-block:: c++
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize-macro-to-enum.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize-macro-to-enum.cpp
index 616e49012c612..06ed2d3f5ca8a 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize-macro-to-enum.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize-macro-to-enum.cpp
@@ -11,6 +11,47 @@
#endif
+// Macros expanding to expressions involving only literals are converted.
+#define EXPR1 1 - 1
+#define EXPR2 1 + 1
+#define EXPR3 1 * 1
+#define EXPR4 1 / 1
+#define EXPR5 1 | 1
+#define EXPR6 1 & 1
+#define EXPR7 1 << 1
+#define EXPR8 1 >> 1
+#define EXPR9 1 % 2
+#define EXPR10 1 ^ 1
+#define EXPR11 (1 + (2))
+#define EXPR12 ((1) + (2 + 0) + (1 * 1) + (1 / 1) + (1 | 1 ) + (1 & 1) + (1 << 1) + (1 >> 1) + (1 % 2) + (1 ^ 1))
+// CHECK-MESSAGES: :[[@LINE-12]]:1: warning: replace macro with enum [modernize-macro-to-enum]
+// CHECK-MESSAGES: :[[@LINE-13]]:9: warning: macro 'EXPR1' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-13]]:9: warning: macro 'EXPR2' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-13]]:9: warning: macro 'EXPR3' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-13]]:9: warning: macro 'EXPR4' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-13]]:9: warning: macro 'EXPR5' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-13]]:9: warning: macro 'EXPR6' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-13]]:9: warning: macro 'EXPR7' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-13]]:9: warning: macro 'EXPR8' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-13]]:9: warning: macro 'EXPR9' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-13]]:9: warning: macro 'EXPR10' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-13]]:9: warning: macro 'EXPR11' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-13]]:9: warning: macro 'EXPR12' defines an integral constant; prefer an enum instead
+// CHECK-FIXES: enum {
+// CHECK-FIXES-NEXT: EXPR1 = 1 - 1,
+// CHECK-FIXES-NEXT: EXPR2 = 1 + 1,
+// CHECK-FIXES-NEXT: EXPR3 = 1 * 1,
+// CHECK-FIXES-NEXT: EXPR4 = 1 / 1,
+// CHECK-FIXES-NEXT: EXPR5 = 1 | 1,
+// CHECK-FIXES-NEXT: EXPR6 = 1 & 1,
+// CHECK-FIXES-NEXT: EXPR7 = 1 << 1,
+// CHECK-FIXES-NEXT: EXPR8 = 1 >> 1,
+// CHECK-FIXES-NEXT: EXPR9 = 1 % 2,
+// CHECK-FIXES-NEXT: EXPR10 = 1 ^ 1,
+// CHECK-FIXES-NEXT: EXPR11 = (1 + (2)),
+// CHECK-FIXES-NEXT: EXPR12 = ((1) + (2 + 0) + (1 * 1) + (1 / 1) + (1 | 1 ) + (1 & 1) + (1 << 1) + (1 >> 1) + (1 % 2) + (1 ^ 1))
+// CHECK-FIXES-NEXT: };
+
#define RED 0xFF0000
#define GREEN 0x00FF00
#define BLUE 0x0000FF
@@ -329,7 +370,7 @@ template <int N>
#define INSIDE9 9
bool fn()
{
- #define INSIDE10 10
+#define INSIDE10 10
return INSIDE9 > 1 || INSIDE10 < N;
}
diff --git a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt
index 253f78eb36ea9..ef90948db61b5 100644
--- a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt
+++ b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt
@@ -25,6 +25,7 @@ add_extra_unittest(ClangTidyTests
GlobListTest.cpp
GoogleModuleTest.cpp
LLVMModuleTest.cpp
+ ModernizeModuleTest.cpp
NamespaceAliaserTest.cpp
ObjCModuleTest.cpp
OptionsProviderTest.cpp
@@ -53,6 +54,7 @@ target_link_libraries(ClangTidyTests
clangTidyAndroidModule
clangTidyGoogleModule
clangTidyLLVMModule
+ clangTidyModernizeModule
clangTidyObjCModule
clangTidyReadabilityModule
clangTidyUtils
diff --git a/clang-tools-extra/unittests/clang-tidy/ModernizeModuleTest.cpp b/clang-tools-extra/unittests/clang-tidy/ModernizeModuleTest.cpp
new file mode 100644
index 0000000000000..38347ded439b9
--- /dev/null
+++ b/clang-tools-extra/unittests/clang-tidy/ModernizeModuleTest.cpp
@@ -0,0 +1,214 @@
+//===---- ModernizeModuleTest.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 "ClangTidyTest.h"
+#include "modernize/IntegralLiteralExpressionMatcher.h"
+#include "clang/Lex/Lexer.h"
+#include "gtest/gtest.h"
+
+#include <cstring>
+#include <iterator>
+#include <string>
+#include <vector>
+
+namespace clang {
+namespace tidy {
+namespace test {
+
+static std::vector<Token> tokenify(const char *Text) {
+ LangOptions LangOpts;
+ std::vector<std::string> Includes;
+ LangOptions::setLangDefaults(LangOpts, Language::CXX, llvm::Triple(),
+ Includes, LangStandard::lang_cxx20);
+ Lexer Lex(SourceLocation{}, LangOpts, Text, Text, Text + std::strlen(Text));
+ std::vector<Token> Tokens;
+ bool End = false;
+ while (!End) {
+ Token Tok;
+ End = Lex.LexFromRawLexer(Tok);
+ Tokens.push_back(Tok);
+ }
+
+ return Tokens;
+}
+
+static bool matchText(const char *Text) {
+ std::vector<Token> Tokens{tokenify(Text)};
+ modernize::IntegralLiteralExpressionMatcher Matcher(Tokens);
+
+ return Matcher.match();
+}
+
+namespace {
+
+struct Param {
+ bool Matched;
+ const char *Text;
+};
+
+class MatcherTest : public ::testing::TestWithParam<Param> {};
+
+} // namespace
+
+static const Param Params[] = {
+ // Accept integral literals.
+ {true, "1"},
+ {true, "0177"},
+ {true, "0xdeadbeef"},
+ {true, "0b1011"},
+ {true, "'c'"},
+ // Reject non-integral literals.
+ {false, "1.23"},
+ {false, "0x1p3"},
+ {false, R"("string")"},
+ {false, "1i"},
+
+ // Accept literals with these unary operators.
+ {true, "-1"},
+ {true, "+1"},
+ {true, "~1"},
+ {true, "!1"},
+ // Reject invalid unary operators.
+ {false, "1-"},
+ {false, "1+"},
+ {false, "1~"},
+ {false, "1!"},
+
+ // Accept valid binary operators.
+ {true, "1+1"},
+ {true, "1-1"},
+ {true, "1*1"},
+ {true, "1/1"},
+ {true, "1%2"},
+ {true, "1<<1"},
+ {true, "1>>1"},
+ {true, "1<=>1"},
+ {true, "1<1"},
+ {true, "1>1"},
+ {true, "1<=1"},
+ {true, "1>=1"},
+ {true, "1==1"},
+ {true, "1!=1"},
+ {true, "1&1"},
+ {true, "1^1"},
+ {true, "1|1"},
+ {true, "1&&1"},
+ {true, "1||1"},
+ {true, "1+ +1"}, // A space is needed to avoid being tokenized as ++ or --.
+ {true, "1- -1"},
+ {true, "1,1"},
+ // Reject invalid binary operators.
+ {false, "1+"},
+ {false, "1-"},
+ {false, "1*"},
+ {false, "1/"},
+ {false, "1%"},
+ {false, "1<<"},
+ {false, "1>>"},
+ {false, "1<=>"},
+ {false, "1<"},
+ {false, "1>"},
+ {false, "1<="},
+ {false, "1>="},
+ {false, "1=="},
+ {false, "1!="},
+ {false, "1&"},
+ {false, "1^"},
+ {false, "1|"},
+ {false, "1&&"},
+ {false, "1||"},
+ {false, "1,"},
+ {false, ",1"},
+
+ // Accept valid ternary operators.
+ {true, "1?1:1"},
+ {true, "1?:1"}, // A gcc extension treats x ? : y as x ? x : y.
+ // Reject invalid ternary operators.
+ {false, "?"},
+ {false, "?1"},
+ {false, "?:"},
+ {false, "?:1"},
+ {false, "?1:"},
+ {false, "?1:1"},
+ {false, "1?"},
+ {false, "1?1"},
+ {false, "1?:"},
+ {false, "1?1:"},
+
+ // Accept parenthesized expressions.
+ {true, "(1)"},
+ {true, "((+1))"},
+ {true, "((+(1)))"},
+ {true, "(-1)"},
+ {true, "-(1)"},
+ {true, "(+1)"},
+ {true, "((+1))"},
+ {true, "+(1)"},
+ {true, "(~1)"},
+ {true, "~(1)"},
+ {true, "(!1)"},
+ {true, "!(1)"},
+ {true, "(1+1)"},
+ {true, "(1-1)"},
+ {true, "(1*1)"},
+ {true, "(1/1)"},
+ {true, "(1%2)"},
+ {true, "(1<<1)"},
+ {true, "(1>>1)"},
+ {true, "(1<=>1)"},
+ {true, "(1<1)"},
+ {true, "(1>1)"},
+ {true, "(1<=1)"},
+ {true, "(1>=1)"},
+ {true, "(1==1)"},
+ {true, "(1!=1)"},
+ {true, "(1&1)"},
+ {true, "(1^1)"},
+ {true, "(1|1)"},
+ {true, "(1&&1)"},
+ {true, "(1||1)"},
+ {true, "(1?1:1)"},
+
+ // Accept more complicated "chained" expressions.
+ {true, "1+1+1"},
+ {true, "1+1+1+1"},
+ {true, "1+1+1+1+1"},
+ {true, "1*1*1"},
+ {true, "1*1*1*1"},
+ {true, "1*1*1*1*1"},
+ {true, "1<<1<<1"},
+ {true, "4U>>1>>1"},
+ {true, "1<1<1"},
+ {true, "1>1>1"},
+ {true, "1<=1<=1"},
+ {true, "1>=1>=1"},
+ {true, "1==1==1"},
+ {true, "1!=1!=1"},
+ {true, "1&1&1"},
+ {true, "1^1^1"},
+ {true, "1|1|1"},
+ {true, "1&&1&&1"},
+ {true, "1||1||1"},
+ {true, "1,1,1"},
+};
+
+TEST_P(MatcherTest, MatchResult) {
+ EXPECT_TRUE(matchText(GetParam().Text) == GetParam().Matched);
+}
+
+INSTANTIATE_TEST_SUITE_P(TokenExpressionParserTests, MatcherTest,
+ ::testing::ValuesIn(Params));
+
+} // namespace test
+} // namespace tidy
+} // namespace clang
+
+std::ostream &operator<<(std::ostream &Str,
+ const clang::tidy::test::Param &Value) {
+ return Str << "Matched: " << std::boolalpha << Value.Matched << ", Text: '"
+ << Value.Text << "'";
+}
More information about the cfe-commits
mailing list