[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