exv created this revision.
exv added reviewers: krasimir, MyDeveloperDay.
exv requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

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

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


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D101702

Files:
  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

Index: clang/unittests/Format/FormatTestCSharp.cpp
===================================================================
--- clang/unittests/Format/FormatTestCSharp.cpp
+++ clang/unittests/Format/FormatTestCSharp.cpp
@@ -358,6 +358,21 @@
   verifyFormat("return _name ?? \"DEF\";");
 }
 
+TEST_F(FormatTestCSharp, CSharpNullCoalescingAssignment) {
+  verifyFormat("test \?\?= ABC;");
+  verifyFormat("test \?\?= true;");
+}
+
+TEST_F(FormatTestCSharp, CSharpNullForgiving) {
+  verifyFormat("var test = null!;");
+  verifyFormat("string test = someFunctionCall()! + \"ABC\"!");
+  verifyFormat("int test = (1! + 2 + bar! + foo())!");
+  verifyFormat("test \?\?= !foo!;");
+  verifyFormat("test = !bar! \?\? !foo!;");
+  verifyFormat("bool test = !(!true || !null! || !false && !false! && !bar()! "
+               "+ (!foo()))!");
+}
+
 TEST_F(FormatTestCSharp, AttributesIndentation) {
   FormatStyle Style = getMicrosoftStyle(FormatStyle::LK_CSharp);
   Style.AlwaysBreakAfterReturnType = FormatStyle::RTBS_None;
Index: clang/lib/Format/TokenAnnotator.cpp
===================================================================
--- clang/lib/Format/TokenAnnotator.cpp
+++ clang/lib/Format/TokenAnnotator.cpp
@@ -1579,24 +1579,29 @@
       }
     }
 
-    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
@@ -3186,6 +3191,15 @@
     if (Left.is(TT_CSharpNullCoalescing) || Right.is(TT_CSharpNullCoalescing))
       return true;
 
+    // Space before and after '??='.
+    if (Left.is(TT_CSharpNullCoalescingAssignment) ||
+        Right.is(TT_CSharpNullCoalescingAssignment))
+      return true;
+
+    // No space before null forgiving '!'.
+    if (Right.is(TT_JsNonNullAssertion))
+      return false;
+
     // No space before '?['.
     if (Right.is(TT_CSharpNullConditionalLSquare))
       return false;
Index: clang/lib/Format/FormatTokenLexer.h
===================================================================
--- clang/lib/Format/FormatTokenLexer.h
+++ clang/lib/Format/FormatTokenLexer.h
@@ -54,7 +54,8 @@
   bool tryMergeJSPrivateIdentifier();
   bool tryMergeCSharpStringLiteral();
   bool tryMergeCSharpKeywordVariables();
-  bool tryMergeCSharpDoubleQuestion();
+  bool tryMergeCSharpNullCoalescing();
+  bool tryMergeCSharpNullCoalescingAssignment();
   bool tryMergeCSharpNullConditional();
   bool tryTransformCSharpForEach();
   bool tryMergeForEach();
Index: clang/lib/Format/FormatTokenLexer.cpp
===================================================================
--- clang/lib/Format/FormatTokenLexer.cpp
+++ clang/lib/Format/FormatTokenLexer.cpp
@@ -97,7 +97,9 @@
       return;
     if (tryMergeCSharpStringLiteral())
       return;
-    if (tryMergeCSharpDoubleQuestion())
+    if (tryMergeCSharpNullCoalescing())
+      return;
+    if (tryMergeCSharpNullCoalescingAssignment())
       return;
     if (tryMergeCSharpNullConditional())
       return;
@@ -310,7 +312,7 @@
     "param",    "property", "return", "type",
 };
 
-bool FormatTokenLexer::tryMergeCSharpDoubleQuestion() {
+bool FormatTokenLexer::tryMergeCSharpNullCoalescing() {
   if (Tokens.size() < 2)
     return false;
   auto &FirstQuestion = *(Tokens.end() - 2);
@@ -327,6 +329,24 @@
   return true;
 }
 
+bool FormatTokenLexer::tryMergeCSharpNullCoalescingAssignment() {
+  if (Tokens.size() < 2)
+    return false;
+  auto &NullCoalescing = *(Tokens.end() - 2);
+  auto &Equal = *(Tokens.end() - 1);
+  if (NullCoalescing->getType() != TT_CSharpNullCoalescing ||
+      !Equal->is(tok::equal))
+    return false;
+  NullCoalescing->Tok.setKind(tok::equal); // no '??=' in clang tokens.
+  NullCoalescing->TokenText =
+      StringRef(NullCoalescing->TokenText.begin(),
+                Equal->TokenText.end() - NullCoalescing->TokenText.begin());
+  NullCoalescing->ColumnWidth += Equal->ColumnWidth;
+  NullCoalescing->setType(TT_CSharpNullCoalescingAssignment);
+  Tokens.erase(Tokens.end() - 1);
+  return true;
+}
+
 // Merge '?[' and '?.' pairs into single tokens.
 bool FormatTokenLexer::tryMergeCSharpNullConditional() {
   if (Tokens.size() < 2)
Index: clang/lib/Format/FormatToken.h
===================================================================
--- clang/lib/Format/FormatToken.h
+++ clang/lib/Format/FormatToken.h
@@ -114,6 +114,7 @@
   TYPE(CSharpNamedArgumentColon)                                               \
   TYPE(CSharpNullable)                                                         \
   TYPE(CSharpNullCoalescing)                                                   \
+  TYPE(CSharpNullCoalescingAssignment)                                         \
   TYPE(CSharpNullConditional)                                                  \
   TYPE(CSharpNullConditionalLSquare)                                           \
   TYPE(CSharpGenericTypeConstraint)                                            \
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to