[clang] a437bef - [clang-format] Add more support for C# 8 nullables

Marek Kurdej via cfe-commits cfe-commits at lists.llvm.org
Thu May 6 02:58:45 PDT 2021


Author: Eliza Velasquez
Date: 2021-05-06T11:58:38+02:00
New Revision: a437befa8f8580b3b4f2226b208a05da078c8b20

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

LOG: [clang-format] Add more support for C# 8 nullables

This adds support for the null-coalescing assignment and null-forgiving
operators.

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-coalescing-operator

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-forgiving

Reviewed By: krasimir, curdeius

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

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/FormatTestCSharp.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h
index 2f53b338379d7..95353482467fb 100644
--- a/clang/lib/Format/FormatToken.h
+++ b/clang/lib/Format/FormatToken.h
@@ -112,9 +112,6 @@ namespace format {
   TYPE(UntouchableMacroFunc)                                                   \
   TYPE(CSharpStringLiteral)                                                    \
   TYPE(CSharpNamedArgumentColon)                                               \
-  TYPE(CSharpNullable)                                                         \
-  TYPE(CSharpNullCoalescing)                                                   \
-  TYPE(CSharpNullConditional)                                                  \
   TYPE(CSharpNullConditionalLSquare)                                           \
   TYPE(CSharpGenericTypeConstraint)                                            \
   TYPE(CSharpGenericTypeConstraintColon)                                       \

diff  --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp
index e9b096370dbb1..b6825c3ec9829 100644
--- a/clang/lib/Format/FormatTokenLexer.cpp
+++ b/clang/lib/Format/FormatTokenLexer.cpp
@@ -92,20 +92,47 @@ void FormatTokenLexer::tryMergePreviousTokens() {
   if (Style.isCpp() && tryTransformTryUsageForC())
     return;
 
+  if (Style.Language == FormatStyle::LK_JavaScript || Style.isCSharp()) {
+    static const tok::TokenKind NullishCoalescingOperator[] = {tok::question,
+                                                               tok::question};
+    static const tok::TokenKind NullPropagatingOperator[] = {tok::question,
+                                                             tok::period};
+    static const tok::TokenKind FatArrow[] = {tok::equal, tok::greater};
+
+    if (tryMergeTokens(FatArrow, TT_JsFatArrow))
+      return;
+    if (tryMergeTokens(NullishCoalescingOperator,
+                       TT_JsNullishCoalescingOperator)) {
+      // Treat like the "||" operator (as opposed to the ternary ?).
+      Tokens.back()->Tok.setKind(tok::pipepipe);
+      return;
+    }
+    if (tryMergeTokens(NullPropagatingOperator, TT_JsNullPropagatingOperator)) {
+      // Treat like a regular "." access.
+      Tokens.back()->Tok.setKind(tok::period);
+      return;
+    }
+    if (tryMergeNullishCoalescingEqual()) {
+      return;
+    }
+  }
+
   if (Style.isCSharp()) {
+    static const tok::TokenKind CSharpNullConditionalLSquare[] = {
+        tok::question, tok::l_square};
+
     if (tryMergeCSharpKeywordVariables())
       return;
     if (tryMergeCSharpStringLiteral())
       return;
-    if (tryMergeCSharpDoubleQuestion())
-      return;
-    if (tryMergeCSharpNullConditional())
-      return;
     if (tryTransformCSharpForEach())
       return;
-    static const tok::TokenKind JSRightArrow[] = {tok::equal, tok::greater};
-    if (tryMergeTokens(JSRightArrow, TT_JsFatArrow))
+    if (tryMergeTokens(CSharpNullConditionalLSquare,
+                       TT_CSharpNullConditionalLSquare)) {
+      // Treat like a regular "[" operator.
+      Tokens.back()->Tok.setKind(tok::l_square);
       return;
+    }
   }
 
   if (tryMergeNSStringLiteral())
@@ -117,16 +144,9 @@ void FormatTokenLexer::tryMergePreviousTokens() {
                                                    tok::equal};
     static const tok::TokenKind JSShiftEqual[] = {tok::greater, tok::greater,
                                                   tok::greaterequal};
-    static const tok::TokenKind JSRightArrow[] = {tok::equal, tok::greater};
     static const tok::TokenKind JSExponentiation[] = {tok::star, tok::star};
     static const tok::TokenKind JSExponentiationEqual[] = {tok::star,
                                                            tok::starequal};
-    static const tok::TokenKind JSNullPropagatingOperator[] = {tok::question,
-                                                               tok::period};
-    static const tok::TokenKind JSNullishOperator[] = {tok::question,
-                                                       tok::question};
-    static const tok::TokenKind JSNullishEqual[] = {tok::question,
-                                                    tok::question, tok::equal};
     static const tok::TokenKind JSPipePipeEqual[] = {tok::pipepipe, tok::equal};
     static const tok::TokenKind JSAndAndEqual[] = {tok::ampamp, tok::equal};
 
@@ -137,28 +157,14 @@ void FormatTokenLexer::tryMergePreviousTokens() {
       return;
     if (tryMergeTokens(JSShiftEqual, TT_BinaryOperator))
       return;
-    if (tryMergeTokens(JSRightArrow, TT_JsFatArrow))
-      return;
     if (tryMergeTokens(JSExponentiation, TT_JsExponentiation))
       return;
     if (tryMergeTokens(JSExponentiationEqual, TT_JsExponentiationEqual)) {
       Tokens.back()->Tok.setKind(tok::starequal);
       return;
     }
-    if (tryMergeTokens(JSNullishOperator, TT_JsNullishCoalescingOperator)) {
-      // Treat like the "||" operator (as opposed to the ternary ?).
-      Tokens.back()->Tok.setKind(tok::pipepipe);
-      return;
-    }
-    if (tryMergeTokens(JSNullPropagatingOperator,
-                       TT_JsNullPropagatingOperator)) {
-      // Treat like a regular "." access.
-      Tokens.back()->Tok.setKind(tok::period);
-      return;
-    }
     if (tryMergeTokens(JSAndAndEqual, TT_JsAndAndEqual) ||
-        tryMergeTokens(JSPipePipeEqual, TT_JsPipePipeEqual) ||
-        tryMergeTokens(JSNullishEqual, TT_JsNullishCoalescingEqual)) {
+        tryMergeTokens(JSPipePipeEqual, TT_JsPipePipeEqual)) {
       // Treat like the "=" assignment operator.
       Tokens.back()->Tok.setKind(tok::equal);
       return;
@@ -310,45 +316,20 @@ const llvm::StringSet<> FormatTokenLexer::CSharpAttributeTargets = {
     "param",    "property", "return", "type",
 };
 
-bool FormatTokenLexer::tryMergeCSharpDoubleQuestion() {
-  if (Tokens.size() < 2)
-    return false;
-  auto &FirstQuestion = *(Tokens.end() - 2);
-  auto &SecondQuestion = *(Tokens.end() - 1);
-  if (!FirstQuestion->is(tok::question) || !SecondQuestion->is(tok::question))
-    return false;
-  FirstQuestion->Tok.setKind(tok::question); // no '??' in clang tokens.
-  FirstQuestion->TokenText = StringRef(FirstQuestion->TokenText.begin(),
-                                       SecondQuestion->TokenText.end() -
-                                           FirstQuestion->TokenText.begin());
-  FirstQuestion->ColumnWidth += SecondQuestion->ColumnWidth;
-  FirstQuestion->setType(TT_CSharpNullCoalescing);
-  Tokens.erase(Tokens.end() - 1);
-  return true;
-}
-
-// Merge '?[' and '?.' pairs into single tokens.
-bool FormatTokenLexer::tryMergeCSharpNullConditional() {
+bool FormatTokenLexer::tryMergeNullishCoalescingEqual() {
   if (Tokens.size() < 2)
     return false;
-  auto &Question = *(Tokens.end() - 2);
-  auto &PeriodOrLSquare = *(Tokens.end() - 1);
-  if (!Question->is(tok::question) ||
-      !PeriodOrLSquare->isOneOf(tok::l_square, tok::period))
+  auto &NullishCoalescing = *(Tokens.end() - 2);
+  auto &Equal = *(Tokens.end() - 1);
+  if (NullishCoalescing->getType() != TT_JsNullishCoalescingOperator ||
+      !Equal->is(tok::equal))
     return false;
-  Question->TokenText =
-      StringRef(Question->TokenText.begin(),
-                PeriodOrLSquare->TokenText.end() - Question->TokenText.begin());
-  Question->ColumnWidth += PeriodOrLSquare->ColumnWidth;
-
-  if (PeriodOrLSquare->is(tok::l_square)) {
-    Question->Tok.setKind(tok::question); // no '?[' in clang tokens.
-    Question->setType(TT_CSharpNullConditionalLSquare);
-  } else {
-    Question->Tok.setKind(tok::question); // no '?.' in clang tokens.
-    Question->setType(TT_CSharpNullConditional);
-  }
-
+  NullishCoalescing->Tok.setKind(tok::equal); // no '??=' in clang tokens.
+  NullishCoalescing->TokenText =
+      StringRef(NullishCoalescing->TokenText.begin(),
+                Equal->TokenText.end() - NullishCoalescing->TokenText.begin());
+  NullishCoalescing->ColumnWidth += Equal->ColumnWidth;
+  NullishCoalescing->setType(TT_JsNullishCoalescingEqual);
   Tokens.erase(Tokens.end() - 1);
   return true;
 }

diff  --git a/clang/lib/Format/FormatTokenLexer.h b/clang/lib/Format/FormatTokenLexer.h
index 6b08677e33697..a9e3b2fd498a1 100644
--- a/clang/lib/Format/FormatTokenLexer.h
+++ b/clang/lib/Format/FormatTokenLexer.h
@@ -54,8 +54,7 @@ class FormatTokenLexer {
   bool tryMergeJSPrivateIdentifier();
   bool tryMergeCSharpStringLiteral();
   bool tryMergeCSharpKeywordVariables();
-  bool tryMergeCSharpDoubleQuestion();
-  bool tryMergeCSharpNullConditional();
+  bool tryMergeNullishCoalescingEqual();
   bool tryTransformCSharpForEach();
   bool tryMergeForEach();
   bool tryTransformTryUsageForC();

diff  --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 54b73d6e75bb6..260d09bbae467 100755
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -1055,13 +1055,6 @@ class AnnotatingParser {
         CurrentToken->Previous->setType(TT_OverloadedOperator);
       break;
     case tok::question:
-      if (Tok->is(TT_CSharpNullConditionalLSquare)) {
-        if (!parseSquare())
-          return false;
-        break;
-      }
-      if (Tok->isOneOf(TT_CSharpNullConditional, TT_CSharpNullCoalescing))
-        break;
       if (Style.Language == FormatStyle::LK_JavaScript && Tok->Next &&
           Tok->Next->isOneOf(tok::semi, tok::comma, tok::colon, tok::r_paren,
                              tok::r_brace)) {
@@ -1085,7 +1078,7 @@ class AnnotatingParser {
             (Tok->Next && Tok->Next->isOneOf(tok::r_paren, tok::greater)) ||
             (Tok->Next && Tok->Next->is(tok::identifier) && Tok->Next->Next &&
              Tok->Next->Next->is(tok::equal))) {
-          Tok->setType(TT_CSharpNullable);
+          Tok->setType(TT_JsTypeOptionalQuestion);
           break;
         }
       }
@@ -1571,39 +1564,29 @@ class AnnotatingParser {
       // The token type is already known.
       return;
 
-    if (Style.isCSharp() && CurrentToken->is(tok::question)) {
-      if (CurrentToken->TokenText == "??") {
-        Current.setType(TT_CSharpNullCoalescing);
-        return;
-      }
-      if (CurrentToken->TokenText == "?.") {
-        Current.setType(TT_CSharpNullConditional);
-        return;
-      }
-      if (CurrentToken->TokenText == "?[") {
-        Current.setType(TT_CSharpNullConditionalLSquare);
-        return;
-      }
-    }
-
-    if (Style.Language == FormatStyle::LK_JavaScript) {
-      if (Current.is(tok::exclaim)) {
-        if (Current.Previous &&
-            (Keywords.IsJavaScriptIdentifier(
-                 *Current.Previous, /* AcceptIdentifierName= */ true) ||
-             Current.Previous->isOneOf(
-                 tok::kw_namespace, tok::r_paren, tok::r_square, tok::r_brace,
-                 Keywords.kw_type, Keywords.kw_get, Keywords.kw_set) ||
-             Current.Previous->Tok.isLiteral())) {
-          Current.setType(TT_JsNonNullAssertion);
-          return;
-        }
-        if (Current.Next &&
-            Current.Next->isOneOf(TT_BinaryOperator, Keywords.kw_as)) {
+    if ((Style.Language == FormatStyle::LK_JavaScript || Style.isCSharp()) &&
+        Current.is(tok::exclaim)) {
+      if (Current.Previous) {
+        bool IsIdentifier =
+            Style.Language == FormatStyle::LK_JavaScript
+                ? Keywords.IsJavaScriptIdentifier(
+                      *Current.Previous, /* AcceptIdentifierName= */ true)
+                : Current.Previous->is(tok::identifier);
+        if (IsIdentifier ||
+            Current.Previous->isOneOf(
+                tok::kw_namespace, tok::r_paren, tok::r_square, tok::r_brace,
+                tok::kw_false, tok::kw_true, Keywords.kw_type, Keywords.kw_get,
+                Keywords.kw_set) ||
+            Current.Previous->Tok.isLiteral()) {
           Current.setType(TT_JsNonNullAssertion);
           return;
         }
       }
+      if (Current.Next &&
+          Current.Next->isOneOf(TT_BinaryOperator, Keywords.kw_as)) {
+        Current.setType(TT_JsNonNullAssertion);
+        return;
+      }
     }
 
     // Line.MightBeFunctionDecl can only be true after the parentheses of a
@@ -3178,33 +3161,17 @@ bool TokenAnnotator::spaceRequiredBefore(const AnnotatedLine &Line,
       return Style.SpacesInSquareBrackets;
 
     // No space before ? in nullable types.
-    if (Right.is(TT_CSharpNullable))
-      return false;
-
-    // Require space after ? in nullable types except in generics and casts.
-    if (Left.is(TT_CSharpNullable))
-      return !Right.isOneOf(TT_TemplateCloser, tok::r_paren);
-
-    // No space before or after '?.'.
-    if (Left.is(TT_CSharpNullConditional) || Right.is(TT_CSharpNullConditional))
+    if (Right.is(TT_JsTypeOptionalQuestion))
       return false;
 
-    // Space before and after '??'.
-    if (Left.is(TT_CSharpNullCoalescing) || Right.is(TT_CSharpNullCoalescing))
-      return true;
-
-    // No space before '?['.
-    if (Right.is(TT_CSharpNullConditionalLSquare))
+    // No space before null forgiving '!'.
+    if (Right.is(TT_JsNonNullAssertion))
       return false;
 
     // No space between consecutive commas '[,,]'.
     if (Left.is(tok::comma) && Right.is(tok::comma))
       return false;
 
-    // Possible space inside `?[ 0 ]`.
-    if (Left.is(TT_CSharpNullConditionalLSquare))
-      return Style.SpacesInSquareBrackets;
-
     // space after var in `var (key, value)`
     if (Left.is(Keywords.kw_var) && Right.is(tok::l_paren))
       return true;

diff  --git a/clang/unittests/Format/FormatTestCSharp.cpp b/clang/unittests/Format/FormatTestCSharp.cpp
index 51617aa6eb2f3..875ff74304d3a 100644
--- a/clang/unittests/Format/FormatTestCSharp.cpp
+++ b/clang/unittests/Format/FormatTestCSharp.cpp
@@ -358,6 +358,39 @@ TEST_F(FormatTestCSharp, CSharpNullCoalescing) {
   verifyFormat("return _name ?? \"DEF\";");
 }
 
+TEST_F(FormatTestCSharp, CSharpNullCoalescingAssignment) {
+  FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp);
+  Style.SpaceBeforeAssignmentOperators = true;
+
+  verifyFormat(R"(test ??= ABC;)", Style);
+  verifyFormat(R"(test ??= true;)", Style);
+
+  Style.SpaceBeforeAssignmentOperators = false;
+
+  verifyFormat(R"(test??= ABC;)", Style);
+  verifyFormat(R"(test??= true;)", Style);
+}
+
+TEST_F(FormatTestCSharp, CSharpNullForgiving) {
+  FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp);
+
+  verifyFormat("var test = null!;", Style);
+  verifyFormat("string test = someFunctionCall()! + \"ABC\"!", Style);
+  verifyFormat("int test = (1! + 2 + bar! + foo())!", Style);
+  verifyFormat(R"(test ??= !foo!;)", Style);
+  verifyFormat("test = !bar! ?? !foo!;", Style);
+  verifyFormat("bool test = !(!true && !true! || !null && !null! || !false && "
+               "!false! && !bar()! + (!foo()))!",
+               Style);
+
+  // Check that line break keeps identifier with the bang.
+  Style.ColumnLimit = 14;
+
+  verifyFormat("var test =\n"
+               "    foo!;",
+               Style);
+}
+
 TEST_F(FormatTestCSharp, AttributesIndentation) {
   FormatStyle Style = getMicrosoftStyle(FormatStyle::LK_CSharp);
   Style.AlwaysBreakAfterReturnType = FormatStyle::RTBS_None;


        


More information about the cfe-commits mailing list