[clang] f93182a - [clang-format] Handle Verilog numbers and operators

via cfe-commits cfe-commits at lists.llvm.org
Thu Jul 28 17:39:02 PDT 2022


Author: sstwcw
Date: 2022-07-29T00:38:29Z
New Revision: f93182a887889828fc6c963174afd7d7b3625e5a

URL: https://github.com/llvm/llvm-project/commit/f93182a887889828fc6c963174afd7d7b3625e5a
DIFF: https://github.com/llvm/llvm-project/commit/f93182a887889828fc6c963174afd7d7b3625e5a.diff

LOG: [clang-format] Handle Verilog numbers and operators

Reviewed By: HazardyKnusperkeks

Differential Revision: https://reviews.llvm.org/D126845

Added: 
    

Modified: 
    clang/lib/Format/FormatToken.h
    clang/lib/Format/FormatTokenLexer.cpp
    clang/lib/Format/FormatTokenLexer.h
    clang/lib/Format/TokenAnnotator.cpp
    clang/unittests/Format/FormatTestVerilog.cpp
    clang/unittests/Format/TokenAnnotatorTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h
index 73e32979853f..3e7e07fbc97c 100644
--- a/clang/lib/Format/FormatToken.h
+++ b/clang/lib/Format/FormatToken.h
@@ -135,6 +135,8 @@ namespace format {
   TYPE(UnaryOperator)                                                          \
   TYPE(UnionLBrace)                                                            \
   TYPE(UntouchableMacroFunc)                                                   \
+  /* for the base in a number literal, not including the quote */              \
+  TYPE(VerilogNumberBase)                                                      \
   TYPE(Unknown)
 
 /// Determines the semantic type of a syntactic token, e.g. whether "<" is a
@@ -368,6 +370,9 @@ struct FormatToken {
   }
   bool isTypeFinalized() const { return TypeIsFinalized; }
 
+  /// Used to set an operator precedence explicitly.
+  prec::Level ForcedPrecedence = prec::Unknown;
+
   /// The number of newlines immediately before the \c Token.
   ///
   /// This can be used to determine what the user wrote in the original code
@@ -697,6 +702,8 @@ struct FormatToken {
   }
 
   prec::Level getPrecedence() const {
+    if (ForcedPrecedence != prec::Unknown)
+      return ForcedPrecedence;
     return getBinOpPrecedence(Tok.getKind(), /*GreaterThanIsOperator=*/true,
                               /*CPlusPlus11=*/true);
   }
@@ -1119,6 +1126,7 @@ struct AdditionalKeywords {
     // Symbols that are treated as keywords.
     kw_verilogHash = &IdentTable.get("#");
     kw_verilogHashHash = &IdentTable.get("##");
+    kw_apostrophe = &IdentTable.get("\'");
 
     // Keep this at the end of the constructor to make sure everything here
     // is
@@ -1511,11 +1519,14 @@ struct AdditionalKeywords {
   IdentifierInfo *kw_verilogHash;
   IdentifierInfo *kw_verilogHashHash;
 
+  // Symbols in Verilog that don't exist in C++.
+  IdentifierInfo *kw_apostrophe;
+
   /// Returns \c true if \p Tok is a keyword or an identifier.
   bool isWordLike(const FormatToken &Tok) const {
     // getIdentifierinfo returns non-null for keywords as well as identifiers.
     return Tok.Tok.getIdentifierInfo() != nullptr &&
-           !Tok.isOneOf(kw_verilogHash, kw_verilogHashHash);
+           !Tok.isOneOf(kw_verilogHash, kw_verilogHashHash, kw_apostrophe);
   }
 
   /// Returns \c true if \p Tok is a true JavaScript identifier, returns
@@ -1644,6 +1655,11 @@ struct AdditionalKeywords {
     }
   }
 
+  bool isVerilogWordOperator(const FormatToken &Tok) const {
+    return Tok.isOneOf(kw_before, kw_intersect, kw_dist, kw_iff, kw_inside,
+                       kw_with);
+  }
+
   bool isVerilogIdentifier(const FormatToken &Tok) const {
     switch (Tok.Tok.getKind()) {
     case tok::kw_case:

diff  --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp
index 3f9b68ccbb39..f97b140f278a 100644
--- a/clang/lib/Format/FormatTokenLexer.cpp
+++ b/clang/lib/Format/FormatTokenLexer.cpp
@@ -193,6 +193,78 @@ void FormatTokenLexer::tryMergePreviousTokens() {
     if (tryMergeTokens(JavaRightLogicalShiftAssign, TT_BinaryOperator))
       return;
   }
+
+  if (Style.isVerilog()) {
+    // Merge the number following a base like `'h?a0`.
+    if (Tokens.size() >= 3 && Tokens.end()[-3]->is(TT_VerilogNumberBase) &&
+        Tokens.end()[-2]->is(tok::numeric_constant) &&
+        Tokens.back()->isOneOf(tok::numeric_constant, tok::identifier,
+                               tok::question) &&
+        tryMergeTokens(2, TT_Unknown)) {
+      return;
+    }
+    // Part select.
+    if (tryMergeTokensAny({{tok::minus, tok::colon}, {tok::plus, tok::colon}},
+                          TT_BitFieldColon)) {
+      return;
+    }
+    // Xnor. The combined token is treated as a caret which can also be either a
+    // unary or binary operator. The actual type is determined in
+    // TokenAnnotator. We also check the token length so we know it is not
+    // already a merged token.
+    if (Tokens.back()->TokenText.size() == 1 &&
+        tryMergeTokensAny({{tok::caret, tok::tilde}, {tok::tilde, tok::caret}},
+                          TT_BinaryOperator)) {
+      Tokens.back()->Tok.setKind(tok::caret);
+      return;
+    }
+    // Signed shift and distribution weight.
+    if (tryMergeTokens({tok::less, tok::less}, TT_BinaryOperator)) {
+      Tokens.back()->Tok.setKind(tok::lessless);
+      return;
+    }
+    if (tryMergeTokens({tok::greater, tok::greater}, TT_BinaryOperator)) {
+      Tokens.back()->Tok.setKind(tok::greatergreater);
+      return;
+    }
+    if (tryMergeTokensAny({{tok::lessless, tok::equal},
+                           {tok::lessless, tok::lessequal},
+                           {tok::greatergreater, tok::equal},
+                           {tok::greatergreater, tok::greaterequal},
+                           {tok::colon, tok::equal},
+                           {tok::colon, tok::slash}},
+                          TT_BinaryOperator)) {
+      Tokens.back()->ForcedPrecedence = prec::Assignment;
+      return;
+    }
+    // Exponentiation, signed shift, case equality, and wildcard equality.
+    if (tryMergeTokensAny({{tok::star, tok::star},
+                           {tok::lessless, tok::less},
+                           {tok::greatergreater, tok::greater},
+                           {tok::exclaimequal, tok::equal},
+                           {tok::exclaimequal, tok::question},
+                           {tok::equalequal, tok::equal},
+                           {tok::equalequal, tok::question}},
+                          TT_BinaryOperator)) {
+      return;
+    }
+    // Module paths in specify blocks and implications in properties.
+    if (tryMergeTokensAny({{tok::plusequal, tok::greater},
+                           {tok::plus, tok::star, tok::greater},
+                           {tok::minusequal, tok::greater},
+                           {tok::minus, tok::star, tok::greater},
+                           {tok::less, tok::arrow},
+                           {tok::equal, tok::greater},
+                           {tok::star, tok::greater},
+                           {tok::pipeequal, tok::greater},
+                           {tok::pipe, tok::arrow},
+                           {tok::hash, tok::minus, tok::hash},
+                           {tok::hash, tok::equal, tok::hash}},
+                          TT_BinaryOperator)) {
+      Tokens.back()->ForcedPrecedence = prec::Comma;
+      return;
+    }
+  }
 }
 
 bool FormatTokenLexer::tryMergeNSStringLiteral() {
@@ -412,15 +484,28 @@ bool FormatTokenLexer::tryMergeTokens(ArrayRef<tok::TokenKind> Kinds,
 
   SmallVectorImpl<FormatToken *>::const_iterator First =
       Tokens.end() - Kinds.size();
-  if (!First[0]->is(Kinds[0]))
+  for (unsigned i = 0; i < Kinds.size(); ++i)
+    if (!First[i]->is(Kinds[i]))
+      return false;
+
+  return tryMergeTokens(Kinds.size(), NewType);
+}
+
+bool FormatTokenLexer::tryMergeTokens(size_t Count, TokenType NewType) {
+  if (Tokens.size() < Count)
     return false;
+
+  SmallVectorImpl<FormatToken *>::const_iterator First = Tokens.end() - Count;
   unsigned AddLength = 0;
-  for (unsigned i = 1; i < Kinds.size(); ++i) {
-    if (!First[i]->is(Kinds[i]) || First[i]->hasWhitespaceBefore())
+  for (size_t i = 1; i < Count; ++i) {
+    // If there is whitespace separating the token and the previous one,
+    // they should not be merged.
+    if (First[i]->hasWhitespaceBefore())
       return false;
     AddLength += First[i]->TokenText.size();
   }
-  Tokens.resize(Tokens.size() - Kinds.size() + 1);
+
+  Tokens.resize(Tokens.size() - Count + 1);
   First[0]->TokenText = StringRef(First[0]->TokenText.data(),
                                   First[0]->TokenText.size() + AddLength);
   First[0]->ColumnWidth += AddLength;
@@ -428,6 +513,14 @@ bool FormatTokenLexer::tryMergeTokens(ArrayRef<tok::TokenKind> Kinds,
   return true;
 }
 
+bool FormatTokenLexer::tryMergeTokensAny(
+    ArrayRef<ArrayRef<tok::TokenKind>> Kinds, TokenType NewType) {
+  return std::any_of(Kinds.begin(), Kinds.end(),
+                     [this, NewType](ArrayRef<tok::TokenKind> Kinds) {
+                       return tryMergeTokens(Kinds, NewType);
+                     });
+}
+
 // Returns \c true if \p Tok can only be followed by an operand in JavaScript.
 bool FormatTokenLexer::precedesOperand(FormatToken *Tok) {
   // NB: This is not entirely correct, as an r_paren can introduce an operand
@@ -1004,12 +1097,19 @@ FormatToken *FormatTokenLexer::getNextToken() {
   }
 
   if (Style.isVerilog()) {
+    static const llvm::Regex NumberBase("^s?[bdho]", llvm::Regex::IgnoreCase);
+    SmallVector<StringRef, 1> Matches;
     // Verilog uses the backtick instead of the hash for preprocessor stuff.
     // And it uses the hash for delays and parameter lists. In order to continue
     // using `tok::hash` in other places, the backtick gets marked as the hash
     // here.  And in order to tell the backtick and hash apart for
     // Verilog-specific stuff, the hash becomes an identifier.
-    if (FormatTok->isOneOf(tok::hash, tok::hashhash)) {
+    if (FormatTok->is(tok::numeric_constant)) {
+      // In Verilog the quote is not part of a number.
+      auto Quote = FormatTok->TokenText.find('\'');
+      if (Quote != StringRef::npos)
+        truncateToken(Quote);
+    } else if (FormatTok->isOneOf(tok::hash, tok::hashhash)) {
       FormatTok->Tok.setKind(tok::raw_identifier);
     } else if (FormatTok->is(tok::raw_identifier)) {
       if (FormatTok->TokenText == "`") {
@@ -1018,6 +1118,15 @@ FormatToken *FormatTokenLexer::getNextToken() {
       } else if (FormatTok->TokenText == "``") {
         FormatTok->Tok.setIdentifierInfo(nullptr);
         FormatTok->Tok.setKind(tok::hashhash);
+      } else if (Tokens.size() > 0 &&
+                 Tokens.back()->is(Keywords.kw_apostrophe) &&
+                 NumberBase.match(FormatTok->TokenText, &Matches)) {
+        // In Verilog in a based number literal like `'b10`, there may be
+        // whitespace between `'b` and `10`. Therefore we handle the base and
+        // the rest of the number literal as two tokens. But if there is no
+        // space in the input code, we need to manually separate the two parts.
+        truncateToken(Matches[0].size());
+        FormatTok->setFinalizedType(TT_VerilogNumberBase);
       }
     }
   }
@@ -1060,6 +1169,13 @@ FormatToken *FormatTokenLexer::getNextToken() {
     StateStack.push(LexerState::TOKEN_STASHED);
   }
 
+  if (Style.isVerilog() && Tokens.size() > 0 &&
+      Tokens.back()->is(TT_VerilogNumberBase) &&
+      FormatTok->Tok.isOneOf(tok::identifier, tok::question)) {
+    // Mark the number following a base like `'h?a0` as a number.
+    FormatTok->Tok.setKind(tok::numeric_constant);
+  }
+
   // Now FormatTok is the next non-whitespace token.
 
   StringRef Text = FormatTok->TokenText;

diff  --git a/clang/lib/Format/FormatTokenLexer.h b/clang/lib/Format/FormatTokenLexer.h
index bff2c181d81e..950305a37d68 100644
--- a/clang/lib/Format/FormatTokenLexer.h
+++ b/clang/lib/Format/FormatTokenLexer.h
@@ -60,7 +60,14 @@ class FormatTokenLexer {
   bool tryMergeForEach();
   bool tryTransformTryUsageForC();
 
+  // Merge the most recently lexed tokens into a single token if their kinds are
+  // correct.
   bool tryMergeTokens(ArrayRef<tok::TokenKind> Kinds, TokenType NewType);
+  // Merge without checking their kinds.
+  bool tryMergeTokens(size_t Count, TokenType NewType);
+  // Merge if their kinds match any one of Kinds.
+  bool tryMergeTokensAny(ArrayRef<ArrayRef<tok::TokenKind>> Kinds,
+                         TokenType NewType);
 
   // Returns \c true if \p Tok can only be followed by an operand in JavaScript.
   bool precedesOperand(FormatToken *Tok);

diff  --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 6b65b69caa09..2c4ea9fbbb33 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -1842,7 +1842,8 @@ class AnnotatingParser {
           Current,
           Contexts.back().CanBeExpression && Contexts.back().IsExpression,
           Contexts.back().ContextType == Context::TemplateArgument));
-    } else if (Current.isOneOf(tok::minus, tok::plus, tok::caret)) {
+    } else if (Current.isOneOf(tok::minus, tok::plus, tok::caret) ||
+               (Style.isVerilog() && Current.is(tok::pipe))) {
       Current.setType(determinePlusMinusCaretUsage(Current));
       if (Current.is(TT_UnaryOperator) && Current.is(tok::caret))
         Contexts.back().CaretFound = true;
@@ -3996,6 +3997,23 @@ bool TokenAnnotator::spaceRequiredBefore(const AnnotatedLine &Line,
           Left.MatchingParen->endsSequence(tok::l_paren, tok::at)))) {
       return true;
     }
+    // Don't add embedded spaces in a number literal like `16'h1?ax` or an array
+    // literal like `'{}`.
+    if (Left.is(Keywords.kw_apostrophe) ||
+        (Left.is(TT_VerilogNumberBase) && Right.is(tok::numeric_constant))) {
+      return false;
+    }
+    // Don't add spaces between a casting type and the quote or repetition count
+    // and the brace.
+    if ((Right.is(Keywords.kw_apostrophe) ||
+         (Right.is(BK_BracedInit) && Right.is(tok::l_brace))) &&
+        !(Left.isOneOf(Keywords.kw_assign, Keywords.kw_unique) ||
+          Keywords.isVerilogWordOperator(Left)) &&
+        (Left.isOneOf(tok::r_square, tok::r_paren, tok::r_brace,
+                      tok::numeric_constant) ||
+         Keywords.isWordLike(Left))) {
+      return false;
+    }
   }
   if (Left.is(TT_ImplicitStringLiteral))
     return Right.hasWhitespaceBefore();

diff  --git a/clang/unittests/Format/FormatTestVerilog.cpp b/clang/unittests/Format/FormatTestVerilog.cpp
index 3c48dfad4d1a..5c887e533115 100644
--- a/clang/unittests/Format/FormatTestVerilog.cpp
+++ b/clang/unittests/Format/FormatTestVerilog.cpp
@@ -45,6 +45,27 @@ class FormatTestVerilog : public ::testing::Test {
   }
 };
 
+TEST_F(FormatTestVerilog, BasedLiteral) {
+  verifyFormat("x = '0;");
+  verifyFormat("x = '1;");
+  verifyFormat("x = 'X;");
+  verifyFormat("x = 'x;");
+  verifyFormat("x = 'Z;");
+  verifyFormat("x = 'z;");
+  verifyFormat("x = 659;");
+  verifyFormat("x = 'h837ff;");
+  verifyFormat("x = 'o7460;");
+  verifyFormat("x = 4'b1001;");
+  verifyFormat("x = 5'D3;");
+  verifyFormat("x = 3'b01x;");
+  verifyFormat("x = 12'hx;");
+  verifyFormat("x = 16'hz;");
+  verifyFormat("x = -8'd6;");
+  verifyFormat("x = 4'shf;");
+  verifyFormat("x = -4'sd15;");
+  verifyFormat("x = 16'sd?;");
+}
+
 TEST_F(FormatTestVerilog, Delay) {
   // Delay by the default unit.
   verifyFormat("#0;");
@@ -139,6 +160,64 @@ TEST_F(FormatTestVerilog, If) {
                "  {x} = {x};");
 }
 
+TEST_F(FormatTestVerilog, Operators) {
+  // Test that unary operators are not followed by space.
+  verifyFormat("x = +x;");
+  verifyFormat("x = -x;");
+  verifyFormat("x = !x;");
+  verifyFormat("x = ~x;");
+  verifyFormat("x = &x;");
+  verifyFormat("x = ~&x;");
+  verifyFormat("x = |x;");
+  verifyFormat("x = ~|x;");
+  verifyFormat("x = ^x;");
+  verifyFormat("x = ~^x;");
+  verifyFormat("x = ^~x;");
+  verifyFormat("x = ++x;");
+  verifyFormat("x = --x;");
+
+  // Test that operators don't get split.
+  verifyFormat("x = x++;");
+  verifyFormat("x = x--;");
+  verifyFormat("x = x ** x;");
+  verifyFormat("x = x << x;");
+  verifyFormat("x = x >> x;");
+  verifyFormat("x = x <<< x;");
+  verifyFormat("x = x >>> x;");
+  verifyFormat("x = x <= x;");
+  verifyFormat("x = x >= x;");
+  verifyFormat("x = x == x;");
+  verifyFormat("x = x != x;");
+  verifyFormat("x = x === x;");
+  verifyFormat("x = x !== x;");
+  verifyFormat("x = x ==? x;");
+  verifyFormat("x = x !=? x;");
+  verifyFormat("x = x ~^ x;");
+  verifyFormat("x = x ^~ x;");
+  verifyFormat("x = x && x;");
+  verifyFormat("x = x || x;");
+  verifyFormat("x = x->x;");
+  verifyFormat("x = x <-> x;");
+  verifyFormat("x += x;");
+  verifyFormat("x -= x;");
+  verifyFormat("x *= x;");
+  verifyFormat("x /= x;");
+  verifyFormat("x %= x;");
+  verifyFormat("x &= x;");
+  verifyFormat("x ^= x;");
+  verifyFormat("x |= x;");
+  verifyFormat("x <<= x;");
+  verifyFormat("x >>= x;");
+  verifyFormat("x <<<= x;");
+  verifyFormat("x >>>= x;");
+  verifyFormat("x <= x;");
+
+  // Test that space is added between operators.
+  EXPECT_EQ("x = x < -x;", format("x=x<-x;"));
+  EXPECT_EQ("x = x << -x;", format("x=x<<-x;"));
+  EXPECT_EQ("x = x <<< -x;", format("x=x<<<-x;"));
+}
+
 TEST_F(FormatTestVerilog, Preprocessor) {
   auto Style = getLLVMStyle(FormatStyle::LK_Verilog);
   Style.ColumnLimit = 20;

diff  --git a/clang/unittests/Format/TokenAnnotatorTest.cpp b/clang/unittests/Format/TokenAnnotatorTest.cpp
index b2c50aaea6d7..13c03414966d 100644
--- a/clang/unittests/Format/TokenAnnotatorTest.cpp
+++ b/clang/unittests/Format/TokenAnnotatorTest.cpp
@@ -38,6 +38,8 @@ class TokenAnnotatorTest : public ::testing::Test {
   EXPECT_EQ((FormatTok)->Tok.getKind(), Kind) << *(FormatTok)
 #define EXPECT_TOKEN_TYPE(FormatTok, Type)                                     \
   EXPECT_EQ((FormatTok)->getType(), Type) << *(FormatTok)
+#define EXPECT_TOKEN_PRECEDENCE(FormatTok, Prec)                               \
+  EXPECT_EQ((FormatTok)->getPrecedence(), Prec) << *(FormatTok)
 #define EXPECT_TOKEN(FormatTok, Kind, Type)                                    \
   do {                                                                         \
     EXPECT_TOKEN_KIND(FormatTok, Kind);                                        \
@@ -764,6 +766,67 @@ TEST_F(TokenAnnotatorTest, UnderstandsLambdas) {
   EXPECT_TOKEN(Tokens[7], tok::l_brace, TT_LambdaLBrace);
 }
 
+TEST_F(TokenAnnotatorTest, UnderstandsVerilogOperators) {
+  auto Annotate = [this](llvm::StringRef Code) {
+    return annotate(Code, getLLVMStyle(FormatStyle::LK_Verilog));
+  };
+  // Test that unary operators get labeled as such and that operators like '++'
+  // don't get split.
+  tok::TokenKind Unary[] = {tok::plus,  tok::minus,    tok::exclaim,
+                            tok::tilde, tok::amp,      tok::pipe,
+                            tok::caret, tok::plusplus, tok::minusminus};
+  for (auto Kind : Unary) {
+    auto Tokens =
+        Annotate(std::string("x = ") + tok::getPunctuatorSpelling(Kind) + "x;");
+    ASSERT_EQ(Tokens.size(), 6u) << Tokens;
+    EXPECT_TOKEN(Tokens[2], Kind, TT_UnaryOperator);
+  }
+  // Operators formed by joining two operators like '^~'. For some of these
+  // joined operators, we don't have a separate type, so we only test for their
+  // precedence.
+  std::pair<prec::Level, std::string> JoinedBinary[] = {
+      {prec::Comma, "<->"},       {prec::Assignment, "+="},
+      {prec::Assignment, "-="},   {prec::Assignment, "*="},
+      {prec::Assignment, "/="},   {prec::Assignment, "%="},
+      {prec::Assignment, "&="},   {prec::Assignment, "^="},
+      {prec::Assignment, "<<="},  {prec::Assignment, ">>="},
+      {prec::Assignment, "<<<="}, {prec::Assignment, ">>>="},
+      {prec::LogicalOr, "||"},    {prec::LogicalAnd, "&&"},
+      {prec::Equality, "=="},     {prec::Equality, "!="},
+      {prec::Equality, "==="},    {prec::Equality, "!=="},
+      {prec::Equality, "==?"},    {prec::Equality, "!=?"},
+      {prec::ExclusiveOr, "~^"},  {prec::ExclusiveOr, "^~"},
+  };
+  for (auto Operator : JoinedBinary) {
+    auto Tokens = Annotate(std::string("x = x ") + Operator.second + " x;");
+    ASSERT_EQ(Tokens.size(), 7u) << Tokens;
+    EXPECT_TOKEN_TYPE(Tokens[3], TT_BinaryOperator);
+    EXPECT_TOKEN_PRECEDENCE(Tokens[3], Operator.first);
+  }
+  // '~^' and '^~' can be unary as well as binary operators.
+  auto Tokens = Annotate("x = ~^x;");
+  ASSERT_EQ(Tokens.size(), 6u) << Tokens;
+  EXPECT_TOKEN_TYPE(Tokens[2], TT_UnaryOperator);
+  Tokens = Annotate("x = ^~x;");
+  ASSERT_EQ(Tokens.size(), 6u) << Tokens;
+  EXPECT_TOKEN_TYPE(Tokens[2], TT_UnaryOperator);
+  // The unary operators '~&' and '~|' can only be unary operators. The current
+  // implementation treats each of them as separate unary '~' and '&' or '|'
+  // operators, which is enough for formatting purposes. In FormatTestVerilog,
+  // there is a test that there is no space in between. And even if a new line
+  // is inserted between the '~' and '|', the semantic meaning is the same as
+  // the joined operator, so the CanBreakBefore property doesn't need to be
+  // false for the second operator.
+  Tokens = Annotate("x = ~&x;");
+  ASSERT_EQ(Tokens.size(), 7u) << Tokens;
+  EXPECT_TOKEN(Tokens[2], tok::tilde, TT_UnaryOperator);
+  EXPECT_TOKEN(Tokens[3], tok::amp, TT_UnaryOperator);
+  Tokens = Annotate("x = ~|x;");
+  ASSERT_EQ(Tokens.size(), 7u) << Tokens;
+  EXPECT_TOKEN(Tokens[2], tok::tilde, TT_UnaryOperator);
+  EXPECT_TOKEN(Tokens[3], tok::pipe, TT_UnaryOperator);
+}
+
 } // namespace
 } // namespace format
 } // namespace clang


        


More information about the cfe-commits mailing list