diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h --- a/clang/lib/Format/FormatToken.h +++ b/clang/lib/Format/FormatToken.h @@ -112,9 +112,6 @@ 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.h b/clang/lib/Format/FormatTokenLexer.h --- a/clang/lib/Format/FormatTokenLexer.h +++ b/clang/lib/Format/FormatTokenLexer.h @@ -54,8 +54,7 @@ bool tryMergeJSPrivateIdentifier(); bool tryMergeCSharpStringLiteral(); bool tryMergeCSharpKeywordVariables(); - bool tryMergeCSharpDoubleQuestion(); - bool tryMergeCSharpNullConditional(); + bool tryMergeNullishCoalescingEqual(); bool tryTransformCSharpForEach(); bool tryMergeForEach(); bool tryTransformTryUsageForC(); diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp --- a/clang/lib/Format/FormatTokenLexer.cpp +++ b/clang/lib/Format/FormatTokenLexer.cpp @@ -92,20 +92,47 @@ 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 @@ 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 @@ 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 @@ "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/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -1055,13 +1055,6 @@ 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 @@ (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 @@ // 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 @@ 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 --- a/clang/unittests/Format/FormatTestCSharp.cpp +++ b/clang/unittests/Format/FormatTestCSharp.cpp @@ -358,6 +358,39 @@ 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;