[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